🤖 Agent: no_no.json only: 592 game keys filled, each key exists in English and keeps its placeholders, JSON valid with no duplicate keys; 68 stay as in English (names, units, commands).
🤖 Agent: gd_gb.json only: 578 game keys filled, each key exists in English and keeps its placeholders, JSON valid with no duplicate keys; 64 stay as in English (names, units, commands).
🤖 Agent: eo_uy.json only: 574 game keys filled, each key exists in English and keeps its placeholders, JSON valid with no duplicate keys; 44 stay as in English (names, units, commands).
🤖 Agent: hy_am.json only: 577 game keys filled, each key exists in English and keeps its placeholders, JSON valid with no duplicate keys; 44 stay as in English (names, units, commands).
🤖 Agent: spawners test guests' raw positions with the same distance formula instead of building every player's record each tick, and mob_world_base reads the first player only; same answers, tick_block_entities -2.3 to -2.6 ms/tick on the bench.
🤖 Agent: guest_pickups only scans items when one reaches the player's columns; the columns follow the item list's stamp (scan first after any write), so a /tp or explosion mid-pass cannot hide an item. Bench lan_guests -2.4 to -5.7 ms/tick at 134 players.
🤖 Agent: ticking_chunk now asks one 3x3 resident count instead of 9 chunk-map lookups; ChunkMap keeps it on insert (new keys only), remove and clear, so load/unload order cannot leave it stale (unit test covers all three). Bench: sn_candidates -0.6 to -0.9 ms/tick at 134 players; union harnesses: no failure that main 81403ce does not share.
🤖 Agent: verify failed only on main's old catalog_matches_hook_points bug (fixed by #13028); I merged main into the branch and requeued, nothing in the PR changed.
🤖 Agent: verify failed only on main's old catalog_matches_hook_points bug (fixed by #13028); I merged main into the branch and requeued, nothing in the PR changed.
hy_am.json had 482 of the 1059 keys in en_us.json. The 577 it lacked fell back to English.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13321
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
eo_uy.json had 485 of the 1059 keys in en_us.json. The 574 it lacked fell back to English.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13366
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
gd_gb.json had 481 of the 1059 keys in en_us.json. The 578 it lacked fell back to English.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13489
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
no_no.json had 467 of the 1059 keys in en_us.json. The 592 it lacked fell back to English.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13351
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
launcher/lang/fur.json had 124 of its 1266 strings still
equal to the English source. Translated in place; keys, order and
{name} placeholders unchanged.
Fixes#13363
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: the mesh key snaps to 4-cell steps with the radius grown by 4 cells, so the drawn window still covers the view; inside the cloud layer the sub-cell offset keeps the faces in place, and the new test pins the same world cells across a snap step.
🤖 Agent: pure move of menu clicks out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests.
🤖 Agent: pure move of the frame's CPU geometry out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests.
🤖 Agent: pure move of dropped items and orbs out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests.
🤖 Agent: registered slot_source ids resolve only for execute if/unless items, after literal ranges and inline sources, as SlotSourceArgument tries them; the ignored test writes its world under the cwd, so run it from a scratch dir.
🤖 Agent: pure move of falling block entities out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests. I merged main: #12992 had rewritten tick_one_falling in game.rs, so falling_blocks.rs now holds main's code, checked verbatim.
🤖 Agent: pure move of the startup resource loader out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests. I merged main (AGENTS.md row only).
🤖 Agent: pure move of lighting a nether portal out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests.
🤖 Agent: pure move of a level's block tick phases out of game.rs (added vs removed lines differ only in the module header, use lines and doc rows); it merges cleanly with the other moves in this series and passed a union build and tests.
🤖 Agent: stacked on #13205 (queued first); a filtered modifier now applies only to particles whose source block or item matches, instead of being skipped.
🤖 Agent: /tp ends with setOnGround(true) for any entity not fall-flying (TeleportCommand.performTeleport); the projectile, rocket, trident and falling block impls ignored it, and the corpus now reads OnGround for every kind.
🤖 Agent: checked as a pure move: the added and removed line multisets match except the module header and three rustfmt line joins; no non-test code changes.
🤖 Agent: build timing output now follows the requested target directory instead of the default target/, so --target-dir runs don't write into the checkout.
🤖 Agent: a piston push clears OnGround on boats and minecarts as PistonMovingBlockEntity.moveCollidedEntities' setDeltaMovement path does; green in a union with this wave.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of af_za.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of et_ee.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of is_is.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of lv_lv.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ta_in.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of kk_kz.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of eu_es.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of sl_si.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of bs_ba.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of sr_sp.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of sv_se.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of fa_ir.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ro_ro.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of lt_lt.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of sk_sk.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of el_gr.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of hr_hr.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ur_pk.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of cs_cz.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of bg_bg.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of gl_es.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of fil_ph.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of pt_pt.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of hi_in.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of kn_in.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ca_es.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of hu_hu.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of th_th.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of id_id.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of nl_nl.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ms_my.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of he_il.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of tr_tr.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ar_sa.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of vi_vn.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ko_kr.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ru_ru. The lang test now checks the English fallback on la_la, since ru_ru has horizon.mods.not_built now.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of ja_jp.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of zh_cn.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of pl_pl.
🤖 Agent: checked by script against en_us.json: valid JSON, no key removed, every key exists in en_us, and each value keeps the English placeholders (%s, %1$s, {name}); spot-read a sample of uk_ua.
🤖 Agent: the tab list draws whatever rectangles the caller's mark callback returns, with the same 9-pixel column rule; the game passes horizon_cosmetics' MARK for listed Horizon players and the vanilla probe passes none, so tab_list.rs no longer names horizon-cosmetics.
🤖 Agent: the crash path now runs registered hooks (crash::on_report) under an RwLock that ignores poisoning, and entry.rs registers telemetry::keep_crash before anything else in main, so every report still reaches telemetry exactly as before; render loses its telemetry and bench dependencies (47 → 45).
af_za.json had 489 of the 1059 keys in en_us.json. The 570 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 118 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13195
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
et_ee.json had 481 of the 1059 keys in en_us.json. The 578 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 123 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13193
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A particle modifier's `filter` (a block or an item, or a list of them) now
limits it to the particles that carry one of them, as BlockParticleOption
and ItemParticleOption do: particles sent from the server (/particle with a
block state), landing and sprinting dust, and item particles now carry
their block's or item's id. Observed on the real mod: a filtered
`minecraft:block` modifier changes /particle's stone particles and not its
dirt ones, and no modifier reaches the crumbs of a broken block, which
vanilla makes without the particle providers (as Horizon does).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
is_is.json had 483 of the 1059 keys in en_us.json. The 576 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 123 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13191
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
lv_lv.json had 472 of the 1059 keys in en_us.json. The 587 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 126 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13188
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ta_in.json had 498 of the 1059 keys in en_us.json. The 561 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 114 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13185
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
kk_kz.json had 491 of the 1059 keys in en_us.json. The 568 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 117 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13180
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
eu_es.json had 486 of the 1059 keys in en_us.json. The 573 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 123 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13178
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
sl_si.json had 483 of the 1059 keys in en_us.json. The 576 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 120 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13176
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
bs_ba.json had 433 of the 1059 keys in en_us.json. The 626 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 148 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13170
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
sr_sp.json had 505 of the 1059 keys in en_us.json. The 554 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 109 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13167
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
fa_ir.json had 491 of the 1059 keys in en_us.json. The 568 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 116 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13165
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ro_ro.json had 471 of the 1059 keys in en_us.json. The 588 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 131 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13163
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
lt_lt.json had 483 of the 1059 keys in en_us.json. The 576 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 122 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13161
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
sk_sk.json had 466 of the 1059 keys in en_us.json. The 593 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 127 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13159
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
el_gr.json had 480 of the 1059 keys in en_us.json. The 579 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 120 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13157
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
hr_hr.json had 447 of the 1059 keys in en_us.json. The 612 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 117 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13155
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ur_pk.json had 413 of the 1059 keys in en_us.json. The 646 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 134 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13153
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
cs_cz.json had 469 of the 1059 keys in en_us.json. The 590 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 126 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13151
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
bg_bg.json had 488 of the 1059 keys in en_us.json. The 571 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 118 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13149
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
gl_es.json had 480 of the 1059 keys in en_us.json. The 579 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 123 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13147
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
fil_ph.json had 439 of the 1059 keys in en_us.json. The 620 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 148 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13145
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
pt_pt.json had 431 of the 1059 keys in en_us.json. The 628 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 126 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13143
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
sv_se.json had 468 of the 1059 keys in en_us.json. The 591 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 131 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13141
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
hi_in.json had 453 of the 1059 keys in en_us.json. The 606 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 116 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13139
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: the float sums match Boat: getWaterLevelAbove returns blockY + height in float, floatBoat sets waterLevel to getY(1.0) and subtracts getBbHeight in float before the double 0.101; I merged main and kept main's corpus with this PR's cases added.
🤖 Agent: verify failed only on main's old catalog_matches_hook_points bug (fixed by #13028); I merged main into the branch and requeued, nothing in the PR changed.
kn_in.json had 498 of the 1059 keys in en_us.json. The 561 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 114 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13137
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ca_es.json had 481 of the 1059 keys in en_us.json. The 578 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 124 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13135
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
hu_hu.json had 474 of the 1059 keys in en_us.json. The 585 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 124 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13133
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
th_th.json had 505 of the 1059 keys in en_us.json. The 554 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 110 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13131
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
id_id.json had 440 of the 1059 keys in en_us.json. The 619 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 122 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13129
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
nl_nl.json had 485 of the 1059 keys in en_us.json. The 574 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 123 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13127
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ms_my.json had 477 of the 1059 keys in en_us.json. The 582 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 125 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13125
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
he_il.json had 483 of the 1059 keys in en_us.json. The 576 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 117 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13123
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
tr_tr.json had 236 of the 1059 keys in en_us.json. The 823 it lacked fell
back to English across the pvp strings (191), the splitscreen strings (89), the studio strings (78), and 164 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13121
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A block modifier with "disable_particles": true stops its blocks' own
particles: their Block.animateTick doesn't run, so a torch has no flame
or smoke. Measured on the real mod (Polytone 26.3-6.8.6, a pack with
only that modifier on torches): the torches show neither. Block
modifier emitters run apart from animateTick and are left as they are
(not measured together with disable_particles).
A file that only disables particles is now kept (it used to be dropped
for having no emitters).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack's `#tag` in a modifier's `targets` now resolves: block tags for
block modifiers, entity type tags for entity modifiers, particle type tags
for particle modifiers, dimension type tags for dimension modifiers, as
biome tags already did for biome modifiers; an emitter's `biomes` takes
biome tags too. Tags are read from the merged packs' data with nested tags,
optional entries and cycles handled, once each (`pack_tags::Tags`).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ar_sa.json had 508 of the 1059 keys in en_us.json. The 551 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 107 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13119
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
vi_vn.json had 496 of the 1059 keys in en_us.json. The 563 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 115 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13117
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ko_kr.json had 451 of the 1059 keys in en_us.json. The 608 it lacked fell
back to English across the pvp strings (191), the studio strings (78), the controller strings (49), and 115 flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13115
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ru_ru.json had 243 of the 1059 keys in en_us.json. The 816 it lacked fell
back to English across the splitscreen setup and its prompts, the on-screen
controller guide, PvP and its HUD widgets, colours and editor, Studio's top
bar, timeline, replay list, keyframes, paths and freecam, the mod browser,
a pack's settings page, the Music page, the moderation commands, and 162
flat keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
One test used ru_ru's missing horizon.mods.not_built to prove the English
fallback; with the file complete it uses la_la, which is still incomplete.
Fixes#13111
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ja_jp.json had 505 of the 1059 keys in en_us.json. The 554 it lacked fell
back to English across the on-screen controller guide, PvP and its HUD
widgets, colours and editor, Studio's top bar, timeline, replay list,
keyframes, paths and freecam, the mod browser, a pack's settings page, the
moderation durations, and 113 flat keys whose English sentence is also the
key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13106
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Part of #13091. Snapshot loaded definitions and resolve them only for execute condition reads, preserving unsupported item write consumers.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
zh_cn.json had 371 of the 1059 keys in en_us.json. The 688 it lacked fell
back to English across the splitscreen setup and its prompts, the on-screen
controller guide, PvP and its HUD widgets, colours and editor, Studio's top
bar, timeline, replay list, keyframes and freecam, the mod browser, a pack's
settings page, the Music page, and 125 flat keys whose English sentence is
also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13104
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
pl_pl.json had 375 of the 1059 keys in en_us.json. The 684 it lacked fell
back to English across the splitscreen setup and its prompts, the on-screen
controller guide, PvP and its HUD widgets, colours and editor, Studio's top
bar, timeline, replay list, keyframes and freecam, the mod browser, a
pack's settings page, the Music page, and 123 flat keys whose English
sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13102
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: the UUID comes from the mob's own random before LivingEntity's yaw draw, and the spawn yaw keeps the level's random, as Entity.<init> then Mob does; seed_sync cases 8-13 pin it.
🤖 Agent: main moved use_hand into game/use_hand.rs, so I ported the fix there (apply_use_cooldown after a successful useOn, as ItemStack.useOn's applyAfterUseComponentSideEffects) and dropped the one-off docs/parity note and probe.
🤖 Agent: I scaled the kept-tile budget to the view (fit_to_view: KEPT_BYTES times tiles / 4700, the 512-chunk view's count, capped at KEPT_BYTES) so a small view no longer holds the full budget, with a test; merged main.
🤖 Agent: stacked on #13029 (queued first); the override map is rebuilt from the registry on each pack load, so a pack swap drops overrides the new pack no longer has.
🤖 Agent: g.rain is now (rain + thunder) / 2 in both the particle and post expressions, and the fog-end tests pin a few rain/thunder pairs with independent numbers.
🤖 Agent: a layered HDR frame keeps its own targets through the scaled screenshot; only the non-layered path takes and releases them, so the next frame after the shot needs no rebuild.
🤖 Agent: a model quad whose sprite is missing from the atlas is skipped, not drawn with a zero rect, so a pack with a typo shows a gap instead of a stretched texture.
uk_ua.json had 917 of the 1059 keys in en_us.json; the 142 it lacked fell
back to English: the whole on-screen controller guide (48), the Studio top
bar, timeline, keyframes, paths and freecam (48), a pack's own settings
page (11), the gameplay guide and Nintendo button options (7), and 26 flat
keys whose English sentence is also the key.
Translated from the English source; the new keys sit at their en_us.json
neighbours and no existing line moved. Keys and placeholders unchanged.
Fixes#13099
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: the moved constants keep their old public names (horizon_telemetry::DEFAULT_ENDPOINT, horizon_friends' STUN list, etc.) as re-pointers to horizon-addresses, so callers don't change; main merged in with telemetry taking both horizon-addresses and horizon-platform, and all four workspaces' locks are --locked clean.
🤖 Agent: check_file_size.py diffs against the merge base with rename detection, so a file over 3000 lines that is renamed and grows still fails, while big files that shrink or stay the same pass; no pinned list of files.
MeshKey held the camera's own cell, so every cell the camera crossed rebuilt
the cloud window and copied its 148k vertices (6.5 MB) to the GPU in
r_materials: 206 ms of CPU over 17 frames of a 10 s flight, worst frame 28 ms.
Anchor the origin to a 4-cell grid and let Uniform::offset carry the rest of
the shift, widening the window by the grid so the ring the shift uncovers
stays past the cloud fog end, where entity.wgsl's fade already has alpha 0.
The 3x3 inward faces around the camera still follow its own cell while it is
inside the layer, where they are front-facing, and nowhere else.
Measured on a GTX 1650, Vulkan, render distance 32, 10 s flight at 19
blocks/s: rebuilds 18 -> 6, upload CPU 205.7 -> 82.0 ms, p99 11.8 -> 6.9 ms,
1% low 68 -> 87 fps, for 4.7% more cloud vertices (148,564 -> 153,492).
Fixes#13090
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
TabInputs::horizon (a "plays Horizon" flag drawn with
horizon_cosmetics::mark::MARK inside the renderer) becomes TabInputs::mark,
an extension point returning the rectangles to draw after the head, or
none. The game passes Horizon's mark from horizon-cosmetics; the vanilla
probe and the tests pass none. Same pixels, same column rule.
tab_list.rs no longer names horizon-cosmetics; extras.rs and extras_ui.rs
still do, so render -> cosmetics stays until they move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the probes move unchanged; System::detect_in now copies a horizon_system::Machine into the same payload fields, and telemetry reaches horizon-system only through horizon-platform's re-export, so no new Horizon → engine exception (still 47).
A pack's custom particle under a vanilla id (`minecraft:rain` from
assets/minecraft/polytone/custom_particles/rain.json) now replaces that
vanilla particle wherever the game spawns it, drawn from the vanilla
type's sprite set, as the wiki describes. Visual Effects+ turns rain
splashes into ripples on the ground this way; they now show as in the
real mod.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A custom particle type with a `model` is now drawn as that block model's
faces instead of being hidden: elements, faces, `#texture` references,
parents and element rotations are read the vanilla way, the particle's
size scales the model and its rotation mode turns it as it turns a
sprite quad. `none` keeps the model in the world's axes.
Checked against the real mod with an asymmetric test model: `look_at_xyz`
turns the model's +Z to the camera, `none` keeps +X east and +Z south.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
TeleportCommand.performTeleport calls setOnGround(true) on every target that
isn't fall-flying; these four kinds keep the flag since #12994 but ignored
Entity::set_on_ground. The "teleport lands" group now reads OnGround for every
kind, trident and firework rocket added.
Fixes#13080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack's `g.rain` read the rain level alone, so plain rain counted as a
thunderstorm. Measured on the real mod with a sky colormap whose row is
`g.rain`: clear sky reads the colormap's bottom row, rain its middle row
and a thunderstorm its top row, so `g.rain` is (rain + thunder) / 2.
With rain counted as a storm, a pack that picks its sky by `g.rain`
showed its storm sky and fog in plain rain; its rain sky now matches the
real mod's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Game::menu_session, menu_click, menu_ops, inventory_quick_move,
creative_throw_carrying, creative_display_click and click_inventory leave game.rs; private
methods become pub(super), the same reach they had in game.rs. Port map rows and the
AGENTS.md / docs/architecture.md tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Black Level 0 now lifts nothing, and Peak steps by 500 above 2000 up to 10 000 nits; same content as reviewed, with main merged in by the author.
🤖 Agent: verify failed only on main's catalog_matches_hook_points bug (fixed by #13028) or the old test_nightly_set one. I merged main into the branch and requeued; nothing in the PR changed.
🤖 Agent: verify failed only on main's catalog_matches_hook_points bug (fixed by #13028) or the old test_nightly_set one. I merged main into the branch and requeued; nothing in the PR changed.
🤖 Agent: verify failed only on main's catalog_matches_hook_points bug (fixed by #13028) or the old test_nightly_set one. I merged main into the branch and requeued; nothing in the PR changed.
🤖 Agent: verify failed only on main's catalog_matches_hook_points bug (fixed by #13028) or the old test_nightly_set one. I merged main into the branch and requeued; nothing in the PR changed.
🤖 Agent: verify failed only on main's catalog_matches_hook_points bug (fixed by #13028) or the old test_nightly_set one. I merged main into the branch and requeued; nothing in the PR changed.
🤖 Agent: verify failed only on main's catalog_matches_hook_points bug (fixed by #13028) or the old test_nightly_set one. I merged main into the branch and requeued; nothing in the PR changed.
The 36 tests of horizon-protocol's packets/play.rs move unchanged into
a test module of its own (`#[cfg(test)] mod tests;`); rustfmt dedents
them one level and joins three statements that now fit on a line.
horizon-protocol keeps all its tests, with the same names, and play.rs
goes from 4,173 to 2,730 lines.
Part of #13063.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Validate the shared shard index and count before slicing cases or
choosing an output label. Use the same parser in the redstone catalog
harness before it loads game resources.
Pure move (#11857). The item and orb ticks (tick_items, tick_dropped_items, tick_experience),
spawn_destroyed_contents and the orb spawn and merge methods leave game.rs; private methods
become pub(super), the same reach they had in game.rs. Port map rows, the orb merge test that
reads their source and the AGENTS.md table follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the portable rule reads the same cargo metadata packages as the layer check; a listed crate that stops being an engine crate fails too, so a rename can't silently drop the rule. Merged main in (#12983 landed); 16 tests and the check pass on the result.
🤖 Agent: each projectile's run is closed with an id-0 run, so geometry drawn after it never inherits its motion; potions with no id yet keep the camera's.
render/telemetry.rs moves, unchanged apart from its GPU source, to
horizon-client-lib (crates/horizon-client/src/telemetry.rs), still at
horizon_client::telemetry. The renderer keeps only engine facts and
extension points:
- gfx::Adapter / gfx::adapter() / gfx::gpu_kind: the adapter as found,
which telemetry's system() reads (was telemetry::set_gpu).
- crash::on_report(hook): runs each report before it is saved; entry.rs
registers telemetry::keep_crash at startup (was a direct call).
horizon-client-render no longer depends on horizon-telemetry or
horizon-bench: two layer exceptions go (47 -> 45).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: over the ~500-line guideline (+1731/−1519), but it is one screen moving onto the shell plus the shell parts that screen needs, so it lands as is. The collapsed page list lays out from the drawn font, so the clicks match what is shown.
🤖 Agent: mod fluids join minecraft:fluid after vanilla's (source, then flowing) and the content identity, so a client without them is refused. Nothing places them yet.
🤖 Agent: FluidKind becomes a table index with no behaviour change; same_family and is_source compare source rows, and FluidMask stays u8 until mod fluids need more.
🤖 Agent: removed tools/probes, the harness and the doc page: they run only against paper-pumpkin-parity, which lives outside this repo. The 16-line PlayerList.saveAll fix and its 3 Game tests stay (they pass with prepared resources).
🤖 Agent: merged main, keeping both piston_push fns (yours and main's block triggers); pushes only read bodies within 2 blocks of a moving block, which is safe because only the pushed body moves during the pushes.
512 MB is about one 512-chunk view. A shorter far distance now keeps the same
share of it as its share of the tiles that view plans, so travelling with a
short far distance doesn't fill the whole budget with old places.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Game::cracks, celestial_geometry, first_person_arm, hand_geometry and
weather_geometry leave game.rs; private methods become pub(super), the same reach they had in
game.rs. Port map rows, the hand hook test that reads hand_geometry's source and the AGENTS.md
table follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: check_addresses.py tokenizes Rust (strings, raw strings, comments masked) and strips #[cfg(test)] items, so test URLs and translation keys like mco.info don't count as hosts; allowed in tools/addresses.json can only shrink against --base.
The OS, CPU, memory, drive, power and GPU probes move, unchanged, from
horizon-telemetry/src/system.rs to a new engine crate, horizon-system, with
a `Machine` description. The crash report and the renderer's GPU info read
it directly; the crash report's own copy of `command_output` goes (the
shared one opens no console window on Windows).
The Horizon layer depends on the platform only (docs/layers.md), so
telemetry reaches the probes through a first platform crate,
horizon-platform, that re-exports horizon-system as `system`.
`horizon_telemetry::system` stays as a path for the launcher and the
benchmark; `System::detect_in` copies a `Machine` into the payload.
No new layer exception: render -> telemetry stays until M3 moves the
renderer's telemetry module out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the client placement preview stays client-side: TntBlock.prime and PistonBaseBlock.onPlace act only on the server; new harness client_redstone_preview.
🤖 Agent: merged main after #12899: one load_with_visibility_models_and_defaults takes both the decoded entity-models document and the item defaults, so neither is read twice.
🤖 Agent: merged main (vibration_tests.rs: kept both test tails); trampled farmland and broken turtle eggs now emit their game events where vanilla does.
🤖 Agent: fake-player part only now (the per-tick box cache went to #13051); guests still build the others' current boxes each turn, as main does. Benchmark posted above.
🤖 Agent: tools/check_layers.py only reads cargo metadata (--no-deps --offline), so nothing builds and nothing players run changes; with --base it refuses any new exception in tools/layers.json unless the crate itself is new (a split-out crate keeps its parent's).
tools/probes/item_on_block_cooldown ran only against paper-pumpkin-parity, a
framework outside this repo, so nobody here (or CI) can run it. The fix and its
Game tests stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: right fix for main's catalog_matches_hook_points failure: push_voice is a helper in its fragment, and the test reads every native_ fn in the hook-points block as a hook; the moved function is byte-identical. My miss on #12869 (I ran the stamp test, not this one).
AbstractBoat.checkInWater and isUnderwater add a block's y and its water
height in float, and floatBoat lands a falling boat at the float
getWaterLevelAbove() - getBbHeight(), with waterLevel = getY(1.0). Horizon
summed in double, so high up a boat floated about 3e-6 off vanilla's height.
Fixes#13022.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FishingHook.onHitBlock calls Projectile.onHitBlock before it rebounds,
so a bobber powers a target block (target_hit, the criterion), rings a
bell struck on its body (bell_ring), chimes amethyst and tilts a big
dripleaf. Horizon's bobber only posted PROJECTILE_LAND.
The bell and target helpers take a ProjectileHitter (owner, guest,
arrow, type, position) that thrown projectiles and the bobber both
build. New harness test_fishing_hook_blocks_parity.py casts onto
target blocks on vanilla's and Horizon's dedicated servers.
Fixes#12936
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- android_gamepad: Android key codes and motion axes to gamepad::PadState
- Y axes flipped to up-positive; hat D-pad adds to a key D-pad
- Triggers from LTRIGGER/RTRIGGER, BRAKE/GAS or digital L2/R2, so Edges sees all three
- Presses shorter than a frame survive one poll; drift and key repeats never take over
- Selection: picked input mode vs Auto following the last device used
- Pure logic with unit tests; nothing calls it yet
- Adapted from Starling (MIT), credited in THIRD_PARTY_NOTICES.md
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Leashable.computeElasticInteraction turns every attachment point with
Vec3.yRot(float), Mth's float cosine and sine of a float angle, and
checkElasticInteractions adds the whole push in one addDeltaMovement:
v + (force * axis + relative * 0.11). Horizon turned a single lead's
points with exact trigonometry (only the quad lead used Vec3.yRot) and
added the push as (v + force * axis) + relative * 0.11, so a pulled
mob's Motion and yaw drifted once it had turned.
Both ends of a single lead now turn with the same Vec3.yRot helper as a
quad lead, and the push is added as one sum. The sum is the line the
quad lead shares, so a quad lead's Motion now follows vanilla's formula
too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#12869 made push_voice a helper (tools/native-hook-fragments/push_voice.json lists it under
"helpers", with no hook) but left native_push_voice in the hook points' impl block, which
catalog_matches_hook_points reads as the list of hook points: the test failed on main with
push_voice as the only difference. The method moves, unchanged, to the helpers section next to
native_add_toast; its path (game::native_hooks::Game::native_push_voice) is the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reuse the existing component cooldown helper after the final successful
placement and hand replacement, preserving block-first interaction order.
Add real Guest/Game packet regressions and a scoped connected-client probe.
Co-Authored-By: Codex <[email protected]>
tools/test_connected_statistics_save_parity.py and its probe ran only against
paper-pumpkin-parity, a framework outside this repo, so nobody here (or CI) can
run them. The fix and its Game tests stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.move casts the move (up to 8 blocks) through fall-damage-resetting
blocks and water when the entity has a fall and moves a block or more, and
resets the fall on a hit before the move's own drop counts. Boats, derailed
minecarts and a LAN guest's boat move through their own collision and
skipped it. Water is clipped through the shared fluid shape cache, as the
player's ray does.
Fixes#13005.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With HDR on the float frame, an F2 taken at a Screenshot Quality above 1
or a Resolution Scale above 100% drew its larger frame without HDR: no
HDR lighting, no HUD layer and no tone curve. The saved PNG was the plain
SDR picture, much darker than the same view's unscaled F2, and it had no
-hdr.png.
The scaled frame now keeps HDR on the layered float frame and draws in
HDR's own targets at the larger size, without presenting. Its PNG is the
unscaled F2's picture at that size (the world mapped to SDR as
Hdr::sdr_screenshot maps it, the HUD over it by coverage), with its
-hdr.png beside it. Then the large targets are let go, so they don't
stay in video memory, and the window's next frame makes its own again.
HDR off, frames that aren't layered, unscaled screenshots and the
window's own frames are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the large-PR process says the spec check is run by maintainers' agents; nothing enforces it in CI yet, so reviewers should ask for the 'Spec: #' link on PRs over ~500 lines.
🤖 Agent: tools/parity_gaps.py anchored its badge fallback on the vanilla-parity badge this removes; it now inserts at the end of the badge row, and test_parity_gaps checks that order.
From the 2026-11-08 contributors' stage: game.rs (~21.7k lines) and
gfx.rs (~10k) slow incremental builds. tools/check_file_size.py
compares each added, changed or renamed .rs file with the merge base:
over 3000 lines it may shrink or stay, never grow; a new file over the
limit fails. No list of files; third_party/ and @generated files are
not checked. Runs on pull requests (with their base) and in pr_ready.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Writes down the project's decisions of 2026-11-09: pull requests over
~500 lines start from an approved spec issue and an agent check against
it; parity work stays on 26.3 (redstone first), newer servers via Via;
AMD FSR may be vendored but NVIDIA-licensed libraries only as an
optional native mod that downloads them; the JVM bridge is a separate
optional download, never on by default. The PR template points large
PRs at the spec issue.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ludovic, 2026-11-09: the Horizon layer moves to its own repository
after the split, so it builds against the platform's public API and
never the engine. tools/layers.json now says which layers each layer
may depend on (engine: engine; platform: engine, platform; horizon:
platform, horizon). The launcher's and horizon-server's 7 engine
dependencies join the exceptions (47).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The README now says what the harnesses compare instead of a parity
percentage: the "vanilla parity 100%" badge and the "Java parity: 100%"
heading go, the harness table and the player-visible gaps block stay.
tools/parity_gaps.py no longer anchors its badge on the parity badge,
and the README tests check the table without a percentage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Kept tiles at the seam with the near chunks (`Plan::stands_in`) are dropped
on a return and sampled first, as before kept tiles: brought back, they drew
grey ground and caves there until the near terrain loaded. The fine levels
(cells a chunk wide or less) wait while the near terrain loads and the seam
is open, ten seconds at most: their thousands of meshes held up the near
chunks' and the seam's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- SummonCommand.createEntity loads a mob summoned without NBT from
{id:..}: yaw 0 and no level-random draw (Horizon drew one, which also
shifted every later level roll).
- EntityType.create snaps a spawn egg's, a dispensed egg's and a mob
bucket's mob to Mth.wrapDegrees(level.random.nextFloat() * 360).
- SpawnEggItem.spawnOffspringFromSpawnEgg snaps the baby to yaw 0.
spawn_mob_internal takes a SpawnYaw (Level, Created, Fixed); other
callers keep the old draw. Seed sync proof gains three cases (18/18;
main's code: 16/18).
Fixes#12973
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). startup_cpu_tests, runtime_reset_tests, falling_air_tick_tests and
player_save_projection_tests become game/<name>.rs, like the game module's other test
files; game.rs keeps their #[cfg(test)] mod declarations where the blocks were. The bodies
lose one level of indentation and nothing else.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: findNearestMapStructure searches ring by ring across every random-spread set, takes each set's first hit per ring and stops at the first ring with a hit.
A datapack's @e[type=marker,tag=...] selectors got quadratically slower
with every other entity in the level: select_in_level ran the tag/score/team
filter on every candidate before the type test, and tags_of finds each
entity by a linear search (about 40% of the server thread with 8,000
falling blocks around).
EntitySelector tests the type first. With type= and no scores= or team=
(whose holders may be made as they are read), the rest of the extra filter
is a pure test of each candidate, so the candidates of another type now
leave before it. The type test moves into selector_types_match, used for
both passes.
Fixes#13003
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merges main (keeping its NativeServerPlayer fields next to `fake`) and drops
the two parts that reach every server, as asked in review: the players' boxes
built once per tick (BUILDING_CACHE) and `send_shared` for broadcasts. The
first moves to its own PR with a fix and tests; the second only saved a copy
for fake players and is dropped. The fake-player tick benchmark stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Game::attack_mob (Player.attack on a mob) joins the rest of the
player's melee attack in game/melee.rs; it was pub(super) already and stays so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: push_voice keeps one FIFO per (mod, source), so a speaker's blocks play in order while speakers play together; the helper is a stamped fragment.
Write actual per-connection statistics after the existing player body
queue, retaining the current UUID mapping and JSON writer. Cover missing
and stale files through the real Game save command and connected input
fixture, with separate disconnect positives.
Co-Authored-By: Codex <[email protected]>
🤖 Agent: docs only; the order engine < platform < Horizon follows Ludovic's rule (engine never depends on platform), which makes today's exceptions 40, all from the four mixed engine crates.
Pure move (#11857). Game::portal_shape (PortalShape) and try_light_portal
(BaseFireBlock.onPlace's portal) leave game.rs for the nether portal file, which imports
Result and level_flags from the game module for them; private methods become pub(super),
the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: every applyEffectsFromBlocks projectile (throwables, hurting projectiles, shulker bullets) is pushed by the bubble columns it moved through, unclamped, on the server as on the client.
Entity.load reads OnGround and saveWithoutId writes the live flag. Arrows,
thrown items, fireballs, spit, bullets and tridents move by setPos and keep
the loaded flag; a firework rocket and a falling block take theirs from
Entity.move, and a piston push sets it on all of them. Llama spit and
shulker bullets also keep their saved or summoned UUID.
Fixes#12898.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: an ender chest and an end portal get their block entities, and the ender chest's holds none of the player's items, so none are saved to the chunk (EnderChestBlockEntity saves nothing).
push_voice mixed every queued chunk at once, so a speaker's second block
played over its first, and finished chunks were popped from the front of
one shared queue whatever their source. Each (mod, source) pair now has
its own queue: a source plays its head chunk, hands the rest of a buffer
to the next one, and sources play at the same time. Positioned audio
fades linearly to silence at 48 blocks (distance_gain(None, _) was always
1). A source holds two seconds at most, 64 sources play at once, and
nothing is queued without an output device or outside the window's game.
The generated host files are gone on main: push_voice is a fragment
(tools/native-hook-fragments/push_voice.json), stamped at build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
New crate (Horizon layer, no dependencies) holding the official server
and its earlier and status addresses, the cosmetics and telemetry
services, the STUN servers and the Friends help page.
horizon-telemetry, horizon-cosmetics and horizon-friends keep their
public constants, now read from it. tools/addresses.json loses 7
entries (82 -> 75). Same values, so players see no change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/layers.md#engine: horizon-core, -world, -protocol, -items,
-command, -client-core and -assets hold vanilla's rules with no desktop
in them, so a port can build on them. The check now fails when one takes
a crate of the repository outside that list, or winit, wgpu, cpal,
gilrs, arboard or ureq (normal and build dependencies; tests may use
anything). All seven pass today.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A few thousand falling sand blocks took the server tick to 600-800 ms:
tick_one_falling took each block out of self.falling and put it back, every
tick, and each of the two shifted every block after it (55% of the server
thread in memmove). The block was only taken out so fall_step could borrow
it beside self; nothing the move reads looks at the falling blocks.
It now moves a copy and writes it back where it is. One that lands, drops
or goes still leaves the list before its block is placed or dropped, and
on an error the list keeps it as it was.
Fixes 12991
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/check_addresses.py reads the string literals of shipped Rust
(tests and #[cfg(test)] items skipped) and finds every host: a URL's
host or a whole host-name string; placeholders and local IPs skipped.
tools/addresses.json: the Horizon layer's home, vanilla's endpoints,
strings that are not addresses (with reasons), and today's 82
addresses outside the home, which may move with their code but never
gain a host or grow (--base). Messages point at docs/layers.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/check_layers.py reads the four workspaces' cargo metadata (no
deps, offline) and tools/layers.json (engine < platform < horizon, plus
tool). It fails on a crate with no layer, an upward normal/build
dependency 'allowed' doesn't list, an 'allowed' entry a branch adds
between known crates (--base), or shipped code on a dev tool; each
message names the docs/layers.md section. Stale exceptions are notes.
Starts with today's 40. The PR template gets a Layer section with the
reviewer checklist.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Game::tick_blocks and tick_blocks_with_scratch leave game.rs; private
methods become pub(super), the same reach they had in game.rs. The AGENTS.md table follows
the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Per Ludovic's direction the engine never depends on the platform or
Horizon. Each layer says what belongs, real examples, what never
belongs and what it may depend on; a decision table says where a new
feature goes; a reviewer checklist. Linked from CONTRIBUTING.md and
CLAUDE.md. 40 upward dependencies today.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: removes Qwerner88880's trusted-reviewer line after the Team vote; the pinned login set in test_nightly_set is updated in the same commit, so verify stays green.
Pure move (#11857). Game::tick_one_falling, fall_step and drop_falling_block leave game.rs;
private methods become pub(super), the same reach they had in game.rs. The port map row
follows the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Step 2 of #6301 (horizon-world, and what the client needs to build): FluidKind
is a fluid's index in a table, its minecraft:fluid network id, with vanilla's
five first and the enum's names kept as constants. Each row has the fluid's
template (water or lava behaviour), its family's source and flowing forms,
and NeoForge's tick delay, drop-off, slope distance and source conversion,
which vanilla's leave to their template's rules (lava's ultrawarm ones
included). FlowingFluid's code reads them from the table; matches on the
five fluids match on the template. No behaviour change: fluid masks stay
8 bits (they widen with the behaviour cache's version when mod fluids join).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: an old chunk keeps the blending heights computed from its neighbours in its save, as vanilla's BlendingData does, instead of recomputing them on load.
Maps each crate and the mixed crates' modules to a layer, lists today's
11 upward dependencies and every built-in outside address with its use.
From duckybertduck's 3-layer proposal and arismoko's mod API boundary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). GameCpuResources, EntityModels, Loaded, LoaderMsg, PendingWorldEntry,
WorldRestLoader and their impls, run_loading and write_report leave game.rs, which imports
them back; private items, fields and methods become pub(super), the same reach they had in
game.rs. The port map row and the AGENTS.md table follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: an open double chest menu closes when one of its halves is broken (the container's size no longer matches), as CompoundContainer.stillValid does through ChestMenu.stillValid.
🤖 Agent: Polytone particle emitters and dimension environments now load in assemble's existing scoped batch instead of after it; the values and their order are unchanged.
A clock of a thousand pistons spent most of each tick on its pushes: every
moving block walked every entity in the level and read its box, and the
collision view looked each piston up by scanning two lists.
- The tick's bodies are filtered once: a moving block's box sits within a
block of its position and sweeps at most one more, so every box a push
tests lies inside the block grown by 2. Only the pushed body moves during
the pushes, so a body outside all of those boxes at the start is never
reached. Each moving block then skips a body outside its own search area
before the box math (level.getEntities in moveCollidedEntities).
- Step heights are read once per tick, not once per moving block.
- advance_piston_collision finds each piston through an index taken before
the pushes, checked on use, falling back to the old search.
Fixes#12974
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrowableProjectile.tick and AbstractHurtingProjectile.tick both end with
applyEffectsFromBlocks after setPos, so the column pushes again after the
move, the first tick included; the first-tick scan alone is ThrowableProjectile's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's Entity makes its UUID from the first two draws of its own random
(Mth.createInsecureUUID), and LivingEntity.<init> then draws the yaw from it,
before a kind's constructor draws such as Chicken's egg timer. Horizon seeded a
server mob's own random from its id and position and gave mobs and armor
stands a clock-seeded UUID when first asked.
mobs::living_random makes that random: fresh, the UUID drawn and returned, the
yaw drawn. Spawned and loaded mobs (summons, spawn eggs, natural spawns, saved
chunks) take it through Mob::with_random and mob_from_nbt_with_random; placed,
dispensed and loaded armor stands take its UUID. A UUID in the tag still wins.
Mob::new keeps its id-seeded random for the client's copies. Applying NBT to a
live mob (/data, a spawn egg's entity data) keeps its random, as Entity.load
fills in the same entity.
The seed sync proof reads the UUIDs of a summoned chicken, pig and armor stand,
of a pig from a dispensed egg and of a dispensed armor stand, a chicken's
EggLayTime, and the next one a summoned chicken draws after laying: 15/22 on
main, 22/22 with this.
Part of #12561
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`mip_chain` generated the sprite array's mips one layer at a time: at startup
and at every pack load or reload, a few thousand layers at 64 px for a 64x
pack ran on the pack worker alone, while the rest of the reload's decode
stages already spread over `par::par_map`.
Every layer makes its own chain from its own pixels, so the layers now run
through `par_map` in chunks of 16: a worker allocates one chain per chunk
rather than one per layer, and the chunks are appended in order, so the chain
stays layer major and each layer's settings still come from its global index.
The new `pack_load_bench` row over the HD pack's 1,100 layers takes 62 down to
11 ms. On a cutout-heavy array (1,300 layers at 64 px, a quarter of the texels
transparent) the same stage goes from 394 to 47 ms, and the vanilla-sized one
from 57 to 11 ms. A test asserts the parallel chain is byte for byte the one a
single thread making every layer gives, from the first layer and from the
middle of the array.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
- A float option's slider position is its stop, the maximum the last one,
as `slider_value` reads it: the arrow keys no longer stick when the range
isn't a whole number of steps. `nudge_option` is gone; a test walks a 0.3
step both ways.
- The right button steps no value back, as on vanilla's `CycleButton`.
- Apply keeps its violet primary look.
- A link found by the search goes back to the page it was found on.
- `Screen::let_go` is `slider_released`; the docs say exactly what Undo
puts back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The inventory's player and the smithing table's armour stand turned the
model's vertices into HUD quads keeping only position, UV and colour, so
the armour glint (overlay ARMOR_GLINT) and the item glint were dropped.
- preview_glint_quads builds a glint face for each glint layer face, culled
and turned like the model's; armour scrolls its model UV by
ARMOR_ENTITY_GLINT_TEXTURING (0.16), items keep their transformed UV.
- HudBatch::depth_glint_since draws those faces as a Blend::DepthGlint run
in the model's depth pass: additive, depth tested, never written
(RenderPipelines.GLINT).
- hud.wgsl binds enchanted_glint_armor and picks it for u >= 4.
Fixes#12666
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Game::update_world, prepare_lod and clear_lod leave game.rs; private
methods become pub(super), the same reach they had in game.rs. Port map rows and the
AGENTS.md table follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Their vertices were drawn under the camera-only run, so frame generation and
temporal upscaling reprojected them as if they stood still. Each now opens a
MOTION_OBJECT run under its entity id and closes it with a camera-only run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Now that an ender chest is an EntityBlock (`for_block` returns a Chest), a
generated ender chest gets its real block entity instead of falling through
to the unmodelled raw list. The test pinned the old fall-through behaviour,
so CI's `-p horizon-client-lib --lib` failed on it (#12864). Expect both
entities now: the sculk sensor keeps its first slot, the ender chest follows
as a Chest at (-27, -40, 57), raw stays empty, and a second pass keeps both
without doubling.
Co-Authored-By: GLM <[email protected]>
A half-width Apply beside Undo drew its X glyph over its label. Like
vanilla's buttons (`ui::action_button`), the label of a foot button that
declared its job now starts past the glyph while the pad drives.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pass that writes the item list between questions would rebuild the
columns each time, which costs more than the scan. After a write the
first question now scans as before; the second with nothing written in
between builds them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Game::use_hand, apply_consume_effect and release_bow leave game.rs;
private methods become pub(super), the same reach they had in game.rs (the game module and
its children). Port map rows and the AGENTS.md / docs/architecture.md tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Step 1 of #6301 (#5553's JSON): data/<id>/horizon/fluid/<name>.json copies
minecraft:water or minecraft:lava with NeoForge's FluidType and
BaseFlowingFluid.Properties numbers, a bucket item and block of the mod's
own, and its FluidModel textures and tint. Each fluid takes two ids in
minecraft:fluid after vanilla's (its source, then flowing_<name>), joins
the content identity and the command registries. Nothing placeable yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Forward DryLevel's side flag so client placement does not prime TNT or
queue piston moves. Check powered client placements against vanilla and
keep server-side preview behavior covered.
Co-Authored-By: Codex (GPT-6) <[email protected]>
It reads HORIZON_TEST_RESOURCES, which the unit test run (and CI verify) does
not set, so it failed with an unwrap on None there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Options and its impl (player_name, read, parse) leave game.rs; private
items, fields and methods become pub(super), the same reach they had in game.rs (the game
module and its children). Port map rows and the AGENTS.md table follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tall window was 427x240 at its automatic scale too, the search case
pressed the shown name rather than its next arrow, and two draws ending
the same widget frame read the first one's widgets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
load_songs read data/minecraft/jukebox_song only and named every song
minecraft:<file>, so a content mod's disc never found its song. Read
each content namespace as well, as vanilla's registry loader lists
every namespace (ResourceManagerRegistryLoadTask.load).
Part of #12910.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017xeMvE6sM435T8F5rAyvWU
LocalPlayer.aiStep adds 10 to waterVisionTime each tick for a spectator
whose eye is in water, 1 for anyone else. Horizon always added 1, so a
spectator saw the water darker and foggier for 30 seconds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`decode_components` read the 23 component IDs it models one at a time with
`Tag::get(&str)`: each call encodes the ID to UTF-16 and walks the compound's
`BTreeMap`, whether or not the stack carries it. Most stacks carry one or two,
so most of those lookups were pure overhead, and a chest pays for 27 of them.
`Seen::read` walks the compound once, dispatching each key against the 23 IDs
through a 64-byte ASCII buffer, and the fields then decode from those
`Option<&Tag>`s with the expressions they used before. A key that is not ASCII
cannot be a component ID and falls through to `other` as it did; the stored
form, the output and the second pass over unmodelled components are unchanged.
A one-component stack decodes in 0.36 instead of 0.93 us (2.6x) and an empty
compound in 0.089 instead of 0.56 us (6.3x). The decode stage of a 256-column
save batch drops from 106 to 81 ms (1.31x). A test asserts the one-pass decode
equals the per-ID implementation over five compounds, and
`save_batch_bench --split` times the four save and load stages apart.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Shaders is a `Screen` now: the pack list and the picked pack's screens are
sheets read from the pack, and the shell draws and drives them. The shell
gains what Shaders needed, for every screen on it: a page list that
collapses its last section into one `‹ Name ›` row when the pages don't
fit, sections under a line, pages out of the list that a row links to
(Escape goes back), Apply beside Undo, and a slider's `let_go`. Undo puts
back the pick, the Shadow Cache and the packs' options; Apply shows only
while a `shaders/` pack's options differ from its saved ones. Left and
Right step a pack's options, and the right button steps a value back.
ShaderUi's own drawing, scrolling, focus, search and page list are gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resource packs' particle modifiers now change the particles of the types
they target as each one spawns: colour (channels, packed colour or a
colormap), lifetime, size and speed. Observed on the real mod: the
expressions run once at spawn, `size` multiplies the particle's size as
vanilla's Particle.scale does, and `speed` follows setPower.
Modifiers with a `filter` are skipped: particles here keep the sprite of the
block or item they carry, not the block or item itself.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerClockManager keeps one clock per WorldClock: the End's (the End sky
flash's) is saved in world_clocks.dat and advances with advance_time, apart
from the game time. Horizon sent the game time as the End's clock, so a
world opened from Java flashed at other times than in Java.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under HORIZON_HDR_FRAME=float, a 0.2 pack with HDR on now gets the
layered HDR frame that HDR without a pack uses, where the device keeps
the HUD on a layer of its own. Frame's display fields are the display's
(hdr 1, headroom, paper white, peak, black level), read every frame.
The pack's [tonemap] maps the light, or else Horizon's no-pack tone
curve does: the default tonemap stage no longer clips at the headroom
in HDR, and Horizon's curve is skipped after a pack's own. The HUD is
laid over the picture by coverage as without a pack. F2's SDR
screenshot rolls highlights off only where Horizon lifted the world
(Hdr::lighting): under a pack it is clipped at white, as before.
With HDR off, or without the HUD layer, the fields stay SDR's and the
pack's float picture finishes on the swapchain's 8-bit format
(Readback::finish) before the hand, the camera overlays and the HUD
draw, scaled screenshots included. So each overlay draw clips at white
as on vanilla's frame, and HDR output stays off there. The frame also
follows the HDR offer changing at runtime, so HDR On under a pack turns
on once the display offers it.
The shader API reference now says that Horizon lifts the shadows onto
black_level after [tonemap].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Share the existing pure glide arithmetic with Mob travel while retaining
its collision path, NoAI exclusion and existing damage hooks. Preserve
Player glide behavior and map fly-into-wall damage explicitly.
Co-Authored-By: Codex <[email protected]>
Closing it now moves the items back and leaves the entity empty, and its saved form
has no Items: EnderChestBlockEntity saves nothing of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first X-Forwarded-For hop is what the client sent, so the per-address limit
could be dodged; take X-Real-IP or the hop the proxy appended. revoked.toml is now
checked before a build is dispatched, and status polls (one GitHub API call each)
share a rate limit like requests do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While a new chunk decorates, a full neighbour stands in the region as
ImposterProtoChunk(chunk, false): its setBlockState, setBlockEntity,
removeBlockEntity and markPosForPostProcessing change nothing, and
WorldGenRegion.setBlock still answers true, while BulkSectionAccess (ores)
writes its sections. Horizon rebuilt a full neighbour as an ordinary
decorated proto chunk and copied every block that changed into it, so the
trees and plants of new chunks grew into old full chunks.
proto_beside_final now marks its proto chunk full (not saved). Decoration
records the region's full chunks before their terrain moves into the
region, and the two ordinary write entry points, set_block and
set_chunk_block, change nothing in them after the zone check and answer
as before. Section writes are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
moveEntityByPiston's applyEffectsFromBlocks: the entity-sensing blocks the
pushed box covered sense it during the push, so a plate powers its
neighbours before the piston pushes the next entity.
Harness: tools/test_piston_push_plate_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: set_custom_data accepts the tag as an object or as SNBT text (LENIENT_CODEC) and merges it into the existing custom_data component key by key.
🤖 Agent: Polytone post chains, their targets and uniforms; PolytonePost::read is None without chains, so packs without them pay no frame cost. Holds #12539 and #12653's fade.
cpal reports a dead ALSA stream (alsa::poll() returning POLLERR, over
and over) as a backend error, which was only logged: the game stayed
silent until a restart. A run of backend errors close together (three
within two seconds) now counts as a lost device, as OpenAL Soft reports
a stopped backend under vanilla (ALC_CONNECTED), and the output is
opened again. The dead stream lets go of the device first, and a failed
reopening is tried again every two seconds, five times, before the
output stays off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: AbstractPiglin's CanPickUpLoot defaults to true when absent, Piglin always writes CannotHunt, and bogged save and load sheared; the rebase keeps #12685's tracker asserts.
Projectile.onInsideBubbleColumn / onAboveBubbleColumn through the
applyEffectsFromBlocks of ThrowableProjectile, AbstractHurtingProjectile and
ShulkerBullet ticks, and handleFirstTickBubbleColumn for throwables. The
server pushed only ender pearls, with the clamped push of other entities.
Harness: tools/test_projectile_bubble_column_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resource packs' Polytone post chains (wiki "Shaders") run their vanilla
post_effect chain over the world while their activation_condition holds,
with a uniform block per expression uniform, the PolyGlobals block (the
world's projection and camera, the sun's angle, the time of day and the
frame's tick delta, the player's position) and the packs' post_targets,
kept between frames, in every chain. Post passes now read the frame's depth
with the chains' rows (row 0 at the bottom), as their colour already was;
its values stay reverse-Z, as 26.3's are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A GPU particle type's fade (wiki "GPU Particles": the fractions of its life spent
fading in and out) ramped its alpha linearly. Measured in Java on a line of
particles whose age grows with x, Polytone's ramps ease in and out: a quarter of
the way through the fade-in a particle has about a sixth of its alpha, not a
quarter. Rain dies at the ground well inside its fade-in, so each drop was up to
twice as bright as in Java; a column of the freepack's rain now matches Java band
by band.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The float HDR frame's HUD target was in the frame's float format, so a
HUD draw no longer clipped at white before the next one blended over it:
a text highlight added over a light backdrop, then a 50% black panel,
came out at 0.65 instead of vanilla's 0.5. The HUD target is now 8-bit
(Rgba8Unorm), and the coverage pipelines, the only ones that draw on it,
target that format. The composite subtracts the backdrop as the 8-bit
target holds it, so pixels no HUD draw touches stay exactly the world.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A clock of a thousand pistons froze the window: the client rebuilt
World::moving_pistons after every BlockEvent that starts a move and every
block change that ends one, and each rebuild compared every previous entry
with every current one and scanned the pending uploads for every fallback
entry. A pulse sends about three packets per piston, so it cost O(N^3).
Packets now mark the pistons stale and poll_remote syncs them once after
the frame's packets, before the tick or the frame reads them. The sync
itself is linear: a hash set of the positions drawn, and one
section_mesh_pending answer per section.
Fixes#12901
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resource packs' Polytone config entries (wiki "Polytone Configs") get a page:
the card of a pack with options carries a small sliders button at its right
edge (as Respackopts does), which opens that pack's options in the frame of
Horizon's settings screens. Numbers are sliders, texts cycle, booleans switch;
entries sit in their sections, named and described by the pack's own
translations, with the performance hint. Done keeps what changed in
polytone-configs.json beside options.txt, and the packs load again with it, so
overlays and expressions that read the configs follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NikitaMGrimm's group from #12517: oak and cherry boats, a bamboo raft,
minecarts and a command block minecart, read live, after an unload and
reload, and three ticks later. 73/73 against vanilla.
Co-authored-by: NikitaMGrimm <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: native builds try the instance's commit first and move to the pin only for a pinned mod left out as OtherVersion (made_elsewhere, unit-tested).
🤖 Agent: add_toast is a thin helper over the existing toast path, and toast_allowed gains the kind "mod", so mods' toasts follow the player's toast settings.
A boat's water patch (RenderTypes.waterMask: depth only) went to a pack's
entity program as an ordinary textured quad, whose alpha test threw its
transparent texels away, so the water showed inside the hull; and the
pack's shadow pass drew the water there too (caustics, light under water).
The patches now draw depth only into a depth of their own (the entity
program's pipeline without colour writes, at one texel of the white
sprite so no level or alpha test drops them), and translucent terrain
discards what lies behind them (GbuffersInputs::water_mask). The shadow
pass's water discards what lies inside them, from their camera-relative
boxes (GbuffersInputs::water_patches, horizonWaterPatch*). A hull before
its patch keeps its motion matching (at_velocity).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This PR fills de_de's missing keys, so the status the test used to show the
English fallback is German now. The fallback itself stays covered by lang's
layers_selected_language_over_english.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_set_player_fake / native_expect_fake_player / native_is_player_fake
and NativeServerPlayer.fake. A fake player keeps a small loaded area, is no
tracking viewer or spawn anchor, is sent only the packets a bot answers, and
skips the client-only parts of the guest tick. Adds a tick benchmark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
living_experience_drop, potion_splash_v2, player_died_v2, entities_in,
living_helpers (swing, fire, effects, attribute base, use item, lightning),
npc_mob_path, advancement_display, and entity_join_level for displays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractPiglin reads CanPickUpLoot with default true, so a piglin or brute
summoned or loaded without it picks up loot. Piglin always writes
CannotHunt; the brute doesn't. Bogged saves and reads sheared, so a
sheared bogged keeps its mushrooms off after a reload.
Fixes#12521.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Opening a world in front of a block left its outline on Loading terrain's
panorama, drawn with the panorama's camera. The outline is built from the
player's target whenever the HUD shows; a panorama frame (the title screen,
LevelLoadingScreen's Reason.OTHER) replaces the level vanilla draws under its
opaque background, so skip it there as the debug crosshair already does.
Fixes#12872
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A guest's command (/tp, /data) or an explosion during the players' pass
moves items without changing the list's length or last id, so the
columns could miss an item for the rest of that pass. Key them on the
item list's stamp (StampedItemColumns): any write rebuilds them on the
next question. The scans read the list, so only real writes (a pickup,
a drop, a push) rebuild.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
getBooleanOr and Codec.BOOL take any numeric tag's nonzero byte. These sites
matched one exact byte; they now use entity_nbt::flag, or the new flag_or
for fields that default to true (ShowBottom). The render crate's signs use
the same int_value test.
Fixes#12845.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AllayAi sets ITEM_PICKUP_COOLDOWN_TICKS to 60 when an allay throws an
item, and the Brain saves it as {value: ...} and reads it back. Horizon
kept the cooldown for the allay's pickups (extra.ints[3]) but rebuilt
the saved Brain memories without it, and never read it.
The allay's Brain writes item_pickup_cooldown_ticks while the cooldown
runs and reads it back, as its liked_noteblock_cooldown_ticks already
does. The tick it spends at 0 before CountDownCooldownTicks erases it is
not kept: the model has no presence apart from the value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Other open PRs (and tests) call ParticleEngine::tick(&level); only the game's
render tick needs the tracking-body reader. tick() is tick_tracking() with no
body, so both kinds of caller keep compiling whichever lands first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: this is the top of the agent-control stack (12715 to 12816): screen waits, durability, --sneak, in-the-way errors, events --limit; the stack's rustfmt is fixed.
Implement on-demand native build request handling and status checks in horizon-mod-registry (#11859). Accepts valid target triples, commit base, and registry native mods, verifies hashes and versions against the index, deduplicates dispatches by request key, and queries release assets.
Black Level has no Auto: 0, the default, is true black, and the saved value
is the lift it says. Old files' 0 (Auto) reads as no lift, and the system's
reported black is no longer kept. Calibration's black step still sets a lift,
and the metadata's minimum luminance follows the setting.
Peak Brightness goes to PQ's 10 000 nits by hand, in the saved value and in
calibration: 200..2000 by 50 as before, then by 500. 0 stays Auto, and saved
values keep their meaning.
Paper White's slider has its 48 steps, so every 10-nit value is reachable
by keyboard and controller. Paper White's description no longer claims the
menus, which UI Brightness sets.
Closes#12841
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds Host::push_voice(samples, sample_rate, at): a mod queues mono samples and
the output callback mixes them with the game's sounds, with distance gain and
direction when the mod says where they were spoken.
The roadmap entry "Proximity voice chat as a native mod" names what is missing
for such a mod: a way to carry voice between players and an audio path in the
game. The carrying side already exists (net_send_to_server, native_net_from_server
on a mod's own channel); this adds the audio side.
- horizon-client-render: VoiceChunk and a queue in Mixer, mixed in mix() before
the limiter; MixerBackend::push_voice caps the queue at two seconds and refuses
a burst rather than delaying it; Audio::push_voice forwards to it.
- horizon-client: Game::native_push_voice, listed in native-hooks.json under
helpers so the generator emits Host::push_voice (API 360), its trait default
and the game's implementation.
- tests: a queued chunk is mixed centred and dropped after it played out; a
position pans it by the listener's right vector (facing south, -X is right).
The audio itself could not be listened to on this machine (headless, no output
device), so the mixing is checked by tests only.
🤖 Agent: unblocks main's verify on both counts: the reviewer set matches .github/trusted-reviewers.txt (itarqos5), and the 1.12.2 upgrade case runs only where the datafix tables are prepared (#12756).
🤖 Agent: the raid banner carries item_name/rarity/tooltip_display on the wire like Raid.getOminousBannerInstance; hide_tooltip=false is omitted as optionalFieldOf does.
🤖 Agent: the first-tick bubble column check moved into tick_with, so the server thread runs it too; pearls keep their creature-tick column hooks, as vanilla does both.
Since #12756 the pre-1.13 upgrade reads datafix-tables.json from the prepared
resources. CI's verify has none, so the test panicked there and on every
checkout nothing was prepared in. It now checks 1.12.2 only with the tables,
1.16.5 always.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.teleportTo builds its transition with no motion, so
teleportSetPosition leaves the entity still (/tp, /spreadplayers, a
cross-level /tp, the mod API's teleport), and TeleportCommand.performTeleport
then zeroes the vertical motion and grounds the target unless it is a
fall-flying living entity, and stops a mob's navigation. An end gateway's
or a vehicle's own teleport keeps its motion as before.
Fixes#12834
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new connection cleared the inventory and armor but kept the off-hand item,
so a server that never sends slot 45 left the last server's item as a ghost.
ClientPacketListener.handleLogin makes a new LocalPlayer with an empty Inventory.
Fixes#12809
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla keeps two fields: LivingEntity.damageCooldownTime, the hurt
cooldown a hit sets (never saved, kept by an in-place /data load), and
Entity.invulnerableTime, saved as invulnerable_time, which counts down in
commonTick and makes the entity invulnerable while above 0. Horizon used
one field for both, so a hurt mob saved its cooldown as invulnerable_time
and a summoned invulnerable_time only acted as a hurt cooldown.
Mob gets invulnerable_time, the only field the key loads into and saves
from. It counts down at the start of the mob's tick and joins the
Invulnerable flag in the damage guard (#bypasses_invulnerability passes).
The cooldown stays transient and is carried across a /data rebuild again.
Conversion copies invulnerable_time, and the skeleton trap's riders and
horses start with 60 of it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main landed the pack biomes' ambient particles meanwhile; this branch keeps
their music and ambient sounds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main added saved_templates.rs, whose test wrote resources.templates directly;
the templates are now a TemplateStore, so the test fills its loaded map.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractHurtingProjectile.tick moves the fireball to the hit location for every
hit that is not a MISS; DragonFireball.onHit then does nothing for its owner, so
it waits there with its motion instead of taking the free step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Packs write "tag": "{a:1b}"; the function ignored it, and an existing
custom_data component written as text was replaced instead of merged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The API call failing (or refused) now runs horizon-server stop/restart, whose
error ends the job, instead of reporting success either way. The panic message
reuses the translated "Could not run {program}: {error}" line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ender_chest and end_portal are EntityBlocks in vanilla 26.3 but Horizon's
BlockEntity::for_block had no handler for them: they fell through to the
container_size() fallback, which returns None for both, so no block entity
was created and /data get block answered "not a block entity".
Add explicit handlers: ender_chest -> Chest with 27 slots (matching
container_size), end_portal -> new EndPortal variant with no data. Register
EndPortal in fits/id/contents/slots/slots_mut so it behaves like the other
zero-slot entities (DaylightDetector, Bell).
Also fix two traps found in review:
- close_ender_chest removed the block entity entirely; it now copies the
items back into self.ender_items and leaves the entity in place, so a
/data get block after opening/closing still reports the entity.
- Containers::view excluded Beehive/Campfire/Brushable from being treated
as containers (so droppers don't insert into them); EndPortal needed the
same exclusion.
End-to-end verified: /setblock ender_chest/end_portal then /data get block
returns the correct id, and accessing the ender chest's container leaves
its block entity intact.
Co-Authored-By: Nemotron <[email protected]>
should_suppress_key was only called by its tests; the window now asks it for every
key pressed while a text field is focused, as GLFW never delivers the keys an
input method consumes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractWindCharge.makeBoundingBox builds the box from y - 0.15F. Pistons,
cobwebs, water, other projectiles' hits and the punch pick box all use it.
Harness: tools/test_wind_charge_box_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
alive_player_near (BaseSpawner.isNearPlayer, asked by every spawner with
a player in its chunk, every tick) built each player's full record, box
included, for a distance test. It now tests the guests' positions
directly. mob_world_base collected every player to read the first one's
position; it now takes the first only. Same players, same results.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A replan dropped every sampled tile outside the new plan, so a teleport back,
a trip out and home again or the far distance lowered and raised sampled the
whole view again, and asked again for all its chunks' detail. Tiles leaving
the plan are now kept (lod::kept) with their detail masks, for 10 minutes and
up to 512 MB, the finest dropped first; a return takes them back, the
coarsest level first and the next once the meshes are in, so the view fills
coarse to fine as sampling does. A refresh or a newly kept chunk forgets the
kept tiles it makes stale, and tiles of chunk-wide cells over the full-detail
columns are sampled again as before. status reports lod.kept_tiles and
lod.kept_mb.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A ticking skeleton, zombie or husk that left the snow or the water saved the
conversion and affliction times it was loaded with; the live counters now
win, so it saves -1 as vanilla's ConversionTracker does. apply_entity_nbt
copies in_powder_snow across the rebuild, as it does last_hurt, since
Entity.load leaves isInPowderSnow alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
guest_pickups scanned every item entity of the level for every player,
every tick. The players' pass now asks the item columns first (the
index hoppers already use, kept for one pass in which items do not
move), and scans only for a player whose 1.5-block reach meets a column
an item's box reaches. Items taken and their order are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.addAdditionalSaveData writes last_hurt_by_mob and
ticks_since_last_hurt_by_mob while the mob that hurt it is recorded, and
baseTick drops that record once the attacker is dead or 100 ticks have
passed. Horizon kept the record for its AI but never saved it, and a
value read from loaded data was written back as it was, for ever.
A mob's saved data, and the view advancement criteria test, now write
both keys from the live record while the attacker is a living mob in
the level and at most 100 ticks have passed, and drop both otherwise.
This is a save-time projection: the record itself, its end for the AI,
no_anger, a player attacker and reading the keys back are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ChunkMap keeps, for each column, how many of the 3x3 columns around it are
resident, updated on insert, remove and clear. ticking_chunk (natural
spawning's candidate filter, scheduled ticks' can_tick, random ticks) then
asks one map instead of nine for every ticking chunk, every tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`PalettedContainer::pack` unpacked all 4,096 cells of a section into a
scratch `Vec<u32>` to find its distinct values, then re-packed them. A
section that holds one value (air, stone, anything filled by one block)
saves as one palette entry and no indices, and that is most sections of a
column, so the unpack was most of their save cost: 3.85 us to pack one.
`uniform()` answers from the stored words instead and returns the same
`Packed` at 0.03 us; a list or global palette compares its words with the
repeated pattern of the first cell, so nothing is allocated and a mixed
section pays one scan that stops at the first mismatching word (measured
at no cost). The saved bytes are unchanged: one distinct value was already
`bits: 0` with no data. One saved column goes from 802 to 704 us.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
An eye_of_ender from /summon or a saved chunk was kept as an inert
compound: it never moved, never shattered, and saved no Item. Vanilla's
EyeOfEnder.tick moves it by its motion (it has no target without
signalTo), and on tick 81 it shatters without dropping its item; it
saves only its Item (one of it, an eye by default), not its life.
Fixes#12807
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A person at the computer could spoil a screenshot sequence, a
HORIZON_TICK_SCRIPT scenario or a record-replay with a stray key or mouse
move, and an agent's window took their pointer.
- While one of those runs, the window's keyboard, mouse, wheel, cursor and
IME input of origin user, raw mouse motion, mods' raw keys and the
hardware pad don't reach the game and aren't recorded. The run's own
input (the socket, the replay) still plays; closing the window still
works.
- A person clicking into the window or away from it during a run takes
effect when the run ends: a replay's look and clicks depend on the focus
and the grab.
- During a run the pointer grab stays inside the game; when it ends, the
system pointer follows the grab the run left.
- A visible game an agent starts opens without the focus, and its grab
stays internal (as --offscreen's does) until a person clicks into it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each player's range check, removals and RemoveEntities packet are its own slice of the level's entities, so the sweep of the players of a level now runs through horizon_threads::par_chunks_mut (#8158): what they start tracking meets the pairs after, in the players' order, and every player's packets keep their order, as vanilla's do. HORIZON_SERIAL_TICK=all keeps the sweep on the server thread, the serial reference the parallel phase is checked against.
Co-Authored-By: ZCode (GLM) <[email protected]>
With HDR Lighting off Horizon lifts nothing, yet the SDR screenshot's
shoulder still rolled 0.6..1 of paper white down, so white came out at
0.8 in linear light (230 of 255) against the HDR-off screenshot's 255. The shoulder is now a
pipeline constant, off while HDR Lighting is off: the world is clipped at
white as SDR draws it, and the HUD composite gives the HUD's target back.
Refs #12628
Co-Authored-By: Claude Opus 5.5 <[email protected]>
inventory_changed is heard once per changed kind per tick; the *_typed
helpers return NativeInventoryError; Cursor and Crafting kinds (live only);
create_player_file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Add touch::aim: camera turn from a drag, tap ray through the view, hotbar slot under a finger
- Add which held items act on a short tap and which aim with the crosshair
- Move the attack gauge to the hotbar when the touch scheme has no crosshair
- Add look sensitivity (overall, horizontal, vertical) to the touch settings, sanitized
- Adapted from Starling (MIT, mostly by Vasysik); credited in THIRD_PARTY_NOTICES.md
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Use the picked manifest's frame before a load outcome is known, preserving
one upfront renderer replacement and one compile on a successful pick.
Follow the resolved selection and its recorded outcome so a refusal,
validated API change, missing path or disabled owner can update the frame.
Keep rejected fingerprints across renderer resets and retry only changed
files. Frame attempts stay bounded by key changes; pack inputs are unchanged.
Known limits: existing pick/drop and hot-reload transition-frame ordering;
invalid edits followed by a later rebuild fall back to no pack until fixed;
split guests and shaders-format pending lifetime beyond this key change.
No request replay or transactional publication. Step 5, API 0.2 HDR output
and linear blending remain out of scope.
Closes#12799
Co-Authored-By: GPT 6.1 Sol <[email protected]>
A mod's configured features kept their 1.21.1 `config` wrapper: the carry
only rewrote block states, while the `config` unwrapping, patch and
placement rewrites lived in the world-pack upgrade. 26.3 skips a feature
that keeps `config`, so no carried mod ore (Occultism silver, Iron's
mithril, Powah uraninite) ever generated. The carry now runs the
world-pack upgrade's feature and placed-feature rewrites too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rotated_grid took each of its four taps at the two mip levels around the
exact level and mixed them by fract(exact) in the shader: eight texture
instructions. The sprite sampler's mip filter is linear, so one tap at the
fractional level is that mix; it now takes four. GPU time per frame on the
benchmark scenes (RX 580, 1080p, render distance 16, same binary): forest
-17%, ocean -15%, village -17%, mountain -14%. The picture differs only by
the GPU's limited-precision level blend.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`light_level` is the block light a particle gives itself (wiki "Custom Particle Types"): the
particle is drawn with at least that block light, as the GPU particles already were. Measured
beside the real mod at night: a lit, light_level 8 particle draws warm and bright where Horizon drew
it with the dark world light, as a pack's light shafts under a canopy.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player.tick resets the attack strength only for a different item, but
LivingEntity.detectEquipmentUpdates re-applies the equipment's attribute
modifiers whenever the stack differs (!ItemStack.matches). tick_hand gave
the player its modifiers only on the item-change reset, so swapping to the
same tool with Efficiency (or enchanting it in place) never set
minecraft:mining_efficiency and digging stayed at the plain tool speed.
Fixes#12384
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
An agent ran `collect 20` to pick up the gear it dropped on death. A
creeper blew up (16 damage) and a zombie kept hitting, and collect kept
walking from item to item until it returned with the player at 2 health.
The hurt events were in the event log, but nothing read them.
collect now reads the events after each item and stops with kind "hurt",
listing the hurt events, the inventory change so far and the items left.
Items it could not reach carry `why`: the goto error, when there was one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each usage of a mod's command keeps its own permission node; a usage can
replace a vanilla root (NativeCommand::replace_vanilla); a command name may
start with slashes. Adds the permission_groups hook and primary_group,
whitelist_add/remove, log, set_game_mode and the server_stopped hook.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The staged api package can name the bundle's [[crate]]s by relative path;
they are rewritten to their staged copies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
player_chat_v2 (with the sender's permission level), chat_decorate (the
shown line sent as unsigned content beside the signed body), chat_as,
player_join_message, player_leave_message and the join/leave helpers
(has_played_before, motd, broadcast_quiet).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
block_grow (crops, stems, cactus, cane, bamboo, spreads), tnt_primed_v2,
hanging_break_v2 and entity_change_block_v3 can cancel the change; the
explosion_detonate hook names the entity that exploded.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
effects_v2/set_effects_v2 carry the icon flag, attribute_modifiers lists the
transient modifiers, NativeServerPlayer gains using_item, blocking,
sprinting and gliding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LockCode.canUnlock uses Player.getMainHandItem, whichever hand the click came
from, so a key in the off hand does not unlock a container. The lock check
read the acting hand; it now reads the selected main-hand slot, and the test
covers an off-hand key being refused.
Entity.load reads OnGround and fall_distance and saveWithoutId writes the
live values; Horizon wrote false and 0 and read neither. Boats now count
their fall as AbstractBoat.checkFallDamage does (on a host's tick and a
LAN guest's move), reset it in water, and minecarts no longer reset it on
a move up or none, which vanilla's Entity.checkFallDamage never does.
Fixes#12517
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An iron pickaxe broke halfway through a tunnel. The next break of stone
replied only "timed out before the block broke", which doesn't say the
player is now mining with a bare hand. break now includes `held` in its
reply, both on success and on a timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FillCommand.fillBlocks places each block with BlockInput.place, which
loads the tag into the block entity there and counts a block whose tag
changed its block entity even when the state was already in place. A
hollow fill's core is plain air. Horizon placed the state and dropped
the tag, so every block entity /fill made kept its defaults.
Fixes#12814
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ChestBlock's menu provider builds a CompoundContainer over both halves, whose
stillValid asks both block entities; breaking either half ends the menu.
Horizon closed the menu only when the clicked half was removed, so the other
half breaking left a shorter menu over the remaining half.
LecternBlock.useItemOn accepts any stack in ItemTags.LECTERN_BOOKS; Horizon
matched the two vanilla item ids directly, so a data pack adding its own book
to the tag could not put it on a lectern.
LecternBlockEntity.getRedstoneSignal adds one only when hasBook(): the stack
carries a written_book_content or writable_book_content component. Horizon
added one for any stack in the Book slot, so a non-book item a command left
in a lectern read 15 at its last page where vanilla reads 14.
BaseContainerBlockEntity.createMenu asks canOpen, whose LockCode takes the
player's main hand or a spectator; a refusal sends the container.isLocked
action bar naming the provider and one chest-locked sound. Horizon kept a
container's lock on disk and checked it for LAN guests, but the integrated
player's open_menu went straight to the loot roll and the menu, so a locked
chest opened for anyone.
The local open funnel now checks every block entity of the provider - both
halves of a double chest - before it opens, with vanilla's message and sound.
Fixes#12801. In a short view (427x240 GUI px: an 854x480 window at GUI
scale 2, or 720p at its automatic scale) Shaders' page list has room for
four of a pack's screens, and the rest were dropped with nothing to say
so. Now, when they don't all fit, they collapse into one `‹ Name ›` row
with pips (`tabs::collapsed`). Left of the name opens the screen before,
right of it the one after; while searching, the nearest ones with
matches. Each part is a `Tab` target, so RB and LB still step through
every screen and back to the pack list. When the screens fit, the list
is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`hud_kit::tabs::collapsed` lays a page list out in one row: the selected
name between `‹ ›` arrows, cut to fit, with a pip per page under it. An
arrow with no page that way is dimmed, and when the pips don't fit across
the row they are a window round the selected one. `draw` paints it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`goto 8 46 82` aimed at the corner of block (8, 82), so with the default
--near 0.6 it reported arrived while the player still stood in block
(8, 81), one block up a staircase. Whole numbers now name the block and
aim at its center (8.5, 82.5); decimals stay exact positions, so callers
passing entity positions are unaffected.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`LivingEntity.getDamageAfterArmorAbsorb` calls `hurtArmor` before it reads
the armor and toughness attributes, so a piece that breaks on a hit takes no
part in the reduction of that same hit. #11696 showed a chestplate at 239 of
240: vanilla saves Health 16.0, Horizon 16.64.
Every source that reaches `hurt_player_from` (mob attacks, projectiles,
`/damage`, fireworks, the wither, pufferfish) and `hurt_player_by` (thorns,
the dragon, lightning, ender pearls, the world border) now takes the
synchronous equipment path that `damages_helmet` already used: it wears the
armor between the hit's selection and its armor read. Explosions, potions and
the library's environmental ticks (`motion.rs`, `landing.rs`, `hunger.rs`)
still queue the wear for the next drain and are left for a follow-up.
The helmet-phase advancement keeps its reported amount, so no hit's
`ENTITY_HURT_PLAYER` value changes. `tests/fixtures/helmet_damage.json` gains
a `mob_attack` hit that breaks a boot beside one that leaves it worn; the
helmet-damage harness compares both against real vanilla.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
SnowGolem writes Pumpkin and reads it with getBooleanOr("Pumpkin", true).
Horizon had no snow golem case in entity_nbt, so a golem sheared in game
wore its pumpkin again after a reload and {Pumpkin:0b} was ignored.
Fixes#12778.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mining a coal vein left 20 of 24 coal on the ground: the drops fell into the
pit, out of pickup range, so break replied picked_up {} and the agent had to
read entities and goto each item by hand. collect [RADIUS] walks to every item
within RADIUS (default 8), nearest first, trying each once, and replies with
inventory_change and the items it could not reach.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HoglinAi.maybeRetaliate broadcasts the attacker to the visible adult
hoglins, and each takes it only when it is not pacified and its current
attack target is not strictly nearer (setAttackTargetIfCloserThanCurrent).
Horizon's hoglin alerted from its hurt-by goal instead: every hoglin in
range without a target got the attacker, overwritten after the tick. A
hoglin hit in the same tick as a neighbour hit its own attacker once,
then took the neighbour's attacker behind the glass and never hit again.
The hoglin's hurt-by goal no longer alerts. hoglin_hurt broadcasts a
retaliation to the sensed visible adult hoglins, and the event applies
the pacified guard and the nearest-target rule. Piglin alerts and the
generic SetTarget are unchanged. With a player on either side the
current target stays, since each mob's player is its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Animals and monsters move several blocks between an agent's calls, so goto
to an entities position followed by attack ID kept failing with "out of
reach". hunt ID re-reads the position, walks within reach, attacks, waits for
the weapon to recharge and repeats, then walks over the drops. It replies with
hits and inventory_change.
interact-entity's help now says it fails when the entity is not under the
crosshair and replies with inventory_change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
events with no --since returned all 256 kept events, which is too much for an
agent that only wants to know what just happened. --limit N keeps the newest N
and the reply says how many older ones it skipped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Off Linux's virtual screen, the vanilla 26.3 client that VanillaTickScript
drives sits on the person's desktop, and vanilla's MouseHandler took the
person's pointer: the join grabbed and hid it, a mouse move turned the
player, and every screen the script opened warped the pointer to the
window's middle (releaseMouse has no focus check).
- SDL drops the keyboard, text and mouse events once the script is
installed, and releases what was held; the script's lines never went
through them.
- MouseHandler is replaced by one whose grab is the flag alone, so screens
open and close without touching the system pointer.
- The harness opens the window without the focus
(SDL_WINDOW_ACTIVATE_WHEN_SHOWN=0). Window.focused starts true and only
SDL focus events change it, so the window asks SDL for its real focus
while resources load: the join no longer warps the pointer.
- Window.onResize re-grabs whenever the flag is set: SDL drops the window's
resize events (pixel-size events still resize the frame).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chunks of a slice run on helper threads under a scope, the calling thread working too; HORIZON_SERIAL_TICK=all keeps every chunk on the caller as the serial reference. Not yet used: the hub's per-player pass and the mob loop's speculate-and-commit, the pieces of #8158 it is for, follow separately.
Co-Authored-By: ZCode (GLM) <[email protected]>
ServerLevel.tick ticks an entity only in an entity-ticking chunk. Thrown
potions, lingering-potion and plain area effect clouds, firework rockets
and experience orbs ticked anywhere loaded, so they kept moving and
ageing while their chunk unloaded or past the simulation distance.
Fixes#12769
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: End gateways now skip entities past the simulation distance, as vanilla's entityInside does from the entity's own tick. Its new test fails on main's end.rs and passes here. One risk to watch: an entity already in a gateway when its chunk stops ticking keeps its traveller state until the chunk ticks again. The 13 End/portal/server harnesses pass on this head.
interact-entity right-clicked whatever look-at left under the crosshair and
replied ok, so shearing a sheep behind a tree or out of reach looked like it
worked. It now fails naming what is in the way (an entity, a block, or nothing
in reach), and its reply adds inventory_change: the items gained or lost, such
as the wool a shear drops into the inventory.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The paced test landed between #[test] and the base64 test, so the base64 test
lost its attribute and stopped running (duplicate_macro_attributes, dead_code).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1f9719f9b2 added 169198624 itarqos5 to .github/trusted-reviewers.txt and the
team page, but left the expectation in test_the_repos_list_names_its_reviewers_by_id
at the five names it had. A pull request's verify checks out the merge with main,
so the mismatch fails the job on main and on every open pull request, behind the
failures those jobs are meant to report.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A server's player copy skipped makeStuckInBlock's fall reset (its client
moves it), and ran its landing check before the block effects, so a
player falling into a cobweb was hurt on landing. handleMovePlayer moves
the player first (checkInsideBlocks) and only then runs doCheckFallDamage:
add an early pass that applies just the fall/impulse reset of a cobweb or
sweet berry bush the moves touched.
Fixes#12382
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
0.2 becomes the only line served at the HDR note's step 5, so the
additions we expect (which pixels are light sources for post, a
full-screen peak, an SDR output's display values) must not break packs.
A pack that builds `PostInput(...)` positionally would stop compiling
when a field is added.
- `horizon-shader check` refuses, for 0.2 packs, a `Compose` of `Frame`,
any stage input (every `StageKind`'s input and `DisplacementInput`) or
the post helpers' `SurfaceData` in the pack's files, found in naga's IR
(function bodies and module constants) and located by span. Reading
fields and passing a received value along are unchanged; `Surface`
stays constructible.
- 0.1 is left unchanged: it is retired at step 5 and its structs won't
grow.
- The 0.2 reference: `[tonemap]` is a pure mapping to the output its
`frame` describes, once per pixel of each output (Horizon may add
outputs such as an SDR screenshot); alpha is coverage only, never
light; `Surface.color` is non-negative unbounded linear light; `Frame`,
the inputs and `SurfaceData` may gain fields. Changelog entry in
docs/api/shaders.md.
Closes#12673
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Accounts and installs, versions and builds, the updater, the appearance
settings, the AI agent and data collection pages, and the single words the
screens reuse.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
What the launcher says about a mod, its jar and what of it came over, the
resource, shader and data packs, and the Modrinth entries.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The screen that reaches a server on another computer: what to type, why ssh
is missing, why {host} refused the key, and the horizon-server messages that
come back over the connection.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Accounts and installs, versions and builds, the updater, the appearance
settings, the AI agent and data collection pages, and the single words the
screens reuse.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
What the launcher says about a mod, its jar and what of it came over, the
resource, shader and data packs, and the Modrinth entries.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The screen that reaches a server on another computer: what to type, why ssh
is missing, why {host} refused the key, and the horizon-server messages that
come back over the connection.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
In single player the client is the integrated server's client and its
combat tracker stays empty: the cause now comes from the server's
DamageEvent (damage type and the causing entity's type).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
(cherry picked from commit 9724a9ae96)
Accounts and installs, versions and builds, the updater, the appearance
settings, the AI agent and data collection pages, and the single words the
screens reuse.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
What the launcher says about a mod, its jar and what of it came over, the
resource, shader and data packs, and the Modrinth entries.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The screen that reaches a server on another computer: what to type, why ssh
is missing, why {host} refused the key, and the horizon-server messages that
come back over the connection.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Every language file keys its translation of the HDR setting's
description by the English text. Changing that text left them stale,
and the language test refuses a key en_us.json no longer has. The old
description is still true with Auto, so keep it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Accounts and installs, versions and builds, the updater, the appearance
settings, the AI agent and data collection pages, and the single words the
screens reuse.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The screen that reaches a server on another computer: what to type, why ssh
is missing, why {host} refused the key, and the horizon-server messages that
come back over the connection.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ScoreHolderArgument resolves @s to the source's entity, and every entity
is a score holder named by its UUID. Horizon gave orbs no holder name
('no UUID here'), so execute as <orb> store ... score @s found no entity
and stored nothing, and a score set by the orb's UUID was reported under
the UUID text instead of the orb's name.
An orb's holder name is now its registered UUID, the identity its saved
UUID reads already use. Projectiles are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.makeStuckInBlock: a cobweb or powder snow an entity's box meets sets
its stuckSpeedMultiplier, and its next Entity.move (a piston's push) sets
its movement to zero. Projectiles never took either: their own tick
(AbstractHurtingProjectile/ThrowableProjectile.tick's
applyEffectsFromBlocks) and a piston's push (moveEntityByPiston's
applyEffectsFromBlocks) left a cobweb's projectile flying. They now set
Projectile::stuck_speed, and a block's push spends it.
Harness: test_projectile_piston_cobweb_parity (dedicated servers, frozen
and stepped: a wind charge a piston lifts by a cobweb stops, one in open
air keeps its speed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The controller page and the lines it prints while you rebind, the pad notice,
the recipe-transfer messages, and the Studio timeline and top bar.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The whole `horizon.pvp` block: the custom crosshair, the block outline, the hit
colour, the HUD widgets with their corners, chat timestamps, compact chat, copy
and search, the preset, the HUD editor, combat feel and the 1.8 rules, and the
visual tweaks.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The emote wheel, `/mute`, `/mutelist` and `/tempban` with their durations and
reasons, the remaining options and three page entries.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Flat keys, the ones whose English sentence is also the key, so only the value
moves: GPU Culling, Upscaling Sharpness, Opaque Ice, Hidden Faces and the
sentences that say what each setting costs. Plus the Realms sign-in line, the
Extras entries, friends, splitscreen and the pack browser.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The `/studio` commands and the Studio screen, the Music page with every
situation, the page titles and the `...` entries, and the whole mod browser.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The Extras entries, the options added since the file was last touched, and all
48 labels of the on-screen controller guide.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: The copied tables now come from the player's jar. A dump of the old compiled-in tables and the newly loaded ones is byte-identical (sha256 d85fc80c…), and all 6 datafix shards pass. One risk: OUTPUT_VERSION 4 makes every install prepare again once, and a missing datafix-tables.json panics on pre-1.13 data instead of upgrading it wrong.
The controller page and the lines it prints while you rebind, the pad notice and
the recipe-transfer messages.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The whole `horizon.pvp` block: the custom crosshair, the block outline, the hit
colour, the HUD widgets with their corners, chat timestamps, compact chat, copy
and search, the preset, the HUD editor, combat feel and the 1.8 rules, and the
visual tweaks.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The splitscreen setup and prompts, which German was missing whole: monitor and
window picking, the per-player profiles, joining and leaving, and the agent and
repro notices that sit between them.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The page titles and `...` entries, the mod browser, the flat graphics keys whose
English sentence is also the key, the Realms sign-in line and the Extras
entries that belong with them.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The Extras entries, the options added since the file was last touched, and all
48 labels of the on-screen controller guide.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A box whose loot table rolled nothing now saves no Items, which
RolledLoot::restore must take as the roll, or the unopened box would
lose its LootTable. The group gains a box with LootTable minecraft:empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After each acting tool call the pause menu opens (stopping a single-player
world, as vanilla's does) and it closes before the next; reading tools
leave it open.
Fixes#12754
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The controller page: Bindings, Play, Device, Look, Movement, Menus,
Actions, Reset, Detected, Glyphs and Feedback, with the sentence under each
one (dead zones, trigger threshold, sneak toggle, the on-screen keyboard,
Nintendo button positions, the DualSense light and triggers, glyph
families, rumble) and the lines it prints while you rebind. Then the recipe
transfer messages and the Studio timeline and top bar: Freecam: ON, + Key,
− Del, marker, scene, sequence, File/Camera/View and the button glyphs.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The whole `horizon.pvp` block, 198 keys, none of which existed in French:
the custom crosshair (shape, size, gap, thickness, colour, outline, centre
dot and the colour it takes over an entity), the block outline and the hit
colour, the HUD widgets (armour, effects, held count, ping, coordinates,
server, combo, FPS, CPS, keystrokes, clock, reach, speed) with their
corners, background and text, chat timestamps, compact chat, copy on click
and search, the PvP preset, the in-game HUD editor, combat feel with the
1.8 rules, and the visual tweaks (time of day, weather, water, lava, fire,
shield, totem, enchantment glint).
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Flat keys, the ones whose English sentence is also the key, so only the
value moves: `GPU Culling`, `Upscaling Sharpness`, `HDR Lighting`,
`Smooth Gradients`, `Opaque Ice`, `Hidden Faces`, `Entity Culling
Precheck`, the `%s nits` and `%s mobs` units, and the sentences that say
when a feature is off or why it cannot run. Plus the Realms sign-in line
and the pack browser: Add, Install, Filters, Realistic, Simplistic,
Vanilla-like and 16x/32x/64x.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The `/studio` commands come over whole: `record`, `replay` (open, pause,
resume, speed, seek, status, close and their errors), `freecam`,
`keyframe` and `path`, with their usage lines and status text. Then the
Studio screen (saved recordings, the empty-folder hint, open folder, play
replay, refresh, unknown server), the Music page with every situation
(menu, surface, creative, underwater, End, boss, credits, biomes), the
page titles, and the Extras.../Mods.../Replay Studio.../Music... entries.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
71 keys that fell back to English: `horizon.extras.*` (World, chunk
pregeneration and its tooltip, the Chat and Menus headers, the Music page
line, Zoom, the resource-pack browser and the friends relay notice), the
options added since 918 keys (`3D Skin Layers`, `Play in Background`,
`Gameplay Guide` and its three values, `Trigger Threshold`, the Nintendo
buttons and keyboard choices), and every label of the on-screen controller
guide (Use, Open, Take Half, Swap Hands, Release to Shoot...).
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The controller page and the lines it prints while you rebind, the pad notice,
the recipe-transfer messages, and the Studio timeline and top bar.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The whole `horizon.pvp` block: the custom crosshair, the block outline, the hit
colour, the HUD widgets with their corners, chat timestamps, compact chat, copy
and search, the preset, the HUD editor, combat feel and the 1.8 rules, and the
visual tweaks.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
`/mute`, `/mutelist` and `/tempban` with their durations and reasons, the
remaining options, and three page entries.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Flat keys, the ones whose English sentence is also the key, so only the value
moves: GPU Culling, Upscaling Sharpness, Opaque Ice, Hidden Faces and the
sentences that say what each setting costs. Plus the Realms sign-in line, the
Extras entries, friends, splitscreen, the pack browser and the emote wheel.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The `/studio` commands and the Studio screen, the Music page with every
situation, the page titles and the `...` entries, and the whole mod browser.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The options added since the file was last touched and all 48 labels of the
on-screen controller guide, which is the first thing a new player reads.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
hurt (amount, health, damage type, attacker), heal and food events; hurt
and death carry the player position, where a death drops the inventory.
Fixes#12749
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A native mod pinned to an older commit moved the whole build there,
even when it would have built on the Nightly the player picked (a
player's report on 11-04, and again with Freecam+ on fdc87ae).
- native_mods::build compiles every mod on the base's own commit first.
Only a mod that pins another commit and is left out for not building
there moves the build to the pinned revision, once; a build that
can't move keeps the first one (a MISMATCH still asks Build anyway).
Rebuild on Horizon (on_base) never moves.
- The fit line for such a version says so.
- The instance's Mods tab shows each native mod's standing: in the
last build on the Horizon played, moved to another commit (amber),
left out and why (red), or, before a build, how it should build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A chest with exploration maps resolves each one through findNearestMapStructure with skip_existing_chunks, whose skip check read the saved chunk's record (a region read and an NBT parse) for every candidate of every search, and allocated a String per candidate for the reference set. The cost grew with every map resolved and every chunk saved: on the official SMP, uses grew from 0.27 s to 1.23 s inside use_block (#10255). The session now keeps each saved chunk's answer, as vanilla's StructureCheck cache does, and the reference set is checked by name without allocating. The reference file's format is unchanged.
Co-Authored-By: ZCode (GLM) <[email protected]>
The id tables of the 1.13-era data fixers (flat_tables.rs) and BlockStateData
(block_state_data.txt) were copied from vanilla's code into the repo. They now
come from the player's own server jar, like the other prepared resources:
- VanillaDataFixTablesExporter reads the fixers' static tables by reflection
and records BlockStateData's register calls in call order (its register is
prefixed with a recorder in a private copy of the class, java.lang.classfile),
then checks the recorded calls rebuild vanilla's MAP and ID_BY_OLD_NAME.
horizon-prepare runs it as a new step and writes datafix-tables.json;
OUTPUT_VERSION 4, so installs prepare again.
- horizon-world's datafix/tables.rs reads that file once into the same
&'static slices the fixes used (set_resources from the game's --resources,
else HORIZON_RESOURCES, else the checkout's .local); no per-call cost.
- check_public_tree.py rejects the retired paths and any tracked file holding
50+ entries shaped like those tables; gen_flat_tables.py and
gen_block_state_data.py are gone.
The loaded tables dump byte for byte the same as the old compiled-in ones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolve live position and posed bounds for each emission tick. Retain a removed target at its final body until expiry, and detach before entity-ID reuse.
Co-Authored-By: Codex (GPT-6.1 Sol) <[email protected]>
🤖 Agent: discrete GPUs never see the DXC DLL: FXC is now set explicitly, because wgpu's default Auto would load the dxcompiler.dll the zip puts beside the exe for every GPU. Only an IntegratedGpu on DX12 reopens with DXC, once at device creation, and keeps FXC if the DLL fails or the DXC instance picks another GPU.
🤖 Agent: comments only; java_sort's body is byte-for-byte unchanged, the comments now cite listsort.txt and the Arrays.sort Javadoc instead of OpenJDK's private method names.
The partnership ended, but #9299 still adds Afri-Network below Horizon
Official on every first start. Stop offering it, and take it out once
from lists where the game added it: horizon_partner_servers.txt lists it
and the entry still has the name the game gave it. A player's own entry
for that address (their name) stays. Then the marker goes, so this runs
once. servers::remove_where keeps the other entries and their fields;
insert_after had no other caller and goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While the live particles tick they are out of the engine's list, and what their
particle_emitters spawned was made right away: its type's limit and
exclusion_radius counted only the particles spawned so far this tick. A pack's
emitter particle spawning a light ray every tick at one spot (exclusion_radius 1)
stacked a ray per tick where Java keeps one. These spawns now wait until the live
particles are back, still within the tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A custom particle with render_type additive_translucent takes the fog over its
colour in Java, as vanilla's particle shader gives it. Horizon drew it through the
additive entity pass, which does not, so the freepack's light rays came out bright
yellow where Java's are tinted by the pack's dense fog. Their quads now carry their
own overlay, which the additive pass fogs. Measured in Java on a single light ray
against a black wall: shape, tone and summed light now match within the variation
between shots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured in Java with the freepack's light rays and a still copy of them: a custom
particle drawn movement_aligned has its texture's top against its velocity, so a ray
rising toward the noon sun hangs its glow below the leaves it starts at. Horizon
turned it along the velocity, and the rays shone up through the canopy instead of
down under it. A GPU particle keeps its texture's top along its motion, as measured
with the freepack's rain.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A custom particle with physics and kill_when_still (on by default) was removed on any
tick it did not move, so one that rests on purpose died on its first tick. Measured
in Java with a copy of the freepack's light ray chain (an emitter with physics that
rests beside a leaf and emits every tick of its 20): Java's emitter lives out its
life, Horizon's emitted once or twice, about a fifteenth of the particles. A particle
now goes when a collision stopped it, as a landed one is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
exclusion_radius (wiki "Custom Particle Types") keeps a custom particle from
spawning within that distance of a live one of its type. Horizon read it and spawned
anyway. The freepack's rain splash, which replaces the vanilla one, sets it to 1: its
ripples covered the ground about twice as densely as in Java.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`en_us.json` grew from 308 to 1047 keys in the week before #11826, which
took `es_es` to 918. The keys added after it fell back to English on every
Spanish screen: the whole controller guide (49), Studio's freecam, keyframe,
path, timeline and top bar (48), the new options (8), and 23 flat keys such
as `Upscaling Sharpness`, `Opaque Ice` and the DualSense and Steam keyboard
descriptions.
All 128 are translated now. The file keeps `en_us.json`'s key order, which
it already followed, so the diff is the new lines only; keys, `%s`
specifiers and every existing value are unchanged. `es_ar`, `es_cl`,
`es_ec`, `es_mx`, `es_uy` and `es_ve` read this file, so they gain the text
too.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: removes IS_IRIS/IRIS_VERSION/IRIS_FEATURE_* as OQ-2 decided; real_packs over BSL, Complementary (both) and Sildur's: no pack loses a program, Complementary now loads terrain and water (9 skipped → 3).
Vanilla's server never reaches Ready when the world's packs have a
predicate that references a predicate no pack defines (the registry
freezes with unbound values) or predicates that reference each other
(loot validation: recursively called). Horizon read the packs' data
lazily after Done and started such a world.
The dedicated start now reads the data store right after the world
loads, and a partial check of the predicate graph (all_of/any_of terms,
inverted term, inline conditions followed) logs vanilla's diagnostics
and ends the start before anything listens, with exit code 0. Other
holder positions, other registries, single player, LAN and /reload are
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`lang.rs`'s test checks a bundled file for stale keys, repeated keys, empty
values and changed placeholders, and deliberately not for missing ones: a
language is allowed to lag, and `Language::load` falls back to `en_us`. So
nothing reports the size of the lag, and `fr_fr` missing 510 of 1047 keys is
invisible.
`tools/lang_coverage.py` prints, for the game's `assets/horizon/lang`:
missing, not in the reference, empty, repeated key or wrong indentation,
changed `%s`/`%1$s`/`%.2f`/`%%` placeholders (the same rule as `lang.rs`), and
values identical to English; and for `launcher/lang`: values identical to
English and a `{hole}` a value drops or adds. `--summary` is the line a pull
request pastes, `--json` the same as data, `--show-keys` the keys behind each
count, and `--fail-under` exits 1 so a later CI job can gate it.
It reads files only: no `.local/`, no network, nothing written.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Keep main's controlling-passenger check and shared controller movement helper, while retaining the boat attack guard and regression coverage.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
goto arrives when the horizontal distance is within near (and the height
within a block or so), but its reply gave only the 3D distance, so an
arrival one block below read as farther than near. Report
horizontal_distance next to it.
Fixes#12707
Co-Authored-By: Claude Opus 5.5 <[email protected]>
step_break took any state change for the break, so dirt spreading to
grass while mined ended the task with ok and no drops. Count the block
broken only when it is gone (air, or the bare fluid of a waterlogged
block or ice); otherwise track the new state and keep mining.
Fixes#12706
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In single player and as a LAN guest the menu of a crafting table,
furnace or chest opens when the server's packet arrives, after the
2-tick report. Wait up to 20 more ticks while nothing came of the click,
as place already does, so the reply's screen is the opened menu.
Fixes#12705
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Checkpoint before the fix: the startup harness and its contract (a valid
world serves its reference; a missing reference or a cycle ends the
start by itself before Ready, with vanilla's diagnostics).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.applyEffectsFromBlocks fixes the tick's movements before stepOn,
so a magma, cactus or campfire hurt that teleports an enderman no longer
moves the rest of the scan; a baseTick hurt that teleported it reads the
fire and lava contact at the landing spot (snapTo resets the old
position); and a hurt left from outside the mob's tick still teleports
before the rest of it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clear the spyglass used by the new regression before the existing fist-based boat destruction check.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Find mods on an instance opened the Mods page as it was left, often on
the last project's page, and the Versions tab kept the fit worked out
for the instance selected when the page opened.
- Find mods opens the registry's mod list with "Only what works on
{instance}" on: native mods with no version that builds there, or
moves the build to its commit without a clash, stay out.
- Each registry native mod's row says how it works on the instance.
- With the filter on, the Versions tab hides the versions that don't
work there and says how many.
- Picking another instance opens the project page again, searches
again and drops the last message.
Closes#12709
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apply the existing busy-hands attack guard to the server-thread client and include controller steering in the shared busy state. Add explicit parity scenarios and host/guest regression checks.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Enderman.hurtServer draws and teleports inside every hurt (even one the
hurt cooldown turns away) and Shulker.hurtServer inside a low-health one,
so the hurts of a mob's own tick (baseTick, tickEffects, block contact,
cramming, water) now each run hurt_teleports right after their damage
instead of one call after all of them.
Closes#12660
Co-Authored-By: Claude Opus 5.5 <[email protected]>
At 25 keys, Java HashMap capacity doubles from 32 to 64 (threshold 24), splitting `immovable` (bucket 5) and `pose` (bucket 37) into different buckets. Using a 22-key mannequin compound keeps capacity at 32 where both keys collide in bucket 5.
Preserve live gravity and grounded state while encoding retained entity
flags with Entity.saveWithoutId's canonical bytes and optional defaults.
Co-Authored-By: Codex (GPT-6.1 Sol) <[email protected]>
🤖 Agent: docs only; every line of the three published files was read for private material, and only local paths, the machine name, a private rules file name and the unpublished plan link were replaced (each file says so at the top).
`java::installed()` looked in every PATH entry's `bin` for a file named `java`. Windows spells the program `java.exe`, so `canonicalize` never found one and a JDK the PATH alone names (not JAVA_HOME, not one of the folders installers use) stayed invisible. A Java instance then downloaded Mojang's runtime instead of using it, which also fails offline, and `horizon-launcher java list` did not show it.
The scan is now `homes_from_path()`, which asks for the name the OS ships (`java.exe` on Windows), with a test over a fake home.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ServerLevel.tick walks its entities with EntityTickList.forEach, over the
list as it was when the pass began: an entity that joins during the pass
(a breeze's wind charge, a skeleton's arrow) first ticks on the next tick.
Horizon ticked the projectiles mob events add in that same tick, so a
mob's shot took a step before anything could meet it where it was fired.
Projectiles whose id is above the pass's start (ticked_entities_through,
as dropped items already use) now wait; a player's or a dispenser's shot,
which joins before the pass, still ticks at once.
Harness: test_mob_shot_join_tick_parity (dedicated servers, frozen and
stepped: a breeze's first wind charge is first seen where it was fired, a
dispenser's has already flown).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ConversionTracker.addAdditionalSaveData writes the affliction time only while
the mob is afflicted. Mobs now cache isInPowderSnow each tick, so a skeleton
outside powder snow saves FreezingTime -1, as a fresh or NoAI one does in
vanilla. Zombie villagers and zombified piglins save Zombie's tracker too;
Horizon never starts their water conversion, so DrownedConversionTime is -1.
Fixes#12522.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EndGatewayBlock.entityInside comes from the entity's own tick
(applyEffectsFromBlocks), and ServerLevel.tick ticks only the entities in
entity-ticking chunks. find_end_gateway_portals swept every item, mob, boat,
cart and pearl in the End through the gateway check, so an item frozen past
the simulation distance could enter a gateway, and each one cost a block
sweep every tick. It now skips them, and handle_end_gateway_portals leaves a
waiting one's state alone and returns at once when nothing is in a gateway
(it built a Vec and a HashSet of every entity in the End each tick).
The tracker's send pass skips the entities with nothing to send (idle, not
moved, no sync asked, leash as sent) that no client starts tracking, without
looking their state up again, and sweeps the states only when one left.
The official SMP's AFK carpet dupers in the End pile up tens of thousands of
such items (31,972 on 2026-11-08). With 16k of them and one player, the
dedicated server's tick goes from 12.5 to 9.3 ms (end_gateways 2.18 -> 0,
lan_track_entities 3.39 -> 2.8).
Fixes#12682
Co-Authored-By: Claude Opus 5.5 <[email protected]>
request_samples and dispatch_meshes locked the job queues that the far terrain
threads share, and those threads run behind the render thread
(far_terrain_current). One preempted while holding a lock kept the frame
waiting: on a busy 8-core laptop at render distance 24 with LOD distance 1024,
lod_wanted took up to 216 ms of wall time with no CPU time. Both now try_lock
and, when the queue is busy, send their requests on a later frame.
ItemStack.matches compares the component patch, and on a non-damageable item
minecraft:damage:0 is a real component (its prototype has none). Horizon read
the number into the damage field and forgot its presence, so two books with
and without it merged.
Test: a book carrying minecraft:damage:0 and a plain one, both enchanted with
sharpness, give no grindstone result; two identical zero-damage books still
merge to two.
SmithingTrimRecipe's pattern is a TrimPattern holder: a registry name or an
inline asset_id/description/decal definition. Horizon parsed only the string
form, so an inline pattern became an empty name and the trim wore no pattern.
Test: a smithing_trim recipe whose pattern is the inline coast definition
gives a diamond chestplate whose trim holds that direct pattern.
ResourceOrIdArgument.lootTable decodes an inline table while the command
is parsed. A minecraft:loot_table entry's value is a HolderSet<LootTable>
(HolderSetCodec over RegistryFileCodec), so an id the loot table registry
does not hold fails the decode, and the command never runs. Horizon
accepted any compound and ran the table.
The inline decode now follows the table's pools, entries and composite
children to each such entry's value, and checks one identifier against
the registry's ids (the world's tables, refreshed on /reload), with
vanilla's either messages. A nested inline table passes its error up.
Lists, tags, other fields and other codec errors are not checked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemStack.getMaxStackSize is getOrDefault(MAX_STACK_SIZE, 1), so a
!minecraft:max_stack_size patch caps the stack at one. Horizon fell back to
the item's own size, letting two stone stack into a single slot.
Test: in an inventory menu, a stone with the component removed and another
on the cursor stay at one each after a pick-up; on main they merge to two.
AnvilMenu.createResult first asks EnchantmentHelper.canStoreEnchantments,
which is ItemStack.has of ENCHANTMENTS (stored for a book). A removal patch
leaves no component, so vanilla gives no result, not even a rename. Horizon
assumed every 26.3 item has one and renamed the stack.
Test: a diamond sword with !minecraft:enchantments and an enchanted book
with !minecraft:stored_enchantments both give no result at cost 0; the same
items without the removal rename at cost 1.
🤖 Agent: the hero's Buy and the prices now share one ownership rule (shop::owns), and a Shop picture's cache key changes when its item files arrive, so the bare-player bake no longer sticks.
The comments named OpenJDK's private TimSort methods. They now describe the
algorithm (one run, then binary insertion) and cite Tim Peters's listsort.txt and
the Arrays.sort Javadoc. No code change (#6741).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The adapter is first requested on an FXC instance; an integrated GPU on
DirectX 12 is requested again on an instance that loads dxcompiler.dll
from beside the game, once at device creation. A missing DLL, one that
fails to load, or an instance that offers another GPU keeps FXC with one
log line. WGPU_DX12_COMPILER still overrides the choice.
The Windows x64 game build downloads DXC v1.9.2609 from Microsoft's
release (zip and DLL SHA-256 checked) and ships its licence files and a
notice beside it. Measurements by Mayonesu0 and Cottleta on #8513.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Horizon no longer defines IS_IRIS, IRIS_VERSION, IRIS_HAS_TRANSLUCENCY_SORTING or
any IRIS_FEATURE_<name>, as the clean-room decision OQ-2 says: Horizon is not that
loader and does not claim its identity. iris.features.required still refuses a
pack that needs a feature Horizon lacks (Macros::feature_notes); features Horizon
has stay available, and packs take the path they take without those macros.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Replaces the pre-reconstruction files from 16d1d18 with the exact SHA-verified main files patched for #12373 (fixing snbt::tests::compact_form_matches_vanilla, removing unused t_or helper, and preserving target-arch NaN sign bits).
The specification the shader-pack rewrite was built from, its provenance log and
the two-team rules, as written in October, with local paths, machine names and
private file names replaced. Linked from the docs index; CONTRIBUTING.md and the
pull request template ask shader-pack PRs to name their sources (#6739, #1555).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With world packs that change biomes, lan_host read
the sync data with the world's packs, then read it all again for the world's
biome registry: the recipes and advancements twice from the same files (~0.7 s
on the server thread before the first frame). SyncData::load_over keeps them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A world pack that replaces a vanilla biome (Terralith's) dropped every
mod's biome modifier features and spawns: the content root baked them into
its biomes before the packs loaded. NeoForge applies modifiers once every
pack is in, against the final biomes and tags. The content root now keeps
its modifiers (biome-modifiers.json), and the generator's data and the
biome table apply them again over the packs' biomes and biome tags.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An entry without a weight weighs 1; 26.3 reads only {data, weight} entries.
Regions Unexplored's 79 tree, shrub and flower groups used it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Gson read them; 26.3's codec does not, so Tom's Simple Storage's cable
connectors lost their whole block state.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The game already applies data/<ns>/neoforge/biome_modifier files; Regions
Unexplored's 34 vanilla_changes were left. A mod's own <ns>:config predicate
is taken at its default (on); any other predicate leaves the modifier out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A Shop picture's key now counts only the item files already loaded. The catalogue is read
from disk at once and the files come a moment later, so the first picture (the bare player)
stayed for good: the cosmetics never showed on the Shop's looks and pack.
- The pack's hero shows "Owned" alone when the account owns every item of it, instead of
"Owned" beside a Buy button (`shop::owns`, also used by the open offer and the prices).
- Without its own skin in the cache, the account wears the default skin its UUID has in the
game (Steve, Alex…), from the player's prepared game files, and its own skin once the
profile heads have fetched it, instead of a plain figure.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientboundTakeItemEntityPacket is what makes a client add the item
pickup particle and play the pickup sound (ClientPacketListener.
handleTakeItemEntity). Only player pickups were broadcast, so when a
mob picked an item up the item simply vanished for every client
watching, with no flight into the mob (#12493).
add_pickup now sends Hub::take_item for a mob collector from the
server, with the taken entity's id and the stack's size. Player takes
keep their existing broadcast in the guest pickup path.
Co-Authored-By: Qoder <[email protected]>
ProjectileUtil.getHitResultOnMoveVector grows the boxes of the entities on
a projectile's way by computeMargin: max(0, min(0.3, (tickCount - 2) / 20)),
nothing for its first two ticks and 0.3 from its eighth. Horizon grew them
by 0.3 from the first tick for wind charges, fireballs, thrown items, llama
spit and shulker bullets, so a fresh one burst on anything it passed within
0.3 of (arrows already used the margin).
Harness: test_projectile_hit_margin_parity (dedicated servers, frozen and
stepped: a wind charge passing 0.2 beside a mob on its second tick flies
on, one passing as close on its eighth hits).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#11860 stopped the wheel unconditionally on Home's patch log and the console logs, so a log
with little content, or one already at its top or bottom, swallowed the wheel and the page
behind it never moved. A shared `TakeWheel::take_wheel` now stops propagation only when the
list's own offset, clamped, actually changed with the step; otherwise the event goes on to
the page's #content scroller. Scrollbar::wrap uses it too.
Fixes#12650
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Preserve pickup modes, owner identity and projectile flags through save/load
and data edits. Tick tridents and other projectiles only in entity-ticking
chunks so unloading cannot advance a saved fireball's flags.
Add differential coverage for defaults, pickup modes, owner proximity,
flight versus stuck ticks and guarded chunk reloads against vanilla 26.3.
Co-Authored-By: Codex (GPT-6) <[email protected]>
/give @s apple[death_protection={}] still showed a plain totem when it saved the player: the wire reader kept component 36 as opaque bytes, so the stack's data never held minecraft:death_protection and findTotem's hand lookup fell back to a new totem.
Decode the component as DeathProtection.CODEC saves it: the death_effects list (absent when empty), each ConsumeEffect in its codec form with defaults left out. A patch that removes the component now also reaches the stack as !minecraft:death_protection, so totem_of_undying[!death_protection] stops protecting.
Fixes#12632
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The GitHub stars of a registry project (index `stars`) showed only on its
page, spelled out in the meta line ("1 stars on GitHub"), while the list
and the side card said "No ratings yet" for a project people had already
starred.
Now a project's GitHub stars show wherever its rating does, as GitHub's
mark, a star and the number: in the registry's rows before the downloads,
in the side card and on the page beside "Sign in with GitHub to rate".
"No ratings yet" stays for a project with neither ratings nor stars. The
page's meta line drops the words, which also had no singular.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jh5A2TmRHLoVrJS29k1sF9
Load optional material programs and route tagged geometry only when the
variant is available. Keep legacy grouping/state for packs without usable
variants, and retain block identity and ordinary block shadows.
Add original loader, CPU material-run and explicit offscreen render controls.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
`PiglinAi.throwItemsTowardPos` swings the off hand once before a
non-empty throw, so a barter leaves the hand the piglin admired it
in; the throws themselves looked like items appearing from nowhere
(#12512).
The guest flush sent every mob swing as the main hand. It now reads
the arm `swing_hand` recorded, so an off-hand swing reaches guests as
`ClientboundSwingAnimationPacket` with hand 1.
Co-Authored-By: Qoder <[email protected]>
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
The glass sidebar's "A new version is available" card and "Join the Discord" button had fixed
heights, and the card's title and the Discord label no room to shrink: a longer translation
spilled out of them. The heights are now minimums (`ui::solid` too) and the labels wrap and
centre, so a long label grows its box. Labels that fit look as before.
Fixes#12645
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The index listed a registry icon at the same address however often it
changed (icons/<id>.png on the registry's site), and the launcher keeps
images by address for a week before asking again: an author who uploads
a new icon sees the old one in the launcher for up to seven days, while
the website already shows the new one.
An icon kept in the registry is now listed with ?v= and the first 12 hex
digits of its SHA-256. The same icon keeps its address; a new one gets a
new address, so every cache keyed by address fetches it at once. GitHub
Pages ignores the query. Screenshots keep their plain address: the edit
form reads their registry paths from it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jh5A2TmRHLoVrJS29k1sF9
Nintendo's art was Xbox's files copied (a red B, LB for L), and View,
Create and − were one plain minus. tools/pad_glyphs/gen.py draws the
missing ones in the hand-made set's style: plain white A, B, X, Y on the
dark key, L, R, ZL, ZR, − and +, two squares for View, three lines for
Menu, and their own icons for Create and Options. It uses the standard
library only and rewrites the files byte for byte. Xbox's face buttons
keep their colours.
Nintendo's shoulders, triggers, − and + get their art back in
pad_glyph::ART, and a test fails if two families share a picture other
than the sticks and the D-pad.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
cells_without_collision, which every moving particle asks before resolving its movement,
read the block of each inner cell of its swept box and looked at each cell around it, so a
particle moving 0.4 blocks a tick cost about eight times a nearly still one (poof, smoke and
firework sparks about 205-225 ns, flames and end rods about 25). It now visits a column and
a section at a time and skips every section whose palette holds nothing that collides and
nothing that reaches out of its cell; where a section does hold such blocks and none reaches
out, only the inner cells are read. The answer is unchanged.
On the stress_particles mix (isolated benchmark, particles/tick_bench.rs) the engine tick goes
from 3.21 to 1.77 ms. The section test compares the new scan with the old per-cell one on 4000
random boxes, as well as with the full resolution.
tick_particles gets lap spans (tp_*), so the game's own trace shows where a particle tick goes.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
EnchantmentMenu's cost and list use ItemStack.get(ENCHANTABLE) and
ItemStack.isEnchantable, so a patch changes the value or removes it. Horizon
read the item's default, so a patched sword offered the default list and a
sword with the component removed still offered enchantments.
Test: a diamond sword patched to value 1 offers costs 5/13/30 with clues
unbreaking 1, sharpness 2 and smite 4; removing the component gives no
offers.
ServerRecipeBook.addRecipes sends a ClientboundRecipeBookAddPacket entry
for each display of a recipe, and TransmuteRecipe.display gives one
shapeless display per material count: map cloning, with 1 to 8 maps, has
eight. The server's book entries skipped every recipe with more than one
display and had no transmute writer, so a learnt transmute recipe never
reached a player's book, although its removal did.
The book now writes a transmute recipe's entries, one per material count
with the recipe's own display ids: the input and that many materials, its
result (the result item, or every item of the input's tag with the
materials added to its count) and the crafting table, then the usual
group, category, placement and flags. A recipe it cannot write in full
gets no entry at all. The ghost recipe still uses only a single-display
recipe, as before.
Part of #12633
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SmithingTrimRecipe.applyTrim reads PROVIDES_TRIM_MATERIAL from the addition
stack, a named material or an inline definition. Horizon read only the item's
default, so a gold ingot patched to provide copper applied a gold trim.
Test: a coast trim with a gold ingot patched to copper gives a diamond
chestplate trimmed copper/coast.
GrindstoneMenu.mergeItems refuses a non-damageable pair when
ItemStack.getMaxStackSize is below 2. Horizon read the item's default, so a
pair of enchanted books patched to max_stack_size 1 merged to a count of two.
Test: two books with max_stack_size 1 and the same enchantment give no
result; unpatched books still merge to a count of two.
AnvilMenu.createResult asks ItemStack.isValidRepairItem, which reads the
REPAIRABLE component of the input stack: a component patch overrides the
item's default list. Horizon read only the default, so a sword patched to
repair with a stick refused it.
Test: a diamond sword patched to max damage 100, damaged 20 and repairable
with a stick takes one stick and gives damage 0 at cost 1.
`StackData` keeps the raid banner as its `ominous_banner` flag rather
than the name, rarity and hidden layers it stands for, so a stack sent
to a guest carried only the patterns and read as a white banner. Send
the components `Raid.getBannerComponentPatch` describes, and write the
tooltip display NBT the way its codec does (#12530).
Co-Authored-By: Qoder <[email protected]>
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
`python3 tools/stamp_native_hooks.py --only world_warp world_hold`. The other pending fragments stay pending.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Map the layered float world's light through a hue-preserving SDR shoulder
for ordinary screenshots and world icons. Reconstruct the HUD at its drawn
brightness over the mapped world without changing HDR presentation.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
SpawnEggItemBehavior.execute spawns the mob with EntitySpawnReason.DISPENSER
and never calls Mob.setPersistenceRequired, so it saves
PersistenceRequired 0b and can despawn. Horizon made it persistent; a
spawn egg used by hand already spawned a non-persistent mob.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.load reads Motion and Entity.saveWithoutId writes it back; the
spawner never moves, so the given motion stays. Horizon saved
Motion [0, 0, 0] for every spawner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ShulkerBoxBlockEntity.saveAdditional calls ContainerHelper.saveAllItems
with alsoWhenEmpty false, so an empty box writes no Items. Horizon wrote
Items: [] like the other containers, which always write the list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While the integrated server's owner plays, its attachments live in
`Game::guest_attachments`; `store_owner` (autosave, pause save) stored the
hub guest's empty copy, so `players/data/<singleplayer_uuid>.dat`, the file
the owner loads from, lost them until a clean quit rewrote it. A crash after
an autosave rolled mods' player data back while the inventory stayed
current. `store_owner` now stores the live attachments, as `save_guest`
does on leaving; the map stays their one owner. Fixes#12612.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
The client encrypts its shared secret and the server's challenge with
whatever public key the server's Hello carries. RsaPublicKey::from_der
had a minimum modulus size but no maximum, and no limit on the
exponent; an encryption costs the modulus squared times the exponent's
bits, so one oversized key (2 KiB to 4 KiB already takes seconds per
encryption, a full 2 MiB frame years) pinned a core and held the login
on "Encrypting…" with nothing to cancel it. No account is needed:
it happens before encryption, on any server a player joins.
from_der now applies RSAKeyFactory.checkKeyLengths, which vanilla's
KeyFactory runs on the same key: a modulus of at most 16384 bits and,
past 3072 bits, an exponent of at most 64 bits. Within those limits the
worst case is about 0.1 s per encryption. Real keys (2048-bit, e =
65537) are unaffected.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
a_bridge_gives_its_slot_back_when_either_end_closes failed on Windows with
"bridges stuck at 1". When the guest leaves, the relay's copy from the guest
shuts the host's socket down, but Windows doesn't wake the read the other
copy already has blocked on it (as in #6183); that read returns once the
host closes, which the test's host never did. The test now reads the end
of the bridge on the host's side and closes it, as a host's pump does. On
other systems it still checks first that the slot comes back without the
host's help.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Retain per-quad source state, emission and ownership through meshing, sorting and arena uploads. Resolve pack material aliases by state and retain native geometry when metadata is unavailable.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
DragonFireball is an AbstractHurtingProjectile: it moves and accelerates
as every hurting projectile does, saves and loads with its chunk, and on
impact bursts into dragon's breath, except on its own owner, which it
flies through. Horizon kept the dragon's fireballs in a list of their
own, ticked only in the End with its own motion, and loaded a summoned
or saved one as a static entity that never moved.
Every dragon fireball is now a projectile of its own kind in the shared
projectile engine. The dragon's attack makes one owned by the dragon
(its direction normalized as vanilla does); a summoned or loaded one
reads its motion, power and owner; it is saved, selected, tracked and
sent to clients like the other fireballs, with its dragon breath trail.
Its impact keeps the old list's cloud and burst unchanged, and the End
still ticks the breath clouds once. The old list and its tick are gone.
Fixes#12584
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With its block entity, an enchanting table took the container branch of
open_menu. It opens its station first, as a beacon does. The command
harness reads a station menu's slots, and the corpus group uses a table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`TemptGoal` finds its player with `TargetingConditions.forNonCombat()`,
which never picks a spectator. Horizon only checked that the player was
alive, so a spectator still holding wheat tempted nearby animals.
`native_client_fireworks(at, velocity, explosions)` (client,
`host.client_fireworks`) draws a firework's explosions as
`ClientLevel.createFireworks` does for an exploding rocket: the existing
`ParticleEngine::fireworks` starter, with its shapes, colours, fade,
trail, twinkle, flash and blast and twinkle sounds, and the smoke puff
for none. No entity, no damage, nothing on the server. `FireworkExplosion`
and `FireworkShape` are re-exported from `mod_api`. False on a server,
without a client player or level, for non-finite numbers and past 64
explosions. Fixes#12597.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
With --offscreen the game's first window is created hidden, but
splitscreen showed windows anyway: ending splitscreen called
set_visible(true) and turned the run windowed (options.offscreen =
false, a windowed renderer), Window 1 was shown again once a player
played in it, extra player windows were created visible, and the
compositors presented on every window (which maps a Wayland window
whatever set_visible says).
Split keeps the run's --offscreen: windows are created and set hidden,
nothing is presented, the frame limiter paces the frames, and ending
splitscreen draws offscreen again. Without --offscreen nothing changes.
Fixes#12586
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`NeutralMob.isAngryAtAllPlayers` is the universal-anger rule: with it on,
a still-angry bee targets any valid player even when its persistent anger
target is gone. Horizon's `BeeBecomeAngryTargetGoal` only accepted the
stored target, so a bee whose target was cleared never picked the player
under the rule.
The second tick of a throwable in a column is in the column's water, so its
drag is getWaterDrag's 0.8F rather than the air's 0.99F; the assertion now says
so instead of expecting the air drag.
ToggleTooltips updates the item's minecraft:tooltip_display component:
each false toggle adds the component to hidden_components, each true one
removes it, and a hide_tooltip an earlier function set stays. The loot
engine had no arm, so a datapack table's toggles changed nothing. The item
layer's TooltipDisplay already reads the component from `other`, so only
the loot arm was missing.
SetWrittenBookPagesFunction runs the function's ListOperation over the
pages of the item's minecraft:written_book_content component, keeping the
title and author an earlier function wrote. The loot engine had no arm, so
a datapack table's pages changed nothing. The item layer's
written_book_from_tag already reads the content, so only the loot arm was
missing.
SetCustomDataFunction merges its compound into the item's
minecraft:custom_data component, a nested compound key by key. The loot
engine had no arm, so a datapack table's data changed nothing. The item
layer already reads the component from `other`, so only the loot arm was
missing.
SetLoreFunction runs the function's ListOperation over the item's
minecraft:lore component and writes the lines back. The loot engine had no
arm, so a datapack table's lore changed nothing. The item layer already
reads the component from `other` (the tooltip's lore_texts, the LAN item
sync and /item modify), so only the loot arm was missing.
`native_add_toast(id, icon, title, description, challenge)` (client,
`host.add_toast`) queues a toast drawn as vanilla's `AdvancementToast`:
the item icon, the mod's title in the frame label's place (yellow, or pink
with the challenge sound) and the description as the white line, wrapped
and faded in when long. It shares the five slots and the queue with the
advancement, recipe and system toasts. `native_toast_add` hears it first
as kind `mod` with the mod's id, so a cancelled one isn't queued. False on
a server and without a window. Fixes#12590.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
`native_player_inventory_changed(player, stacks)` is heard on the server
when the per-tick `InventoryChangeTrigger` check finds a player's
inventory changed, before any criterion is looked at, so it fires whether
or not the tree has `inventory_changed` advancements. `stacks` are the 41
slots in `Inventory` numbering (main 0..36, armour feet to head 36..40,
off hand 40). The player's state is swapped in. It fires for the single
player, LAN guests and a dedicated server's players, never on a client
of a remote server, and once at a player's first check after joining.
With no mods the slots aren't copied. Fixes#12587.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
The Windows check only looked at the game's runtime slots, so removing a
version that horizon-ctl, horizon-server or horizon-prepare ran from went
ahead, stopped at the running image with os error 5, and left the version
half deleted. Any running .exe in the version's folder now counts, as any
process naming the folder does on Unix. The cleanup after a Nightly update
checks too: it removed old versions without asking.
Fixes#12588
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ExperienceOrb.readAdditionalSaveData reads getShortOr("Value", 0) with no
clamp and saves (short) getValue() back. Horizon read a missing, 0 or
negative Value as 1, so such an orb saved Value 1s and gave 1 XP. The
orb's value is now a signed int; a pickup gives only a positive value, as
playerTouch gives only a positive remainder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Nintendo glyph art for L, R, ZL, ZR, − and + was a byte copy of
Xbox's LB, RB, LT, RT, View and Menu, so a Nintendo pad was told to press
LB or LT. Those parts now show their label on a key (L, R, ZL, ZR, −, +),
as parts without art already do, and the copied files are gone.
The face buttons keep their copied art: Xbox's A, B, X and Y print the
same letters as the Nintendo buttons, only in Xbox's colours. A test
fails if a Nintendo picture is an Xbox copy that prints another label.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The animate tick, the music and the ambient sounds read the built-in
biomes' files only: in a biome a data pack adds or replaces, the game
showed the built-in biome's particles (or none) and played the built-in
biome's or the dimension's music and ambience, where vanilla reads the
pack's biome (Biome.getAttributes, its visual/ambient_particles,
audio/background_music, audio/music_volume and audio/ambient_sounds).
- BiomeInfo keeps a biome's visual/ambient_particles. The animate tick
uses a pack biome's own list (the tables keep the built-in ones); a
particle type Horizon lacks still draws, so the others keep their place
in the random sequence.
- MusicTables::at and AmbientTables::at read a pack biome's file
(Biomes::pack_file); an attribute it leaves out comes from the
dimension type, as vanilla's attribute layers do, not from the built-in
biome it replaces.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SeonVnpkKZ9a5aRRAWs2Qb
`RunAroundLikeCrazyGoal` asks `isVehicle()`: any player or mob on the
horse makes an untamed one bolt (unless a zombie horse is
`isMobControlled()`). Horizon checked only the controlling player, so a
mob-ridden wild horse stayed calm.
`Mob.canAttack` refuses a ghast for every mob, and
`HurtByTargetGoal.canUse` ends with `canAttack(lastHurtByMob)`. Horizon's
`spares_target` only knew the iron golem's creeper rule, so a ghast's
fireball made a mob take the ghast as its target.
Prism Launcher 11.1.1 keeps its Minecraft component as
meta/net.minecraft/26.3.json, not meta/net.minecraft/26.3/26.3.json,
so preparing from a Prism root failed with "Missing .../versions/26.3/26.3.json".
The flat file is now the last candidate; existing candidates keep their order.
The launcher already reads this path (launcher/src/sources.rs meta_client_jar).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SulfurCube.tick checks for power (primeWhenOnPoweredPosition, which
needs the body's explosionData) before super.tick adopts the body
(detectEquipmentUpdates → collectEquipmentChanges). A cube loaded with
a TNT body on power therefore primes on its second tick. Horizon's
priming pass ran after the mobs' ticks and read the live body, so such
a cube primed on its first tick and blew up a tick early. The pass now
considers only the cubes whose adopted body explodes as the tick starts.
Part of #12572
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ConduitBlockEntity keeps its Target (the destroyTarget reference) and
EnchantingTableBlockEntity its CustomName, so /data reads and changes
them and a save keeps them. The conduit's server tick reads and writes
the target through the block entity. /data get block prints a block
entity's own fields before components, id and position, as
saveWithFullMetadata writes them.
Fixes#12519
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The cold overlay of a large modpack (156k entries) spent half its time cloning files
one by one and a third waiting for the tables. A mod's own namespace folder the base
lacks is now cloned whole (clonefile, one call), the files the copies leave out
deleted from the clone; the other files are copied on up to 8 threads, while the
tables are rewritten beside them. The overlay's tree is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
start_mods (the window's, then the server thread's) read every installed mod's
content again to tell whether it switches live, after content_root had read it:
3-4 s each with a large content-mod modpack. The verdict is now kept by path and
modification time, seeded by content_root; --dev-mod folders are always read.
Also traces mods.content_root, mods.overlay and world.preload (HORIZON_STARTUP_TRACE).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The row dismissed the method as a field write, but set_damage_value is the
documented clamped write and the anvil, grindstone and mob-slot paths call
it.
The row said arriving entities only wait in portals.arrivals. The ticket is
in the tree: Game::place_portal_ticket adds a PORTAL ticket of radius 3 for
300 ticks, server side, on the nether and end arrival paths.
The row skipped the dump as debug only, but HudAtlas::dump_contents is
documented as the method and F3's dump-dynamic-textures shortcut writes
the atlas PNG through it.
The row said Horizon only runs the old track behaviour. It runs both:
redstone_direction is called from move_along_track_new when
minecart_improvements is on, so point the row at it and name the gate.
Fixed typos, grammar issues, and awkward phrasing in the Czech translation.
Improved terminology consistency to better align with standard Minecraft concepts.
Unified key terms across the file for launcher, pack, build, and instance.
Corrected grammatical cases, declension, and gender agreement.
Converted button labels to infinitive form for consistency.
Translated remaining English strings.
Fixed inconsistent T/V forms to keep formal address throughout.
Corrected product name spelling and its declension.
Replaced mistranslations with accurate terms.
Fixed preposition usage.
Corrected typos.
Improved category translations to match Minecraft/Modrinth conventions.
Iris 1.11.7 leaves no rain or snow in depthtex0, whether rain.depth is
absent, true or false, and whether gbuffers_weather or its fallback draws
them: probe packs of ours, read in final, in our rain scene and in a
snowy_plains patch of it. Horizon wrote their depth unless rain.depth was
false, so a pack's later passes saw the rain columns as near geometry.
The weather passes now test depth without writing it, and rain.depth is
no longer read. The GPU weather test now expects the scene's depth after
rain and snow for every rain.depth value.
Part of #10492
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The GUI atlas, which the particles draw from, left out every texture over 1024 wide or 512 tall
without a word: a cap meant to keep GUI textures small. Vanilla's particle atlas stitches any
size, and packs draw light shafts, fog banks and flares from 1024- and 1500-wide particle
textures, which drew nothing. Particle sprites are no longer capped; GUI textures still are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A render hook, native_world_warp, lets a client mod give the frame a world warp: its
world_warp(pos, section) -> Warped moves (and tints) every terrain vertex, far terrain's tiles
included, and every entity vertex; after a line // [fragments] its world_fragment(f) -> Painted
paints, retextures or cuts every terrain pixel by where the point really is. It reads sixteen
floats, the camera split for precision and the frame's partial tick at group 2.
The stock shaders hold only comment markers where it goes in; gfx/world_warp.rs fills them in
copies, validates them with naga and builds the warp's own pipelines (no back-face culling) the
first time a source is seen. While it draws, nothing is culled by where it was (frustum, caves,
CPU and GPU occlusion, section faces) and the frame keeps to the per-section draws. A frame
without a warp costs nothing more.
Tests: the warped shaders validate with and without a pixel stage and stay inside GL's 15
inter-stage variables, the stock shaders hold none of it, a broken source is refused with
naga's message, the hook keeps the last mod's warp. Example world_warp_check draws it through
the real renderer offscreen (and --bench times a frame with and without one).
docs/native-mods.md "World warps".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
test_mob_ai_parity.py --writers runs the oracle under a JFR method trace of
the traced fields' setters (JDK 25) and marks where each tick starts, so
each divergence knows which vanilla methods set the field that tick. blame
lists first those that set it at the divergent tick and not at the one
before, then the others that set it that tick, then the static candidates.
When the motion differs the position isn't blamed separately: it follows.
With FloatGoal users not marked as floaters again (#542), the pig's first
divergence (tick 2, v) now lists LivingEntity.jumpInLiquid and
Mob.jumpInLiquid -> jump_in_liquid first and second; the static order put
jump_in_liquid sixth.
A level change sends ClientboundRespawnPacket with KEEP_ALL_DATA, so the client's new player
keeps the old one's synched shared flags (on fire, invisible, crouching...). The hub forgot what
it had sent, so a flag that cleared after the change never reached the client: a creative player
who left lava through a dimension change kept the fire on its screen until the next time the
flags changed. The flags sent now survive the change, and ServerEntity.sendDirtyEntityData's
update reaches the player as in vanilla.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrowableProjectile.tick starts with handleFirstTickBubbleColumn: every bubble
column block in the box, entered precisely, pushes the projectile before its
gravity and inertia, so the push takes the tick's drag with it.
Horizon had that scan only in Projectile::tick_remote, the client tick of a
server's projectile, so a snowball or an egg thrown into a bubble column rose
for a guest and sank for the host, whose creature tick never ran it for them
(it pushes pearls through their own onAbove/onInsideBubbleColumn pass, which is
a different vanilla call and does not cover the first tick).
The scan is now Projectile::first_tick_bubble_column, run by the shared tick
before its gravity and inertia, so tick and tick_remote both get it once; the
swept columns applyEffectsFromBlocks pushes stay where they were.
LivingEntity.travelInWater sets the vertical movement to 0.2 when
horizontalCollision && onClimbable(). Horizon checked the spider flag
instead, so a mob in waterlogged scaffolding or on a waterlogged ladder
never climbed, and a spider got the climb even when it was not climbing
(Spider.onClimbable is its climbing flag, which on_climbable returns).
Harness: test_mob_water_climb_parity (dedicated servers, frozen and
stepped: a mob in waterlogged scaffolding moving into a wall climbs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.isPushable reads onClimbable live from the block the mob
stands in. Horizon's cached flag was only refreshed by the mob's own push
step, so on the first tick after a load or a spawn (or after it moved)
other mobs pushed it out of a ladder or scaffolding. It is refreshed from
where each mob stands before the tick's snapshot.
Harness: test_mob_climbable_push_parity (dedicated servers, frozen and
stepped: a pair on plain ground pushes apart, a pair in scaffolding stays).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.move refreshes the supporting block (setOnGroundWithMovement's
checkSupportingBlock) before checkFallDamage, whose landing block is
getOnPosLegacy, the supporting block when there is one. Horizon's mob
move refreshed it after the fall, so on the landing tick the fall used
the column under the mob's centre: a pig resting on stone with its
centre over honey took honey's fifth of the fall. The support is now
refreshed first.
Fixes#12536
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BlazeAttackGoal fires with levelEvent(1018) at the blaze's block, unless it
is silent: the client plays the shot at volume 2 with its own pitch. Horizon
played the sound itself, at volume 1, drawing its pitch from the blaze's
random, which put every later draw of its AI out of step.
Blaze.aiStep multiplies a falling blaze's motion by 0.6 before
super.aiStep(), so before travel adds this tick's gravity. Horizon did it at
the end of the AI step and slowed the new gravity too: a blaze fell at
-0.04704 instead of -0.0784 on its first tick.
- The installed list shows a version picked on the Versions tab, with
a button to go back to the recommended one.
- A build that leaves a registry native mod out installs that mod's
next older version that says it builds here, and builds once more.
- The details say when an older version fits and the newest doesn't.
- Updates are looked for again when the instance's Horizon changes.
- The Versions tab shows each version's changelog.
Closes#12534
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Custom particles take their `colormap` (the vanilla grass, foliage, dry
foliage and water colours, or a pack's colormap) multiplied into their
colour, sampled every tick, per block or once as its `cache` says. Block and
fluid modifiers' colormaps tint the blocks and water they target in the
chunk mesh, keeping the block's own tint where the colormap's pixel is
transparent. Colormaps read the named samplers (`biome_id` with its mapper,
in pixel columns, `temperature`, `downfall`, `y_level`, `day_time`), so
packs' sky and fog colormaps that pick a column per biome load too.
Particle and block sound emitters play their sound events.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resource packs' GPU particles (`"type": "gpu"`) are spawn records whose
position, size, colour and frame are a function of the time: gravity and
friction as drag, fade, sway, spin, colour and size toward their ends,
texture strips, `area` spreading one spawn over many quads, and quads under
the open sky only with `kill_below_heightmap`. Custom and GPU particles turn
as their `rotation_mode` says (facing the camera, around Y, flat, along their
motion), and `additive_translucent` ones go to the frame's additive pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The declaration passes horizon-mods' item check: max_stack_size 1 is the field (1..=99), and minecraft:equippable with only slot is a valid 26.3 Equippable. The #tag walk stops at 8 levels and reads only the carried jar's tags, so the oracle path and untagged items are unchanged.
🤖 Agent: Same diff as #12490, which I reviewed before its author closed it (its verify failure was a linker crash on a full-disk runner). A server's registry ids can no longer walk out of the resources folder (no ., .., empty or upper-case segments), predicate components get the same nesting guard as potions, and banner layer counts are bounded by the bytes left (2 per layer).
🤖 Agent: DebugEntries has load, set_status, toggle_status, enabled and save, and the F3+F6 editor is game/debug_options.rs, so the 'no editor' skip note was out of date.
Controller Settings' binding rows take the player's resolved `Glyphs`
(`Context::glyphs`, from `OptionsUi::pad_glyphs`). docs/ui-kit.md keeps
main's HUD kit guide and gains the settings shell as its own section and
table row.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: PersistentEntitySectionManager refuses a UUID already in the level, so a duplicate inside a summoned tree stays a saved passenger under Passengers. Cost to watch: target_with_uuid scans every known UUID, now once per loaded entity with a UUID. I pushed #[ignore] on its second test, which unwraps HORIZON_TEST_RESOURCES like its ignored sibling (verify failed on it).
🤖 Agent: Columns decode on up to 4 workers and are delivered strictly in arrival order (seq). I pushed a fix: the attachments retain runs after the column's block entities load again, so a resent column keeps its block entities' attachments.
🤖 Agent: Builder::weather_layers and weather_layer are in horizon-world's environment_system.rs on main; environment.rs no longer has apply_weather, so the two rows point at the code that runs.
🤖 Agent: NonTameRandomTargetGoal: canUse needs an untamed mob, and the target keeps matching the selector (Turtle.BABY_ON_LAND_SELECTOR now checks !isInWater), so a baby turtle that reaches water is let go.
🤖 Agent: pack_lighting reuses one stack array (every row below h is fully written, the rows above refilled), the server's object states reuse the pooled States, and the guest turn skips the 5-stack clone when the gear is unchanged.
🤖 Agent: TraderLlama.maybeDespawn discards in aiStep, so a removed trader llama leaves the mob list in that tick (tick returns false); tame or persistent ones stay.
🤖 Agent: EntityEquipment.tick runs each equipped item's inventoryTick, so a stand's lodestone compass loses its target when the lodestone goes; only compass stacks pay the lookup.
🤖 Agent: AbstractArrow.tick: a stuck trident falls when the block at its position differs from lastState (the hit block) and nothing is within 0.06; startFalling's three nextFloat()*0.2F draws scale x, y, z in that order.
🤖 Agent: An unused invalid fill_layer in a selected pack now fails world preparation through the existing leaf codec, as vanilla's registry load does, instead of the dedicated server falling back to a flat generator; its capture harness (--kind/--output) runs by name only (pushed).
🤖 Agent: SpiderAttackGoal.canUse is super.canUse() && !isVehicle(): Mob.rider (a player on its back, set by mounts.rs and the remote/LAN vehicle paths) now counts beside extra.rider (a mob jockey).
🤖 Agent: HurtByTargetGoal.canUse returns false for a player's hit when universal_anger is on (checked after the timestamp, before the ignore classes, as vanilla); Who has only Player/Mob, so guests' hits take the same path.
🤖 Agent: MovementPredicate.matches as vanilla: per-tick velocity × 20, speed and horizontal_speed against squared bounds (a negative bound squares, as MinMaxBounds.Doubles does), vertical_speed on |y|, exact bounds with no tolerance; only an unmounted, unridden mob gets the snapshot, so other subjects keep today's false.
🤖 Agent: DefendVillage reads the villager's gossips by the selected player's own UUID instead of level.player_uuid, so a hated guest on a server is attacked and a hated host no longer turns the golem on a guest. I pushed a commit dropping a doc paragraph and a constant copied from another test file.
🤖 Agent: Merged main in (game.rs frame block and the add/add on horizon-polytone particles.rs, which matches main plus tick_source); the merged diff equals the last commit. Without a Polytone pack, polytone_animate/polytone_entity/make return at the first `let Some(registry)`, so the animate tick and spawn paths pay nothing.
🤖 Agent: Stands collide with shulkers, boats and a still adult happy ghast through Entity.collide's getEntityCollisions, with an obstacle grid built only when a stand moves, and Shulker.makeBoundingBox's lid lifts what rests on it.
🤖 Agent: Grindstone max_damage fix for main's menu_click 7998/8000 (#11434): writes the merged MAX_DAMAGE only when it differs from the item's own, as PatchedDataComponentMap.set does; unit test with the failing case
wgpu's indirect multi-draw is ExecuteIndirect on DX12, and on Qualcomm's Adreno
each per-section entry costs the GPU more than the flat path's compute copy and
one index list per page. Measured on an Adreno X1-45 (Windows 11 ARM64), the
four perf_bench scenes held still, three alternating rounds: 3-11% higher FPS at
render distance 12, 2-13% at 32, the same picture.
ArmorStand.brokenByAnything calls dropAllDeathLoot before it pops the
equipment: a stand that is not small (isBaby is isSmall) rolls its
type's table minecraft:entities/armor_stand when mob_drops is on. Horizon
only popped the equipment, so a data pack's loot for armor stands never
dropped when a stand burnt out, was blown up or broken by /damage. The
break now rolls that table first, with seed 0 (the table's random
sequence, else the level's random), and spawns each stack at the stand.
Part of #12514
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A carry without the oracle's facts declared every item that has no block as
a stick, so a mod's helmets and chestplates could not be worn. Items the
carried minecraft:{head,chest,leg,foot}_armor tags name (directly or through
the mod's own tags) now get minecraft:equippable with that slot and a stack
size of 1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Blaze.aiStep plays the burn sound and makes two large smoke particles only
in its isClientSide() branch; the window's game already makes them
(ambient::client_ai_step). The server's copy drew 13 more numbers from the
blaze's random every tick (a nextInt(24) and six nextDoubles), so every
later draw of its AI came out different, and a client following a server
heard the burn twice.
ObstacleGrid keeps main's reused scratch list (near takes &mut self) with
this branch's roots, near_except and huge-area path; the shulker keeps one
progress_aabb with a scale and bullet_origin is the centre of bounds_at;
the shulker shell tick and main's trader llama tick both run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When a new chunk generates next to a chunk an older version saved,
BlendingData.getOrUpdateBlendingData calculates the old chunk's border
heights into its own BlendingData, and BlendingData.pack saves them with
the chunk. Horizon calculated them into SavedOldWorld's cache only; the
save kept the old chunk's blending_data as it was loaded, so the heights
were lost.
The Overworld folder now takes the calculated data of a chunk from that
cache when its save is requested, without calculating anything, and keeps
it with the queued record. The encoder replaces blending_data with
BlendingData.pack only when the loaded record has blending_data with the
same section range. The lookup reaches the cache through a weak link, as
the old world holds the folder. Other folders, new chunks and chunks no
calculation reached save as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The row skipped the account check as a service. AccountStatus::allows_multiplayer
is documented as the method and the title screen enables its Multiplayer and
Realms buttons from it.
The row skipped the predicate, but save::dimension_is_vanilla_like is
documented as the method and its caller world_gen_unstable drives the
experimental-settings warning the note mentions.
The row skipped the predicate as a plain constant, but Slot::is_armor is
documented as EquipmentSlot.isArmor and read by slot_for and mob loot
replacement.
apply_weather is gone; the weather layers live in horizon-world's
environment_system: Builder::weather_layers adds one per attribute rain or
thunder changes, and Builder::weather_layer registers the entries weather
runs.
- Guard recursive `Component::read` calls in `skip::component_matchers`,
`gameplay::block_predicate`, and `v1_20_3::from_tag` with `Nested::enter()`
to prevent stack overflow DoS via deeply nested `CAN_PLACE_ON` / `CAN_BREAK`
exact component matchers, and tighten `banner_layers` pre-allocation cap.
- Validate registry and entry resource location segments in
`net::fill_known_entries` before joining paths and reading local files on
`Clientbound::RegistryData`.
- Fix data race UB in `horizon_threads::heap_census` (`slot` / `slot_name`)
by publishing `NAME_PTR` with `Release` and loading with `Acquire`, and
guard `CensusAllocator::alloc` / `realloc` against size addition overflow.
- Fix provenance UB in `horizon_threads::boot_paint` where an immutable
reference `&count` was passed as Win32 `AddFontMemResourceEx`'s mutable
`pNumFonts` out-pointer, and require `'static` font byte slices.
A portal ticket's streaming area had radius ENTITY_TICKING - level (1 for
the level-30 ticket), so chunks finished only out to ring 2 and a ring-2
chunk never had all its neighbours: its scheduled block and fluid ticks and
block events never ran. Vanilla keeps ring 3 FULL (level 33), so ring 2
(BLOCK_TICKING) runs them. The area's radius is now BLOCK_TICKING - level.
tools/test_portal_ticket_ticking_parity.py sends an item through a Nether
portal on real vanilla and Horizon dedicated servers and compares a
repeater, a piston and water in rings 1, 2 and 3 of the exit's ticket.
Fixes#12288
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GrindstoneMenu.mergeItems sets MAX_DAMAGE to the larger of the two, and
PatchedDataComponentMap.set drops a value equal to the prototype's. Since
#11434 Horizon always wrote it, so merging two plain diamond pickaxes gave
one carrying max_damage:1561 (menu_click 7998/8000 on main).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`SpiderAttackGoal.canUse` is `super.canUse() && !isVehicle()`, and
`isVehicle()` is true for any rider. Horizon's `SpiderAttack` checked only
mob riders (`extra.rider`), so a spider with a player on its back began
the melee goal.
`HurtByTargetGoal.canUse` refuses a player's hit when the
`universal_anger` game rule is on, leaving that hit to the anger target
goals. Horizon's `HurtByTarget` always retaliated against the player, so
the rule changed the anger bookkeeping but not the direct retaliation.
A tick-by-tick parity trace says where a run first differs from vanilla, not
which code computed the value. `blame ENTITY FIELD...` lists the vanilla
methods an entity's tick runs (overrides resolved, plus the goals its
registerGoals builds) that write the field, directly (putfield) or through
its setter, its own class first, each with its Rust port and the ledger's
gaps on it. test_mob_ai_parity.py prints these under each mob's first
divergence when a ledger has been built, and the port of the vanilla frame
of the first random draw when a draw differs.
A vanilla method that adds `0.3F` to a double works with 0.30000001192092896
(javac folds the widened float into the bytecode); a port that writes 0.3
computes another number. The new `float` gap kind finds a method's widened
float constants in the jar and flags its port (or the fn whose inline comment
names it) when the port, or a fn it calls, spells the value only as a double:
not as `x_f32`, `f64::from(x_f32)`, an f32 const, or the widened value
written out.
🤖 Agent: Includes #12475 and #12476. Particle and emitter files are parsed with every expression compiled at load (a typo fails the load); nothing spawns them yet, so no runtime cost.
`NonTameRandomTargetGoal.canContinueToUse` re-tests the goal's
`targetConditions` while hunting, so the `Turtle.BABY_ON_LAND_SELECTOR`
(a baby turtle out of water) drops out as soon as the turtle enters
water. Horizon's `NearestTarget` only ran the base continue rules, and its
turtle filter never checked the water, so a wolf or cat kept hunting the
turtle.
The client kept only the display id of each entry of a
ClientboundRecipeBookAddPacket and looked it up in the numbering of its
own recipes. A server that numbers its recipes differently (a plugin's
recipes, a datapack, another load order) had its recipes dropped, or
shown as another recipe, such as TNT missing on a Paper server.
Vanilla's ClientRecipeBook stores the RecipeDisplayEntry it is sent. The
book now takes each entry as it came: its display, result, category,
group and the ingredients the craftable filter checks. The book's own
entry stays when it is the same recipe (same result, category, layout
and ingredients), so a server of the client's own recipes shows what it
did before, and the entries taken join the groups of the ones kept. An
entry this book cannot show removes the recipe that had its number; an
entry of a category the client does not know changes nothing.
Fixes#11705
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: Tooling only: save_roundtrip compares relative .dat paths as POSIX on every OS, so Windows' backslashes no longer make every file look renamed.
🤖 Agent: A surface rule's biome_is keeps vanilla's biomes and the pack's own; others (a "comment" entry, another mod's biome) are dropped and reported as lossy, since 26.3 refuses the pack over them.
Resource packs' custom particle types now run in the particle engine: the
initializer sets the particle up, the ticker moves, recolours and removes it,
its emitters spawn more, emitter values seed the particles they spawn, and
`limit` caps how many live at once. Block modifiers' emitters run from the
block animate tick, the way torches spawn flames; entity modifiers' emitters
run on the player each client tick (fog, dust and weather around them).
Expressions see the particle, the blocks and biome around it, the player,
the camera, the time and weather.
Model and GPU particles aren't drawn yet (model ones still tick and emit);
additive blending and rotation modes fall back to the plain translucent
billboard; entity emitters ignore bones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reads what a pack's particles do (wiki "Custom Particle Types", "Block
Properties Modifiers", "Entity Modifiers"): a custom particle type's kind
(custom, semi custom copying a vanilla one, GPU), render type, liquid
affinity, physics and despawn flags, limit, tick interval, exclusion
radius, light level and rotation mode; its initializer and ticker
expressions with their named variables; and the particle emitters that
particles, blocks and entity bones carry (the particle by id or with its
options, chance, count, position, motion, colour, size and customs, the
state predicate, biomes, spawn location, bone, max distance). Every
expression is compiled when the pack loads, so a typo is a load error.
Nothing spawns them yet.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An attribute value or argument can name a Polytone colormap (wiki
"Colormaps"): an image read at the column and row two expressions pick,
each 0 to 1 with 1 the top-left corner, rounded to the nearest pixel by
default, the row scaled by the column when triangular. Without a .json
the axes are the biome's temperature and downfall, which the frame now
passes along. The colour is the modifier's argument whenever the layer
applies; a transparent pixel falls back to the value before the pack (the
entry the modifier replaced), as fallback_colormap's default "original".
Colormaps load once per pack reload, shared by the three dimensions'
stacks, and only those a modifier names, decoded to RGBA ints as on
Java. The biome_id axis isn't supported yet.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A number an expression gives outside its attribute's range goes through the range as the sampled
value does (AttributeRange.sanitize) instead of failing validation and leaving the value as it
was. A pack whose fog end goes below zero deep underground (14 * view distance - 128 under the
surface) now gets its thickest fog there, as on the real mod, where the cave was left with no fog.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Controller Settings now describes its pages and rows as a settings_shell
Screen, and the shell draws and drives it, as it does Extras and Graphics.
controller_ui loses its own drawing, scrolling, focus, search, click and
Undo code; Undo is the shell's, comparing the whole PadSettings as before
(Screen::differs).
The shell gains Value::Binding: a row that binds a key or a controller
button. The shell starts the wait (Output::Bind), marks the row while the
host waits, draws the button's glyph art, writes the strip and draws the
row's reset (Reset::{None, Default, Changed}). Extras' sort key and
Graphics' HDR shortcut use it in place of Value::Key and Output::BindKey.
Value::Read is a value the focus reaches and nothing presses.
What players get that is new: the search gives the keys back once the
pad moves the focus off it, no stale ring on the page list after RB or
LB, glyph art in binding rows, a "Not bound" note in the strip, Left and
Right stepping choices, rows scrolled into view when focused, and the
same look and focus as Extras and Graphics. Part of #11487.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: HDR output now takes its gain from Hdr::draw_params instead of the removed mark-resolve lane; the change stays behind HDR, which is off by default, and frame_identity's test covers the no-pack path.
DefendVillageTargetGoal asks the villagers in the golem's box for the
reputation of the player (Villager.getPlayerReputation: the gossips
about that player's UUID). Horizon read one reputation per villager,
keyed by the level's own player, while each golem's goal looks at the
player it selects, often a nearby guest: on a server, a hated guest was
never attacked, and a hated host could turn the golem on a guest it
did not hate. The goal now reads the villager's gossips about the
selected player's UUID.
Fixes#12487
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: 1.21.1 biome effects move to 26.3's visual/audio attributes (colours as #rrggbb), carvers become the air list, and liquid carvers are reported as lossy.
🤖 Agent: A bare component in an item argument ([custom_data]) now fails with expected '=' as ItemParser's reader.expect does, instead of being ignored; !removals are unchanged.
🤖 Agent: Frustum select picks the same corner as the branches did (-0.0 >= 0 both ways) and the resident bounds are origin()'s arithmetic inline. I pushed a fix: upload order measured the camera section as floor(y/16) while keys count from the world's bottom, four sections off in the overworld.
🤖 Agent: Pack expressions are compiled at load and evaluated against the frame only when a pack's environment stack is loaded (pack.environments), so the vanilla path sets no frame and keeps folding constant layers.
🤖 Agent: Tooling only: loot_mine_location_block asserts its .local/h079/capture is new, so it fails at startup on the Mac runner after one run (#12405); it joins the other capture harnesses in named_only.
🤖 Agent: The room is rebuilt each frame from the boss bars (BossHealthOverlay: y 12, step 19); with 0-1 bar the notice stays at y 18, and HudBatch::new there allocates nothing. I merged main (client_lib re-export list: hud_kit beside main's social).
🤖 Agent: A block item whose block has another name (seeds) is carried with place_block; one whose block is left out stays in items_unknown. I merged main (both branches added a test at the same spot; both kept).
Entity.kill posts entity_die for every entity it removes, so /kill now
does for dropped items (a wool stack dampens it, ItemEntity
.dampensVibrations), cushions, end crystals (after EndCrystal.kill's
onDestroyedBy, even an invulnerable one) and the entities Horizon only
stores (markers, displays, interactions). An armor stand's own death is
a living source no warden can target.
A blast's kill of a painting, item frame, cushion or armor stand is
credited to its causing entity (BlockAttachedEntity.kill /
ArmorStand.kill with DamageSource.getEntity): the mob, the player who
lit the TNT or broke the crystal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Reads what a pack's particles do (wiki "Custom Particle Types", "Block
Properties Modifiers", "Entity Modifiers"): a custom particle type's kind
(custom, semi custom copying a vanilla one, GPU), render type, liquid
affinity, physics and despawn flags, limit, tick interval, exclusion
radius, light level and rotation mode; its initializer and ticker
expressions with their named variables; and the particle emitters that
particles, blocks and entity bones carry (the particle by id or with its
options, chance, count, position, motion, colour, size and customs, the
state predicate, biomes, spawn location, bone, max distance). Every
expression is compiled when the pack loads, so a typo is a load error.
Nothing spawns them yet.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An attribute value or argument can name a Polytone colormap (wiki
"Colormaps"): an image read at the column and row two expressions pick,
each 0 to 1 with 1 the top-left corner, rounded to the nearest pixel by
default, the row scaled by the column when triangular. Without a .json
the axes are the biome's temperature and downfall, which the frame now
passes along. The colour is the modifier's argument whenever the layer
applies; a transparent pixel falls back to the value before the pack (the
entry the modifier replaced), as fallback_colormap's default "original".
Colormaps load once per pack reload, shared by the three dimensions'
stacks, and only those a modifier names, decoded to RGBA ints as on
Java. The biome_id axis isn't supported yet.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A number an expression gives outside its attribute's range goes through the range as the sampled
value does (AttributeRange.sanitize) instead of failing validation and leaving the value as it
was. A pack whose fog end goes below zero deep underground (14 * view distance - 128 under the
surface) now gets its thickest fog there, as on the real mod, where the cave was left with no fog.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Reuse the pooled EntityState entries in States (`out.add`) across all 21
object and projectile loops in `lan_entities.rs`, and push vehicle, armor
stand pose, and arrow metadata directly into `e.meta`.
- Look up vehicle driver mobs via `self.mob_index` in `lan_riding` instead
of a linear scan over `self.mobs`.
- Compare `play.equipped` by reference before cloning 5 `ItemStack`s on
every `enter_guest` turn, and reuse `play.craft_grid` via `std::mem::swap`.
- Eliminate per-fluid-cell `Vec::new()` allocations in `mesher/fluid.rs` and
deduplicate animated fluid layers directly into `out.sprite_layers`.
- Reuse `SectionMesh::packed` capacity and a thread-local scratch buffer in
`mesher/packed.rs` during `sort_solid`.
- Pack uniform/single-channel lighting from a stack buffer in `chunk.rs`
(`Light::from_slice`) instead of allocating and dropping a 4 KiB `Box`.
- Check `!self.saves_beside_final` and neighbor readiness before cloning
`Chunk` in `Streaming::update`.
Keep entity-context geometry and state-dependent light in the same content compiler. Exercise both selectors in the light propagation fixture.
Co-authored-by: Codex <[email protected]>
Java 26.3 `ItemParser.State.readComponents` expects '=' after a component
type unless it begins with '!'. Return an error for bare components rather
than silently ignoring them.
Co-Authored-By: Antigravity <[email protected]>
It writes .local/h079/capture and asserts the capture is new, so every automatic
run on a checkout that ran it once fails at startup (the Mac mini runner).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A consumable or blocks_attacks the server's patch adds (components 24 and 37) now starts a use like the item's own (getUseDuration reads the stack); items with their own consumable are unchanged.
Pending upload keys count sections from min_y, so floor(y / 16) put the camera
four sections low in the overworld.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Realms answers 6002 for a new account: RealmsTermsScreen shows (Agree posts /mco/tos/agreed, then the join resumes; Disagree goes back), with vanilla aka.ms/MinecraftRealmsTerms and mco.terms.* keys.
A pack's attribute value or argument can be a scripting expression
(wiki "Environment Attributes"): fog that closes in with the rain and
with depth, a cloud height scaled from the value the layers before gave.
Each one is compiled when the pack loads and evaluated whenever its layer
applies, against what the frame sets: g (rain, time, day time, sky type,
dimension, render distance, moon phase), p (position, in water), c
(position, view distance in blocks), o (the camera's position, sky and
block light), r (random), v (the value so far, colours packed ARGB) and
the pack's config(id). Its result is the modifier's argument; one that
fails leaves the value as it was. A dynamic entry is never folded into
the base value, since it changes every frame.
Expressions are compiled with Arc instead of Rc so a pack's stacks can be
built on the loading worker.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Screen::Studio is opened only by `studio screen open` and draws nothing itself (no chat behind it); Escape closes it. I pushed the rustfmt fix verify failed on.
🤖 Agent: FXC (DX12) failed on discards one call below the entry point; fs_entity now computes entity_color itself and hands it on, same colour on every backend.
dedicated.rs: main's `stop_now` loop condition, keeping the per-tick
`server_tick_deadline` the eager saves read for `haveTime`.
ModifiedChunks gains `clear()` (dirty set only, like `drain`) for main's
new mod_block_entity_tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fekd42N6Dh4LhpXkVwZmHs
With the packs now laid over the content root, rewriting the root's stamp
on every reuse changed a file the pack overlay depends on, so a second
launch with packs on rebuilt the overlay (53 s instead of 18 s). Mark the
reuse in `<root>.used` beside the root; pruning takes the newer of the two
times and removes the marker with its root.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Pack half-lives replace OptiFine's defaults in the same units (600/200/10/1). I pushed a scripted-clock version of its test: backdating u.last was the wall-clock flake #12354 removed.
A mob wearing Thorns now hurts the mob that attacks it, as
EnchantmentHelper.doPostAttackEffects runs after each vanilla attack
caller: melee, stings, cube contacts, golem hits, charges, spear stabs,
direct arrows, tridents, wind charges, fireballs, spit, shulker bullets,
wither skulls and owned evoker fangs. Ravager roars, guardian beams and
retaliation, hot-cube contact and other non-attacks get their own
DamageMob event that skips it. The damage rolls from the attacker's own
random, the chance from the level's.
The dragon's head and wing contacts run it for mobs (part by part, as
knockBack and hurt walk each list) and for players, whose Thorns now
hurts the dragon through its body part.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
🤖 Agent: Plain files are copied with the same size and path checks read() made; tags, atlases, declarations, biome modifiers, worldgen and structures are still read and parsed.
🤖 Agent: The friends worker never holds the lock across HTTP (reads the etag, unlocks, fetches, relocks), and nothing starts unless the account's FRIENDS_ENABLED flag is set.
🤖 Agent: The flake builds the launcher from the repo itself, so it follows main, unlike #11200's pinned tarball; both outputs evaluate on NixOS (horizon-launcher-0.2.0.drv, nix-shell.drv); CI does not build it.
Reuse the existing loaded-target predicate for actual stand equipment inside
the entity-ticking guard. Preserve player behavior and bounded tracker rules.
Fixes#12395
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
forAllEdges and forAllAxisEdges were skipped as data, but the walk is in
crates/horizon-core/src/geometry/shape.rs: VoxelShape::edges is documented
as forAllEdges, and its inner axis scan is forAllAxisEdges.
The class row was written with a `port` verdict and the function that
represents it. Class rows take `home` with the files that represent the
class; list datapack.rs (folder_stamp) and options_ui.rs (watch_data_packs).
The creative inventory screen moved out of game.rs into its own module.
draw_creative, creative_tooltip_tabs and click_creative are unchanged; only
the module path in the six rows that name them was left behind.
`Game::shear_pig_saddle` was renamed and generalized to `Game::shear_saddle`
when shears began taking the saddle off any saddleable mob. Its doc names
`Mob.attemptToShearEquipment` and `Mob.shearItem`; re-point the two rows.
`Game::infested_hurt` was split. `Game::spawn_infested` spawns a silverfish
per drawn velocity at half the bearer's height with the hurt sound, and
`Game::player_infested` is the player's roll and spawn. Re-point the two
rows that still name the old function.
A new account's first join gets error 6002; GetServerDetailsTask shows
RealmsTermsScreen instead of the generic error. Port it:
- The title, the first sentence and the underlined terms link at
row(5), with the hover colours and the link's measured hit box
(extractRenderState's onLink). A click opens
CommonLinks.REALMS_TERMS (https://aka.ms/MinecraftRealmsTerms).
- Agree (width/4) and Disagree (width/2 + 4), each width/4 - 2 wide at
row(12). Agree posts mco/tos/agreed (RealmsClient.agreeToTos) on a
worker, then runs GetServerDetailsTask again from the same screen;
a failure logs and stays. Disagree and Escape return to the screen
that started the join (GetServerDetailsTask.lastScreen, now carried
by Connecting).
Co-Authored-By: ZCode <[email protected]>
Review follow-up. Glyph::of and Glyph::menu took any GlyphStyle, so a
caller could pass the raw setting and lose a Nintendo pad's letters,
and NintendoLetters leaked into the setting's enum.
pad_glyph::Glyphs { family, letters } is now opaque. Only
Glyphs::of(&PadSettings, family) makes it, plus Glyphs::unheld for
splitscreen's prompts. GlyphStyle is the setting again. A join prompt
now shows the joining pad's own family, not the viewer's.
The on-screen keyboard reads its input through MenuJob, the map its
hint glyphs use. The tests build letter mode from parsed settings and
pin A, B, X and Y on their own. The source scan is gone, since the
types enforce the rule now. An unknown action in GlyphOf::Bound trips
a debug assert, and pad status lists each guide row's label.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Backdating u.last measured the gap with the wall clock; a few ms of runner delay
moved wetness past the 1e-3 tolerance (the flake #12354 fixed in the Iris test).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Moving the retain before the block entities loaded dropped every attachment of a
resent column, and skipping it for new columns kept those of forgotten ones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server that adds blocks_attacks and a long consumable to a sword (1.8
style sword blocking, #11817) sends both components in the stack patch,
which the client reads past. The client never started the use itself and
remote_tick_using dropped the use the server's living flags started on
the next tick, so the right click kept restarting the use and the input
slowdown flickered (GrimAC NoSlow).
Read the added components from the wire patch, as Item.use does from the
stack's components: Consumable.startConsuming / startUsingItem start the
use, Item.getUseDuration takes the consumable's consumeTicks (else 72000
for blocks_attacks), and the use is held until the button lets go or the
server stops it, as LivingEntity.updateUsingItem never completes a use on
the client (LocalPlayer.onSyncedDataUpdated keeps it running).
On Windows, Git Bash's sha256sum writes `<digest> *<name>`, so the
optional release assets test kept the `*` in every name and failed for
all four masks. Both forms are valid sha256sum output; accept them.
Fixes#12454
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The content root was built over the pack overlay, so every pack change
(and a rebuilt overlay at launch) gave it a new stamp and a full rebuild:
84k files and 2-3 minutes on Windows with a 23-mod modpack. Build it over
vanilla instead and lay the packs over it: vanilla, then mods, then packs,
which is also vanilla's order (a pack that retextures a mod block wins).
Fixes#12403
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #12282, fs_entity called entity_written_color, which called entity_color.
FXC (every DX12 game: Horizon ships no dxcompiler.dll) fails on that with
"internal error: argument pulled into unrelated predicate" while creating the
first entity pipeline, and the game crashes at start. entity_written_color now
takes entity_color's result instead of calling it.
Addresses review feedback from #11200:
- Builds horizon-launcher from repository source via rustPlatform.buildRustPackage
instead of fetching a pinned binary tarball by hash.
- Native build support for both x86_64-linux and aarch64-linux.
- Added essential POSIX tools to devShells.default for pr_ready test suite.
Co-Authored-By: Pluto <[email protected]>
- Parallelize chunk decompression across a bounded worker pool while enforcing sequential retirement (FIFO arrival order) via monotonic sequence ordering.
- Filter resident neighbors and batch section dirtying with column masks.
- Skip attachment and block entity cleanup scans for fresh (non-resident) chunks.
- Enable TCP_QUICKACK on Linux streams and reset empty reader buffers to prevent drift.
- Elevate network worker thread QoS on supported platforms.
🤖 Agent: Same fixed [u64; 128] bitset with spill as the generator's (#12346): spilled words keep index 128 + offset, so the decorated chunk's biomes stay ascending, and the exact reserve avoids regrowth.
🤖 Agent: ClientLevel.tickEntities ticks in add order: after login (and after a respawn re-adds the player last) a mob added later pushes from where both ended the tick, so the push reaches the player's next move, which is what GrimAC's Simulation check expects.
🤖 Agent: client_mod_command is the chat-side mod hook; the SMP and other server mods use native_perform_command, so gating guests (lan_acting) out of it removes a level-0 path without touching server commands. The 4 GiB zip budget is per opened archive, and every Zip lives for one load.
🤖 Agent: PORTAL tickets are saved in chunk_tickets.dat with level and ticks left, reload on restart (prepareLevels), and do not expire while their chunk is still loading (canTicketExpire). I fixed the main merge: #12232's ticket fast path compared (chunk, level) pairs with this PR's PortalTicket (LAND-STOP: E0308); it compares (ticket.chunk, ticket.level) now.
🤖 Agent: item_defaults::shared is the cached item_defaults::read, so the enchantment properties read the same snapshot Items already decoded; the standalone loader still reads its own.
Game::hud_room gathers the boss bars the HUD shows each frame; hud empties the room every
frame first. The notice and the join strip go below the bars when they would cover them,
and stay at y 18 otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
hud_kit::room holds a view's keep-outs, today the boss bars, and tells a centred overlay
where it goes: where it wanted unless the bars reach past it. boss_bars_bottom follows
BossHealthOverlay's layout. hud_kit::plate draws the notice's box.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's ClientLevel ticks entities in the order they were added: after
login the player comes first, so a mob added later pushes it from where
both ended the tick, and the push reaches the player's next move. After a
respawn the new player is added after what the level holds, so those
entities tick before it. Horizon applied every mob's push before the
player's move, from where the mobs had just moved: walking into a mob
the server is pushing away tripped anticheats (GrimAC Simulation 0.02-0.04
on every push).
🤖 Agent: The spawner and trial-spawner lists keep their tick order and are taken around the client effects, so a client tick can't see a half-filled list.
🤖 Agent: Indexing the retained buffers by mob index is safe: nothing between the fluid loop and the rain loop adds or removes remote mobs, and an early return only loses the warm capacity.
🤖 Agent: The outline strokes keep their order (black then bright) and the custom PvP colour; both scratch vectors are bounded by MAX_OUTLINE_EDGES, so the retained capacity stays small.
The live getFriends answer an account with no friends gets is
{"empty":true,"friends":[],"incomingRequests":[],"outgoingRequests":[]};
the parser reads the three lists and ignores the flag. Recorded as a
regression case beside the populated answer.
Co-Authored-By: ZCode <[email protected]>
ArmorStand.hurtServer returns false for an explosion, fire that ignites
armor stands and burning, after it has acted on the stand: a blast breaks
it (its equipment pops, then it is killed), in_fire lights a stand that
is not burning for 5 seconds, and on_fire burns 4 health off one with
more than 0.5. The command route had no armor stand arm, so /damage left
the stand as it was. It now runs those sources with the stand's guards
and still fails as vanilla's command does.
Part of #12380
Co-Authored-By: Claude Opus 5.5 <[email protected]>
It needs --binary, --work-dir and --reference-dir, which an automatic
parity run doesn't pass (#12372).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- BoxReach (the boats' and carts' mob pre-filter) is worked out from each
mob's real box, so a shulker whose open lid grows its box sideways or
below its position is still found: a boat runs into the lid and picks
the shulker up, as in vanilla.
- ArmorStand::shove makes up for Horizon ticking stands before mobs: a
stand that joined after its shulker gets vanilla's push-then-travel
ground and speed, one that joined before gets Entity.move's own.
- /data merge keeps a mob's and a stand's join order (Entity.load changes
the entity in place).
- A shulker a boat or a minecart picks up turns to AttachFace DOWN
(Shulker.startRiding).
- The stand's box uses the float height 1.975F, canStayAt the shulker's
scale.
- The harness adds wall, ceiling and squeezed lids, with the stand joined
after and before its shulker, and the boat that runs into an open lid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Shulker.onPeekAmountChange moves every entity with physics in the lid's
path with Entity.move(MoverType.SHULKER), armor stands included, and
Shulker.makeBoundingBox grows the shell's box by the lid's physical peek.
Horizon pushed only the player and mobs and sized the box from the
dimensions, so a stand resting on a normal shulker sank into it the first
time the lid opened.
- The lid sends MobEvent::LidPush; ArmorStand::shove moves each stand with
physics through the collision resolver. Mob::bounds_at gives a shulker
its makeBoundingBox, scaled and grown by getPhysicalPeek in float.
- Shulker.tick's own part (keep a face, ease the lid) runs without AI
too, and not for a shell that rides something (isPassenger).
- /data merge keeps a shulker's eased currentPeekAmount: Entity.load reads
only the raw Peek.
- A stand leaves out the boxes of mobs riding it
(isPassengerOfSameVehicle), so a stand carrying a /ride shulker rises.
- ObstacleGrid::near tests each box once when the query spans more cells
than there are boxes, and a stand's loaded Motion drops a component past
10 (Entity.load), so a huge Motion no longer stalls the server.
- A rider sits on a stand's float passenger point (1.975F widened).
- The harness adds a stand carrying a riding shulker and the lid opening
and shutting under the first stand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ArmorStand.travel moves with Entity.move, whose Entity.collide adds
EntityGetter.getEntityCollisions: the entities that canBeCollidedWith the
stand (a living shulker, a boat, a happy ghast held still) stop it as
blocks do. Horizon resolved a stand's move against blocks and the world
border only, so a stand dropped onto a shulker sank into it, one dropped
onto a boat fell through to the ground, and one pushed into a shulker
went through it (#11877).
The stand tick now gathers those boxes once, when the first stand
travels (the same set that stops a dropped item, primed TNT or a falling
block), and passes the ones meeting each stand's box stretched by its
movement into the move. It runs in the server's tick, so single player,
LAN hosts and dedicated servers all get it.
The new stand_entity_collision harness drops stands onto a NoAI shulker,
a boat and the stone, slides one into a shulker's side, and compares
their Pos, Motion, OnGround and fall_distance with vanilla's tick by
tick. Without the fix 27 of its 198 records differ.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Packs set these const directives (ShaderDoc, OptiFine's shaders.txt);
Horizon always used the defaults (600/200/10/1 ticks). IterationT sets
wetnessHalflife 200 and drynessHalflife 50, so under Horizon its sky
stayed sunny for a minute after rain started (wetness rose 3x too
slowly) and stayed wet 4x too long after it stopped. The pack log now
names the half-lives a pack sets.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
draw_frame looks every visible tile up 3-4 times a frame and upload_lods walks them all; with
lodDistance 256 (~8.5k tiles) std's SipHash was 8-10% of the main thread.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Lod::dispatch_meshes` collected every active tile's ready samples into a new
map for each batch of mesh jobs (thousands of entries, rehashed as it grew):
5-10% of the main thread in flight. The map is now kept up to date where a
tile turns active or inactive and where its samples change
(`Lod::sync_sample`), in shards of 8 by 8 tiles behind `Arc`s: a batch copies
the shards' handles, and a change copies only its shard while a job still
reads it. Debug builds check it against the full gather on every dispatch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Building a mod set's resource root read every content file and wrote it back out; with the
14 Popular Mods (Chipped's 10k textures) that was about two thirds of a 37 s first start
(sample: open/read/write on the main thread). Files the build does not parse are now copied
with fs::copy (a clone on APFS), checked as reading them was (name, 16 MiB limit).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
draw_frame tests every far tile against the frustum each frame. A Tile held two SectionGpu
inline (~0.5 KB), so with lodDistance 256 (~8.5k tiles) the walk read ~9 MB a frame and was
the main thread's top cost (17-23% in sample). Boxed, a tile is ~90 bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As 26.3 wrote crimson and warped forest vegetation: a simple block on nylium,
tried spread_width² times at a trapezoid offset, where there is air. Terralith's
last feature left as it was.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A BlockItem whose block has another name (Farmer's Delight's cabbage seeds
place `cabbages`, Supplementaries' way signs `way_sign_wall`) was left out
as "a block item without a block": 35 items over the 8 mods. It is now
carried with `place_block` naming its block when that block is carried
(vanilla's or one of the mod's declared ones), and left out as "its block
is left out" otherwise. That check looked the item's own name up in the
left-out blocks instead of its block's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Food was left out ("no food template yet"): 82 items over the eight mods tried, most of Farmer's
Delight. 1.21.1's minecraft:food held what 26.3 splits into food (numbers), consumable (eating
time, on_consume_effects: one apply_effects per effect with its own chance) and use_remainder
(the bowl), as 26.3's golden apple and mushroom stew have them. NeoForge's cures are dropped, and an
effect of the mod's own is left out and reported (the game would refuse the item).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
26.3 has no `block/template_farmland`: its farmland model is
`template_cube_bottom_top_indented` with `bottom`, `side` and `top`, where
1.21.1's template read `dirt` for the bottom and sides. A carried model on
the old template (Supplementaries' raked gravel) now names the new one with
its dirt as side and bottom, instead of drawing the missing model.
`minecraft:block/rope` (Supplementaries' bunting particle) is in neither
1.21.1 nor 26.3: not a rename, left as is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A vanilla-type recipe with neoforge:conditions was kept as 1.21.1 wrote it, and 26.3 refused its
old ingredients: Quark's 1,466, most of Supplementaries'. The oracle's recipes are the ones whose
conditions passed (a mod's own conditions, such as config flags, are only known there): those
lose the conditions and are upgraded; the others go outside, as NeoForge left them out. Without
the oracle, NeoForge's own conditions are read against the mods ported along.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod's tag additions under another mod's namespace (data/create/tags/...) made it claim that namespace
when its own files named it; two such mods in one pack then refused to load together (Popular Mods:
Supplementaries off). Tags alone now stay outside the carry, as other compat files do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Main fix: three lines a union merge brought back read a flag #12242 removed, so every command_exec-driven harness stopped with AttributeError (80 of 173 on #12344's run). Nothing else in the driver uses an undefined args attribute; the corpus builds its 161 groups.
#12242 removed --seed-sync-gaps-diagnostic and the block that read it.
The union merge that landed TheMrZZ's series (#12160) kept the block from
an older base without the flag, so every command_exec run (and every
harness that drives it) stopped at
AttributeError: 'Namespace' object has no attribute 'seed_sync_gaps_diagnostic'.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Tooling only: 17 capture-preserving harnesses that fail at startup under parity.py (no --work-dir / a capture folder that must be new) move to named_only; they still run by name and in the nightly --all audit.
🤖 Agent: A cat or parrot panics from any #panic_causes damage and only a wolf keeps #panic_environmental_causes, as TamableAnimalPanicGoal is built per species; the old fire_ticks gate is gone. I made its test return early without extracted mob data (CI compared None with Some(true)).
🤖 Agent: Primed TNT's box is 0.98F and its eye height 0.15F, widened from float as EntityType.TNT's sized() gives them; the command_exec case reads them back. Merged main (command_exec corpus group order).
Seventeen harnesses need --work-dir (a fresh capture folder) and
--reference-dir, or assert that their fixed capture folder is new.
parity.py passes neither, so whenever --changed selects them they fail
at startup, whatever the pull request changed: 14 of the 18 failures on
#12336's parity run were these. List them under named_only: they still
run when named and in the nightly --all audit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A LAN/dedicated guest's command no longer runs the host's client-side
native mod commands (they ran before any permission check; client mod
commands belong to the local player only).
- The server-files JSON parser refuses documents nested past 128 levels
instead of recursing unbounded.
- The zip reader caps the total inflated bytes across entries (4 GiB per
archive), not just per entry.
Co-Authored-By: Mercury <[email protected]>
# Conflicts:
# crates/horizon-client-render/src/zip.rs
TraderLlama.maybeDespawn discards an eligible llama in its aiStep when its
DespawnDelay runs out, so it leaves the level's entity list in that tick.
Horizon's countdown set m.removed, but ai::tick still returned true, so
the creature tick kept the llama in game.mobs until the next tick. During
that extra tick the next mob snapshot and the native leave bookkeeping
still saw it. Commands, the tracker and collisions already skip removed
mobs, so commands showed no difference.
The final return of ai::tick now reports a trader llama that its own tick
removed, and the creature tick drops it in that tick, as it does for a
despawned mob. The rest of the llama's tick still runs, and other species
are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Port the service behind vanilla 26.3's friends list, without its screens:
- authlib's FriendsService over the discovery document: getFriends,
updateFriends, updateAttributes and updatePresence with the account's
bearer token, If-None-Match/ETag caches, Retry-After poll intervals and
the ten-second read cooldown (MinecraftServicesFriendsService).
- RemoteFriendListUpdateHandler's worker and state: one read a minute
(five in the background), a forced read after a successful action, the
result-code-to-state mapping and the cached lists.
- PlayerSocialManager's flags and accessors, with no screen using them.
- PresenceHandler: the sharePresence option picks the public status, the
client supplies getPresenceStatus each tick, and closing posts OFFLINE.
Minecraft's constructor decides whether the worker starts from the
account's friendsPreferences (UserFlag.FRIENDS_ENABLED /
ACCEPT_FRIEND_INVITES); title.poll_account now does the same after its
getAttributes answer. The overlay, the Online options switches and the
title/pause icon changes stay for later parts, so no player-visible UI
changes here. A joined Realm is carried to the presence status through
ServerData.isRealm's equivalent (Action::Connect.realm).
No new harness: the list and presence parsers, the state mapping, the
sharePresence levels and Retry-After are unit-tested.
Co-Authored-By: ZCode <[email protected]>
VibrationInfo keeps the UUIDs of its source entity and of a projectile's
owner, and VibrationInfo.CODEC saves them as source and projectile_owner.
The sculk listener now takes them from the save layer's UUID of the
mob the event names, writes them, and reads them back. Loaded vibrations
keep no live entity; a player source stays unnamed.
Fixes#12186
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Server configuration dialogs keep the panorama behind them (DialogScreen inherits Screen.extractBackground) instead of the flat 20/20/20 fill.
🤖 Agent: Includes #12355. A version picked in the Versions tab is kept (chosen-versions.json) until the player installs the recommended one, so the pre-build swap and the automatic updates no longer undo a deliberate pick.
Float writers already store colour and coverage. Remove the unused mark
resolve pipeline and shader, and keep the world scratch and parameters for
the tone curve and HUD composite that still use them.
Choose no-pack HDR gain explicitly without storing a frame lane. Keep the
uniform ABI and existing light arithmetic. TAA tracks baked gain and its
settings, preserves compatibility history when gain is off, and rejects
history when it resumes after a shader pack.
Use a non-float legacy oracle for writer tests and restore exact float,
OIT and visibility-buffer sessions in the identity harness. Preserve the
compatibility mark paths and the non-layered hand brightness sentinel.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Each stream now starts from 21 zeroed bytes (clear + resize, pushed): a bare resize left the previous stream's bytes past a short block, which the decompressor may read, so corrupt-stream checks could differ from a fresh buffer.
🤖 Agent: The retained page list is cleared after the shader pack's post passes (pushed): it keeps its capacity, but no longer holds clones of arena page buffers until the next pack frame, which would keep freed pages alive on the GPU once the pack is off.
🤖 Agent: Test-only: the smoothed-uniforms test steps PackInputs::frame_timing instead of backdating the wall clock, so a slow runner can no longer push centerDepthSmooth past its 1e-3 tolerance (failed verify on #12241 and #12311).
🤖 Agent: This head holds all 13 layers of TheMrZZ's series (12010 already merged; 11732 … 12133 below it), so landing it merges the rest. The top layer's own point: inside the hurt cooldown a hit is compared with `lastHurt` by its incoming amount, before armor and Resistance, and only the difference goes on to `actuallyHurt`, as `LivingEntity.hurtServer` does.
🤖 Agent: Stacked on #12330: landing it lands the chain. Only the float frame (HORIZON_HDR_FRAME=float) changes its alpha; 8- and 16-bit frames keep their pipelines and marks.
🤖 Agent: A spilled word keeps its index (128 + spill offset) in the chained iteration, so the ids stay ascending as with the Vec bitset; 1 KB on the stack per generated chunk.
🤖 Agent: Blender has no interior mutability (two OpenMaps and an Option, read only), so one shared empty Arc is safe for every chunk without old-world data, and non-empty blenders stay per chunk.
🤖 Agent: The ten handoff loaders share only the resource root and the parsed entity document, both immutable; a panic in a worker is resumed on the caller and Creative's error keeps its message, so startup fails the same way. No wall-clock number yet: the gain is the slowest loader instead of the sum.
A registry native mod's Versions tab lists each version's date and how it
fits the Horizon played (the fit line the trust text already uses), marks
the one the launcher would pick Recommended, and has Install on every
version but the installed one.
A version the player picks there stays as it is: no automatic update, no
offered update and no swap before a build, until the player installs the
mod again or picks its recommended version (mod-browser/chosen-versions.json).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The start's automatic update moved native mods to their newest version, which the build then swapped back when it doesn't build on the Horizon played. It now updates them only to the newest version that builds there, when that is known.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
smoothed_uniforms_take_iris_half_lives backdated the wall clock and let
update() measure the gap, so any delay between the two calls moved
centerDepthSmooth (0.1 s half-life) past its 1e-3 tolerance. It failed
verify on busy runners for unrelated PRs (#12241, #12311). Use
PackInputs::frame_timing, which update() already takes as the frame's
delta, so every step is exact.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PotionItem.getName and TippedArrowItem.getName name these items by
their contents (PotionContents.getName: the contents' custom_name, the
potion, else empty), over item_name. brewing::contents_name_key gives
that key, and the five functions that name a stack take it after the
custom_name component and before item_name.
New command corpus group: potion contents names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resource packs' Polytone biome and dimension modifiers now change the
environment attribute stack the frame draws (wiki "Environment
Attributes"): a dimension modifier's base group changes the dimension's
own entries, rain and thunder change the weather's, post_process adds a
layer after all others; a biome modifier's base group changes its target
biomes' entries (ids, #tags through the biome tags, patterns) and
post_process adds a biome layer after the weather. REMOVE drops an
entry; modifiers apply lowest priority first. Values that are scripting
expressions or colormaps are left for the next steps.
The stacks are built when packs load, only when a pack has modifiers, and
only the frame reads them: gameplay (sky darkening, the server's sun
angle) keeps vanilla's. Without such a pack nothing changes.
With such a pack on, its biome and dimension modifiers load and apply;
the environment_render and environment_system harnesses still match
vanilla.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: LAN join and respawn packets carry the world spawn and its rotation, not the acting player's personal spawn, and /setworldspawn now updates connected players in every dimension (SetWorldSpawnCommand.setSpawn, PlayerList.sendLevelInfo).
Polytone packs keep most of their effects in pack overlays that a
polytone_condition turns on: a scripting expression over the pack's own
config entries (config('pack:fog') == 'ON', modOn('minecraft', range)),
or an object with require_config, require_mods and override. A pack's
config entries (assets/<namespace>/polytone/config_entries/*.json) are
now read at their default values, and an overlay with a condition is
active when it holds: an expression decides alone (packs pair it with
formats no version matches), an object only alongside the formats unless
it overrides them. Overlays without one work as before.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Environment now builds the dimension's EnvironmentAttributeSystem as
addDefaultLayers does: the dimension type's attributes, a biome layer for
every attribute some biome sets, the timelines its tag lists (in order),
and the rain and thunder layers where Level.canHaveWeather. The frame
samples it with the camera's Gaussian biome mix (the probe's order) and
the biome at the camera, so every attribute blends and changes with the
weather the vanilla way, not only the sky and fog colours, and the
hand-written timeline sampling, biome colour blending and apply_weather
are gone. Biomes keep their attribute maps for the biome layer.
environment_render now passes with no tolerance except one rounding in
Sky's light level fraction (was 218 inexact cases), and covers cloud
height.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Installing a registry native mod took its newest version, and a build
left out any mod made for another Horizon, though the registry often has
an older version made for the Horizon the player picked.
native_fit::best picks the newest version that builds on the Horizon
played, else the newest that moves the build to its own commit without
clashing. Installs and updates use it, and before each build every mod
turned on whose installed version doesn't build is swapped for that
version, with a line saying which version is used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Nintendo pad going by its letters reads its A as the part that
confirms and jumps, but only the binds list and the on-screen keyboard
showed that A. Each caller had to apply the letters itself, and the
guide, declared buttons and notices didn't.
Letter mode is now part of the resolved glyph style:
GlyphStyle::NintendoLetters, which only PadSettings::glyph_style
produces. It is never saved or offered. GlyphStyle::on_pad turns a part
into the button that plays it, the same way input is read.
Glyphs are asked for by what the button does. GlyphOf::Bound(action)
follows the player's binding and GlyphOf::Menu(MenuJob) the fixed menu
map. pad_glyph::Glyph is built only from those, and draw, width, rect,
art, label and color take only a Glyph, so no code outside pad_glyph
can turn a bare button into art or text. Splitscreen's join and
lost-controller prompts stay by position through Glyph::unheld, and a
test keeps that the only exception.
horizon-ctl `pad family FAMILY` and `pad connection connected FAMILY`
let the e2e harness pose as a Nintendo pad. docs/ui-kit.md starts the
UI kit guide with the glyph chain.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A biome id past the table after a proxy's server switch names plains instead of panicking (#12148: "the len is 67 but the index is 67"), and the reconfiguring client plays menu music as ServerReconfigScreen lets it.
🤖 Agent: Vegetation patches iterate their position sets directly instead of copying them, and multiface growth shuffles fixed arrays; the shuffle draws the same number of randoms in the same order.
🤖 Agent: Pack names, clouds, the forcefield and custom sky load on four scoped threads; panics still propagate (resume_unwind) and the values are the same.
🤖 Agent: The fishing_medium native hook lets a mod make a bobber float and fish in another block, with its own loot table and lure range; in water the vanilla path is untouched.
🤖 Agent: The displacement pass collects its nearby sections into a SmallVec sized to its 64-section cap and reuses its section table, removing the per-frame heap allocations; the GPU writes are unchanged.
🤖 Agent: A loot set_components patch for a component Horizon models (potion_contents and the rest) is read into its field as a saved stack's is, so a rolled potion gives its effect; damage and custom_name keep their own paths.
🤖 Agent: The single-player owner is the signed-in account's UUID and older worlds move their owner to it on open; I made the migration keep the offline player files as .dat_offline backups instead of deleting them.
The give branch of cmd_loot spawned what give couldn't fit at the
command's position and counted every drop. LootCommand.playerGive puts
each drop in with Inventory.add: what fits stays, the rest is lost, and
the drop counts only when add returns true, which it does when all of
it went in or for a creative player.
New command corpus group: loot give full inventory.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A mob's max_health is refreshed whenever Health Boost's amplifier changes or it ends (load, conversion, milk, death protection), and health is clamped to the new maximum, as refreshDirtyAttributes does. I merged main: #12255 already refreshes Health Boost in apply_death_effect and the trader's drink, so this PR's copies of those blocks were dropped and its test kept.
🤖 Agent: Piston block events and sculk charge cursors check the block-ticking ticket range (ServerLevel.shouldTickBlocksAt), not chunk ticking readiness, so they run in a freshly ticketed chunk; moving pistons still wait for readiness, as the oracle shows. Merged main (isPositionTickingWithEntitiesLoaded's doc now names the entities).
Choose float alpha storage independently of the no-pack HDR gain. Plain
draws and the mirror keep their RGB; opaque finishes write alpha 1 and
blended draws keep coverage. Keep the existing written-light arithmetic.
Run light-mark redraws and hand unmark/remark only on compatibility
frames. Preserve the non-layered 0.2 hand's UI-brightness sentinel. Stop
resolving float alpha into light, while retaining the resolve oracle and
lane state for the next cleanup.
Check real shader outputs on the GPU and preserve the existing pictures
with differential HDR, pack, OIT, visibility-buffer and hand captures.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
getOffset divides the pointer's distance from the widget edge by the
widget height as floats: 26.3's bytecode is iload_3, i2f, iload_2, i2f,
fdiv before Mth.lerp and Math.round(float). The decompiled text without
the casts made widget_offset an int division, so a hovered widget's
tooltip stayed height - 3 below its edge until the pointer was a whole
height away, then jumped. On a 20 px button it sat up to 10 px lower than
vanilla's. The offset now follows the pointer as vanilla's does, rounded
with jmath::round_f32, and the test expectations are the values the real
26.3 MenuTooltipPositioner returns for the same inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The title screen's icon row (Friends, Language, Accessibility) drew its
sprites but no tooltip: the tooltip pass after the button loop only knew
Create World, Select World and Multiplayer. Vanilla gives these buttons a
Tooltip (CommonButtons.language and accessibility, FriendsButton while
friends are available), which WidgetTooltipHolder draws while the button
is hovered, placed by MenuTooltipPositioner and split at 170 px. The
accessibility onboarding screen builds its Accessibility Settings... and
Language... buttons with the same CommonButtons, so they lacked the same
tooltips.
Title::button_tooltip now names each screen's button tooltips, and both
the narration hints and the draw loop read it. The draw loop remembers
the hovered button's tooltip and draws it after the widgets (and after
the onboarding fade, which vanilla applies to widgets only) with the same
positioner (ui::tooltip_menu), before the toast so the toast stays on top.
Fixes#12067.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractChestBoat.remove and AbstractMinecartContainer.remove spill the
container through Containers.dropContents for every removal reason that
destroys the vehicle: the kill of VehicleEntity.destroy and a creative
hit's discard alike. That spill has no entity_drops gate; only the hull
item and chestVehicleDestroyed's second, empty spill follow the rule.
Horizon's damage path got both vehicles wrong. A chest boat dropped each
stack whole at its position with an item's own motion and a pickup
delay of 10, and never drew chestVehicleDestroyed's per-slot spots. A
container minecart scattered its contents only with entity_drops on, so
with the rule off (or from a creative hit with it off) the contents
vanished.
Both now share one Containers.dropContents helper (the /kill path used a
second copy): the contents scatter from a spot in the vehicle's block in
pieces of 10 to 30 with no pickup delay, before the ENTITY_DIE event and
before a boat's lead, as Entity.kill and AbstractBoat.remove order them.
The hull item and the empty spill still need entity_drops and a
non-creative hit. Every damage source (players, LAN guests, explosions,
lightning, /damage) goes through these two functions, so single player,
LAN and dedicated servers all get it.
The vehicle_command_damage harness now breaks a chest boat with /damage,
and a chest minecart with entity_drops off both by /damage and by a
creative player's hit, against the real 26.3 server. Each break must
leave the 5 diamonds inside the vehicle's block with no pickup delay,
and the 40 dirt as piles in that block whose counts add up to 40. A
gameplay test seeds the level random, breaks a chest boat and a chest
minecart, and checks that the random ends where vanilla's two
Containers.dropContents calls leave it (the empty second spill's draws
included).
The port map rows for AbstractChestBoat.remove and
AbstractMinecartContainer.remove point at the shared helper instead of
the removed /kill copy. The chestVehicleDestroyed row now says what is
left there: a player's hit on a container minecart angers no piglins yet.
Fixes#12121
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The HDR float frame follows HDR, Smooth Gradients and the picked pack at once by rebuilding the renderer; all of it is behind HORIZON_HDR_FRAME=float, so the default path only pays one bool check per frame.
A shulker's bullet started at the centre of the shulker's block cell.
Vanilla's ShulkerBullet constructor starts it at
owner.getBoundingBox().getCenter(), and Shulker.makeBoundingBox grows
that box toward the open side by the lid's physical peek (times the
scale), so an open floor shulker fires from the top of its block
(#11974). shulker::bullet_origin now builds that box with
getProgressDeltaAabb at the shulker's scale, from the peek the goal
sees (the attack goal runs before Shulker.tick eases the peek, in both
games).
In flight, a bullet turned only before a full collision cube. Vanilla
asks Level.loadedAndEntityCanStandOn(next, bullet): the top face of the
block's collision shape, in the bullet's collision context, is full
(Block.isFaceFull). Top slabs, upside-down stairs, closed top trapdoors
and the like now turn it too, instead of the bullet flying into them
(#11970). collisions::projectile_can_stand_on implements the test, and
ProjectileWorld::solid becomes can_stand_on(pos, entity_y), since
scaffolding reads the bullet's height.
New harness shulker_bullet: the real ShulkerBullet constructor for all
six attach faces, every peek of an opening lid and four scales, and
loadedAndEntityCanStandOn over forty block states at four bullet
heights, compared bit for bit with the Rust probe.
Fixes#11974Fixes#11970
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#12232's portal_matches read self.portal as (chunk, level) pairs; here it holds
PortalTicket, so it compares ticket.chunk.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#12255 already refreshes Health Boost and clamps absorption in apply_death_effect
and the wandering trader's drink; main's blocks are kept, this PR's copies of them
dropped, and its clear-all-effects test kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The warden digging test ticks the warden at age 1, since the client moves tickCount on before it ticks; it had failed on main since #12110 wherever the vanilla data is prepared.
🤖 Agent: F3 shows the debug overlay on the title screen and F3+F6 opens the debug options over the panorama, closing back to the title without grabbing the pointer, as TitleScreen allows.
🤖 Agent: A piston push of primed TNT applies the blocks it moves through (powder snow, soul sand, slime), as the piston's Entity.move does. Stacked on #12290.
🤖 Agent: On the float frame the visibility buffer writes light itself; behind the HDR_WRITE_LIGHT override, false on the default frame. Stacked on #12282.
🤖 Agent: An option button's pointer cursor follows hover only, while its focus still highlights it (AbstractWidget.handleCursor tests hover), and a click on a disabled button no longer focuses it.
🤖 Agent: Screen drag and wheel get v2 hooks that lend the game mutably, heard just before v1; v1 keeps its signature and still hears the turn unless v2 cancels it or closes the screen.
🤖 Agent: A resting item in a lava cauldron burns: each lava cauldron its box meets (the cauldron's inside shape, not the air above) applies LAVA_IGNITE and its own lavaHurt, stopping once the item is gone; netherite stays immune, as the new oracle shows.
Choose the frame format and HUD layering together. No-pack float HDR needs
the HUD layer, so devices without it keep the compatibility frame. A 0.2
pack still gets its float frame in SDR and HDR.
Rebuild when picking or dropping a pack changes the layering even if the
format stays float, so the renderer always has the layering a fresh start
would give it.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The migration renamed nothing back if it went wrong: it deleted the offline
.dat and .dat_old after writing the account's file. They now stay beside it as
.dat_offline and .dat_old_offline.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
StackData::set_loot_component kept every component but trim in other,
its saved form, so a potion a chest or a mob's loot rolled with a
set_components potion_contents gave no effect: brewing::contents_effects
reads potion and potion_extra. A modelled component is now read into
its field the way a saved stack's is (decode_components). Damage stays
in other, as the stack holds it, and custom_name keeps its own reading.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Villager.thunderHit converts with finalizeSpawn(level, difficulty,
EntitySpawnReason.CONVERSION, null); only Pig.thunderHit skips it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's ServerReconfigScreen is a plain Screen, not a
LevelLoadingScreen, so MusicManager.tick starts the MENU music that
getSituationalMusic picks once the player is gone. Horizon treated the
reconfiguration as loading (no player, no title, no credits) and stayed
silent until the next world; a reconfiguring connection no longer
counts as loading.
A Game test (prepared resources, ignored by default) puts a column
holding a biome id one past the table at the origin, reads it through
biome_at and biome_name_at (plains), then checks that a reconfiguring
client picks and starts the menu music and that the level probe after
it reads plains' music without a panic.
Biomes::name's doc now says that Biomes::get clamps such an id to the
last entry instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A proxy that moves the player to another server reconfigures the
connection. Horizon keeps the last server's columns until the next
Login clears them, but Reconfigured already swaps in the next server's
biome table. When the last server had a biome the game lacks (added to
the table as id 67) and the next one does not, those columns hold an id
past the 67-entry table. situational_music still probed the level at
the origin during the reconfiguration, and biome_at indexed the table
with that id: "index out of bounds: the len is 67 but the index is 67".
Vanilla never reads that level: handleConfigurationStart calls
clearClientLevel, which nulls the player, so getSituationalMusic
returns the menu music. The music probe now asks Remote::has_level
(Login seen, no reconfiguration since) instead of logged_in.
The three places that indexed the biome table directly (biome_at for
music, ambience and custom skies, biome_name_at for random textures and
fishing, and /fillbiome's filter) now go through Biomes::name, which
names plains for an id past the table, as biome_at already did for a
missing column and Biomes::id does for an unknown name.
Fixes#12148
Co-Authored-By: Claude Opus 5.5 <[email protected]>
with_built_test_world runs nothing where the data is missing (CI), and the
test then compared None with Some(true); it now returns early there, as the
other with_built_test_world tests do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the batch-starting line now goes to the /test command's own source inside the command (TestBatchSummaryDisplayer); the test-instance block still uses the server source. Lifecycle stays 95/99: its 4 if-loaded records are an oracle artifact.
🤖 Agent: mob_died no longer resets scores at the death; vanilla does it in onDestroyed, so unloaded corpses keep them. The #12185 shulkers get fixed UUIDs so /scoreboard players list can match at all.
🤖 Agent: A new item's own random now makes its UUID (Mth.createInsecureUUID, the first two draws), then bobOffs, yaw and motion in that order, instead of a process-wide clock-seeded UUID; I merged main (the seed-sync gaps diagnostic this PR retires stays out).
🤖 Agent: Block contact scratch lists are kept on Game and restored on every Result path; I replaced extend(drain(..)) with append, the clippy error that failed verify (clippy -D warnings passes now).
`RealmsResetWorldScreen`'s Generate New World tile is enabled: it opens the Create
New World screen (`RealmsCreateWorldFlow.createWorld`), and its Create builds the
temporary world the upload packs instead of entering a local one.
- The tile returns the Realm, slot and reset kind to the title, which shows Create New
World with the default options. Create (`create_or_upload`) builds a `Level` from
those options (name, seed, game type, difficulty, cheats, game rules, feature flags
and data packs) and hands it back to the Realms screen; Back/Escape returns to the
configuration screen (or the list for a new Realm).
- `realms::create_temporary_world` mirrors `createTemporaryWorldFolder`: a temporary
folder with level.dat from `WorldData.createTag(null)` (no `singleplayer_uuid`), the
world generation and game rules, and the Create World screen's data packs moved into
`datapacks/`. No chunks: the Realm generates them.
- The upload runs the same `RealmCreationTask` → `SwitchSlotTask` → `packAndUpload`
chain as a local-world upload, with `RealmsWorldOptions.createFromSettings` (slot
name, game mode, difficulty, the game's version) and the hardcore setting. While it
runs `AlertScreen` (`mco.create.world.reset.title`) shows a Cancel; success returns
to the configuration screen, or plays the Realm just created
(`RealmsMainScreen.play`); cancel returns to the reset screen; a failure shows
`mco.create.world.failed`.
- `Level::write_upload` writes only the upload's files (no player file, weather or
clocks), with `singleplayer_uuid` omitted as `createTag(null)` writes it.
Co-Authored-By: ZCode <[email protected]>
🤖 Agent: `execute on passengers` finds the players riding a vehicle on a server (the player in place, then the hub guests by their vehicle); I merged main, whose new mob-children arm now follows this PR's player_passengers, so both players and mob riders are found.
On the settings shell (Extras, Graphics), a value between `<` `>` could
only be stepped back with Shift-click or, with an Extras setting on, the
wheel: a pad or keyboard player could only go forward. Left and Right on a
focused choice now step it back and on, as its arrows show, from the D-pad
and the arrow keys alike. They are a Shift press and a press, the same path
as a click, so Undo and saving follow.
They step at once rather than after A arms the row, as sliders do: A keeps
its step forward and there is no mode to find, and up and down passing over
a choice never change it. Up and down leave the row, and LB and RB reach the
page list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #12110 a warden's client tick plays the heartbeat when its age is
a multiple of the beat delay. The test ticked a new warden at age 0, so
it heard a heartbeat. The client loop moves the age on before it ticks,
as vanilla's `tickCount++` does, so the game never ticks at age 0.
Fixes#12312
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: An export that fails on a missing lib/tzdb.dat now names the Java to repair, matched only on the FileNotFound/NoSuchFile line for that exact path; other failures keep their message.
🤖 Agent: /loot and pack functions take the world's packs' loot tables: the id list comes from the loaded tables (root plus pack overlay) and is refreshed on /reload.
🤖 Agent: On the float frame every shader writes its own light; all of it sits behind the HDR_WRITE_LIGHT pipeline override, false on the default frame, so the SDR path compiles it out. Stacked on #12220 and #12247.
🤖 Agent: Other crates' records, native mods included, now reach latest.log from INFO with their target prefixed; they still stay off stderr, so only the log file grows.
🤖 Agent: match_tool and loot predicates now test minecraft:custom_data as NbtUtils.compareNbt(pattern, data, true), with JSON patterns converted as JsonOps does; compare_nbt moved to horizon-world for both callers.
🤖 Agent: native_player_break_block breaks a block as a given player through the game's own player-break path (spectator, adventure can_break, spawn protection, native_destroy_block, loot, wear, stats).
🤖 Agent: Death protection and milk removal clamp a mob's absorption to its remaining max_absorption (onAttributeUpdated), instead of subtracting 4 per Absorption level.
🤖 Agent: CPU terrain occlusion tests each section's padded mesh bounds instead of the whole 18-block cell, so empty space no longer keeps a hidden section drawn; sections without bounds keep SECTION_BOX.
🤖 Agent: Realms world slots draw their own pictures and template pages arrive page by page. Cost to note: the four 1024-tall slot pictures now live in the HUD atlas for every player, about one 1024-tall shelf of GPU memory; a follow-up should load them only while Realms screens are open.
🤖 Agent: /loot now applies set_name and set_components to its drops, as a chest's roll already did, and names each drop by its display name in the feedback.
🤖 Agent: Ports vanilla's timelines, keyframe tracks and all 32 easings into horizon-world with Java's float/double steps; nothing in the game calls it yet, only the two new oracles' probes.
🤖 Agent: One hdr_mark_light function serves the display and resolve passes; a GPU test checks it against the old code for every 8-bit mark and both easing modes.
🤖 Agent: The recipe_sources native hook lets recipe placement into a mod menu type's grid take ingredients from containers the mod names; stacked on #12240 and #12233, lands after them.
🤖 Agent: An unbounded arbitrary @e walks the joined families in insertion order (EntityLookup.getEntities), falling back to the old order if the walk fails; it builds one count map per such query, other selectors keep their path.
🤖 Agent: #minecraft:tick functions now run before the levels tick, so they read the game time from before the tick, and /schedule'd ones run in ServerLevel.tickTime once the clock has moved, as MinecraftServer.tickChildren orders them.
A mod answers `native_fishing_medium(dimension, hook, position, owner, medium)`
for a bobber that is flying or floating, not in water and not hooked to an
entity. `Some(NativeFishingMedium)` makes the spot a medium: in the block of
its `surface` or below, the bobber floats, waits, bites and is reeled in as
in still water; the catch rolls its `loot_table` (vanilla's
`minecraft:gameplay/fishing` when None) and `lure_ticks` replaces vanilla's
100 to 600 tick wait when set. It is asked on the server and on the clients
that draw the bobber, so both move it alike. No answer keeps vanilla, and
water is never asked about, so vanilla fishing is unchanged.
`horizon-ctl status` shows the hook's loot table. Fixes#12300.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
With Improved Transparency on, the float frame's default lane no longer
stays marked: each see-through layer converts its own colour to light
(hdr_written) before it is weighted and accumulated, so the composite
lays light over the light behind it, as the forward passes do. Text,
shadows, weather and the border keep their source gain, as they do in
the forward passes; cracks keep their multiply blend outside the
accumulation, since it scales the light under them. Only a shader pack
keeps the frame marked now. The identity harness's float OIT session is
no longer an identity case; Improved Transparency with HDR off and on
the 16-bit frame are, and fail unless the option changed the picture.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Datapack-added or replaced biomes keep their minecraft:visual/ambient_particles, so ambient particles use the world pack's biome instead of the prepared vanilla table.
- Add touch::layout: control schemes, built-in controls, per-scheme placements and the saved settings file
- Visibility follows the scheme unless the player shows or hides a control; left-handed mirroring; D-pad jump/sneak swap
- Load falls back to defaults and sanitizes off-screen or non-finite values; save writes through a temp file
- Nothing uses it yet: the touch controls that read it come in the next pull requests
- Adapted from Starling (MIT, by Vasysik); credited in THIRD_PARTY_NOTICES.md
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
🤖 Agent: perf: unchanged ticket sources skip renormalisation, and item defaults load once per root; I fixed the fast path to compare the dragon arena too (it broke the watched-dragon-arena test).
🤖 Agent: update records that the cover was refreshed at its start, so each waiting chunk no longer compares the ~200-entry extra areas list every tick.
tests/test_game_home.py requires every script that starts the game to use it
(scratch folders, no audio) instead of setting HORIZON_NO_AUDIO by hand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's TestCommand.trackAndStartRunner adds a TestBatchSummaryDisplayer
holding the command's source; GameTestRunner.start runs the first batch
inside the command, and testBatchStarting sends
"Starting environment <env> batch <n>" with sendSuccess(.., true). Horizon
queued the line as a server console broadcast for the next tick, so `/test
run` answered only "Running 1 test(s)..." (world_test_def 94/96,
world_test_def_lifecycle 6 of its 10 differing records, as #11992 noted).
A runner a command starts now sends the line through that command's source
while the command runs (`feedback`). A test instance block's runner keeps the
server's source (TestInstanceBlockEntity.runTest), as do later batches, which
start from the tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla resets an entity's scores in ServerLevel.EntityCallbacks.onDestroyed
(Scoreboard.entityRemoved), when the entity is removed for good: a mob's body
twenty ticks after LivingEntity.die, never when it is unloaded. Horizon reset
them in mob_died, at the death. A mob killed in a frozen world and then
unloaded kept its scores in vanilla and lost them here, so a later
`/scoreboard players list` named 32 fewer holders (command_exec, after
#12185's shulker command damage case).
mob_died no longer resets them; score_destroyed_entities already resets every
removed entity's scores once a tick, and keeps an unloaded one's.
#12185's 32 shulkers had random UUIDs, which that list then named: Java and
Horizon could never print the same list. The case now gives them fixed
UUIDs, so the record is exact again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Entity.applyEffectsFromBlocks` runs the supporting block's `stepOn`
before the blocks the entity is inside, so slime slows TNT before a
bubble column beside it lifts it. TNT's own tick had them the other
way round, and the piston push shares the same steps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_screen_mouse_dragged_pre and native_screen_mouse_scrolled_pre take
&self because their NativeScreenView borrows the game, so a mod can cancel
a drag or a wheel turn over a screen but can't click a slot there.
Mouse Tweaks-style mods (Mouse Tricks) inject into the private
inventory_drag_move and tweak_scroll instead and pin one Horizon commit.
Add native_screen_mouse_dragged_pre_v2 and
native_screen_mouse_scrolled_pre_v2 with &mut Game: the view's id and
pointer come by value, the rest as v1's. Each fires at v1's point, just
before v1, which hears the screen as v2 left it and nothing of an input
v2 canceled. Cancelling does what cancelling v1 does. v1 is unchanged.
Fragment tools/native-hook-fragments/screen_mouse_input_v2.json; native
bodies filled by `cargo run -p xtask -- mod-api`.
Fixes#12286
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
With HORIZON_VISBUFFER=1, the float frame's default lane no longer stays
marked: the visibility buffer's resolve writes its solid pixels' light
as fs_opaque does (solid_light), so the frame is written whole. The
identity harness's float visbuffer session is no longer an identity
case; the visibility buffer with HDR off and on the 16-bit frame are,
drawn flat so that the buffer runs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Block entities and scheduled ticks wait until their chunk's entities have loaded, as ServerLevel.isPositionTickingWithEntitiesLoaded gates them; the entities_pending set is cloned once per call.
🤖 Agent: A spawner's delay counts down without building a level view when no player is in range, the common case, and builds it only when it may spawn.
🤖 Agent: Decoration ranks are unique, so only chunks without a rank are measured and the queue is a heap that pops until the workers are full, instead of a full sort per update.
🤖 Agent: An item pushed out of a solid block takes its gravity step before the escape push (#12018), and the new command_exec case pins two ticks whose height and motion are exact whatever the random push speed.
🤖 Agent: The Graphics page describes its rows and the shell draws and drives them; I merged main's three-state GPU Culling into the new test helpers (focus_on, key), graphics_ui tests pass.
🤖 Agent: /setblock and /fill notify neighbours after all shape updates (BlockInput.place, ServerLevel.updateNeighboursOnBlockSet), so a wire beside a block that pops off ends as a cross; I merged main (fill's note-block normalisation kept) and marked the two resource-backed tests #[ignore], as the other HORIZON_TEST_RESOURCES tests are, since CI has no resources.
The visibility buffer draws only frames drawn flat, which frames without
a pack are by default only on Metal and some Intel drivers, so elsewhere
float_visbuffer compared the forward passes. It now sets
HORIZON_FLAT_DRAWS=1 and fails unless the game built the buffer. The
module docs list the sessions added on this branch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Once `limitPistonMovement` has used the tick's 0.51, `Entity.move`
returns at once, but `moveEntityByPiston` still runs
`applyEffectsFromBlocks`, so a second piston's push still lets slime
under the TNT slow it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The commands' minecraft:loot_table ids came from the resource root's
folders, so /loot refused a world data pack's table, and a pack's
function naming one was dropped at load. ResourceOrIdArgument looks the
id up in the server's registries, which hold the world's packs. The ids
are now the world's loot table map (Items::loot_table_ids): set when the
world opens and again before admit_store parses the packs' functions.
New command corpus group: loot world pack tables.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`IntegratedServer`'s constructor sets `setSingleplayerProfile(minecraft.getGameProfile())`,
and `ServerLoginPacketListenerImpl.handleHello` gives a login whose name matches that
profile the profile itself; the local memory hello names the account's profile id
(`Minecraft.handleClientLogin`'s `ServerboundHelloPacket(getUser().getName(),
getUser().getProfileId())`). The integrated server's owner, its gate/op entry and its
login are now the account's UUID; an offline start keeps the offline profile.
`MinecraftServer.saveAllChunks` saves with `saveDataTag(worldData,
getSingleplayerProfile().id())`: level.dat's `singleplayer_uuid` follows the owner on
every save. A world without one (a Realms download; `FileDownload.untarGzipArchive`
drops it) then loads the owner's `players/data/<account uuid>.dat` instead of starting
a new player.
Worlds older Horizon builds made name their player by the offline UUID everywhere:
level.dat's `singleplayer_uuid`, the player file and its `UUID`, and entity references
(`TamableAnimal`/`AbstractHorse`/`Projectile`/`AreaEffectCloud`/`EvokerFangs` `Owner`,
`ItemEntity`'s `Owner`/`Thrower`, `Fox`'s `Trusted`, `NeutralMob`'s `angry_at`,
`Leashable`'s `leash.UUID`). The first open under the account moves the player file to
the account's UUID (the newer of the two when both exist), rewrites level.dat before the
first session save, and rewrites each entity as its chunk loads, so the next save keeps
the account's UUID. A world renamed since it was made stays as it is: the old name its
offline UUID came from is not stored, so it cannot be identified.
Co-Authored-By: ZCode <[email protected]>
🤖 Agent: Studio's top bar and timeline are Horizon-only and off unless Studio is opened; the only gameplay touch is the fov/play_input hooks, which read the new state only while Studio is open.
🤖 Agent: A mod menu's crafting grid crafts through the crafting table's own recipe lookup (lan_crafting), so mod grids and the table agree on every recipe.
🤖 Agent: A replaced end gateway's cached server cooldown is discarded, so a new gateway at the same spot does not inherit its teleport cooldown; covered by the new gateway_replacement oracle.
🤖 Agent: A mob-owned arrow may break a block only with its own saved can_break predicate and mob_griefing on; predicates with nbt, components or predicates conjuncts fail closed instead of matching on blocks alone.
🤖 Agent: Rebased onto main: the hot-path buffers (command args, protocol buf, redstone, json folder) are reused instead of reallocated, with the args tests covering the reused path.
🤖 Agent: All geysers of one block-entity pass share one mob grid kept until the mob list's next write; the effect and push loops restamp it only because they change no mob box.
🤖 Agent: A cart's push and pickup read neighbouring carts and mobs from grids; any mutable access to the mob list bumps its stamp, and with native mods loaded both full scans stay, since a hook can move entities unseen.
🤖 Agent: A guest's effect and sting hits now credit the mob that swung, matching LivingEntity.hurtServer's attacker; stacked on #12204, lands after it.
🤖 Agent: Melee hits and game events read the mobs near them from a grid instead of walking every mob; the grid is rebuilt on the mob list's next write (JoinList stamp), so a mob spawned mid-tick is still found.
A watcher arriving at or leaving the End fight changes only Sources.dragon;
the unchanged-sources skip ignored it, so the arena never got (or lost) its
TicketType.DRAGON levels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Entity.move(MoverType.PISTON)` uses up TNT's pending stuck multiplier
(without scaling the push), reads its fall for powder snow's top, adds
to that fall and resets it on landing, and applies the block speed
factor. `moveEntityByPiston` then runs `applyEffectsFromBlocks`, so
powder snow it is pushed into makes it stuck, and slime under it slows
it. Horizon did none of this, so in a chunk that does not tick
entities pushed TNT kept its motion in powder snow and on soul sand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After a fall over 2.5, `PowderSnowBlock.getEntityInsideCollisionShape`
is the 0.9-high top, so the snow only makes the TNT stuck once a step
reaches that top. A TNT passing 2.5 just above the snow used to be made
stuck early, lose its fall and sink through.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Block loot's `match_tool` (horizon-items) and the loot engine's
ItemPredicate (`item_matches`: `filtered`, entity `equipment`) ignored
`predicates."minecraft:custom_data"`, so any stack passed. They now test
it as `CustomDataPredicate` does (NbtPredicate.matches ->
CustomData.matchedBy -> NbtUtils.compareNbt(pattern, data, true)): a typed
NBT subset, lists matched element by element, absent custom data read as
`{}`, the pattern SNBT or a JSON object converted as DataFixerUpper does.
`compare_nbt` moves down to horizon-world (re-exported from nbt_path) so
the loot engine can use it, and a killer's equipment keeps its custom data
for entity loot predicates.
New harness loot_custom_data: a data pack's dirt table with a pool per
pattern behind match_tool, `loot give @s mine` with sticks carrying
different custom data, 66/66 records equal to vanilla. The loot engine's
`filtered` path gets the same vanilla matrix as a horizon-world test.
Fixes#12196
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
The give branch of cmd_loot skipped every target but the player in
place, so the console's /loot give reached nobody on a dedicated
server, and a command block's /loot give @p gave nothing in single
player. Each target now goes through for_player, as cmd_give does
(LootCommand.playerGive gives to every target ServerPlayer).
New lan_e2e check: loot_give_guest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content mod's menu type may declare `"links": {"from", "count"}`: those
slots show the slots of block containers a native mod names each tick the
menu is open (`native_menu_links(player, menu, pos, links)`), in order, as
real slots of the menu. Clicks, shift-clicks and drags run on the server's
menu engine and move the containers' own items, a changed container is
saved and its comparators update, and the block entity never holds the
linked slots. Linked slots with nothing behind them hold nothing and are
left out of clicks, as a hidden slot is.
This is what a crafting station's side panel needs (Tinkers' Construct's
shows the chest beside it); a tab per neighbour is a menu button that
changes what the mod links for that player.
Part of #12198.
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
Co-Authored-By: Claude Opus 5.5 <[email protected]>
position_riders gave every mob rider of a mob its vehicle's body yaw each
tick. 26.3 does so only in AbstractHorse.positionRider and
Chicken.positionRider, and in AbstractSkeleton.rideTick and Drowned.rideTick
for a rider steering a PathfinderMob; any other rider's body turns on its
own. A rider of a living vehicle is drawn by LivingEntityRenderer.solveBodyRot
from the vehicle's body, which only the window's player model did: mobs now
go through the same function.
Fixes#9335.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Retain per-tick block contact, random tick, and entity-inside vectors on Game while restoring scratch across result exits and preserving vanilla ordering.
Co-Authored-By: Codex <[email protected]>
CI has no HORIZON_TEST_RESOURCES, so they panicked there; like the other
resource-backed game tests they now run with --ignored.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The items loot engine, which /loot rolls with, skipped both functions:
the item came out without its name, lore, potion or book contents,
while a chest rolling the same table had them. set_name sets the
component its target names, as a chest's roll does. set_components
applies the patch as ItemStack.applyComponents does and reads the
stack back as saved components are read, so a potion's contents make
it a potion. The feedback names each drop by getDisplayName.
New command corpus group: loot set name and components.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reuse resident section bounds for phase-one retests and deferred occlusion tests, retaining a one-block total pad and the existing full-cell fallback.
Co-Authored-By: Codex <[email protected]>
Under HORIZON_HDR_FRAME=float, with HDR on, no shader pack, and neither
Improved Transparency nor the visibility buffer requested, the world's
shaders write their own light (hdr_written) instead of a mark, and the
passes that only write marks, the hand's unmark and remark and the
resolve pass are skipped. A frame is all written or all marked. TAA's
history is dropped when that or the HDR parameters it holds change.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Fixed: `MobWorld` has no `end` field, so the fox village stroll now reads `dimension_type.has_fixed_time`, as `Level.isBrightOutside` does; it builds with main merged (lib and tests).
🤖 Agent: Requeued after a main merge (clean); the earlier clippy failure was in `horizon-core/src/density/interpolation.rs`, which this PR doesn't touch.
🤖 Agent: Requeued after a main merge (clean); the earlier failure was main's native mod API stamp (`native_level_play_sound`) at that moment, not the brake thresholds.
🤖 Agent: Requeued after a main merge; the earlier verify failure was a 60 s timeout in `test_entity_shadow_ownership` on a loaded runner, not this PR's launcher scroll change.
🤖 Agent: Requeued after a main merge: in report.rs, main's installed-data-pack branch comes before the carried-mod branch, which still marks 1.20 layouts Partial; port-pack checks clean.
🤖 Agent: Requeued after a main merge: the `graphics_ui.rs` conflict kept Opaque Ice's switch, and main's tri-state GPU culling no longer has a plain on/off arm there.
MobWorld has no end field. Level.isBrightOutside is false in every
dimension with a fixed time (the Nether and the End).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Primed TNT is an Entity in vanilla, so `checkFallDamage` adds each step
down, narrowed to a float, to its double fall_distance, which resets on
landing or in water and is saved and read and written by /data. Powder
snow uses that distance: over 2.5 it holds TNT on a 0.9-high top, and
otherwise TNT goes in and is stuck (0.9/1.5/0.9) like in a cobweb. The
stuck multiplier is used on the next move, which then zeroes motion.
Double and float lists in saved entity data now store -0.0 as 0.0, as
`DoubleTag.valueOf` does, so TNT resting on a block reads Motion 0.0d.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. The level in place reads its own saved `world_border` when the world opens, before any command, so a first `/worldborder set` is no longer overwritten later.
🤖 Agent: jukebox stop now captures was_playing before clearing j.playing, so the stop vibration fires once. On main merge 904b3036a, sculk, server_player_sculk, server_wool_vibration and mob_command_damage_vibration pass on playhorizon. command_exec's only 2 mismatches are main's gateway_item case (same 16819/16821 on bare main b13dbf068).
Ports net.minecraft.world.timeline and the keyframe machinery under it:
Timeline (clock, period, tracks, time markers, validateInternal),
AttributeTrack's codec (a modifier, override by default, and keyframes of
its argument), KeyframeTrack.validateKeyframes and validatePeriod,
KeyframeTrackSampler's looping segments and sampling,
argumentKeyframeLerp for every modifier, EasingType (cubic Bézier with
its Newton-Raphson and bisection solve) and all 32 Ease curves, with
Java's float and double steps (jmath, Mth's sine table). The layer stack
takes timelines as time-based layers that read their clock's ticks.
The new harness environment_timeline compares 3,464 cases with the pinned
client (every easing, the vanilla day, moon and early game tracks, and
synthetic and invalid tracks); environment_system now stacks the real
timelines between the biome and weather layers (36,000 lines).
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The world slots' pictures follow RealmsWorldSlotButton.State: the
minigame image (or presets/isles.png when it has none), the empty
frame, a slot's own template image, then the slot's panorama. The
atlas now keeps the four slot pictures (the three 1024-tall faces and
isles.png) at their own size: they were excluded as title/background
and presets, and even once let through were dropped by the 512-tall
GUI texture cap.
- WorldTemplate/WorldTemplateList read like JsonUtils' getStringOr,
getIntOr and getUriNullable: numbers read as their JSON text (some ids
and player counts are numbers, some counters are strings), and an
empty link is no link — the row icons are not drawn and no empty URL
can be opened.
- The template list fetches one page per job and appends it as it
arrives, chaining the next page until isLastPage or an empty page,
instead of waiting for every page before drawing the first row.
- The minigame list (RealmsWorldsTab.switchToMinigame) returns to the
Worlds tab on Back and Escape, not to a reset screen.
Tests: 2250 lib tests pass (71 realms), scoped clippy clean.
Co-Authored-By: ZCode <[email protected]>
`TamableAnimal.TamableAnimalPanicGoal` passes a damage tag to `PanicGoal`:
`Wolf` gives it `#panic_environmental_causes`, while `Cat` and `Parrot`
keep the default `#panic_causes`. Horizon's `TamePanic` gated all three on
`fire_ticks > 0`, so only a burning pet panicked: a cat hit by a player or
a wolf freezing stood still where vanilla flees.
`TamePanic` now delegates to the `Panic` it wraps. The wolf's panic is
marked environmental (fire, freezing, cactus, lightning, lava); the cat's
and parrot's take any panic-causing damage.
`native_recipe_sources(player, menu, pos, sources)` lets a mod name
block containers (chests and the like, mod containers) that recipe
placement into a mod menu type's crafting grid may also take
ingredients from, after the player's inventory, as a crafting station
takes them from the chests beside it. No answer keeps vanilla's
placement.
Part of #12198.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's Entity takes its UUID from the first two draws of its own random
(Mth.createInsecureUUID); ItemEntity then draws bobOffs and the yaw from it,
and the constructor without a given motion draws the motion last. Horizon gave
every entity a clock-seeded UUID when first asked, drew a new item's motion
from a fresh random's first draws, its bobOffs from the particle random and a
loaded item's from the level's, and left the yaw at 0.
spawn_item_own makes all four from one own random in that order and replaces
dispenser::new_item_motion at its callers. A loaded or summoned item makes its
UUID (unless the tag has one) and bobOffs from its own random, so summoning no
longer shifts the level's random. Primed TNT from a block takes its UUID from
its own random too.
The seed sync proof now reads those UUIDs, the drop's rotation and motion, and
a TNT kick after a summoned item: 8/15 before, 15/15 after.
Fixes#12217
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Runs hand-built circuits and random "soups" of redstone components through the real
tick loop in vanilla 26.3 (the bench mod's client, no server, no EULA) and in Horizon
(horizon-ctl), in a frozen world stepped with `tick step 1`, and prints the first tick
where the blocks differ and which ones. Narrowing: --near/--drop reduce a difference
to a few commands.
- RedstoneDiff + ServerLevelTickMixin in the bench mod snapshot the box before the
first stepped tick and after each.
- `simulation blocks X1 Y1 Z1 X2 Y2 Z2` (horizon-ctl) reads the server's blocks, not
the window's replica, which lags the server by design.
- agent.rs: `blocks_json`, shared by `blocks` and `simulation blocks`.
Only blocks are compared; entities are recorded, not diffed.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
Vanilla places a command's block with UPDATE_CLIENTS | UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS
(2|256), so Level.setBlock notifies no neighbour. The shape updates run inside
setBlock; ServerLevel.updateNeighboursOnBlockSet follows afterwards (for /fill,
after every block is in). Horizon placed with UPDATE_ALL (3), so the neighbours
heard of the block before its shape updates ran.
That is visible when the notice makes a block pop off: a comparator with nothing
under it breaks at its first notice and takes an attached wall torch with it. In
vanilla the wire between them had already connected to the torch, so it ends as a
cross; in Horizon the torch was gone first and the wire ended as a dot.
Found by tools/redstone_diff.py (two of 48 random circuits differed on vanilla
26.3 vs Horizon, reduced to four commands); after the change 144 random circuits
over three seeds match vanilla tick for tick.
- Behaviours::update_neighbours_on_block_set ports ServerLevel.updateNeighboursOnBlockSet.
- place_state takes `command`: setblock and fill use the vanilla flags; clone keeps
its own flow. The strict variants are unchanged.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
🤖 Agent: queued. Piston restitution bounces the entity's own velocity and runs for server entities only; the ground flag comes from #12168 (queued first).
Carry the actual top-level projectile predicate through ArrowState load/save
and consult the existing matcher only for resolved Mob arrows under both
rules. Refuse unsupported conjuncts and preserve other projectile/drop paths.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The recipe book's place packet and Horizon's viewer transfer channel go
to an open mod crafting menu (`lan_mod_craft_apply`) over its block
entity's grid, as a click does, with a ghost recipe when the items are
missing; the client's `native_place_recipe` and `native_transfer_recipe`
accept such a screen, so JEI ports and the Recipes mod fill it as they
fill a crafting table.
Part of #12198.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. A mannequin saves `profile`, `hidden_layers`, `main_hand` and `pose` always, and no `Mob` fields, since `Mannequin` is an `Avatar`, not a `Mob`.
🤖 Agent: queued. Literal `random_chance` predicates in commands now draw from the world's random, as `LootContext` gets `level.getRandom()`; main merged in.
🤖 Agent: queued. Realms screens follow vanilla's layout, state and input; the owner-UUID change was reverted (it would orphan tamed pets in Horizon-made worlds), and `under_world` skips parts with ':', with a test.
🤖 Agent: queued. Equippable assets for armor, wings, capes and camera overlays; the End flash now uses `random_source::create()` after main dropped `SoundEngine::unseeded`, with a skybox gate in `tick_end_flash`.
🤖 Agent: queued. An item a scheduled block tick drops ticks in that tick's entity pass, as `EntityTickList.forEach` runs after the block ticks; a boat's lily pad drop still waits.
🤖 Agent: queued. Health Boost and Absorption hooks run for every accepted effect and `add_effect` returns its result after them; after #11883, #11959 and #12108.
🤖 Agent: queued. Startup trace step stats (count, median, p95, RSS) with a test; the compression bench was taken out, since the zlib reuse it measures is not on main.
🤖 Agent: queued. GPU culling Auto turns it on where the CPU limits the frame and keeps it only if frame time improves; the client_lib.rs conflict kept both `autotune` and main's `random_source`.
🤖 Agent: queued. Styled container names stay as the `CustomName` tag in the live block model, so a styled chest name survives a break; main's spill test now compares tags.
🤖 Agent: queued. `/test pos` picks the block from a connected source player's view, as `TestCommand` reads `source.getPlayer()`; rustfmt fixed after the main merge.
🤖 Agent: queued. Undo, a row's cost and the greyed-row reason are generic shell features with tests; Extras gets no Undo, and Graphics moves onto them in the next PR.
🤖 Agent: queued. Arrows and snowballs breaking a chorus flower now drop it via `ChorusFlowerBlock.onProjectileHit` (`destroyBlock` with drops); main merged in.
🤖 Agent: queued. GLSL overload resolution now prefers an exact argument match over a conversion, as the GLSL spec ranks them, so packs that overload on int/float pick the right function.
🤖 Agent: queued. Environment attributes now come from the layer stack (dimension, then biome with blending, then weather) in `EnvironmentAttributeSystem`'s order; main merged in since the review.
🤖 Agent: queued. The remaining Russian strings in the launcher and the game are translated, and the keys match en_us, so no string falls back to English.
🤖 Agent: queued. A template start is read back only when every child decodes; one unreadable child sends the whole start to #11652's regeneration, so old saves get nothing worse. I couldn't rerun the ignored `saved_starts_match_generated_layouts` here (no prepared resources on this host); your 7/7 run and the parity run decide.
MinecraftServer.tickChildren runs the #minecraft:load and #minecraft:tick
functions before the world clocks and the levels tick; the Overworld's
ServerLevel.tickTime then raises the game time and runs the scheduled
functions due at the new time. Horizon raised the game time, the weather and
the day clock first, then ran the scheduled functions and the tick functions
after them, so a tick function read both clocks one tick ahead and a
`schedule ... 1t` from it waited a tick more.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A script that syncs runs on fixed seeds to its end, so in the full command-exec
corpus every group after the proof drew from them, at seeds fixed by whatever
came before. With one more proof case (#12217), "experience orb stuck", whose
orb rarely ends otherwise on random seeds, landed on such a seed and failed on
every run. The group is isolated now and runs as tools/test_seed_sync_parity.py;
the main corpus runs without the agent, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: keyring calls now give up after 20 s on a thread of their own; a call still stuck makes later ones fail at once, so the account lock is never held forever (tested with a keyring that never answers).
A Secret Service daemon that never answers (two keyrings racing for the
D-Bus name, a locked collection with no working prompter) held the
account lock forever: Play stayed on "Starting…" and printed nothing.
Every keychain call now runs on a thread and gives up after 20 s
(Bounded): on Linux the secrets file takes writes as before, and a
sign-in that can't be read says so instead of "no owner". While a
given-up call is still stuck, later calls fail at once. Play prints one
stderr line per step, and `account list` prints why a check failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued first. Main's correctness job fails on this test since #12086 met the folder metadata gate (84f695992), and every PR that merges main fails on it; verify is green here.
🤖 Agent: queued. A dispatch over 65,535 workgroups per dimension (the wgpu limit) is now split into several dispatches with the base passed along, so packs whose compute pass sizes from the screen still run at 4K and up.
🤖 Agent: queued. LabPBR `_n`/`_s` maps now reach entities, items, block entities and the hand; `set_shared_material_maps` now runs after `set_custom_sky` in loading_window.rs, so a pack reload binds both.
#11794 made the item's water and air drag floats (0.99F, 0.98F), as
vanilla's are; the test still expected the f64 constants, so it fails
wherever the vanilla data is prepared.
Fixes#12221
Co-Authored-By: Claude Opus 5.5 <[email protected]>
folder_metadata_present (Pack.readPackMetadata) now requires a pack section
with a description, so the {} metadata in #12086's test hid both packs and
seed_create_world returned None. This failed every PR's verify on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: removing an entity's chunk now drops its area-effect clouds too (breath_clouds.retain), and saves keep entity_base_tag's Entity fields. On main merge 8b583a15e, save_roundtrip, entity_nbt_boolean, orb_saved_uuid, stand_fall_save, conversion_water_save, mob_pickup_save and potion_effect_icon pass. command_exec's only 2 mismatches are main's gateway_item case (same on bare main b13dbf068).
LevelChunk.isTicking and ServerLevel.isPositionTickingWithEntitiesLoaded
also need the chunk's entity section loaded (areEntitiesLoaded). It loads at
the end of a level tick, frozen or not (PersistentEntitySectionManager
.processPendingLoads), empty sections too. A chunk that loads now waits for
the end of the level's next pass before its block entities (bells, conduits,
gateways and sculk blocks included), moving pistons and scheduled ticks run.
The command harness's readiness waits mark their chunks loaded, as the
vanilla oracle's do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every gassing geyser scanned every mob every 10 ticks, all on the same tick,
and every erupting one every tick. One grid of mob boxes is kept for the block
entity pass, until the mob list's next write; a geyser's own effects and
pushes move no mob, so the grid holds after them.
The mobs still get their effect and push in list order, and the gas reads
blocks only, so finding the reached mobs before any gets its effect changes
nothing.
(cherry picked from commit 2bad442a93, ported to the fields on Game)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A cave spider's poison bite or a bee's sting on a guest named the mob
nearest to where the hit came from in X and Z, ignoring height, so a zombie
standing under the spider took the credit (the kill message, the hurt-by
advancement, the guest's revenge target). The event names the mob that
swung, as a plain melee hit does since the previous commit.
(cherry picked from commit 3ab80dc9d0, without its deletion of tools/bench_*.rs)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each ticked cart compared its box with every cart, and a moving rideable cart
with every mob twice (and built the mob teams for each). A grid kept for the
tick answers in slot order, as the scans did. The cart grid follows each
ticked cart, is rebuilt when a cart is gone or added, and dropped after a
cart's command; the mob grid holds until the mob list's next write. With
native mods loaded, whose hooks can move either, the scans stay.
(cherry picked from commit 3613b40c6b, ported to the fields on Game)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. A shulker's hurt teleport now runs inside the hurt, before the bullet's levitation, and riders get off where it stood (`unRide`); NoAI shulkers never teleport, as `teleportSomewhere` requires.
A ListStamp names the list and how many times it was written through
`DerefMut` (every push, insert, retain or slot write). A cache built from
the list keeps the stamp and is rebuilt when the stamp differs; a swapped-in
list (another level's) has another name.
(part of commit 6b826d204a)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. Four components (`provides_trim_material`, `provides_banner_patterns`, `can_place_on`, `can_break`) are now encoded in outgoing stacks with the synced registry IDs; anything unsupported drops the whole buffer, as before.
A game event posted outside the mob pass (a block placed, a door opened, an
explosion) looked through every mob of the level for the wardens and allays
that can hear it. The listener slots are now kept until the mob list is
written again (the previous commit's stamp), or another level's list is in
place.
Mob id lookups for game events, thorns and a hit's source go through the
mob index instead of a scan. A guest's melee event names the mob that
swung, so the hit credits that mob rather than the one nearest in x and z
to the player.
(cherry picked from commit 6b826d204a without its join_seq part, ported to the fields on Game)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. The server now waits for the saved tickets' chunks before the first tick (`prepareLevels`), so their saved ticks unpack at the time they were saved at; the wait gives up after 600 s.
🤖 Agent: queued. Tools only: `world_compare.py` and `perf_bench.py --seed`, no generator change; the seed report it answers is documented with the two seeds compared.
🤖 Agent: queued. `--folder` on the CurseForge `Instances` folder now finds its instances, and a Prism `instances` folder (same name where case is ignored) is not mistaken for it.
A ListStamp names the list and how many times it was written through
`DerefMut` (every push, insert, retain or slot write). A cache built from
the list keeps the stamp and is rebuilt when the stamp differs; a swapped-in
list (another level's) has another name.
(part of commit 6b826d204a)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
push_from_ticked_cart searched the cart list for the ticked cart twice and
copied it in and out whole. The caller knows its slot: the ticked copy is
swapped into the slot for the push, and only the pushed cart is copied back,
over the copy it replaced (clone_from reuses its buffers). Lava hurting a cart
looks there first too.
Without mods a push changes velocities and riders, never the list, so the
cart is still in its slot after it (a debug assertion checks). With native
mods loaded a mount hook can change the list: the copies and the search by id
stay. The loot roll stays with the ticked copy, as #11526 made it: it comes
off before the swap and goes back after.
(cherry picked from commit 206905a2d5, without
the loot roll part of fb69aba537)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. Far terrain treats slabs as full cells like stairs, so a waterlogged slab keeps its own material instead of becoming water; the test walks every real slab state.
🤖 Agent: queued. An empty `shaderpacks/` (CurseForge and Sodium create it) no longer counts as a shader pack, for folders and archives alike; tests cover both.
🤖 Agent: queued. After every server push the ground flag is set to `verticalCollisionBelow` (`Entity.move` → `setOnGroundWithMovement`, which runs when `isLocalInstanceAuthoritative`), so a sideways push off a block clears it; #12167 builds on this.
EntityTypes.TNT is sized 0.98F with an explicit eyeHeight(0.15F). The box
now widens the float size, so TNT stopped by a wall or ceiling ends where
Java's does, and the fluid eye height is the type's 0.15F instead of the
default 0.85 of the height.
Fixes#12020
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main still makes a new ZlibEncoder per record, so the bench would compare
two fresh encoders. It belongs with the change that adds the reuse (#12189).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fluids read Info::ice through is_ice_class (instanceof IceBlock, frosted
ice included), so excluding frosted ice there changed fluid meshing with
the option off. Regular ice now gets its own opaque_ice flag.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Restitution bounces the entity's velocity (getDeltaMovement), not the push.
It runs for server entities only, since a player's client moves the player.
It runs even when the step is too small to apply, as Entity.move does.
The ground flag is left to set_pushed_on_ground (#12168), which follows
setOnGroundWithMovement.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. `at_velocity` now covers boats, minecarts, armor stands, frames, paintings, TNT, falling blocks, block entities and moving pistons; the `falling_block_visible` doc comment is back on its function.
🤖 Agent: queued. A wildcard `data modify storage` that fails part way keeps the edits made so far, as vanilla's in-place NbtPath edit does; main merged in.
A helper-only fragment adds `native_player_break_block(uuid, pos,
into_inventory)` (`Host::player_break_block`), so a vein-mining mod can
break blocks as its player without injecting into the private
`Game::break_block`. It runs the game's own player break
(ServerPlayerGameMode.destroyBlock): the player's checks, the
`destroy_block` hook, loot for the held tool and its enchantments, tool
wear, stats, exhaustion and experience. With `into_inventory` the block's
drops go to the player's inventory, the rest dropped from them.
The break now reports whether it removed the block, and a break nested in
another's hook keeps the outer break's own-event position. The acting
guest's spawn protection is kept for their turn so the helper can check
it while the hub is out of reach.
Fixes#12195
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. Command reads keep the real bell block entity (and copper golem statue) instead of a synthesized one, so `data get block` matches vanilla's NBT; main merged in.
🤖 Agent: queued. A jukebox without a song now saves no `ticks_since_song_started`, as `JukeboxSongPlayer` writes it only while playing; main merged in.
A content mod's menu type may declare `"crafting": {"grid", "size",
"result"}`: those slots run vanilla's CraftingMenu logic on the menu
engine (live result, consumption and remainders on take, shift-click
crafting, recipe placement), over a grid the block entity keeps. The
result is the menu's own: the block entity never holds it, so hoppers,
comparators and a broken block can't dupe it, and the hub shows guests
the result the game made, again each tick the grid changed.
Part of #12198.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Horizon-only option, off by default, that draws regular ice opaque in the solid pass and culls faces between adjacent ice blocks. It follows the public description of Luxium's Opaque Ice and the design note in #11563; no Luxium source was read or copied.
Fixes#12192
🤖 Agent: queued. Patch in vendored naga: copies of structs or arrays holding atomics go member by member, up to 64 scalars; larger ones stay whole copies, as noted.
🤖 Agent: queued. The added formats (RGB10_A2, RGB10_A2UI, 16-bit SNORM) map to the wgpu formats `image_format` already binds; formats a GPU can't store into are still left out with a note.
🤖 Agent: queued. A translatable with a fallback keeps the fallback as a literal, as `TranslatableContents` does, instead of looking it up as another key.
🤖 Agent: queued. The panel accepts LAN browsers when bound to 0.0.0.0 or :: only; a loopback bind still refuses them, so nothing new faces the network.
🤖 Agent: queued. Passing terrain's interpolants in 5 locations instead of 12; visbuffer reads `TerrainOut` only internally, so packs see no difference.
Measure startup step distributions and RSS, and compare reused versus fresh zlib streams with deterministic checksums.
Co-Authored-By: Codex <[email protected]>
🤖 Agent: queued. One cost: the stepped tracker paths add a Vec per tracked entity on the tracker tick; small, but it is the hot path, worth a look if MSPT moves.
🤖 Agent: queued. Batch of server click answers; each case matches `Mob.interact`/`InteractionResult` and is in the batch's harnesses, which run in parity.
🤖 Agent: queued. LT + A crafting a recipe repeatedly is new controller input; servers see the same clicks a mouse would send, so nothing server-side changes.
A test-only Java agent makes vanilla's RandomSupport.generateUniqueSeed step a
uniquifier of the calling thread's own, without the clock; HORIZON_TEST_SEEDS=1
does the same in Horizon's unique_seed. A command script's sync_randoms step sets
that uniquifier, the entity-id counter and the level's random in both engines,
and the "seed sync proof" group compares a drop's position, primed TNT's kick, a
dispensed item and the resting phase of four items exactly. The oracle cache
counts the agent jar as an input.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. Comment settings end at their line, as the Iris probes in `read_stage` show it reads them line by line; live code still reads to the `;`.
🤖 Agent: queued. Bold is drawn as the shifted second pass and italic as the skew vanilla's `BakedGlyph` uses, and after the #11863 merge styled glyphs keep the HDR emission tier; the 102 sign cases pass.
🤖 Agent: queued. The Nautilus cooldown memory now exists only while it counts down and is erased at 0, as `Brain.setMemoryWithExpiry` does; its harness runs in parity.
🤖 Agent: queued. A one-block predicate now saves as the bare id and partial `custom_data` keeps only the keys given, which is how `BlockPredicate`'s codec writes them; the residual corpus documents what is left.
🤖 Agent: queued. A sideways push leaves `onGround` false, as `PistonMovingBlockEntity.moveCollidedEntities` only sets it for upward pushes; main merged in since my review, no other change.
🤖 Agent: queued. First of the 12086 → 12087 → 12088 series (each includes the one before): new worlds get the game folder's `new-world-datapacks`, copied at creation only.
🤖 Agent: queued. Ending the tick on the first level skips the dimension swap that did nothing, and the ray chunk cache is per raycast, so no stale chunk outlives one ray.
🤖 Agent: queued. One cost to know: the registry-wide cache has 8192 slots that are never reclaimed; past that it falls back to decoding, so it only stops helping, it doesn't grow.
🤖 Agent: queued. The SWAR check tests 8 palette indices per word and has an exhaustive test against the scalar loop, so a bad index is still caught at the same position.
🤖 Agent: queued. The trident attack continues while `alive && (holding a trident || !nav.done)`, which is vanilla's condition rewritten; a drowned that drops its trident now switches to melee.
🤖 Agent: queued. Tools only: one `git cat-file --batch-check` for the heads instead of a process per PR, and the diffs fetched in parallel; same notes out.
🤖 Agent: queued. `TradeWithPlayerGoal.canUse` also stops on hurt and on distance > 16; it reads `cx.w.player` like the existing LookAtTradingPlayer goal does, so guests on a server share that approximation.
🤖 Agent: queued. A ridden cube mob now skips its jump-move as vanilla's `SlimeMoveControl` does while it is a passenger, so a slime on a boat no longer hops off its seat.
🤖 Agent: queued. Matches `ClientPacketListener.handleMovePlayer`: a passenger keeps its seat when the server's position packet comes, instead of snapping out of the vehicle for a tick.
move_pushed applied the resolved movement to position only, leaving the
entity's velocity untouched. In a ticking chunk the entity's own tick
masks this (PrimedTnt::tick_among restitutes), but in a lazy chunk the
velocity survives: a primed TNT pushed by a piston into a wall, ceiling
or slime keeps 0.4 / -0.8 / -0.8 instead of vanilla's 0 / 0 / 0.64.
Add restitute_piston after apply_push: compare the post-piston-limit
movement (limited) with the post-collision result (resolved) and, as
Entity.move(MoverType.PISTON, …) does, zero blocked horizontal axes,
zero or slime-bounce the vertical axis, and update on_ground. Covers
Player, Guest and every Entity kind including PrimedTnt.
Add unit test a_pushed_tnt_loses_velocity_into_a_wall (needs prepared
resources) asserting the TNT's vx is zeroed by a wall.
Co-Authored-By: Nemotron <[email protected]>
Every session sampled each far tile again (LodSampler::grid): the noise,
biomes and surface rules for 16x16 columns. lod::cache keeps the chunks
generated for detail, but not these samples. lod::bake keeps them in the
dimension's horizon_lod/ folder (append-only bucket files, a record per
tile, zlib), so a reopened world reads them back instead.
- Saved chunks still win over baked samples (SavedLodSampler applies them
on top as before), so edits show as they did.
- A fingerprint guards the folder: lod::cache's build key (version,
commit or executable time, block states, biomes), the level's height
range and the world's data packs (path, size and time of each file).
When it changes the folder is emptied and baking starts again.
- Reduced far detail's approximate samples are kept apart from full ones.
- A record cut short (a crash mid-write) ends its file there; failed
samples (all-empty columns) are never kept.
Measured with a far-grid bench on a world with its data packs (9 tiles
a level, one sampling thread, 16x16 columns a tile; the baked tiles read
back identical):
| world, detail | sample (ms/tile) | write | read after reopening | disk/tile |
|-------------------|------------------|-------|----------------------|-----------|
| To The Max, full | 7.6 / 42 / 48 / 52 | 0.06-0.11 ms | 0.05-0.07 ms | 4-11 KB |
| To The Max, reduced | 4.8 / 13 / 13 / 23 | 0.05-0.08 ms | 0.04-0.05 ms | 2-8 KB |
| vanilla, full (horizon-world lod_grid_bench) | 2.0 / 8.5 / 9.5 / 8.6 | | | |
(levels 0 / 2 / 4 / 6: 16 to 1024 blocks a tile). Writing adds under
1.5% to a tile's first sampling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SeonVnpkKZ9a5aRRAWs2Qb
Per-call costs in decoration, ported from the To The Max fork onto main
(main's own ore-sphere walk, FxHash tick sets and HashMap are kept):
- Biome filter: each biome's feature list is a bitset, not a BTreeSet.
- matching_block_tag: the decoration world keeps the last tag's states
(environment scans ask one tag up to 32 times per position).
- Block ticks: the region keeps the last 16 default states it resolved
instead of going through the registry's name map each time.
- Lakes: the spot divisions once per axis coordinate, not per cell; the
16x16x8 grid as a byte per column, edges tested a column at a time.
- Ores: a bound per region chunk on OCEAN_FLOOR_WG (its heightmap maximum
on first use, raised by the world's own writes, the only writes that
move it) answers "no column" without reading up to 121 entries; the
blob's sin/cos only once a column is found. Target rules that draw
nothing (all of vanilla's) are memoized per (state, Y ranges held).
- Speleothem Column.scan reads each block once.
Identical output: chunkgen_bench snapshots (32x32, seed 0) and the To The
Max world checksums are unchanged. Interleaved pairs, 4 threads, built
with one codegen unit (the fast profile's 16 units moved unrelated
stages by +-10% between builds):
- vanilla chunkgen_bench --size 32: decorate wall 334-340 -> 317-318 ms
(-6%); proto unchanged.
- To The Max (seed 0, 16x16): decoration 4.2-4.4 -> 3.9 ms a chunk
(-9%); finished chunks 152.3-155.7 -> 154.3-155.7/s (terrain-bound
at 4 threads).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SeonVnpkKZ9a5aRRAWs2Qb
Decoding a template pool loaded every element's template at once
(gunzip, NBT, palette), where vanilla's pool elements call
StructureTemplateManager.getOrCreate only when the assembler first
touches them. In To The Max's bench area the abandoned camp's pools
loaded 180 templates and its assembly used 18 (villages: 32 loaded, 5
used), holding the start chunk while neighbouring threads waited on it.
Pool elements now hold a LazyTemplate (the id and the shared template
store) that loads through the same cache on first use, so templates stay
comparable by pointer. Data packs' template files still win over the
prepared ones (the store keeps them). When two threads load one file at
once, both get the first copy kept (before, the second insert replaced
the first). Its Debug prints the template as before, so the
structure-start checksum is unchanged.
Identical output: chunkgen_bench snapshots (32x32, seed 0) and the To The
Max world checksums are unchanged. Interleaved pairs, 4 threads, built
with one codegen unit:
- vanilla chunkgen_bench --size 32: structures 0.563-0.568 -> 0.493-0.524
ms a chunk (-10%); proto wall 1321 -> 1284-1304 ms.
- To The Max (seed 0, 16x16): structures 1.5 -> 1.1 ms a chunk (-27%);
finished chunks 153.6-155.5 -> 155.1-155.9/s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SeonVnpkKZ9a5aRRAWs2Qb
A data pack whose climate reads the interpolated terrain density (To The
Max's ridges are a spline of it) samples that density's input on its 4x4x4
cell lattice in the biome stage. The noise fill then samples the same cells
again for its interpolation. Interpolated samplers now carry their
compiled function's identity. The biome stage keeps the lattice samples
they produce (a 1024-chunk cache beside the biome cache), and the noise
fill reads its corners from its own chunk's and its +X/+Z neighbours'
samples, sampling only the cells none of them holds (the top layer).
This applies only when every sampler under the interpolated input is
pointwise (a new DensitySampler::pointwise_inputs: each value depends on its
own position only, never on the volume's extent or world fields), so the
values are the ones sampling the whole volume gives. Vanilla's climate
doesn't read the interpolated density, so its worldgen is unchanged.
Measured on this branch's base, 35f3dad20, laptop (Ryzen 5 7640U, 4
threads, fast profile), runs interleaved:
- To The Max (seed 0, 16x16 chunks, world_chunkgen_bench): 150.5 -> 162.6
finished chunks/s (+8%); noise 6.55 -> 4.30 ms a chunk. Terrain,
decoration and structure-start checksums unchanged.
- Vanilla (chunkgen_bench, 32x32, seed 0): 5.05 -> 5.10 ms CPU a chunk
(noise); terrain and decorated snapshots byte-identical.
Ported from ThatCakeIsAPie/horizon-ttm (bfec4acb2) onto upstream's
whole-cell interpolator (fill_whole_cells).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SeonVnpkKZ9a5aRRAWs2Qb
Main moved build_renderer to game/loading_window.rs; the shared material maps
are set there now, after the custom sky as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Merge main.
- The owner switch from the offline UUID to the account UUID (integrated.rs,
lan/conn.rs, lan/playerdata.rs, net.rs, remote.rs) is left out: every world
Horizon made names its player by the offline UUID, so tamed pets and other
owner references would no longer know their player. It needs its own PR
with a migration.
- A tar entry part with a colon (a drive prefix or an alternate data stream)
would leave the world folder on Windows: skip it, with a test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Retain top and bottom slab states in LOD chunk summaries instead of
discarding dry slabs or replacing waterlogged slabs with water. Use the
same full-cell approximation as the existing partial-block filter.
Check real summaries, coarse reduction and emitted faces for every
prepared slab state. All 72 LOD tests pass, including resource-backed
coverage of 606 slab states across 101 materials.
Refs horizon-rust/horizon#12180.
Co-Authored-By: OpenAI Codex GPT-6 <[email protected]>
🤖 Agent: queued. When `/tmp` is shared the socket goes into a private `horizon-<uid>` folder, so another user on the machine can't connect to or squat on the control socket.
🤖 Agent: queued at a3fd3597f (main merged in; the draw now uses `random_source::entity()`). Thorns rolls in EquipmentSlot order from the wearer's own random, as `EnchantmentHelper.doPostAttackEffects` does.
🤖 Agent: queued. The defender now gets the Cooldown packet the server sets on the shield (`Player.disableShield`), so its client greys the shield out for the 100 ticks instead of letting it block client-side.
🤖 Agent: queued at 14bfb672f (main and #11750 merged in). UPDATE/REMOVE_MOB_EFFECT ids are renumbered for older clients; effects they don't know are dropped instead of showing as another effect.
🤖 Agent: queued. One cost to keep in mind: `stab_players_for` builds a Vec for every guest using any item each tick (shields, food too); small, but a follow-up could return early when the item is not a spear.
🤖 Agent: queued. Same check as `Camel.canCamelChangePose` (the standing box against the blocks above), and the test uses a block at y+2 that blocks only the 2.375-tall standing pose.
🤖 Agent: queued. The data file is written off the UI thread after `complete_assets`, and a stale one is dropped when the assets change, so a repaired install doesn't read an old index.
🤖 Agent: queued. Matches `LivingEntity.dropExperience`/`dropCustomDeathLoot`: experience and equipment follow `lastHurtByPlayerMemoryTime > 0`, while LAST_DAMAGE_PLAYER still needs the player to be resolvable, and the new test checks both sides.
🤖 Agent: queued at fee1085be (main merged in). A 1.21.3 pick_item now becomes the same container swap a 1.21.4+ client sends, so older clients keep middle-click in survival.
🤖 Agent: queued. The count goes up at the one `NetEvent::Packet` send in `conn.rs` and down at the hub's drain, so the 2× inbox cap bounds what a flooding client can queue between ticks without touching the hot path otherwise.
🤖 Agent: queued. The stop waits only with `[transfer]` on, and its 90 s bound stays under the supervisor's 120 s kill, so a proxy that never answers can't hang a stop. Built with 14 other PRs on main (pr_ready --quick green apart from known NixOS-only tests).
🤖 Agent: I moved the distance blend per pixel (it was per triangle through the flat hdr_code). Before/after screenshots and frame time are in my comment: the pack's GPU frame is 121.1 vs 122.0 ms on lavapipe, same within noise.
ResourceOrIdArgument decodes an inline value with the registry's codec
while the command is parsed, so a feature the ConfiguredFeature codec
refuses fails the parse: the command never runs, and an execute store
around it keeps its value. Horizon's parse accepted any compound for
minecraft:worldgen/feature and ran the codec only when the command
executed, which then failed and stored 0.
Where the server has its live features, the command registries now carry
a check that runs the same inline decode placement uses (the live
features' catalog and block bindings, through LiveFeatures::decode_inline),
without placing anything. A value it refuses fails the parse with
argument.resource_or_id.failed_to_parse. Without live features (a client,
or a server without the worldgen bundle) the parse is unchanged. The loot
table and every other registry keep their decode.
The message is the native codec's own (for example "invalid world
generation field: to_place"), not DataFixerUpper's text.
Fixes#12171.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The End flash moved to tick_end_flash here, while main (0b23212f4) switched
the flash and the dragon fight to the dimension type: keep main's
has_ender_dragon_fight check, and gate tick_end_flash on the End skybox as
main did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main (3443abbde) kept NoAI Nautilus out of the needs_water branch; this PR
gives every Nautilus its own handler, which already refills NoAI ones.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Follow-up to #8718. On main, lan_bot's older_clients_of_each_release_join_and_are_sent_chunks fails at 1.21.1 (the reverse step never adds 1.21.2's particle status to ClientInformation); CI misses it because the test returns early without prepared resources. With this it passes, as do all 440 horizon-protocol tests and, on the union, the particle harnesses block/emitter/entity/marker/native_client_particles, particle_providers, player_effect_amplifier and spell_ticks (8/8).
Each level reads its world_border SavedData when it is made
(ServerLevel.getWorldBorder), so a restarted server has the saved border
in place before its first command. #11016 gave each level its own
border. But world open reset the border of the level in place to the
default, and the saved file was read only later, in load_scoreboard (the
first tick's functions, or the first scoreboard use). A first
/worldborder get after a restart read the default, and a first
/worldborder set was put back to the saved size soon after.
World open now reads the in-place level's border with load_border, as
open_level does for the other levels, and load_scoreboard no longer
touches the border.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Shulker.hurtServer calls teleportSomewhere() and hitByShulkerBullet() inside
the hurt. Mob::damage has no world, so the flags it leaves now run right
after the hurt: in the mob's own tick (hurt_teleports, also without AI) and
in the game layer (settle_hurt_teleports: /damage, explosions, projectile
hits, the creature outbox), before a bullet's levitation. teleportSomewhere
also refuses a NoAI shulker, keeps inside the world border and unRides.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: I pushed 9bc110bae. The new second position_riders() call after the player moves rebuilt the whole mob index on every tick for every client; it now runs only when the riding graph has something on this player. I also added the missing passenger_vehicles field to main's studio Remote initializer (it did not compile on main) and drop passengers whose vehicle is removed. Both ignored tests pass with prepared resources, and so do the riding harnesses on a union with current main: boat_authority, boat_rider, boat_paddle_reset, mob_rider_navigation, mount_submerged, server_entities, vehicle_command_damage, dedicated_server (8/8).
Reads the seed, spawn, biomes, structure starts and blocks two saved 26.x worlds
share, with a run-to-run floor from a second world of one game. Run on seed 12345
and a large negative seed: the spawn, every biome and every structure start match,
terrain blocks match to 99.3 to 99.9%, and decoration sits at vanilla's own floor
except trees, a few points under it. perf_bench.py gets --seed to make the worlds.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
🤖 Agent: Checked against the 26.3 client jar: ServerboundSignUpdatePacket's codec is pos, fixedSizeList(4) of stringUtf8(384), then SignTextSlot as an idMapper VarInt (BACK=0, FRONT=1), so reading the side last and writing it as the old boolean after the position is right. It only runs in the older-server adapter, so 26.3 servers are untouched. I merged main (the #8718 conflict in translate.rs: kept #8718's arms plus this one); 440 horizon-protocol tests and clippy pass.
The partial reason of a carried 1.20 mod leaves out the recipe clause
when no result names `item`, and says "1 recipe result names" for one.
docs/modpacks.md says how the game treats each 1.20-shaped file, as an
offscreen run of a carried synthetic 1.20 jar showed: one file at a
time, never the whole start.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A missing comma after the horizon-server line made ru.json unreadable, so
the whole launcher fell back to English (the i18n tests failed on it).
The {mods} placeholder had been translated, so the count would never be
filled in. Also "подключиться" for two "Couldn't connect" lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
datapacks::READ named four kinds, so a pack of only recipes, loot tables, advancements or
worldgen was refused as changing nothing, and the install note listed them as not read. READ
now lists the kinds the game loads (worldgen by folder, so world presets still count as not
read), the refusal goes through trf(), and docs/datapack-upgrade.md no longer says worldgen is
not applied.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
GPU Culling pays where the CPU is the slower of CPU and GPU and costs where the
GPU is, and a machine's parts don't say which. Auto watches the frames' median CPU
and GPU times, tries it where the CPU is clearly the slower one, and keeps it only
if the frame's busier side dropped under 97%; a trial that didn't help is undone and
waits 5 minutes, then 20, then for good.
gpuCulling in horizon-graphics.txt is now false|true|auto (older files read as before);
Settings > Performance steps Off/On/Auto; horizon-ctl gpu-culling takes auto. Off stays
the default and no preset sets it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
Carry admitted Component tags through name state, saves and drops; let modeled name removal override stale source names.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Main already carries the 1.21.1 and 1.20.6 attribute tables (consolidated in
d79a36101); kept main's generated tables and this branch's mob effect renumbering.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under HORIZON_HDR_FRAME=float, when HDR, Smooth Gradients or the pack picked call for another
frame than the renderer's, the renderer is made again on the same device before the next frame
draws, and the world is meshed again, as after a lost device. Without the switch nothing changes:
the frame stays until the next start.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MinecraftServer.prepareLevels waits for the chunks the restored tickets hold
(ChunkLoadCounter) before the first tick, and the first tick unpacks their
saved block and fluid ticks at the game time they were saved at. Horizon's
dedicated and integrated servers started ticking while the forced chunks were
still streaming in, and the saved ticks were unpacked from the tick the chunk
arrived, one tick or more late.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
move_pushed applied the resolved movement to position only, leaving the
entity's velocity untouched. In a ticking chunk the entity's own tick
masks this (PrimedTnt::tick_among restitutes), but in a lazy chunk the
velocity survives: a primed TNT pushed by a piston into a wall, ceiling
or slime keeps 0.4 / -0.8 / -0.8 instead of vanilla's 0 / 0 / 0.64.
Add restitute_piston after apply_push: compare the post-piston-limit
movement (limited) with the post-collision result (resolved) and, as
Entity.move(MoverType.PISTON, …) does, zero blocked horizontal axes,
zero or slime-bounce the vertical axis, and update on_ground. Covers
Player, Guest and every Entity kind including PrimedTnt.
Add unit test a_pushed_tnt_loses_velocity_into_a_wall (needs prepared
resources) asserting the TNT's vx is zeroed by a wall.
Co-Authored-By: Nemotron <[email protected]>
The CurseForge app, and mods such as Sodium, create `shaderpacks/` in an instance
even when the player never installed a shader pack. Import counted the folder
itself as one "shader pack, conversion pending" file and listed it under "not yet",
so a client-only instance such as one with Sodium, Lithium, Jade, malilib and
Litematica reported 4 "not yet" rows for 3 mods. A folder instance now reports
shaderpacks/ only when it holds something, and a zip only when it has a file
under its shaderpacks/ (a bare directory entry no longer counts).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Mannequin extends Avatar extends LivingEntity; it is not a Mob.
Mannequin.addAdditionalSaveData always writes profile, hidden_layers,
main_hand and pose after LivingEntity's fields, and none of
Mob.addAdditionalSaveData's. Horizon left out the four unless they were
loaded, and wrote PersistenceRequired, CanPickUpLoot, LeftHanded and NoAI.
A mannequin now reads main_hand and hidden_layers into the main-arm and
hidden-layer state it already had (a list with an entry that is not a part
hides nothing, as the codec's failed read), and writes all four from its
live state: the empty profile when it has none, the hidden layers in
PlayerModelPart order, its arm and its pose. It neither reads nor saves
Mob.readAdditionalSaveData's flags, and its save drops the Mob-owned keys
over a stale loaded compound. Every other kind is unchanged.
Fixes#12135.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolve the overlap with #11863 in the styled span and glyph loops. Keep its emission tier on glyphs, outlines and decoration quads, with regression coverage.
Co-Authored-By: OpenAI Codex GPT-6 <[email protected]>
The fade-in's 16-to-32-block distance blend was computed per vertex and
carried in the flat hdr_code, so it was one value per triangle (the
provoking vertex's) and near faces faded in steps. The flat bits now hold
only the section's own visibility (the same for the whole section), and
horizon_material_faded blends it by the interpolated spherical distance,
as terrain.wgsl's interpolated visibility does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reverse step for 1.21.1 clients passed ClientInformation through, so a
host read past its end. The older-server adapter now writes 1.21.1's real
layout (no particle status), which made lan_bot's
older_clients_of_each_release_join_and_are_sent_chunks fail at 1.21.1;
real 1.21.1 clients were refused the same way. The step appends
ParticleStatus.ALL in configuration and play.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityDataAccessor.setData loads the edited compound into the same entity,
and Interaction.readAdditionalSaveData reads its width, height, response
and the attack and interaction records. Horizon kept an interaction only
as a generic entity with its loaded compound, and its live data write
took markers alone, so every /data edit answered "Nothing changed".
A server interaction now holds its settings (InteractionState: width,
height, response and the two PlayerAction records) next to its position.
It reads them when it loads or is summoned, saves from them
(addAdditionalSaveData, a record only when set), and a /data edit decodes
the edited compound into the same entity, which keeps its id and UUID.
Every other kind is unchanged.
Fixes#12131.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeoForge lets mods add item rarities (Immersive Engineering's masterwork) and some blocks carry a
map colour id past 26.3's 61 (Mekanism's gas blocks: 63). The game refuses the whole content
root for either, so one such file left every block of the mod out. They now keep the template's
and the report names them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The oracle reports can_occlude per state, but the carry ignored it: a
full-cube block made with noOcclusion() (one its block entity draws) took
its template's occlusion and hid the faces behind it, leaving a hole.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemEntity.tick applies gravity (or the fluid movement) first and then
moveTowardsClosestSpace, whose 0.75 scales the already-fallen vertical
speed. The push ran before tick_move_before_block_effects, so gravity
came after it. Pass the escape speed into the move and apply it right
after gravity; the native block-contact pipeline is unchanged.
Fixes#12018
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The orb kept its own vertical-only on-ground rule for a piston push. It
now takes the server's end-of-move rule that items, primed TNT and armor
stands use: on ground only when the push's downward part was blocked.
Fixes#12100
Co-Authored-By: Claude Opus 5.5 <[email protected]>
wgpu caps a dispatch at 65535 work groups per dimension and panics past it;
GLSL packs may ask for more (131072). A larger workGroups now runs as the
same number of groups in a shape within the limit, and main works out each
group's place in the declared dispatch, so gl_WorkGroupID,
gl_GlobalInvocationID and gl_NumWorkGroups read what they would there. The
shape uses every group exactly, so no group leaves before a barrier. A
count no shape holds skips the program with a note; an oversized
workGroupsRender dispatch is not run instead of panicking.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
naga's GLSL frontend turns a uint/int that an atomic*() call touches into
an atomic type after parsing. A load or store of a whole struct or array
holding such a member then had the upgraded type: `Cell c = buf.cells[i];`
and `uint copy[4] = shared_counts;` failed validation. A whole struct or
fixed-size array of integers in a buffer or shared memory is now loaded
and stored member by member, so each copy keeps its declared type.
Vendored-naga patch: third_party/naga/src/front/glsl/context.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An image declared RGB10_A2, RGB10_A2UI or a 16-bit SNORM format was made
as an RGBA8, RGBA8UI or 8-bit SNORM texture, so a shader binding it as
layout(rgb10_a2) etc. was refused ("is rgb10_a2 but its texture is
Rgba8Unorm"). Each now gets the wgpu format of its GLSL qualifier; one the
GPU can't store into is left out with a note, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
naga's GLSL frontend never compared an exactly matching argument with the
previous best overload's conversion there, so greaterThanEqual(ivec3, uvec3)
found the uvec3 overload no better than the vec3 one and reported
"Ambiguous best function". GLSL 4.60 §6.1 ranks an exact match above any
implicit conversion: the uvec3 overload wins. Zephyr Starlight's
shadowcomp1-4 failed on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Packs list their const settings in a block comment one per line with no
';' (`const vec4 colortex6ClearColor = vec4(0.0, 0.0, 0.0, 1.0)`). The
reader looked for the next ';' and took every following line as the value,
so each such setting was refused as 'not a vec4' (Zephyr Starlight logged
85 multi-line notes) and its clear colour was lost. In a comment a
declaration now ends at the first ';' or line end; live code still reads to
the ';'.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tick_blocks applies the entities' block contacts (a boat breaking a lily
pad) before the scheduled ticks; vanilla runs them inside each entity's
own tick, so their drops still wait for the next tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With the blur at 0 the plates still read their backdrop bilinearly between
texel centres, and a texel is 3 to 6 logical pixels of pixel art, so the
scene behind the glass stayed slightly soft. With no blur, read the texel
under the point instead.
Fixes#12062
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The render crate read a trail duration of -1 as the pre-1.21.4 marker, and
the client read particle ids -2/-3 as the old entity-effect providers, for
any server: a 26.3 server sending those values got the legacy behaviour.
The client now decides from the connection's release (received_particle)
and passes an explicit legacy_trail_lifetime option; a current server's
-1 stays a lifetime of -1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content mod jar whose data files are mostly in the folders 1.21
renamed (recipes/, loot_tables/, structures/, advancements/,
predicates/, item_modifiers/, functions/ and tags/blocks, items,
entity_types, fluids, game_events, functions) was refused by `carry`.
Now the carry moves each such file to the singular name 26.3 reads.
A file the jar also ships under the new name keeps that copy, and a
1.21 jar's stale plural folder is left as it was.
The JSON stays 1.20's: a recipe whose result names its item as `item`
(before 1.20.5), which 26.3 skips, is kept as it is instead of counted
as upgraded, and NBT in loot functions, ingredients and predicates is
not upgraded yet. The report therefore marks such a mod partial
whatever its harness says, and says why. `check` still refuses 1.20
jars until the JSON steps of #10262 land.
Part of #10262.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The browser's data pack note still said recipes, loot tables, advancements, other tags and
worldgen were not read. The game reads them now. The note goes through tr() and is in all
86 language tables (translated in 14, English elsewhere).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Two words, like the guide's other rows, and the same name as Quick Craft on
Servers. The exact rule (single player, LAN worlds from the server list) stays
in docs/controllers.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LootCommand.dropKillLoot takes the command source's entity as the killer:
a player is the last damage player, so killed_by_player holds, and its
own weapon counts for looting. Under `execute as <connected player>` from
another player, Horizon counted no player kill and used the issuer's
held stack.
For a connected player as the source, /loot kill now reads that player's
selected stack with it in place (for_player); an empty hand stays empty.
The loot roll, the output, the feedback and the result run as before,
after the issuer is back. Other sources keep their current rule.
Fixes#12122.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerLevel.tick runs the block ticks and block events before
entityTickList.forEach, so a dropper's item ticks on the tick it is
dispensed. The item cutoff was taken right after the tick functions;
take it just before the entity pass instead. Primed TNT and falling
blocks keep their own packet cutoff (#10063).
Fixes#12017
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Terrain's vertex stage exported 20 scalars in 12 interpolant locations. A GPU's
vertex stage pays per location (AMD gives each a parameter slot): on an RX 580 the
12 cost 3.2 ns per quad and the same values in 5 cost 1.6. TerrainOut stays the type
the shader code uses; TerrainWire crosses the stage boundary, each component
interpolated on its own and the integers flat, so a pixel gets the same values.
RX 580, Vulkan, 1080p, render distance 16: village 523.7 -> 590.8 FPS (+12.8%),
forest 267.6 -> 323.7 (+21.0%).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
The second position_riders() call after the player moves rebuilt the mob
index every tick for every client; run it only when the riding graph has
something on this player. Also forget passengers whose vehicle is removed,
and add the new field to main's second (studio) Remote initializer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.hurtServer compares a hit inside the hurt cooldown
(invulnerableTime > 10) with lastHurt by its incoming amount, before
armor and Resistance, and keeps that amount as lastHurt. actuallyHurt then
reduces only the difference. Horizon reduced the hit first, then compared
and stored the reduced amount. Under full Resistance every hit reduced to 0,
so a stronger hit inside the cooldown was refused. With armor, a second
hit dealt reduced(8) - reduced(4), not reduced(8 - 4).
The comparison now uses the amount after the helmet, freeze and infinity
changes. The existing armor, Resistance, protection, witch and wolf armor
steps get the accepted difference, and last_hurt keeps the incoming amount.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the client keeps only the server's attribute modifiers, so Speed now ends one tick after RemoveMobEffect as in vanilla (ServerPlayer.doTick runs after sendDirtyEntityData); Paper+GrimAC bench 7-17 Simulation flags at expiry -> 0, 8 selected harnesses pass.
The component parser translated a missing key's fallback as if it were another key, so a fallback that named a
real key showed that key's translation. It now builds the text through Text::translate_with_fallback, as
TranslatableContents.decompose does (Language.getOrDefault(key, fallback)), which also keeps the fallback in the
component's JSON. Fixes#12042.
Bound to every interface, the panel's Host check accepts any IP address with the bind's port (and localhost),
which a rebound DNS name can never be, and the login link names this computer's network address instead of
0.0.0.0. Fixes#12039.
Every spawn start measured its distance to every player to keep those
within reach of its attempts. With more than 8 players, the level keeps them
by the 256-block square they stand in, built once a pass first asks, and a
start reads the 3×3 squares around it: every player within reach is in
them, and the nearest one is the same in any order. The spawn draws are
untouched.
(the player-square part of commit e3b70aace7; its region spawning is left
out)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verifies the per-mod-set build signature with its own Ed25519 key (separate from the release key) before the launcher runs a prebuilt binary; independent review found it sound; opt-in stays off until the rollout steps.
🤖 Agent: the default build resolves the same features as before (390 third-party units with identical fingerprints); every new gate is cfg!(), so CI's existing --no-default-features clippy already compiles the no-extras side and no stub can drift.
TestCommand.showPos takes the source player (getPlayerOrException) and
that player's own pick: its eyes and view, not the execute position. So
`execute as <connected player> run test pos` from another player answers
with the block that player looks at, and the issuer is told. Horizon
only accepted the player in place, so the command did nothing.
For a connected player as the source, /test pos now picks the block with
that player in place (for_player) and only the pick runs there; the test
is looked up in the command's level and the issuer told afterwards. Every
other source keeps its current rule, and the highlight packet is
unchanged.
Fixes#12102.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
VehicleEntity.destroy spawns the cart item through spawnAtLocation, with an
ItemEntity's own motion (0.2 up, random sideways). A container cart's contents
go through Containers.dropContents: a spot in the cart's block, pieces of 10 to
30 with the level random's scatter and no pickup delay, every slot drawing its
spot, and chestVehicleDestroyed drops the emptied container again. Fixes#12099.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ShulkerBoxBlockEntity.moveCollidedEntities pushes through Entity.move too,
so it ends in the same setOnGroundWithMovement as a piston's push.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EnderEyeItem.use now fires the minecraft:used_ender_eye criterion with the squared horizontal distance to the located structure, matched against the distance bounds squared as MinMaxBounds.Doubles.matchesSqr does.
The End flash state now ticks on every client, so servers, LAN guests and single player show the sky flash and its light. Its weather.end_flash sound plays 30 ticks later ten blocks from the camera toward the flash, follows the camera, and is skipped under the credits.
Getting off a boat, minecart or mount whose block is in #minecraft:portals now takes the free-position branch straight up from the rider, as for a removed vehicle, instead of the vehicle's side dismount spot.
Shears now take the saddle off any #can_equip_saddle mob, not only pigs, and play the saddle's shearing sound item.saddle.unequip unless the mob is silent. A mount wearing horse or nautilus armor is left alone, since vanilla shears that body armor first.
A survival player's hit that breaks a chest boat with entity_drops on angers the piglins that see it, as AbstractChestBoat.destroy does through chestVehicleDestroyed.
A mob writes invulnerable_time while it has hurt immunity left and reads it back with a default of 0, so /data get shows it, /data merge sets it, and the immunity survives a save and reload.
time_since_rest now counts every tick the player is not sleeping, dead or alive, as ServerPlayer.doTick does; time_since_death still counts only while alive.
Items a server or datapack gives a camera_overlay now draw it in first person when worn in their equippable slot, and a carved pumpkin whose equippable was removed no longer blurs the screen.
Throwing a snowball, egg, ender pearl, splash or lingering potion or bottle o' enchanting awards the used statistic, as SnowballItem, EggItem, EnderpearlItem, ThrowablePotionItem and ExperienceBottleItem.use do.
A chestplate a server gives elytra wings now shows them and hides the cape, and an elytra whose equippable was changed or removed no longer shows elytra wings.
A frame broken by a player, an explosion or losing its support posts block_change after its drops, as ItemFrame.dropItem(level, causedBy) does, on top of the entity_die it already posted.
Armour a server or a command gives a different or removed equippable now renders with that asset, or not at all, and only in its own slot, on players, mobs and armour stands. The head layer draws a helmet item when its equippable has no asset.
EndermanTakeBlockGoal.tick posts block_destroy and EndermanLeaveBlockGoal.tick posts block_place, each with the enderman and the block state as context.
The first golem to open a chest half posts container_open with the golem as source, the last to close it posts container_close (incrementOpeners / decrementOpeners), and the openers recheck posts a sourceless container_close when the golems have walked off (recheckOpeners).
Filling a bottle from water now plays the fill sound at the player and posts fluid_pickup at the water block; both the water and the dragon's breath branch award the used statistic (BottleItem.turnBottleIntoItem), and collecting dragon's breath fires player_interacted_with_entity on the cloud.
Entity.move(MoverType.PISTON) ends in setOnGroundWithMovement on a server,
for every push: a sideways push clears the flag, and so does a free push
down. The ArmorStand-only repair in move_pushed becomes a trait method that
items, primed TNT and armor stands share.
Fixes#12029
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`replace_word` lowercased the text to find the user's name in any case, and
used that copy whenever it had the same byte length as the text. Equal
lengths don't imply equal boundaries: `İ` (U+0130) grows from 2 bytes to 3
and `K` (U+212A) shrinks from 3 to 1, so a text holding both keeps its
length while its offsets move. A match then sliced the original text
mid-character and panicked.
Use the lowercased copy only when the two share their character boundaries.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
The Device page gets a row beside Button Glyphs that toggles
PadSettings::nintendo_letters, so players no longer edit
horizon-controller.txt to make A confirm and jump on a Nintendo pad.
Its description says what A and B do in each mode. Always shown, like
the DualSense rows. docs/controllers.md points at the row.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityDataAccessor.setData keeps the entity's UUID around Entity.load of
the edited compound, and ExperienceOrb.readAdditionalSaveData reads its
Age, Health, Value and Count into the same orb. Horizon had no experience
orb case, so `data merge entity <orb> {Age:42s}` answered "Nothing
changed" and the orb kept its age.
The orb case decodes the edited compound with the existing orbs_from_nbt
and copies its saved fields into the resident orb, which keeps its
runtime id, join order, tick count and UUID. As Entity.load does, the
saved PortalCooldown is read while a live portal process stays. The
edited compound is kept like the other cases', so Tags follow it.
A falling block's Time is handled separately (#11424).
Fixes#12090.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
with_vault built the list of every player (lan_present: eye heights, effects,
omens) for every loaded vault every tick. VaultBlockEntity.Server.tick reads the
players only when the vault's state may change (stateUpdatingResumesAt, once a
second), so the list is now built at the first ask, from the same game state.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A resource pack's core/sky now draws the overworld's sky disc and its
core/stars the stars, as vanilla's SkyRenderer does with the SKY and
STARS pipelines: the disc of buildSkyDisc(16) with the sky colour in
ColorModulator and the sky's fog, the stars with their brightness, both
turned with the camera only. Without such a pack the sky draws natively.
Packs use this to tint the night sky or add a haze to the horizon. With
Visual Effects+'s sky shaders alone, Horizon's sky now reads within a few
units of the Java client's at noon, sunset and night.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part of #11487. The graphics screen now describes its pages and rows
(`GraphicsScreen`), and the shell draws them, takes the clicks, the wheel,
the stick, the keys and the pad's focus, as it does for Extras. What each
setting does, its saved value, its cost and its description stay in
graphics_ui as they were; Undo puts back the same settings.
What changes beyond what the shell gives every page: the HDR shortcut row
shows its key on a key button, as Extras' Sort key does, instead of between
arrows. The screen's own keyboard, scrolling and drawing code goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main moved key_pressed to game/play_keys.rs and inventory_press to
game/inventory_input.rs; the craft's stop on a key or mouse press moves
with them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: item components read by reference instead of cloned per frame (frame prepare 217→193 µs); earlier parity failures were harness bugs since fixed on main; parity-main covers it after merge.
🤖 Agent: HUD glint sprite looked up once per item instead of per quad (HUD 84→72 µs); render-only, packet harness 16/16; parity-main covers it after merge.
saveConvert pushed one UTF-16 unit at a time and marked a surrogate half with
a NUL sentinel, then ran fix_surrogates over the whole value, which split every
NUL in the result. A \u0000 that is part of a value (legal in a .properties
file) was therefore taken for a marker: "k=\u0000\uD83D\uDE00x" came back from
store with the NUL and both halves of the pair replaced, and since
server.properties is rewritten on every start the value could not survive a
round trip. Walk chars() instead: a Rust str's UTF-16 units always pair up, so
Java's per-char loop needs no marker, and a character past U+FFFF is still
escaped as the two units Java escapes it as.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
LT + A on a crafting book recipe crafts it into the inventory: a tap makes
one, each further LT + A while one is in flight makes one more, and held past
the repeat delay it keeps going until a button comes up, the items run out
or the inventory is full. One request at a time, each waiting for the
server's answer; a result moves only with room for all of it as the client
last saw the inventory. A result that isn't the recipe's stops the craft with
nothing moved, and two seconds with no answer stops it with nothing more
sent. A refused LT + A (a carried stack, another recipe still finishing, not
allowed here) is plain A.
It's free in single player and on LAN worlds joined from the server list's
LAN entries; everywhere else, joins by address included, it waits for Quick
Craft on Servers. On any server but single player's own, placements keep
250 ms apart, under Paper's default limit on recipe placements (5 a second
over 4 seconds); on servers that need the option, every request also keeps
a tick apart.
On the crafting book LT changes the category as it comes up unless A was
pressed meanwhile; RT, and both triggers on the furnace books, change it as
they go down, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Metal, vanilla terrain drew flat only past 1600 listed sections. Cave
culling can drop the listed count below that on a normal view, and the
per-section path then drops cutout leaves on Intel Metal: the canopy above
trunks vanished. Flat draws keep them, so Metal now always draws flat.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
Part of #11487. What the graphics screen has that the shell couldn't say
yet, for any screen that wants it:
- Undo over Done once a row changed something, putting back what the
screen says it changes (`Screen::undoes`, `Screen::undo`), from the
settings as the screen found them. Tab and the arrows reach it.
- A row's cost, a square and the cost in its colour after its name in the
strip.
- Why a greyed row can't be used, in the strip, muted, instead of what it
does.
- `Shell::show` opens a screen at one of its pages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part of #11487.
- The shell keeps the rows it built and builds them again only when what
they read changes: the screen's stamp of its settings, a press or slide,
the search, the page, the size or the language. On Extras, a pad session
of 727 frames built them twice instead of 751 times.
- On a screen with no page list, Down from the search field goes to the
first row instead of Done, for the shell's keys and, through a declared
focus link, for the pad and the keyboard's focus.
- A row the list hides part of scrolls fully into view when the pad or the
keyboard focuses or presses it (AbstractSelectionList.setSelected), so A
presses it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server's client applied its effects' attribute modifiers itself, so it
slowed down as soon as ClientboundRemoveMobEffectPacket arrived. Vanilla's
server removes a Speed that runs out in ServerPlayer.doTick, after the
tick's sendDirtyEntityData, so the movement speed without the effect's
modifier reaches the client a tick later and vanilla's client runs fast for
that tick. GrimAC's Simulation flagged the Horizon client on that tick
(0.051 off with Speed II, decaying over the next ticks).
LivingEntity.onEffectAdded/onEffectUpdated/onEffectsRemoved change
attributes on a server only; a server's client now keeps the modifiers
ClientboundUpdateAttributesPacket sends.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Inserting "Found on this computer" before the last entry of each language
table, instead of after it, adds one line per file and leaves the last line
alone, so other pull requests that append strings don't conflict with it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`let mut keepalive` was only assigned under
`#[cfg(not(any(target_os = "windows", target_os = "openbsd")))]`, so on
Windows and OpenBSD clippy reported `unused_mut` and `-D warnings` failed.
Shadow the binding under the same cfg instead; behaviour is unchanged.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The "Found on this computer" heading says enough on its own, and the caption
cost a string in every language table. The test now checks that each found
instance's slug is the one importing it gives, so the Open button keeps
pointing at the pack an import made.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Imported.datapacks counts the packs in a profile's home/new-world-datapacks,
`can_play` and `apply_profile` accept a profile with only data packs, and
`outcome` and `modpack list` count them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A mod jar with no classes, a pack.mcmeta and a data/ folder is a data pack.
For a 26.2 or 26.3 modpack, `port` now upgrades such jars to 26.3 (the
upgrade-datapack code; 26.3 ones are copied) into the profile's
home/new-world-datapacks/ instead of carrying them as content mods, which
dropped their data/minecraft overrides. The game copies that folder into
each new world it creates.
Before its jar is read, a mod whose Modrinth project also ships as a data
pack counts as data tier, so `fetch` gets the jar; the jar then decides.
The coverage report lists such a jar as partial.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
horizonDynamicLightCount and horizonDynamicLight0..15 give a pack Horizon
Extras' dynamic lights, nearest first (camera-relative position, light
level). horizonTier gives terrain, entity and hand programs a face's HDR
tier: 0 none, 1 to 4 low to very bright, plus 4 on terrain when the whole
face shines. Opt-in: a pack that declares none of them sees no change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.collidedWithFluid sweeps the box along the whole move, so water reaches
the boat down to the lower of its start and end bottoms in every step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Modpacks page now opens with "Found on this computer": the modpack
instances the CurseForge app, Prism Launcher, MultiMC, the Modrinth App,
ATLauncher and the official launcher keep in their usual folders, read with
`other_launchers::scan` on a thread. Each row shows the app, Minecraft
version and loader, and imports the instance with one click through the
same path as "Import an instance folder…" (a Prism instance from its own
folder, so its `mmc-pack.json` still names the version). A pack already
imported opens instead.
Only instances with a `mods` folder of their own are listed: the official
launcher's profiles that play in its own folder are the player's everyday
game, not a pack. The two new strings are in every language table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
StorageDataAccessor.getData returns the stored compound itself, and
DataCommands.manipulateData edits it in place. A wildcard set on a
non-empty byte, int or long array clears the array before it rejects a
string, so the command fails with nothing counted and the storage keeps
the cleared array. Horizon edited a copy and dropped it on the failure.
On that failure, a /data modify on storage now puts the edited compound
back with CommandStorage::set_unsaved: only an id that is stored is
replaced, and the storage is not marked for saving, as vanilla calls no
setData. The error, the result, the success path and the entity and
block copies are unchanged.
Fixes#12057.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
VehicleEntity.destroy spawns the boat item through spawnAtLocation, so it
leaves with ItemEntity's default motion (0.2 up, random sideways), not
straight up at 0.1. Chest contents keep their own scatter. Fixes#12026.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Where folder names ignore case (Windows, macOS), a Prism or MultiMC root's
`instances` folder is also `Instances`, so `in_folder` took it for the
CurseForge app's modding folder and found nothing. It now reads that folder
as CurseForge's only when none of its instances has an `mmc-pack.json`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.checkInsideBlocks meets the blocks of the box a move starts in before
the ones it reaches, each once, and each step lights before it puts out: a
boat that leaves water for fire in one move ends up burning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: stamps the existing chunk_tickets fragments (API 412/413, NATIVE_HOOKS_API 413); tests.test_stamp_native_hooks 10 OK, verify green, and this exact head built into the SMP engine on playhorizon (rtp2), whose /rtp proof passes with no slow tick. The SMP 0.35.1 PR in horizon-mods needs it.
Decoration scheduling measured, sorted and ranked every proto chunk each update.
Ranks are unique, so only chunks without one need their distance; the rest
pop from a heap until the workers are busy. A server with 200 players spread
over the map held ~57,000 protos and spent a fifth of its tick here.
The heap pops in the order the full sort gave; unranked chunks at the same
distance now go by position instead of where the unstable sort left them.
(cherry picked from commit d20e7bf74e)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`instance others --folder` and `copy-from --folder` now accept a folder whose
children are CurseForge instances, the `Instances` folder the app shows the
player, as well as the modding folder above it. Before, that folder found
nothing.
When nothing is found, the message says where it looked (the folder named, or
every default folder the scan reads) and which folders it expects. The
CurseForge folder is joined one component at a time, so its path no longer
shows mixed separators on Windows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Windows users cannot create symlinks without a privilege (WinError 1314), and NeoForge's
unix_args.txt gives "Illegal char <:>" there. Link the server's libraries folder with a
junction and the mod jars with a hard link, copying when that fails; use win_args.txt on
Windows. Linux and macOS keep the symlinks and unix_args.txt. Adds tests/test_neoforge_oracle.py.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: notices list the three cases (Iris, OpenJDK math, OpenJDK HashMap); both clean-room rewrites (#11944, #12043) are merged, so the notice now describes main accurately.
Reuse the existing machine menu cache, shared placement and authoritative
slot writeback for ordinary PlaceRecipe packets. Preserve inventory/table
placement and the separate Horizon recipe-transfer route.
Fixes#12009
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
bees_stay_in_hive built the mob world (every mob and player) for each occupant
whose time was up. The night and rain it reads hold for the hive's turn.
(cherry picked from commit 3430283af0)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every in-range check compared the cover's copy of the extra list (about 200
entries on a spread server) with the live one, for each waiting chunk each
tick. The cover is refreshed at the start of update; update now says so and
the checks trust it, with a debug assertion that they still match.
(cherry picked from commit f61e6ed98d)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A heart built the mob world (every player, the teams, the weather) each tick,
but reads it only in the update about once a second. server_tick is split at
that point; the game builds the world for the update only. The heart's
creaking, asked for a few times a tick, is found through the mob index
instead of a scan.
(cherry picked from commit a1cf6491dd, with the mob index
in place of its own slot map)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BaseSpawner.serverTick only decrements a running delay. The level (every
player, the mob world) was built for each spawner near a player every tick
first.
(cherry picked from commit 1f06b720c9)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Players spread far apart read more start chunks than the cache held, so
outposts' and trial chambers' jigsaws were built again on the server thread
whenever natural spawning asked about them.
(cherry picked from commit da2c8d2d48)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A game folder can hold a `new-world-datapacks` folder. Create New World now
seeds its temporary data pack folder from it and selects those packs, and a
world opened without the Create screen (`--world`, the control socket) gets
a copy when it has no level.dat yet. Each world keeps its own copy. Without
the folder nothing changes.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
AbstractBoat.tick decays the damage before Entity.baseTick burns the boat, also
for a boat its rider's client moves, and the block effects follow the whole
move, so water the boat started in still puts it out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: clean-room java_hash_map (CLRS tree, slab); java_hash_map and java_identity_map oracles identical over 1.18M lines plus 3,028 edge fixtures; 11/12 caller harnesses pass (structure_layout lacks oracle data on the desktop).
🤖 Agent: clean-room jmath; JVM-identical on ~2.5M inputs per function (log/log10/cbrt), 5.9M hypot pairs, 3.05M sin/cos, all 2^32 floats for round_f32; parity-main covers the ~420 dependent harnesses after merge.
DiodeBlock.canSurviveOn asks isFaceSturdy(level, pos, UP, RIGID) of the
block below, in canSurvive and in updateShape with the neighbour's new
state. A retracting piston base is a moving_piston whose block entity
shape holds the extended base, so its top face is rigid. The diode checks
read the raw state's exported facts instead, saw no support, and broke the
repeater or comparator. Both paths now go through the contextual rigid
support, which takes the state a shape update hands over.
The redstone catalog harness gains fixed lever/piston/diode contraptions
for both piston and both diode kinds (#12019).
Fixes#12019
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SulfurCube.mobInteract ends with Bucketable.bucketMobPickup, and a sulfur
cube is picked up with an empty bucket (canBePickedUpWithBucket), not a
water bucket. Horizon's bucket pickup only knew the water bucket's mobs,
so a click with a bucket left the cube where it was.
After the cube's own baby, primed, shears and swallow branches, an empty
bucket now plays the sulfur fill sound, fills a sulfur_cube_bucket, fires
filled_bucket and player_interacted_with_entity, drops the leash, discards
the cube and gives the bucket through ItemUtils.createFilledResult, as the
other bucketable mobs do. The native bucket hook hears it.
The filled bucket carries SulfurCube.saveToBucketTag: the cube's age and
age_locked in bucket_entity_data, and its body block as
sulfur_cube_content when it has one. Other species keep their bucket data.
The command harness gets a mob_interact op: the connected player's real
Interact packet on a mob, on both sides.
Fixes#12044.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The archetype table keeps knockback resistance as a decimal double for
the physics code. The data's modifier amount is the float widened
(0.4000000059604645 for slow_bouncy), so /attribute read 0.4 and 0.7
where vanilla reads 0.4000000059604645 and 0.699999988079071.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A boat now keeps Entity.remainingFireTicks: Entity.baseTick burns it down a
tick at a time and hurts it every 20 ticks, and the two applyEffectsFromBlocks
of AbstractBoat.tick light it on fire blocks, hurt it once per fire block,
let water, powder snow and rain put it out and clear the cooldown. Saves,
/data, portals and the root vehicle write the live Fire; loads and /data
merge read it. Fixes#12025.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 512 lines (Loading, App, run_event_loop, the start checks) to game/loading_screen.rs; the Windows-only hwnd stays in game.rs and stays visible to the moved code.
🤖 Agent: pure move of 506 lines (keys and clicks over a container screen) to game/inventory_input.rs; click_inventory stays because an open draft changes it.
python3 tools/stamp_native_hooks.py --only chunk_tickets chunk_tickets_in_level.
The SMP's /rtp moves from /forceload (which waits on the server thread
until its chunk is generated) to native_force_chunk_in, so it needs a
number to declare.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Post FLUID_PICKUP or FLUID_PLACE after the sound in
CauldronInteractions.fillBucket, emptyBucket and the bottle interactions.
Keep lowerFillLevel's earlier BLOCK_CHANGE and washing-only behavior.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Forward landing block events through the player and mob server paths.
FarmlandBlock.turnToBaseBlock posts BLOCK_CHANGE from the landing entity;
TurtleEggBlock.decreaseEggs and Level.destroyBlock post BLOCK_DESTROY
without a source, carrying the affected block state.
Silence a queued egg event when its block write was cancelled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
SulfurCube.collectEquipmentChanges runs in LivingEntity.tick
(detectEquipmentUpdates) when the body item changed. It removes the
previous body's archetype modifiers and adds the new body's as transient
modifiers: knockback_resistance, explosion_knockback_resistance and
bounciness (add_value), friction_modifier and air_drag_modifier
(add_multiplied_total). /attribute read the defaults for these five on a
cube with a body.
A sulfur cube now has its own attribute map from its type's defaults.
The five attributes go to that map, and the tick adopts the body's
modifiers there with the existing AttributeInstance operations. A
transient modifier shadows a permanent one with the same id, and the
removal takes both, as in vanilla. The archetype table also gets its
explosion knockback resistance, from the 26.3 data.
The save writes the base and the permanent modifiers of these instances,
not the transient ones. A command change of the knockback base or of a
permanent modifier also goes to the store that the bodiless physics
reads, so that effect stays as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The same check for #6741 found that java_hash_map.rs was translated from
OpenJDK's HashMap.java. List it with the other two, and say so in
how-horizon-is-built.md.
Refs #6741
Co-Authored-By: Claude Opus 5.5 <[email protected]>
execute if|unless block, fill ... replace and clone ... filtered tested only
the block state; BlockPredicateArgument also compares the {...} part with the
block entity's full saved data (NbtUtils.compareNbt), and a block without a
block entity fails it.
Fixes#12030
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of cauldron_outcome and use_on_cauldron (96 lines) to game/cauldrons.rs; cauldron_at and cauldron_extinguish stay because an open draft changes them.
🤖 Agent: pure move of 117 lines (GLFW key/button codes, follow_named_keymap, the key routing rules) to game/key_codes.rs; game.rs re-imports the names other game modules reach through super::.
A blended draw over a surface of the other kind leaves one mark for both, so a frame that mixes
marked and written light would clip the written light and lift it again, or leave the marked part
unlifted. Step 4 now goes path by path: the frame-case rebuild first, then every marking shader on
the default path in one pull request, then Improved Transparency and the visibility buffer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.kill is a generic_kill hit that calls LivingEntity.die before
the command returns. Horizon set the mob's health to 0 and left the death
to the next tick's creature phase, so its entity_die vibration reached a
sculk sensor one tick late.
The /kill mob arm now runs the existing death routine (mob_died) at once
for a mob that this kill takes from alive to dead, behind the existing
`died` guard, as a frog's bite already does. The native living_death veto,
the vibration, the catalyst, loot, experience and kill credit now happen
during /kill instead of in the next creature phase. A second kill and the
next tick do not run it again. The body's removal timer is unchanged.
Fixes#12037.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: moves 575 lines of game.rs's inline tests to game/core_tests.rs with unchanged bodies; the horizon-client test count stays 1159, and the tests open PRs touch stay put.
🤖 Agent: pure move of spawn_player (117 lines) to game/player_spawn.rs; test_world_input_capture's loading-completion check reads it from the new file and still sees the player set before the mouse capture.
The previous implementation was translated from OpenJDK's
java/util/HashMap.java (GPL-2.0), which Horizon's MIT licence cannot
carry. This one was written in a two-person clean room: the implementer
worked from a behaviour spec, the Java SE API documentation, CLRS
chapter 13 (red-black trees) and runs on a real JVM, and never read
OpenJDK's source or the old file.
Entries live in a slab of nodes addressed by u32 index. A bucket holds
the first node of its bin's list and, for a tree bin, the root of a
red-black tree over the same nodes, whose links sit in a side table the
map allocates with its first tree bin. The public API is unchanged.
Unit tests pin iteration orders printed by java.util.HashMap on OpenJDK
25: a doubling from 16 to 32 buckets, a tree bin made at 64 buckets
after two doublings below 64, a tree-bin insertion after its tree
parent, movable and cursor removals from a tree bin, copies, and the
cursor's error order.
Refs #6741
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 190 lines (startUseItem, interactOn, the placement/bucket cancel hooks) to game/item_actions.rs; the Player actions docs rows now point fn use_item and fn attack at their files.
`MeleeAttackGoal.canContinueToUse`'s follow branch returns
`isWithinHome(target.blockPosition()) && !(target is a spectator or
creative player)`. Horizon's `MeleeAttack::can_continue` only refused a
creative player: a mob with a home kept chasing past its radius, and a
player who became a spectator was still chased.
`DrownedTridentAttackGoal` overrides `canUse` with the trident test and
inherits `RangedAttackGoal.canContinueToUse`, which calls `canUse()` again
(and falls back to a target it is still walking towards). Horizon's
`DrownedTridentAttack::can_continue` asked only the base ranged goal, so a
drowned whose trident was gone kept the trident attack running once its
path was finished.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
fs_oit_cull writes the nearest opaque see-through depth into the depth
buffer, and the later OIT stages test GreaterEqual against it, so an opaque
fragment is compared with its own depth. On Metal, position.z can differ from
the rasterizer's depth by an ulp, which dropped random pixels of players and
mannequins (horizontal dashes, text behind showing through). Write the depth
4 ulps farther: the fragment still passes, anything visibly behind is culled.
Checked on macOS (Metal, Apple M1) with a mannequin and a text display; not
yet on Linux (Vulkan).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 90 lines (autosave interval, tick-rate change, player autosaves, level.dat metadata) to game/autosave.rs; save_game_with and save_level stay because open PRs change them.
🤖 Agent: pure move of 109 lines (renderer_for, apply_settings, translucent layers, far terrain distances) to game/render_settings.rs; prepare_lod and all_sections_changed stay because an open draft changes them.
🤖 Agent: pure move of 234 lines (title actions, open_world, load_world, save_for_exit, close_world) to game/world_session.rs; finish_open_world and finish_close_world stay because an open draft changes them.
After a close blast a mod can now dull and quieten the world's sounds while
its own ringing plays clearly: native_muffle_sounds(amount, lowpass, ticks,
exempt) turns every sound down by `amount` and low-passes it by `lowpass`
(down to 300 Hz), both fading back over `ticks` client ticks and held while
the game is paused. The sound events in `exempt` play through untouched,
those already playing too.
The mixer mixes the muffled sounds into a bus of their own, runs two
one-pole low-pass stages and the duck over it, and adds it to the exempt
ones. The level glides over 20 ms so tick-sized steps don't click. While no
muffle is set or once it has faded, sounds mix straight into the output as
before. The sound engine marks a channel exempt as it starts, and marks the
playing ones again when the list changes.
Helper-only fragment muffle_sounds. Fixes#12006
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/clone from <sourceDimension> ... to <targetDimension> ignored both
arguments: an unknown name cloned in the current level, and a valid
other dimension wrote into the current level instead of its own world.
As CloneCommands does, sourceDimension is resolved as begin is read and
targetDimension as destination is read, each position checked loaded in
its own level; an unknown name fails with argument.dimension.invalid
(the built-in dimensions, as execute in checks them), the source first.
The overlap check applies within one level only. The blocks are read in
the source level and placed in the target level through the existing
level swap (enter_command_level), each entry checked; the coordinates
are resolved in the command's own context first. The command's own
level is put back on every path, before the command reports.
Fixes#11997
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`CatTemptGoal` overrides `canUse` with `!cat.isTame()` but does not
override `canContinueToUse`; `TemptGoal.canContinueToUse` ends with
`return this.canUse()`, so taming a cat mid-tempt ends the goal. Horizon's
`CatTempt::can_continue` ran only the base `Tempt` continue, which never
asked whether the cat was tamed, so a cat tamed while following a fish
kept following it.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): the tests no open PR changes go to their own test module; the ones open PRs touch (the module header, the scaffolding fall test, the integrated save test) and the tests next to them stay. Test bodies are unchanged; rustfmt re-indented them one level.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The HUD atlas reserved `horizon/modimage` (native_mod_image, API 355) only
when the launcher's native-mods.json listed mods, so a bundle applied with
horizon-mixins by hand or by a mod's own script built a game whose
native_mod_image always returned false.
horizon-mixins leaves .horizon-mixins-stage.json at the root of every
workspace it changes, naming each injection's and module's mod. The build
script turns a record that names mods into HORIZON_MIXIN_MODS, and
built_with_native_mods counts it and installed trait mods as well as
native-mods.json. A release or Nightly has none of them and keeps no slot.
Fixes#12005
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 110 lines (block face and particle sprites, honey particles, destroy particles) to game/block_particles.rs; particle_sprite and crack_particle stay because open PRs change them.
🤖 Agent: pure move of 186 lines (attack strength, crits, sweeps, Player.attack) to game/melee.rs; melee_damage and attack_mob stay because open PRs change them.
🤖 Agent: pure move of the game window's ApplicationHandler (295 lines) to game/window_handler.rs; the input tests read window_event/device_event from the new file first, since Loading and App have handlers with the same names.
🤖 Agent: pure move of 359 lines (slot layout, hit test, item and decoration drawing) to game/inventory_slots.rs; slots/slot_count/draw_inventory stay because open PRs change them.
One Cargo feature, on by default, holds the entry points of Horizon's own
extras so a fork can build the game without them
(`--no-default-features --features audio,controllers`) or delete them:
cosmetics, emotes and the "Horizon player" presence (one gate,
game::cosmetics::enabled), the official and partner servers added to
servers.dat, the Options screen's Horizon Settings and Mods buttons, and
the existing discord, friends and heartbeat features it now turns on.
The default build resolves the same features as before and every gate is
cfg!(), so both sides compile in every build. A build without the extras
names the official build on Credits & Attribution. docs/forking.md lists
the extras, their files and their gates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zombie and Piglin.setBaby put a transient minecraft:baby speed modifier
on a baby. AttributeInstance refuses to add an id it already holds and
removes a modifier whatever its kind; the next setBaby removes
minecraft:baby again and, for a baby, adds the transient one back. The
transient modifier was computed from the baby state, so /attribute
modifier add stacked a permanent one on it and modifier remove found
nothing to remove.
The add now counts the transient modifier, and the remove takes it away
until the next setBaby (a flag on the mob's attributes, not saved: a
load runs setBaby again). setBaby clears that flag and removes a
permanent minecraft:baby, as vanilla's does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After the post effects and before the camera overlays, ITU-R BT.2390's EETF in PQ rolls the
light off into the peak, on its brightest channel so it keeps its hue. It takes over the HDR
lighting's easing and the light sources' cap at the peak, which the resolve pass no longer
applies. The knee stays where the lighting's was, 60% of the peak.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An absolute /tp zeroes the player's speed, as vanilla's does
(TeleportCommand's relatives carry no DELTA), so the check's teleport
right before the hit left the guest at walking speed and the check
failed on main. The guest now runs 19 ticks from 8 blocks out and
clicks in reach. The slowdown assertion is tightened: the first
post-hit sample must be under 0.75 of the sprint speed and then climb,
which fails with the client's 0.6 cut removed (the old 0.9 bound did not).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 237 lines (an item in use, tick by tick) to game/using_item.rs; apply_consume_effect and drop_bundle_content stay in game.rs because an open draft changes them.
The parity work after the per-screen PRs (#9714, #9836, #9909, #9926,
#9968, #9982, #10032, #10391, #10448, #11640), as verified in the local
playtest builds:
- Main list: RealmsMainScreen's header and footer geometry, row content
and status light, scrolling, notices, selection narration, and the
60 s/10 s/300 s data-fetcher periods.
- Configuration screen: the four tabs (MenuTabBar), GridLayout columns,
slot options, region preference, invite codes and their edit screen,
the players tab (invites, operators, removal), the subscription tab
and the tab keyboard.
- World screens: RealmsResetWorldScreen's tiles, the template list, the
upload selection and upload screen, and the PDP rows and keyboard.
- Backups: RealmsBackupScreen/RealmsBackupInfoScreen on the current wire
(lastModifiedDate, metadata as strings, a local changeList), restore
as RestoreTask (retries, abort), and the complete Download Latest flow:
DownloadTask, RealmsDownloadLatestWorldScreen's size check, bar, speed
and cancel, and FileDownload's tar.gz unpack (name cleanup, world/
entries only, level.dat renamed, resource pack SHA-1 checked).
- Settings: RealmsSettingsTab's GridLayout/ScrollableLayout, the
distance sliders, the name validation, and tooltips drawn after every
widget.
- Service: the ops/invites endpoints, the invite answer's players, the
backup age on the wall clock, null gameMode, and news persistence.
- Integrated server: the owner and the memory connection's login use the
account profile (IntegratedServer.setSingleplayerProfile,
ServerboundHelloPacket), so a world without singleplayer_uuid (a Realms
download) loads the owner's players/data file; saveAllChunks'
saveDataTag(profile.id()) is mirrored by save_owner.
Tests: 2166 lib tests pass (65 realms), scoped clippy clean.
Co-Authored-By: ZCode <[email protected]>
/test only knew the test instances of the game's own data: read_test_def
read options.resources, and the selector ids were the exported built-in
ones, so a test a world's data pack defined was "No matches for
selector". Vanilla 26.3 loads Registries.TEST_INSTANCE from the packs the
world enables when it loads: later packs override earlier ones and the
built-in data, and /reload or /datapack do not change it.
When a world opens (the end of load_world_state), the built-in pack's and
the initially enabled packs' test_instance files are read once, in pack
order, through the existing test instance decoder. They become the
world's test definitions (test_defs) and its minecraft:test_instance ids,
replacing the previous world's. /reload and /datapack leave them as they
are. A game with no world opened reads the game's own data as before.
Fixes#11976
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The coverage view is chosen once, when the shader is wrapped; nothing reads the field after.
Also lets a test fill the coverage with its own f64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CI has no prepared resources, so the assert on the test world failed
there. Do what the other test-world tests do: check nothing when the
data is missing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: requeued after merging main: the only conflict was the module list in game.rs (`pointer_capture` from main beside `portal_ticking_tests`), resolved by keeping both; no code change since the checks.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued: spans only, no behaviour change; pr_ready --quick green. Point: the guards wrap the mods' hooks outside every level span, so the SMP's 1-2 s ticks get a name in horizon-report.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The workflow's clone is blobless, so `git cat-file -e` on a head it
does not hold fetches it from the server, one round trip per check.
On busy days the job ran into its 10-minute timeout. Check every head
in one `cat-file --batch-check` with lazy fetching off, and run the
diffs on 8 threads so their blob fetches overlap. A note for #11723
now takes 13 s instead of 170 s; `show` over every open pull request
29 s instead of 785 s, with the same notes.
Closes#11984
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 390 lines (toggled keys, mapped keys, hotbar wheel, play_mouse) to game/play_input.rs; the two hook tests that compile mapped_key_event and the wheel branch read the new file.
🤖 Agent: pure move of 170 lines (title panorama, menu blur, border wall, the screens' render-post hooks) to game/screen_backgrounds.rs; regenerated on main after #11939 since the blocks were adjacent.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AttributeMap.pack saves the attribute instances that exist. A fresh
living entity has none (attributes:[]); its first tick creates
movement_speed (LivingEntity.aiStep -> removeFrost -> getAttribute).
The mob writer added movement_speed to every save, so a mob that had
not ticked yet already saved it.
The writer's invented movement_speed entry now waits for the mob's first
tick (its tick count). The instances the load itself makes stay:
Zombie and Piglin setBaby the speed, Rabbit setVariant the attack damage.
Instances the mob was loaded with are kept as before, and the list is
written even when empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`TradeWithPlayerGoal.canUse` refuses a trader that `wasHurtRecently` or
whose trading player is more than four blocks away, but Horizon's
`TradeWithPlayer::can_use` checked neither. A wandering trader that took a
hit, or whose customer walked off, stayed in the stand-still trade goal
where vanilla ends the trade.
`HappyGhast.HappyGhastFloatGoal` overrides `canUse` with
`!isOnStillTimeout() && super.canUse()`. `FloatGoal` does not override
`canContinueToUse`, so `Goal.canContinueToUse` asks that same overridden
`canUse` on every tick: a player standing on the ghast makes it stop
floating. Horizon's `HappyGhastFloat::can_continue` re-ran only
`Float.can_use`, so the goal kept hopping in water under a player who was
holding it still.
#7820 gave the entity pipeline a constant depth bias of 128 so entities win the tie with
coplanar terrain. HDR output marks what it draws by drawing the same quads again, alpha only,
GreaterEqual against the depth it left, but without the bias: every mark tested a hair behind
that depth and failed. Mobs lost their HDR lighting, light sources in the hand stopped shining,
and the empty hand only looked right by reading the ground's mark behind it.
The marks after entities, the hand (unmark and mark) and translucent particles now draw at the
same bias. Translucent items, additive draws and the OIT stage follow unbiased pipelines and
keep the unbiased mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On the float HDR frame the last pass guessed which pixels still showed the world
(`shows_world`) and clipped everything else at white, so the world's light dropped to
SDR under the chat's see-through background, a menu's backdrop and the coloured camera
overlays. Where the device records HUD coverage (#11896) the frame is now drawn in
layers, as the HDR note's step 3 specifies:
- After the world and the hand, `Hdr::resolve` turns the frame's marks into light once.
The camera overlays, post effects and entity outlines draw on light after it.
- `keep_world` becomes a pass: the world clipped to white, alpha 0, into a HUD target in
the frame's format, which the HUD and menus draw on with their unchanged blends.
- The last pass lays the HUD over the world's light by its coverage, in gamma as the HUD
blends (H = P - (1 - c) B, then H ui^(1/2.2) + (1 - c) W). UI Brightness scales the
HUD's own part.
- The menu blur blurs that composite, light included, and makes it the world the menu
draws over.
Until the tone curve, a screenshot is the HUD target and the world icon the world, both
clipped at white; the -hdr.png is the full picture. The legacy path, the 16-bit frame and
HDR off are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dedicated server's slow tick lines on the SMP show 1-2 s ticks whose
time is in no span: it is spent outside every level, where only the mods'
ServerTickStart/End hooks, the config poll and the idle-level close run.
Each gets a span, so a slow tick line and horizon-report name it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/clone placed the blocks but not the block ticks pending in its source
box, so a strict clone of fresh sand never fell, while vanilla 26.3's
CloneCommands.clone ends with LevelTicks.copyAreaFrom over the whole
source box.
cmd_clone now calls the existing LevelTicks::copy_area with the source
box and the destination offset, after the placements and block
entities and before the empty-count failure, as vanilla does. The core
copy (the box filter, trigger times, priorities, sub-tick reordering
and dedup) is unchanged. Block ticks only, in the same level.
Fixes#11963
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`AbstractCubeMob.CubeMobKeepOnJumpingGoal.canUse` is
`!this.cubeMob.isPassenger()`, but Horizon's `CubeKeepJumping::can_use`
returned true unconditionally. A slime or magma cube riding a boat, a
minecart or another mob therefore kept hopping and steering its vehicle
every tick, where vanilla leaves the vehicle alone.
#7820 gave the entity pipeline a constant depth bias of 128 so entities win the tie with
coplanar terrain. HDR output marks what it draws by drawing the same quads again, alpha only,
GreaterEqual against the depth it left, but without the bias: every mark tested a hair behind
that depth and failed. Mobs lost their HDR lighting, light sources in the hand stopped shining,
and the empty hand only looked right by reading the ground's mark behind it.
The marks after entities, the hand (unmark and mark) and translucent particles now draw at the
same bias. Translucent items, additive draws and the OIT stage follow unbiased pipelines and
keep the unbiased mark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
add_to_container and load_container are only used inside the crate. Two
unit tests pin VillagerState::add_item as add_to_container with eight
slots, and a later same-kind stack merging while eight kinds fill the
slots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 415 lines (first renderer, start_game) to game/loading_window.rs; the Windows-only hwnd stays in game.rs, so no cfg'd re-import is needed.
Entity.handlePortal runs in the entity's own tick, and ServerLevel.tick
ticks only the entities in entity-ticking chunks. handle_portals and
find_portals walked every loaded entity, so a mob in a nether portal past
the simulation distance went through it and its cooldown counted down.
They now skip entities outside entity-ticking chunks and keep the portal
state of the ones waiting there.
Fixes#11960
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep active tuples authoritative and carry only fallback EffectInstance subtrees through Mob load/save and the audited mutation boundaries. Reuse the existing pure duration operation after server work.
Co-Authored-By: Codex <[email protected]>
🤖 Agent: pure move of the world entry impl block (367 lines) to game/world_entry.rs; the world_items!/world_recipes! macros stay defined before the mod list, so the moved code still sees them.
🤖 Agent: merging #11948 (smp-200). The one risk was the filter cutting block contact for entities that do tick: it keys on the same entity_ticking_area test the mob and item ticks use. The new test shows a zombie inside simulation distance 4 still presses its plate while one past it doesn't (and fails without the change). server_entities, mob_ai, cauldron, raid and dedicated_server harnesses pass locally; CI parity covers the rest.
🤖 Agent: pure move of 324 lines to game/pointer_capture.rs; test_world_input_capture's method() now also finds pub(super) methods, so the capture fixture still compiles the real code.
An Allay loaded with a finite minecraft:liked_player memory kept it
forever and saved it without its ttl: the loader read only the memory's
presence and value, the save wrote the value, and nothing counted the
memory down.
- The memory's time to live is kept next to the existing liked-player
state (None for a memory that never expires), read on load and
written on save as MemorySlot.visit does.
- The Allay's AI step, which NoAI skips like customServerAiStep, ticks
it as MemorySlot.tick does: it counts down, and is cleared on the
tick after it reaches 0.
- Giving an item sets the memory without a time to live
(Brain.setMemory), taking it back erases it, and the /data path that
keeps the memory keeps its time to live too.
Fixes#11936
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The shared atlas's textures (items, entities, block entities) now bring
their _n and _s maps to shader packs as paged maps: 16x16 pages of the
atlas, only those holding a mapped texture stored, found through a page
table at the map's start. The programs drawing that geometry read
normals and specular through hz_pagedTexture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
JavaHashMap reproduced java.util.HashMap iteration order by translating
OpenJDK's java/util/HashMap.java (GPL-2.0 with the Classpath Exception):
its tree-bin routines, their signatures and their quirks (#6741). This
keeps the public API and removes the body, which panics with todo!() until
the next commit writes it again from a behaviour description, the Javadoc
and the real JVM's results, by an agent that has not read either source.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
InventoryCarrier.readInventoryFromTag loads the saved stacks with
SimpleContainer.fromItemList: clear, then addItem each one, which fills
earlier stacks of the same item and components up to the limit before it
takes an empty slot, and drops what does not fit in the eight. Piglins
and villagers took the first eight saved stacks as they were, so 2 + 3
stone stayed two stacks and 40 + 40 did not become 64 + 16.
The villager's existing SimpleContainer.addItem port becomes a free
add_to_container, unchanged; both loads add each decoded stack through it
at the type's stack limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: kacpherek's Dir point is fixed (from3DDataValue wrap, Steps unclamped, tested); on top of MSedgeMC's main merges I only moved the no-target doc comment back onto its test; shulker_space and the save harnesses pass on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
🤖 Agent: port_ledger map-check on the four changed TSVs reads 519 rows, no problems, and kacpherek's PackSelectionScreen.Watcher row is restored (rdbtCVS's fix); TSV-only, no game code.
jmath.rs held ports of OpenJDK code: fdlibm::{log, log10, cbrt, hypot}
from OpenJDK's FdLibm.java (GPLv2 with the Classpath Exception),
aarch64::{sin, cos} from HotSpot's AArch64 trig intrinsics (GPLv2), and
round_f32 from OpenJDK's Math.round(float). Horizon is MIT.
Rewritten in a two-person clean room from Sun's fdlibm 5.3 C sources, a
behaviour-only description of the AArch64 sin/cos, and the Java SE API
documentation:
- jmath/fdlibm.rs (new, Sun's notice at the top): log, log10, cbrt and
hypot ported from e_log.c, e_log10.c, s_cbrt.c and e_hypot.c; sin_fma
and cos_fma, fdlibm's sin and cos (s_sin.c, s_cos.c, k_sin.c, k_cos.c,
e_rem_pio2.c) with fused multiply-adds, as Math.sin/cos on AArch64.
- jmath.rs: round_f32 from the Java SE specification of
Math.round(float); the module doc says what each platform computes;
jmath::fdlibm and jmath::aarch64::{sin, cos} keep their paths.
- geometry/trig.rs: Sun's notice above its fdlibm asin/atan/atan2 ports.
- THIRD_PARTY_NOTICES.md: an fdlibm section.
Bit for bit against JDK 25: StrictMath log, log10, cbrt and hypot on
2.5M, 2.5M and 2.7M inputs and 5.9M pairs; Math.sin and Math.cos on an
Apple M4 on 3.05M arguments, compiled and -Xint; Math.round(float) on
all 2^32 floats.
Refs #6741
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Absorption adds 4 per level to max_absorption (an ADD_VALUE modifier),
and LivingEntity clamps the absorption amount to getMaxAbsorption: when
the effect starts (setAbsorptionAmount) and when it goes
(onAttributeUpdated). A mob's max_absorption lived only in the command's
per-target attribute map, without the effect's modifier; /effect clear
left the amount, and expiry subtracted 4 per level, so a mob with a
raised base lost absorption it keeps in vanilla.
The mob's max_absorption now uses the attribute map it already has for
armor; its effective value counts the effect in the ADD_VALUE phase. The
add clamps the amount to it, /effect clear and expiry clamp to what is
left. The command-set base and modifiers stay unsaved, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
THIRD_PARTY_NOTICES.md gets a "Code removed for its license" section for
anyone holding an older version: the shader pack code written from Iris's
source (LGPL-3.0, removed in #1561) and the math functions translated from
OpenJDK and HotSpot (GPL-2.0, #6741). In those versions that code is not
covered by Horizon's MIT license.
CONTRIBUTING.md says OpenJDK's own source is GPL too, so it is off limits
like any GPL project; Java's behaviour comes from its documentation, a JVM's
results, or a permissive original such as Sun's fdlibm.
docs/how-horizon-is-built.md no longer says Horizon holds no Minecraft data
and never downloads it: the datafix tables come from vanilla's code, and the
launcher downloads 26.3 from Mojang for a signed-in account that owns it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without a skin the launcher can read (signed out, offline), the Shop's pictures
and its 3D view drew the plain grey figure. They now use the stand-in the
website's shop draws (its standInSkin, original art painted from rows, not
Mojang's Steve or Alex), so both shops show the same player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In a frozen level, water a command placed in newly forceloaded chunks
before the next world tick never flowed: the chunks' tick containers
were only registered by tick_blocks (a running world tick) or by
postprocessing, so the water's flow tick found no container and was
dropped. Vanilla registers them when the chunk becomes a full chunk
(LevelChunk.registerTickContainerInLevel), before any command sees it.
update_world now runs the existing sync_tick_chunks once the streamed
chunks are resident and the unloaded ones are saved with their ticks,
so registration comes before the tick's commands and world tick. Only
an authoritative game (not a server's client) registers.
Fixes#11873
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block entity's faces were baked with `minecraft_mix_light` and their
normal left zero. A shader pack's `gbuffers_block` (or its fallback)
receives the unshaded colour and the face normal from Iris, then applies
its own lighting, so Horizon's baked shade was applied a second time:
chests, shulkers, banners and the rest came out darker than the terrain
around them (#8390).
`draw_part` now emits the tint alone and the face's world normal
(`normal.w` = 127), and the entity shader applies `minecraft_mix_light`
from it, as it already does for mobs. Lights that are not the level's (an
item preview's `Lighting.Entry`, which the entity shader cannot know)
stay baked into the colour with `normal.w` 0, which is the path
`game/item_draw` gives block-entity items in inventories and hands.
The parity harness compares the tint and the face normal; the loom's GUI
preview keeps its baked `ITEMS_FLAT` colour. The chest cache test now
checks the nether flag reaches the shader instead of changing a baked
colour, so the cached geometry serves both dimensions.
Fixes#8390
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Its vertices fell in the unmarked run that closes the mobs', so in third
person (and in a pack's shadow pass) its at_velocity read zero.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pictures are drawn on background threads and cached by item hash and screen scale, so the Shop never renders on the UI thread; prices and the checkout call are the same rules as before (two live checkouts cancelled via Paddle).
Entity.applyEffectsFromBlocks runs in the entity's own tick, and
ServerLevel.tick ticks only entities in entity-ticking chunks, so a mob or
item past the simulation distance presses no plate and is not hurt by a
cactus. tick_blocks swept every loaded entity: 2.5 ms a tick on the SMP,
where ~2,900 of 4,600 overworld mobs sit outside entity-ticking chunks.
Fixes#11881
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Carry the already selected mob through an optional creation-only level hook. Preserve existing entries and the generation initializer and RNG order.
Co-Authored-By: Codex <[email protected]>
A pack that adds font providers over the game's own can leave the overlay without the vanilla font sheets its merged font/default.json names. Every glyph then became the missing box. Read a bitmap sheet, TrueType file or Unihex file the overlay lacks from the game's own root, as vanilla's resource stack keeps the game below every pack. Font definitions stay the overlay's, so a pack filter can still hide one.
Fixes#8875
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
ClientPacketListener.handleMoveEntity decodes a move's delta from the
entity's position codec base, which AddEntity, a move and a position sync
set and a TeleportEntity leaves alone. Horizon counted it from the
tracked position, which the teleport had moved: when a command teleported
a vehicle, its rider was told the teleport and then the tracker's delta,
and saw the vehicle (and themselves) as far again past where it was.
lan_e2e gains `vehicle-teleport`: a strider the guest rides, teleported
3 blocks, is where the server has it in the guest's game.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack text shader may declare colour outputs past location 0. The HUD's
pipelines have one target, so they went nowhere, but the coverage wrapper kept
them, and one at location 1 collided with the coverage and dropped a shader
that works today.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ludovic (2026-11-07): "In the launcher you just don't see the items." The Shop
now opens on the season pack across the top (its looks drawn side by side, name,
item count, price, Buy), then rows of big tiles: Looks, each collection's items,
the founder set and the Supporter badge. A tile is the item drawn, its name and its
price; it lifts under the pointer. A tile opens it big on the account's own player
in 3D (an emote plays), with a look's items, Buy and Gift.
- Pictures: the Wardrobe's CPU renderer, framed on the item (preview::fitted_view),
at device pixels supersampled 2x, on background threads, kept by item hashes.
- Buy and Gift are one checkout of that offer through the same web checkout;
prices still come from the catalogue and the service's set rules.
- Cut: the page's line, the basket and its notes; three new strings (Buy, Gift,
Looks) in all 86 tables, nine unused ones removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
items.rs tests: keeps this branch's tool-rules test and main's command
loot zero-count test, which both landed at the same place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
end.rs: keeps this branch's live gateway items; main's only change there
(item_entity_to_nbt_with_items) was inside the saved-item path this
branch removes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): defer_world_entry to join_world_rest (the deferred world entry, world creation and rest polling, the world's item/recipe/station tables). Private methods become pub(super), the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): the creative screen's tabs, pages, tooltips, trash, search and clicks. Private methods become pub(super), the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): look_options to regrab_on_refocus (mouse look, pause when inactive, held keys, the pointer grab and cursor icon). Private methods become pub(super), the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A bitmap provider's file may be any texture, and HUD packs keep sheets outside textures/font. HudAtlas already received the sheet from the font commit, but only if the texture was packed: WALKED_TEXTURE_FOLDERS did not include custom paths such as textures/lumiere/hud, so those sheets still fell back to per-glyph run-time copies. Collect every bitmap provider's file while building the atlas, deduplicated against the walked textures, so its glyphs use the sheet's atlas rect.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A bitmap provider may name any texture, and server HUD packs keep their glyph sheets under their own namespace or a path such as textures/lumiere/hud. The HUD atlas already holds those textures, but Font only looked up sheets under font/, so every glyph was decoded from disk and copied into the 512-row run-time region one by one. When that region filled, a glyph became the 6-pixel missing glyph, shifting the rest of the HUD line.
Look the sheet up by its atlas name for every namespace and keep every atlas sprite available to the font. Glyphs now use the sheet's atlas rect, so their advances come from the original texels and the run-time region is not involved.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Prepare starts for eligible actual sources before executing their owned function calls. Keep the existing body, callback, store and fallback paths, and consume or clear invocation slots around leaf execution.
Add frozen Java/Game command parity and causal store, return, error and fallback controls.
Fixes#11824
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
In PQ output the debug view mixed toward PQ (1, 0, 1), so any covered pixel
blew out. Mix toward BT.709 magenta at the paper white instead, encoded like
the picture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Translate still-English UI strings in launcher/lang/ru.json and the game's
crates/horizon-client/assets/horizon/lang/ru_ru.json (particle culling
settings). Keep proper nouns, acronyms, key names, paths and env vars in
English.
Co-Authored-By: Nemotron <[email protected]>
horizon-release-sign gets `native_build`: a build's request (target, base commit, each mod's
id, version and registry file SHA-256, sorted) and its key, the release and asset names in
horizon-mods, and the signed message `horizon native build <key> <target>\n<sha256> <asset>\n`,
which no release or Nightly signature can pass for. The keys are their own
(native-build-keys.txt): horizon-mods' `native-builds` environment holds the private one,
because the registry compiles other people's code there.
tools/ci/sign_native_build.sh signs with OpenSSL as sign_release.sh does, refuses a key whose
public half isn't listed, and checks its own signature; tests sign with it and verify as the
launcher will.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handleMovePlayer applies a position packet only when
the player is no passenger, and still accepts the teleport. Horizon's
client applied it to a rider too, look included: a map whose datapack
teleports the vehicle every tick (`tp @s ~ ~ ~ 0 0` on a strider) snapped
the rider's view back 20 times a second. A rider now keeps its own
position and look and answers the teleport with them.
lan_e2e gains `rider-look`: a tick function teleports the strider the
guest rides, and the guest's view stays where they turned it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
command_exec_corpus.py: keeps this branch's isolated 'guest container
close event' group beside main's spectral duration and item Health groups.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/place feature refused every inline feature value with "An unexpected
error occurred", although vanilla 26.3 places it like a named one
(PlaceCommand.placeFeature over a ResourceOrHolderArgument value) and
reports commands.place.feature.success.inline.
The inline value is read with the existing SNBT-to-JSON pair, decoded
by the same feature codec as a named feature (feature_with_blocks,
uncached), and placed through the same Feature.place path with the
level random. A feature that does not place reports
commands.place.feature.failed; a value that does not decode keeps the
plain failure.
Fixes#11843
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.getPassengers holds players as well as mobs. Horizon's `on
passengers` named the player in place when a mob had a rider flag, and no
server player: a map's datapack, running with no player in place, never
found the player riding its strider, and a command the host ran named the
host instead of the guest riding. It now gives the player in place when
they ride the entity, and each other player of the level riding it.
Mob and boat passengers are left to #11810.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Health Boost is a max_health attribute modifier. The attribute getter
already counted it, but Mob::heal clamps to the cached attrs.max_health,
which the effect paths never refreshed, and nothing lowered Health when
the effect went (LivingEntity.onAttributeUpdated). Adding the effect,
/effect clear and its expiry now refresh the cache and lower Health above
the new maximum; the add still heals nothing.
Sulfur cubes and wolves write their maximum straight into the cache
(sulfur_cube::set_size, tame::set_tame), outside the attribute model,
and keep their current behavior. Other effect removal paths are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HORIZON_COSMETICS_CATALOGUE=<file of GET /v1/catalogue> gives the development preview
real prices, sets and collections, so Shop screenshots show what players see.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With HORIZON_HDR_FRAME=float, HDR on and no shader pack, on devices with independent blending, each
HUD and menu pipeline gets a variant with a second output into an R8 target: how much of the world
behind it the draw hides (docs/design/hdr-linear-light.md, The HUD and menus). The fragment shader
is wrapped as it is, so the primary colour, its blend and alpha are unchanged, and the coverage
follows the draw's final colour and blend: layering by its alpha, additive draws not at all,
darkening ones by how much they darken, inverting and replacing ones fully. HORIZON_HDR_DEBUG=coverage
tints it magenta in the last pass; nothing else reads it yet. A resource pack text shader without a
colour output is dropped as an unusable one is, instead of panicking.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Their overflow_y_scroll lists lacked the on_scroll stop_propagation every other
inner scroller has, so a wheel turn the list could not use (up at the top, or
down at the end) scrolled the whole page behind it.
Fixes#11853
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PvP & HUD, Replay Studio, the music page, Browse Mods, the resource pack
browser, cosmetics and emotes, moderation and tempban, the controller page,
graphics tooltips and recipe transfer. Placeholders, leading and trailing
spaces match en_us; durations use abbreviations (хв, год, дн.) so one form
reads right for every number.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The GL backend reported MAX_TEXTURE_SLOTS (16, WebGL2's minimum) as max_sampled_textures_per_shader_stage even on desktop GL, where drivers usually offer 32. A native shader pack's sky pipeline layout declares the terrain's 10 sampled textures and the frame's 9, so it was refused at 16 although the driver accepts it (#10095).
Query GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS and GL_MAX_TEXTURE_IMAGE_UNITS, take the lower, and cap at the arrays' new 32 slots (MAX_SAMPLERS likewise). On WebGL2 the query still yields 16.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A layout or pipeline the backend cannot take (a stage's bindings past its sampled-texture limit, say) reaches the device's error scope. A native pack built its frame, stages and passes outside one, so wgpu's validation error hit the default handler and crashed the game (#10095). Build the pack in a scope and return the first error, which the shader pack screen already shows as a refusal.
The test asks for OpenGL's minimum 16 sampled textures per stage and builds a sky-only pack whose layout needs 19: the pack is an error, not a validation panic.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Glow ink text was full-bright in the lightmap only: HDR output and shader packs that light
with their own GI (and read the HDR tier, horizonTier) showed it as dark as plain text.
Its glyphs now carry the low HDR tier, like other things that glow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CI's stable Rust deprecates AtomicU64::fetch_update in favour of
try_update, which the workspace's minimum Rust (1.95) doesn't have, so
clippy -D warnings failed. A compare_exchange_weak loop does the same
on every version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
AbsorptionMobEffect.applyEffectTick, asked on every tick, answers false
when the absorption amount is 0, so MobEffectInstance.tickServer drops
the effect before its duration counts down, finite or infinite. A mob's
effect tick had no Absorption case: a used-up Absorption stayed until it
expired. It now ends through the existing removal path; other effects,
infinite ones included, are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client mod asks once a frame (`hold.hold()`, or `hold.hold_within(x, z, radius)` for circles):
sections already on screen that are meshed again are uploaded as ever but wait beside the meshes
drawn, and letting go swaps them all in on one frame with nothing left to upload. Smaller circles
let go of what they leave. In single player, far terrain waits too for the columns the server
changes beyond the view inside the hold, and takes them in when it lets go of them. Bounded: 30 s,
1 GiB of waiting meshes, the arena running out; ended so, it stays off until no mod asks for a
frame. Clearing the sections (another world or dimension) and unloading columns drop what waits;
arena compaction moves waiting meshes too. Renderer::upload keeps its order on the normal path
(before_mesh / upload_mesh / after_mesh). Tests in gfx/hold.rs; docs in native-mods.md; fragment
world_hold.json.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part of #11126. Builds on #11652.
Decode shipwreck, ocean ruin, igloo, ruined portal, woodland mansion,
nether fossil and end city pieces from saved starts. Rebuild template
boxes and placement settings like vanilla's tag constructors, retaining
the whole-start regeneration fallback for unsupported or broken pieces.
Cover every new type with resource-free round trips and extend the
prepared-resource generation/read comparison across all three dimensions.
Piece ids go through #11652's saved_piece_id first; any other id is
matched with or without the minecraft: namespace, not lowercased, as
Identifier.parse reads it.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Older packs write expressions in the legacy syntax: uppercase variables
(AGE, DISTANCE_SQUARED, RAIN...), pi and e, and bare rand(), gaussian()
and noise(). Each variable now runs as the scripting call the wiki maps
it to, and a legacy expression keeps the old argument order of lerp,
smoothstep and color. The color functions (red, color, withAlpha,
lerpColor, hsb, lerpHsb...) follow vanilla's ARGB channel rules, and
dist/distSquare join the math table. All expressions of Visual
Effects+ now compile.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A moving block (and an extending or retracting head) is drawn with the entities while it
moves, and read zero at_velocity: flying machines and piston contraptions smeared under a
pack's TAA. Each marks its own run, (6 << 32) | the cell it moves to, which stays the same
while it moves; a finished one keeps that key until the chunk mesh shows the block.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Expressions such as `o.age() * 0.1`, ternaries, `&&`/`||`, strings,
local variables, `if` blocks and calls on context objects are parsed
once when the pack loads, with every name resolved, so a typo is a load
error. A Host answers the context objects (o, p, c, g, r, v), their
methods and the game's global functions; the math functions follow
Java's Math. 1461 of the 1520 distinct expressions of Visual Effects+
compile; the rest use the legacy uppercase names, which come next.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An attributes_modifiers object with a group (`base`...) no longer refuses
the file over a key that is none of the modifier's groups: it is left
alone, so a pack Polytone loads isn't rejected over an entry neither reads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without XDG_RUNTIME_DIR (containers, some WSL and minimal setups) the
control socket was /tmp/horizon-control.sock, a fixed name in a folder
every user shares. Another user who bound it first made --control fail
to start, and horizon-ctl then talked to their socket.
When the temporary directory is shared (world-writable, as /tmp), the
default endpoint is now horizon-<uid>/horizon-control.sock in it. The
game makes that folder 0700, and both the game and horizon-ctl refuse
it unless it is a real folder, owned by this user, that no one else may
enter. A temporary directory of the user's own (macOS's $TMPDIR) is
used as before, so nothing moves there. The launcher's copy of the
default endpoint (game_tools.rs) follows the same rule.
Separately, `bench -1` or `bench nan` panicked the game in
Duration::from_secs_f64; bench now takes 0 to 86400 seconds and refuses
anything else.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
A mob loaded or summoned with FallFlying:1b kept the flag even when it
could not glide, and a gliding mob's elytra never wore. The flag was
only the raw tag's value: the loader did not read it, the save only
supplied a default, and the mob tick had no updateFallFlying step.
- mob_from_nbt reads FallFlying into the mob, and mob_to_nbt writes the
mob's current flag (LivingEntity.read/addAdditionalSaveData).
- The mob's aiStep, with or without AI, runs updateFallFlying while the
flag is set: canGlide (ground, vehicle, levitation, a glider whose
next damage does not break it) clears the flag; every 20 glide ticks
Util.getRandom picks a glider from the mob's own random.
- The game applies that wear with the existing durability path
(Unbreaking from the level's random) to the mob's kept stack.
- LivingEntity.tick counts fallFlyTicks after aiStep.
Glide travel, checkFallDistanceAccumulation and the ELYTRA_GLIDE game
event are not part of this change.
Fixes#11809
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityType.loadPassengersRecursive mounts only the Passengers entries that
load, and Entity.saveWithoutId saves the riders the entity has.
take_mob_passengers kept every non-mob entry raw, so an unknown or missing
id, or a non-compound value, stayed in the saved Passengers. It now drops
an entry that is not a compound or has no registered type (entity-types,
EntityType.by), and keeps valid non-mob entries raw as before.
Entity.load does not read Passengers either. apply_entity_nbt stored the
whole merged compound as raw, so a mob, boat, minecart or TNT saved copies
of its live riders and any merged entries after /data merge. It now keeps
the Passengers the load kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The launcher's es.json still held 91 English values: the mod-port status sentences, release notices, look-and-feel settings, the SSH hosting panel and several install/update results. Translate them, and fix Back from the body part (Espalda) to Atrás.
es_es.json was 20 keys short of en_us.json: every /studio replay message, Play in Background and the controller-focus note. Add them so Spanish screens no longer fall back to English.
Closes#11814.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
tools/parity.py runs every harness with no arguments, so the harness's
required --work-dir made it fail on every CI run before reaching the
oracle. Without the flag it now captures into a fresh
.local/function-tags-parity; an explicit --work-dir must still be new.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
game.rs: keeps this branch's wax_on_sound (honeycomb) beside main's
transform_copper_chest_neighbour (axe/hoe/shovel transformers, #33ec06ab12).
test_use_item_on_parity.py: keeps both the honeycomb double-chest cases
and main's block transformer cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
view_distance and chunk_center come straight from Login,
SetChunkCacheRadius and SetChunkCacheCenter, any i32 a server sends.
remote_chunk_in_range added 3 to the radius and subtracted the centre,
and the streaming radius and sent_radius added 1: a server sending
i32::MAX or i32::MIN panicked a debug build, and in release (no
overflow checks) the wrapped range dropped every loaded chunk.
The storage range is now in_storage_range: the same ClientChunkCache
rule (radius at least 2, plus 3), with a saturating add and abs_diff,
so nothing changes for any radius a real server sends. The +1s
saturate too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Keeps both imports in command_exec_corpus.groups(): this branch's
player_food_predicate_corpus and main's treasure_map_copies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each distinct pack selection gets its own overlay folder under
.horizon-cache/resourcepacks, holding a full unpacked copy of its zip
packs, and nothing removed old ones. Every server pack, and every new
version of one, left a permanent copy the player can't see: ten
servers with 100 MiB packs kept about a gigabyte.
resource_root now marks the overlay it returns as used by this process
(a shared lock on <overlay>.overlay-cache.use, held until the process
exits, whose time is the last use), then keeps the 8 most recently used
overlays in the folder and deletes the rest. An overlay another
process uses or is building is skipped, so two games sharing a game
folder never pull one out from under each other; the marker goes before
the folder, so a half-deleted overlay is never reused. The lock files
stay, as build() already requires.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
After the encrypted join, the guest offered the session on a random
127.0.0.1 port for 30 s and handed it to the first loopback connection.
Any other local user or process that found the port first got the
authenticated session to the host's server, and a web page aiming
fetch at local ports could send HTTP into the tunnel and make the
player's own join fail.
serve_loopback now returns a Loopback with the port, a random 16-byte
token and the thread. A connection must send the token within a second
of connecting (compared in constant time); anything else is dropped and
the port keeps waiting until its deadline. The client's join_friend
passes the token on, and run() writes it raw before the handshake.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Light written into the frame can't be taken back out from under the HUD, and without the tone curve it clips at
the peak, so step 3 now holds the HUD's own layer and the tone curve, and step 4 the shader groups. The look
target names the differences allowed (rounding, light behaving as light under glass, water and anti-aliasing)
and the artifacts that aren't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The shield check now also asks each player's window what its hotbar
draws over the shield (a new `shield_cooldown_shown` in the control
state: ItemCooldowns' percent for minecraft:shield). Right after the
vindicator's axe disables it the cooldown shows, and after the 100 ticks
it is gone. On main both players' games showed none (#11805).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parity.py and parity-main call tools/test_*_parity.py without --binary or --work-dir, so the harness failed in 0 s there. Use harness_args.ensure_work_dir, as a98ba0273 did for the dolphin and spell-ticks harnesses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerLevel.tryAddFreshEntityWithPassengers checks every UUID of the
summoned tree against the loaded entities before anything joins, and
fails with commands.summon.failed.uuid. summon_with_nbt now walks the
same tree the loaders admit (the root, then the mob riders that
take_mob_passengers takes under mob, boat, minecart and TNT roots) and
refuses it when an entity in the level holds one of its UUIDs.
Duplicate UUIDs inside the new tree are still admitted (vanilla skips
the later one but keeps it under its vehicle's Passengers); the
command harness keeps them as --summon-uuid-internal-diagnostic and
the Game test as a known-unfixed ignored test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In PCP and NAT-PMP a lifetime of 0 means the mapping is deleted, but a
success reply carrying 0 was taken as a mapping. Mapping::due() was
then always true, so the host renewed on every 100 ms wake-up of its
loop (a UDP exchange each time, under the reach mutex) while
advertising a direct address that no longer existed.
pcp_map and natpmp_map now refuse a zero lifetime when they asked for
one (a delete, which asks for zero, still expects zero), and due()
never fires sooner than 30 s after the last renewal, so a router that
grants a few seconds isn't asked again ten times a second either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Applying packs builds an on-disk overlay: every file under assets/ in
every zip pack was inflated (up to 1 GiB each, whole in memory) and
written out, on every worker thread at once. A gigabyte of zeros
deflates to about a megabyte, so a server pack at the 250 MiB download
limit could write some 240 GiB to the player's disk and hold a 1 GiB
buffer per thread, once the player accepted the pack prompt. Vanilla
never unpacks a pack.
Zip::copy_to now streams an entry to its file 64 KiB at a time, taking
each piece from a byte budget shared by the whole build: 16 times the
zips' size on disk, at least 64 MiB, at most 8 GiB. Past it the build
fails with FileTooLarge (so the reload drops the server packs, as for
any failed build) and the half-written overlay folder is removed. An
unreadable entry still costs only that file. Memory per thread is now
the compressed entry plus one piece.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
BlocksAttacks.disable adds the item cooldown (ItemCooldowns.addCooldown
on the shield's group), which ServerItemCooldowns sends a player as
Cooldown(minecraft:shield, 100) and ends with 0. Horizon only set its own
shield counter, so a guest's client never heard of it: no cooldown on the
hotbar, and a held right click could show the shield raised while the
server had it down. The disable now also goes through the item cooldowns.
Fixes#11805
Co-Authored-By: Claude Opus 5.5 <[email protected]>
They were drawn in runs marked only by type, so a pack's at_velocity read zero on them even
while they or the camera moved (TAA smeared them). Each now marks its own motion run:
(3 << 32) | entity id, (4 << 32) | id for an item frame's held item, (5 << 32) | position for a
block entity, with a closing mark after each group; maps stay unmarked (keyed by map id).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chunk sections already put their solid quads in the arena as one 24-byte
record each instead of four 24-byte vertices (`mesher::packed`). Far tiles
never could: that record's coordinates are a block grid of 16, and a far
tile's quads are merged rectangles of a voxel column's exposed side,
hundreds of blocks wide. So they stayed at four vertices a quad, and at
`lodDistance` 256 they were by far the largest thing the renderer held.
A far quad is much more regular than a chunk's, though -- a rectangle in an
axis-aligned plane, one colour, one light and one layer across all four
corners, with UVs `lod::mesh::emit_quad` derives from the positions -- so a
record of its own holds it in the same 24 bytes, and terrain.wgsl's
`far_corner` does what that function did. The two layouts share the arena
and the pipeline; a draw's base vertex says which it is (`FAR_BASE`).
Measured on a settled LodTest at `lodDistance` 256, render distance 16, the
same binary with `HORIZON_FAR_PACK=0` and without, each run settled on the
same 4424 tiles and the same quad count to within four:
far vertices arena pages GPU allocated
four vertices 492.5 MiB 960.0 1241.7 MiB
a record a quad 127.4 MiB 640.0 921.8 MiB
365 MiB less vertex data, and 320 MiB less video memory actually allocated
rather than merely accounted for. 24.8 bytes a quad: a record each, and the
few quads that spill. Both figures repeated exactly over two pairs of runs.
Frame time is unchanged. Over the same flight, the mean of two pairs:
avg FPS 1% low GPU avg
four vertices 171.0 95.8 5.390 ms
a record a quad 170.3 94.9 5.420 ms
The pairs disagree on the sign -- the compact layout was 1.5% slower in one
and 0.7% faster in the other -- so the difference is run variance, not the
cost of expanding a record. Worth stating because one pair alone read as a
regression.
Nothing here reasons about which record is right for a quad: `pack` builds
one, rebuilds the four vertices from it, and keeps it only if they come back
bit for bit. A quad no record reproduces is kept as its four vertices, as
`mesher::packed` keeps one; in practice that is the fluid faces, whose
surface sits at 8/9 of a block, and about 1% of quads.
`lod::mesh`'s `every_quad_the_mesher_emits_packs_and_comes_back` holds the
layout and the mesher to each other over real terrain, which the layout's
own tests cannot: they check its records against its own copy of what
`emit_quad` does, and would pass just as well if the two had drifted.
A same-build control says the picture does not change: two launches of the
identical build differ in 50.7% of pixels (16.0% above the animation noise
floor, worst channel delta 76), because far-tile refinement does not settle
identically between launches. Packed against unpacked differs in 40.5%
(12.8% above the floor, worst 77) -- less than the control.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
Reuse the existing runtime tree projection before the live leash writer. Preserve the original full-body diagnostic and add an independently admitted typed-tree command gate.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
NeoForge 26.1 has neither. A tooltip is drawn from a style's textures, picked
in RenderTooltipEvent.Texture, and the anvil take is AnvilCraftEvent, whose
.Pre comes before the inputs are used and the anvil is damaged (no break
chance). `events` now looks the 1.21.1 names up under those, so ATM10's 6
Color and 4 AnvilRepair users are answered by the hooks that exist.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla Entity.getAirDrag is 0.98F. PrimedTnt, FallingBlockEntity and
ItemEntity scaled motion by the f64 0.98, so after four ticks a TNT's
Motion x was 0.092236816 instead of 0.09223682318…. Item underwater
and lava drags, LlamaSpit air drag, and hurting-projectile inertia
were the same class of float-widened constants.
Fixes#6814
Co-Authored-By: OpenCode <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
Two camels in love in a built world: the breed goal starts, then one loses a
point of health and is refused, and a rider also refuses it. Without the
canParent guard the wounded camel still starts the goal.
`Camel.canMate` is `partner instanceof Camel && this.canParent() &&
camel.canParent()`, and `AbstractHorse.canParent` requires no vehicle or
passenger and full health (a camel's `isTamed` is always true). The shared
`Breed` goal applied those checks only to horses, donkeys and llamas, so a
camel stayed free to breed while hurt or carried.
Camels still pair only with camels: the `camel` flag feeds the canParent
guard only, not the partner species rule.
KineticWeapon.damageEntities and PiercingWeapon.attack hit every entity
along the view, players included (PiercingWeapon.canHitEntity:
canHarmPlayer, not the same vehicle). Horizon's stab targets were the
mobs only. A guest's spear now also reaches the other players of its
level; each stab is worked out as the attacker in their turn and lands
through the PvP path once the hub is back, with the spear's damage type,
the 0.4 push and the weapon's Knockback, the dismount, thorns and the
kill score, then the attacker's wear, stats, exhaustion and hit feedback.
The kill score and thorns of pvp_land move into pvp_aftermath, shared by
both. lan_e2e gains a `spear` check: a jab and a sprinting charge hurt
the host, and with the pvp game rule off neither does.
Fixes#11790
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test builds a world with a stone ceiling cell inside the standing box
but above the sitting box, then asks RandomSitting, the arm the goal
selector calls: 200 asks must all be refused. A second camel with open sky
fires within the same 200, so the refusal is the pose check and not the
state setup. Dropping the can_change_pose conjunct fails the first loop.
`CamelAi.RandomSitting.checkExtraStartConditions` ends with
`body.canCamelChangePose()`, which is
`wouldNotSuffocateAtTargetPose(isCamelSitting() ? STANDING : SITTING)`.
RandomSitting did not ask, so a camel sitting under a ceiling that blocks
its 2.375 tall standing box (its sitting pose is 0.945 tall) stood up into
it. The `can_change_pose` helper already existed for the elastic leash pull.
The fade-in rides the high 16 bits of the flat hdr_code varying (no new
location); solid and translucent colours mix toward the fog colour after
the pack's material, alpha kept.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The atlas holds 256 map pictures. Every framed map in range claimed one, seen or not, so past 256 the maps took slots from each other within one frame and showed each other's pictures. Now a frame's outline is culled before its map claims a slot, the nearest claim first, and a slot shown this frame is never given away: past the slots, the far maps go undrawn.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Drives the real goal selector on a built test world: hurt the frog with a
PANIC_CAUSES damage, tick the goals once, and assert the panic goal started
and left vanilla's 2.0 in `Navigation.speed_modifier`, the value
`SmoothSwimmingMoveControl` multiplies. Reverting the constant fails it with
1.25 against 2.0.
`FrogAi.initCoreActivity` builds the brain's core activity with
`AnimalPanic(2.0F)`, but the goal list used 1.25.
`PanicGoal.start` hands the multiplier to `PathNavigation.moveTo`, and
`SmoothSwimmingMoveControl.tick` computes
`speedModifier * getAttributeValue(MOVEMENT_SPEED)` and then multiplies by
0.1 on land or 0.02 in water. A frog's movement speed is 1.0, so a fleeing
frog should move 0.2 blocks a tick on land and 0.04 in water; at 1.25 it
moved 0.125 and 0.025, 60% slower.
26.3 ends sign_update with the side as a VarInt; 26.2 and older read it as a
boolean right after the position. Forwarding the packet unchanged made the
server misread it and kick the player on Done.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
push_from_ticked_cart cloned the ticked cart into the list and back each tick,
and with it a chest cart's rolled loot: its whole Items list as NBT. The stored
cart already goes without the loot for the tick (tick_carts_now takes it), so
the copies do too and the loot stays with the ticked cart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A saved area resolves the same dozen block states in every section of every column, but decode_column built a format!-ed String key and parsed each entry through BlockState::from_nbt into a per-column HashMap. The registry now memoizes palette entries by their NBT in an FxHashMap keyed by the tag itself, so a repeated entry costs one hash and one comparison and allocates nothing; the per-entry resolution rules move with it unchanged. Loading the 529-column saved area drops Persistent::load from 1,467.8M to 1,284.3M instructions.
Part of #9996.
A first draft by DeepSeek V4.1 Flash, via pi, reviewed and reworked.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Loading a generated area spends 16% of Persistent::load (the worker's
chunk load) in PalettedContainer::from_packed, and nearly all of it is
check_indices: a scalar bounds check of every entry of every section
(~10.6 instructions per entry, 22M entries per 529-column area).
check_indices now rejects a whole word at once. Adding 2^bits - palette
to a word lifts every field by that much, so a field carries out of its
own bits exactly when its index is past the palette; the carry out of
each bit is computed for the whole word with (a & b) | ((a | b) & !sum).
A field can only appear bad where a genuinely bad field precedes it in
the same word (a carry adds one to it), which never turns an accepted
input into a rejected one, and the offending word is rescanned entry by
entry on the error path so the reported index is the same first
offending entry as before. The last word's padding fields are biased by
zero, so they cannot look bad, as the entry-by-entry scan ignored them.
The same set of inputs is accepted: the early return when the palette
holds exactly 2^bits entries was already there and stays.
Part of #9996.
A first draft by DeepSeek V4.1 Flash, via pi, reviewed and reworked.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The saved sky and block nibble layers are already the resident Light::Sky and Light::Block layout. Choose the resident variant from the layers and copy the stored layer when only one kind of light varies, so only sections where both vary still expand their light into a full array. Missing layers still inherit the bottom row above and dimensions without sky light still ignore saved sky layers.
Part of #9996.
A first draft by DeepSeek V4.1 Flash, via pi, reviewed and reworked.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add a direct Unbreaking and Mending durability comparison over the real
enchantment registry: item_damage remove_binomial (small and large paths)
and repair_with_xp, with --no-mending reproducing the Unbreaking corpus.
position_riders re-seated every mob whose vehicle is not a mob through
passenger_riding_position, which puts every boat rider in seat 0 from
the boat's position. seat_boat_mobs had already placed them, so remote
players on a moving boat jumped between the two seats each pass.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The ring got stuck at its smallest on servers whose normal tick sits
between 25 and 40 ms, since it only grew back under 25 ms.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla LivingEntity.dropExperience and Mob.dropCustomDeathLoot key on
lastHurtByPlayerMemoryTime, not whether that player is still in the
world. Horizon folded both into Death.player, so a guest who left
within the 100-tick memory made drops::die skip experience and
equipment.
Give die a player_killed flag from the timer. LAST_DAMAGE_PLAYER stays
None when killer_uuid cannot resolve them.
Fixes#11674
Co-Authored-By: OpenCode <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
1.21.2 dropped the generic./player./zombie. attribute prefixes, so the id tables of 1.21.1 and 1.20.6 (matched by
name) had no attribute runs and every update_attributes value was left out for those clients. They are regenerated
with the renames. Mob effect ids were never renumbered: update_mob_effect and remove_mob_effect now go through the
client's table, and an effect added after its release is not sent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1.21.3 and older clients send pick_item with the inventory slot that holds the picked block's item; 26.3 has no
such packet and the adapter dropped it. It now becomes a container_click swap of that slot with the held hotbar
slot (tracked from set_carried_item and set_held_slot), state id 0 so the server resends the inventory.
Fixes#11722
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 1.21.8 and 1.21.1 explode steps re-read a payload the renumbering had already put in the client's ids with
the pinned codec, so the particle's layout was chosen by the wrong id and sending any explosion failed. They now
read and write through the client's ids. 1.21.1's step read the knockback as three doubles where 1.21.4's step
leaves a double and two floats. 1.21.1's strength is 26.3's radius instead of a fixed 3.0.
Fixes#11721
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A version's first start read its thousands of small files one by one, about
100 s under Windows Defender here, because only the game wrote
horizon-data.bin, and only after that slow start. The launcher now writes it
in the background once an install or import has its resources, reading on up
to 8 threads (43 s against 281 s on one, on a fresh copy); the start then
loads in 1.2 s. The game stays the fallback, on its one idle thread.
A reused version no longer copies the data file, which names its own root.
complete_assets removes the file when it adds something, as the file answers
missing for what it lacks. A write removes what a stopped writer left.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Evaluate the supported damage-only subpredicate against modeled damage and
existing effective component presence. Preserve removal precedence and leave
unsupported conjunctions on their existing path.
Fixes#11715
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Plugins seat players on text displays, interactions, armor stands or area
effect clouds that ride another player. Resolve the seat through the whole
chain of vehicles, keep the packet riding graph, move the local rider with a
moving seat, and use the Avatar attachment height for player vehicles.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
tools/test_warden_memory_full_diagnostic.py is not a test_*_parity.py harness, so nothing runs it, and it is 816/839 by design. The canonical owned-memories harness stays. Drops the docs/parity run logs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's LocationPredicate checks Level.isLoaded before the block predicate, so an offset into an unloaded chunk fails instead of holding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's LocationPredicate checks Level.isLoaded before the block predicate, so an offset into an unloaded chunk fails instead of holding. Drops the docs/parity run logs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Thorns on the player answered a mob's hit with generic damage that had no
attacker, so the mob was not knocked back, did not turn on the player and
dropped no experience when it died of it. It now hurts the mob by
`minecraft:thorns` damage the player caused (`DamageEntity` with the
wearer as owner), rolled from the mob's own random, and the held weapon's
Breach applies as on any hit by the player.
The whole equipment counts, in `EquipmentSlot` order: the main hand, the
off hand, then feet up to head (Thorns' slots are `any`), so a Thorns
piece held in a hand answers too. Each piece rolls its chance, hurts the
attacker and wears in that order, also when the attacker is already dead.
A piece that takes no wear, or a creative player's, is left alone.
A mob hit by thorns damage plays `enchant.thorns.hit` where it stands
when the hit lands whole, silent or not (`playSecondaryHurtSound`).
Part of #9954.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pose test asserted that Temper lands in the legacy variant3 byte. #11483/#11659 move every equine's temper to the signed equine_temper field; the assertion was outside this test's subject (LastPoseTick), so drop it rather than pin the storage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both PRs added a signed AbstractHorse temper: #11483 for the seven horse and llama species, this one for camels. Keep one field, equine_temper, for every equine: the loader reads Temper unclamped for all of them, temper()/modify_temper() use it for camels too, and the camel saver writes it. The camel_temper field and the camel byte-clamp path are gone; tests follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Run logs are not docs; the PR body keeps the numbers. tools/inline_trim_packet_capture.py only produced the dropped packet log and nothing calls it; the protocol test keeps the captured payload.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FogState now holds the torch flicker's LegacyRandom, so main's rain fog
test (settled_rain_fog_is_its_target_and_easing_is_unchanged) moved
`start` twice; verify failed on E0382.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The three loot functions add arms and tests at the same spots in loot.rs; all kept. Land #11643, then #11655, then this.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both add a set_loot_component arm, a loot.rs function arm and a loot test at the same spots; both kept. Land #11643 first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#11631 adds saved_piece_id (PiecesContainer.load's RENAMES) to the same file; read_jigsaw_start now asks it instead of its own copy of the four old ids. The upper-case id case is dropped from the test: Identifier.parse refuses upper case, so vanilla never reads 'MINECRAFT:JIGSAW' as a jigsaw piece. Land #11631 first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
command_exec corpus: keep main's craft-map group in ISOLATED_GROUPS and its import beside guest_close_event. Oracle: keep main's merchant_saved_stats op; close_menu goes through handleContainerClose. The test's Joined literal gains main's new configured field.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's #11661 added LevelAccess::container_open and its test for the open copper chest; this branch keeps that name and its test and drops its own container_has_viewers, folding the copper golems' chests into GameLevel::container_open. The recheck_container_openers doc comments #11661 left stranded above container_open are back on their function.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
load_guest_records read stats/<uuid>.json directly, and on the server thread (the default single player) and on LAN and dedicated servers every player joins through it, so locateStatsFile only reached the in-frame path. The guest's own name goes to locate_player_json.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep main's vibration_tests module beside the new test module; the test's Joined literal gains main's new configured field.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Identifier.parse reads 'nvi' as minecraft:nvi, so RENAMES applies to a bare id too. The new saved_piece_id had been inserted between tag_json and its doc comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vindicator.addAdditionalSaveData writes Johnny: 1b only when isJohnny is true; a vindicator that is not Johnny saves no key (and drops a stale Johnny: 0b from its raw compound).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since a pack with no program for terrain or translucent terrain gets the
notes "no program for terrain; nothing draws it" and "no program for
translucent terrain; nothing draws it", standard_pack_gpu_entities and
entity_shadow_ownership failed their no-notes assertions: their packs had
entity programs only. Both packs now also have gbuffers_terrain and
gbuffers_water. The fixtures draw no terrain, and every other assertion is
unchanged.
Fixes#11710
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add a direct parity suite for the landing branch of
FallingBlockEntity.tick: real falling entities into air, solid blocks,
water, plants, slabs, cobwebs and moving pistons, with concrete powder's
water contact and the anvil landing event behind --no-contact.
ordered_tick: main's obstacle grid for the orb move kept after the
merge scan and the followed-player pick. A kept followingPlayer now
stays at exactly 8 blocks: ExperienceOrb.followNearbyPlayer re-picks
only past distanceToSqr > 64. Orb merge fixture keeps merge_new_orb.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep main's fall_step(entities) signature with the air guard first;
keep main's vibration_tests module beside the new test module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With [transfer] on and players on, a stop (/stop, a stop signal, the
supervisor's stop or restart) now waits stop_wait_seconds (10 by default,
0 to 60, 0 stops at once as before) so a proxy told server/stopping can
move the players out first. The log says "Stopping in 10 s: a proxy may
move the players out first", which horizon-server takes as the start of
the stop. Every login is refused as server_shutdown, and the server ticks
on and answers the proxy's requests.
The wait ends as soon as no player is placed, at its time when no
hand-over is frozen, and at the latest a freeze's longest time later, so a
hand-over started in time finishes; the whole wait is cut at 90 s, within
the supervisor's 120 s for a stop, so stop_wait_seconds and
max_freeze_seconds may add up to 90 at most when the stop waits. No freeze
starts that this limit would cut: a player its source saved after the
target placed it would be on both servers. A freeze lasts ticks, so its
length is reckoned at the tick length of the moment (the rate's, or longer
on a server slower than its rate). A second stop ends the wait. Then the
stop goes on as vanilla's.
Refs #7484
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's thorns check counts events from before the hurt; the PR's helmet
path runs after that count like the plain one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep main's non_mob_tag base with the PR's class-owned container fields,
and both new test modules and corpus groups.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The portal parity suite only sent entities through portals: the PortalShape
search that turns an obsidian frame into portal blocks was never exercised.
Add a shape family over real frames and a --no-shape switch that reproduces
the previous corpus byte for byte.
HoneycombItem.useOn gives a double chest's other half its game event and
particles and plays a second sound from the clicked block, on the server and
in the client's prediction. The wax sound leaves out the acting player and
goes out in the blocks source.
WeatheringCopperChestBlock.randomTick skips the attempt, and its draw, while
getEntitiesWithContainerOpen has anyone: a player's menu, or a copper golem
(CopperGolem.hasContainerOpen), a double chest counting for both halves.
Fixes#11378
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Exclude actual crafters from automatic dropped-item name capture while
preserving chest/shulker names, contents and the other copy policies.
Cover the real destruction route against a frozen fresh Java reference.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The row said layers from loot are not set and no vanilla table uses the
function; a datapack table does, and the row now names the arm and the item-side
parse that puts the layers on the banner.
The loot table test fills a chest with a white banner through set_components
(a red stripe at the bottom) and then set_banner_pattern with append: the
component carries both layers, in that order; with append false it carries only
the function's blue border. Without the arm the append case keeps the single
red layer.
The item test hands set_loot_component a banner_patterns list and reads
`banner_patterns` back as two BannerLayers (pattern and dye), and a null
component empties them again. Without the arm the list is empty - the banner
wears nothing.
set_loot_component only knew the trim, so a rolled banner_patterns list landed
in `other` as a raw tag: the renderer and the tooltip read
`StackData::banner_patterns`, which stayed empty, and the banner from a
datapack table (or a set_components function) came out undyed. The arm parses
the component with the same `layers_from_nbt` the item's own component uses, so
the layers - pattern and dye - reach the fields the game reads.
SetBannerPatternFunction sets the stack's banner_patterns to the function's
layers, or with append adds them to the end of the component's own list
(BannerPatternLayers.Builder.addAll twice). A layer is a pattern id and a dye
name, so the list the table wrote is the component's list as it stands. The arm
was missing, so a datapack's banner pattern changed nothing: the banner came out
bare, or with only the layers an earlier function wrote.
A block edit relit in the frame changes light up to 15 blocks into the
columns around it, but only the edited column was marked unsaved. With
eager saves a neighbour saved a moment earlier kept its old light on disk.
Vanilla's ChunkHolder.sectionLightChanged marks every chunk whose light
section changed unsaved; mark_light_changes now does the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
apply names the ListOperation helper its four lists go through, and run names
the arm that draws the floats and colours and keeps the component the stack
already had. The rows said "no vanilla loot table uses it", which was true but
not a reason for a table that does to change nothing.
A table writes custom_model_data through set_components (floats [1.0], strings
["old"]) and then set_custom_model_data: floats appended from a constant
provider, flags and strings replaced, a colour inserted first. The rolled stack
carries 1.0 and 2.0, [true], ["new"] and [16711680] - without the arm the
component keeps the first function's lists and has no colours at all.
The helper's own test covers what a table rarely reaches: a replace_section of
one element, one with no size (the new values' length), an insert past the end
that changes nothing, and a mode the codec would refuse reading as a
replacement.
SetCustomModelDataFunction reads the stack's custom_model_data (EMPTY when it
has none) and replaces each of its four lists the function names - floats and
colors from number providers drawn with the loot random, flags and strings as
written - by that list's ListOperation over what was there: replace_all,
replace_section (offset and an optional size), insert at an offset, or append.
The arm was missing, so a datapack's model data changed nothing at all.
ListOperation itself is a small helper beside the number readers; an offset
past the end leaves the list as it was, as vanilla logs and ignores it there.
The component then reaches the item through set_loot_component's generic arm,
which keeps it in `other` where the model selection, the CIT rules and the LAN
sync already read it - that half needed nothing new.
The row said no vanilla loot table uses set_book_cover, so nothing was missing;
a datapack's table does reach it, and it now names the arm that applies the
cover and the item-side parse that reads the component back.
The loot table test fills a chest with a written book through two modifiers: one
set_components with a title, author and page, then set_book_cover with another
title, an author and generation 2. The cover replaces the title, author and
generation, and the page the other function wrote is still the book's. Without
the arm the component keeps the first title.
The item test hands set_loot_component a written_book_content as the loot engine
builds it and reads the book back: title, author, generation and both pages, and
a null component leaves none. Without the arm the book has no content at all.
A component a loot function writes goes through StackData::set_loot_component
when the rolled stack becomes an item, and only the trim had an arm: a written
book's content landed in `other` as a raw tag nobody reads, so set_book_cover
(or set_components) titled a book whose tooltip and reading screen still showed
nothing. The content is now parsed the way the item's own component is - the
parse moves out of from_nbt into written_book_from_tag, shared by both - so the
title, author, generation and pages come out of a rolled book.
SetBookCoverFunction updates the stack's written_book_content, over the
component it already carries: the title as a Filterable (the raw text, and the
filtered text when a server filtered it), the author, and the generation, while
the pages and the resolved flag stay as the earlier functions left them. A loot
table's written book got none of that: the modifier ran and changed nothing, so
a datapack book came out with no cover at all.
A connection's network thread hands every play packet to the hub over
an unbounded channel. The hub's inbox caps (INBOX_LIMIT packets,
INBOX_BYTES bytes) apply only as it drains that channel at the start of
a tick, so while one tick runs long (an autosave, chunk generation) a
client can queue tiny packets without bound, each a boxed Serverbound
many times its size on the wire.
Each connection now counts what it handed the hub and the hub hasn't
taken yet (Undrained, shared between the network thread and the
guest's PlayerSender). Past twice the inbox caps the network thread
disconnects the client with the same message as the packet-rate kick
(disconnect.exceeded_packet_rate) and logs a warning; the hub
uncounts each packet as it takes it. A client within the inbox caps
never comes near.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
The row said the name-based stats file is not moved to the uuid file. It names the function that
does it, and the segment rule that decides whether vanilla would look for it at all.
The test works in a temporary world folder: nothing is written when there is nothing to move, the
`players/stats/<name>.json` file lands on the uuid name and is not left behind, a uuid file that
already exists wins and leaves the old file alone, and the segment check refuses capitals and
separators while accepting a lowercase name with dashes, dots and digits.
PlayerList.locateStatsFile: the stats file is `stats/<uuid>.json`, but a version that wrote
`stats/<player name>.json` left its numbers under the old name. When the uuid file does not exist
yet and the name is one vanilla would write as a path segment
(FileUtil.isValidPathSegment: `[-._a-z0-9]`, and not `.` or `..` — the check is on
`<name>.json`, so a capital letter in the player's name refuses it), the old file is moved to the
uuid name and that is the file that is read; a failed move leaves the old one in place.
Horizon always resolved the uuid path, so a world opened after such a version started with empty
stats. The 26.3 advancements file has no name-based form (`PlayerList.getPlayerAdvancements`
resolves the uuid directory directly), so only stats move.
A cushion is an ordinary entity in vanilla (a BlockAttachedEntity), but
Horizon had no command target for it: @e never matched one, so execute
if entity, tag, data get, tp and kill missed every cushion. Target::Cushion
joins the level's candidates like a painting; /kill removes it with no
drop and lets its rider go, /tp moves it with its block, data get writes
it through cushion_to_nbt.
Fixes#11626.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PiecesContainer.load now names saved_piece_id, where the rename table lives,
and StructureStart.loadStaticStart spells out that a jigsaw start (the renamed
old ids included) comes back from its saved pieces while another structure's
start comes back from the generator's rebuild of its chunk.
A piece tag named minecraft:nvi, pcp, bastionremnant or runtime comes back as
jigsaw, as does minecraft:jigsaw itself; a monument, stronghold or jungle
temple piece (OMB, SHCC, TeJP) comes back as nothing so the start keeps the
generator's rebuild, and so does a tag with no id or one that is not a
compound. With the table narrowed to minecraft:jigsaw alone the first four
fail.
PiecesContainer.load maps four piece ids - minecraft:nvi, minecraft:pcp,
minecraft:bastionremnant and minecraft:runtime - to minecraft:jigsaw before it
looks the type up: a save from before those pieces were jigsaw wrote them under
the older names with the same pool-element tag. The saved-start reader only
took the literal minecraft:jigsaw id, so such a start fell through to the
generator's rebuild of its chunk, and where that rebuild makes a different
layout (a world whose structure data or version moved on) the start came back
with no pieces at all: nothing placed, nothing bearding the terrain.
saved_piece_id now holds that rename table for the reader; a piece of another
type (a monument's or a stronghold's, whose NBT is not a pool piece's) still
sends the start to the rebuild.
Encode bare item templates using the primary record form while preserving
compound counts and components. Cover actual canonical load/save and the
real command route against a frozen fresh Java reference.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
RideCommand.mount forces the ride (startRiding(vehicle, true, true)), so
any entity takes a rider. Horizon only took boats, carts and mobs. A rider
now sits at the vehicle's default passenger attachment (its box height,
less the rider's own vehicle attachment) and gets off on sneak on top of
it, as Entity does for a vehicle that overrides neither.
Fixes#11603.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RideCommand.mount and dismount work on any ServerPlayer in vanilla. Horizon's
/ride reached only the player in place, so the console, a function, a command
block or another player could not seat a guest (every player of a server or a
LAN world, and in single player too): the guest's turn now runs through
for_player, as native_mount does. A guest as the vehicle answers
cant_ride_players, as any player does.
Fixes#11602.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Propagate required dependency failures before expanding function tags so
invalid tags cannot run partial members. Skip whole invalid optional children
while preserving the existing ordered, deduplicated expansion.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Ports EnvironmentAttributeSystem: every attribute is its default run
through layers in the order they are added, leading constant layers folded
into the base (bakeLayerSampler) and the result sanitized. The dimension
type's attributes are constant layers; the biome layer reads the biome at
the position, or for spatially interpolated attributes the
SpatialAttributeInterpolator mix that GaussianSampler accumulates; the
weather layers (WeatherAttributes.RAIN and THUNDER) blend by the rain and
thunder levels with each type's state-change lerp. Timelines plug in as
time layers and come next; nothing in the game uses the stack yet.
Entries of types not ported yet (append, overlay) are kept as no-ops so a
biome's other attributes still load.
The new harness environment_system compares 900 stacks with the pinned
client (36,000 lines), with the vanilla biome and dimension type data.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reject folders without parseable metadata, an object pack section and a non-null description before exposing their contents. Preserve existing parser and format behavior.
Co-Authored-By: Codex <[email protected]>
Ports vanilla 26.3's environment attribute registry (EnvironmentAttributes:
types, defaults, ranges) and one EnvironmentAttributeMap entry: a value or
a {modifier, argument} object, parsed as its codec does (hex, list and int
colors, FloatWithAlpha, BlendToGray) and applied as AttributeModifier.apply
with float math in Java's order (ARGB.alphaBlend, addRgb, subtractRgb,
multiply, greyscale, scaleRGB, srgbLerp; Math.min/max). The layer stack
that sums these comes next; nothing uses the module yet.
The new harness environment_attribute_modifier compares 8,835 cases with
the pinned client, the registry included.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Validate candidate functions at actual compilation permission before exposing them, using refreshed world content and candidate registry IDs.
Co-Authored-By: Codex <[email protected]>
Carry the active Player grace time and fall-particle flag through the existing
save context, restore owner snapshots after guest calls, and write the official
empty player Brain schema. Keep tracker projection outside this change.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
ItemModels::components cloned the item's whole default component map (an
enchanted sword's: a few dozen JSON values and strings) for every stack
with a patch, and the model resolver did it on every draw: the hotbar,
each held item of every player in view, the first-person hand twice. A
PvP fight with enchanted gear paid it a dozen times a frame.
ComponentsView reads the patch, then the defaults, with no copy; the
resolver, the swap animation scale, attack range, use duration and the
use animation read through it. components() stays for what needs the
merged map (bundle fullness, still built only for bundles).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
submit_quads hashed the quad's sprite name into the glint atlas for every
quad of an enchanted item drawn in the GUI (a generated sword: hundreds of
side quads, one sprite). It now keeps the last sprite's glint rect, as it
already does for the item atlas rect: the same uvs, one lookup a sprite.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's Player.aiStep widens the pickup area to the union with the vehicle's box while riding; ExperienceOrb.tick keeps its followingPlayer until they leave 8 blocks, runs scanForMerges in its own turn before the move, and floats by the eye's fluid rather than the feet's block. Fixes#11389.
Co-Authored-By: ZCode (GLM) <[email protected]>
`tools/port_map/game2-004.tsv` recorded
`Vindicator.addAdditionalSaveData` as `missing` and `Vindicator.setCustomName`'s
gap as "the Johnny goal reads the current custom name each tick; vanilla latches
the flag once a name is Johnny". Both rows now name what the port does, with a
row for `readAdditionalSaveData` beside them.
`vindicator_server_ai_step` is the AI step, and `mob_to_nbt`/`mob_from_nbt` are
the pair `Game::mob_tree_nbt` and `Game::load_mob` go through. A vindicator
named Bob is not Johnny; named Johnny it is; renamed to Bob it stays Johnny.
The save writes `Johnny: 1b`, the reloaded vindicator is still Johnny with the
name Bob, and a compound with no `Johnny` (its `CustomName` reading Johnny)
loads false and saves `Johnny: 0b`.
Without the latch the test stops at the first name: `left: false`, `right:
true`.
`Vindicator.setCustomName` latches a flag the first time the name it is given is
`Johnny`, and nothing ever clears it:
public void setCustomName(@Nullable Component name) {
super.setCustomName(name);
if (!this.isJohnny && name != null && name.getString().equals("Johnny")) {
this.isJohnny = true;
}
}
`VindicatorJohnnyAttackGoal` starts for `isJohnny` (`addAdditionalSaveData`
writes it, `readAdditionalSaveData` reads it back with `getBooleanOr("Johnny",
false)`), so a vindicator renamed from Johnny to Bob keeps attacking every
living entity, and one whose save says `Johnny: 1b` does so after a reload.
Horizon read the live custom name in the goal's `when` each tick and saved no
flag, so a renamed vindicator stopped attacking everything, and a reloaded
vindicator that had been named Johnny but is now Bob never started.
`extra.johnny` is the latch, taken from the live name in
`vindicator_server_ai_step` (the AI step is where a name set since the last step
is seen, and it never clears), read by the goal, written as `Johnny` and read
back from it. A save with no `Johnny` loads false, whatever the name in the
compound, as `getBooleanOr` does.
Without the change the first reads `Err(InvalidData, "external chunk stub has
inline data")` and the second `Err(Os { code: 2, kind: NotFound })` instead of
the 4000 bytes in `c.-32.0.mcc` and an absent chunk.
`RegionFile.getChunkDataInputStream` never trusts the stub's length once the
external flag is set: `streamLength != 0` only logs "Chunk has both internal and
external streams" and the stream comes from the `.mcc`. When the `.mcc` is not a
regular file, `createExternalChunkInputStream` answers `null`, so the chunk is
absent. Horizon refused both: a stub that also declared inline data failed with
"external chunk stub has inline data", and a stub whose `.mcc` was gone failed
with a `NotFound` io error, which aborts a reader where vanilla skips the chunk.
Use the existing air predicate and Gone result at the start of the actual falling-block tick. Cover admitted command actors with a real Game tick regression and frozen Java parity controls.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
PlayerPredicate.matches calls FoodPredicate.matches on the player's
FoodData: the food level in `level`, then the saturation, a float
compared as a double, in `saturation`. The player facts carried no food
and the matcher never read `food`, so any food bounds passed.
PlayerFacts now holds the food, always filled (two numbers), for the
player in place and for a server's other players, and player_facts tests
it after the level, as vanilla does.
The command_exec harness gets a group that fills and drains the player's
food in player loops and tests named food predicates, a non-player, and
a tick advancement whose player condition asks only for food.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both cases read the emitted tag, so they fail on the live palette: 342 longs
of `data` where vanilla's `SimpleBitStorage(4, 4096)` is 256, a palette of
`[0, 1, … 16]` where first use is `[1, … 15, 0]`, and `(2, Some(256))` where a
section edited back to one state is `(1, None)`.
`PalettedContainer.pack` re-encodes the cells into a fresh palette before a
section is written: a state no cell holds any more is dropped, the entries are
in first-use order, and the bit count is `getConfigurationForPaletteSize` of
the states that remain. `PalettedContainer::to_nbt` wrote the live palette
instead, which keeps the slot of every state a block edit replaced (the `free`
list needs it) and packs at that wider count: a section whose 17th state was
edited away saved 342 longs where vanilla writes 256, and a section edited back
to a single state saved a 2-entry palette with a 256-long `data` where vanilla
writes one entry and no `data`.
Left in the air at y 163, the player fell into the sea below while prepare() ran, which unlocked the boat
recipes: their toast showed over the first shots at a different moment each run, so main against itself
failed in the top right corner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The command-execution furnace family could not give a checkable gap control
on this host, so this is a direct AbstractFurnaceBlockEntity.serverTick
harness instead: a real RecipeManager and a furnace, blast furnace or smoker
in a block-update level, compared with FurnaceCooking::tick. It pins the
cooking_fuel burn table for every fuel item, the 200/100/100 cook times and
speed multiplier, fuel consumption and LIT state, RecipesUsed experience, a
full output slot, timer decay, and an input changed mid-cook. --no-change
reproduces the corpus before that family.
757/757 cases match the pinned client.
DropperBlock.dispenseFrom finds its target with HopperBlockEntity.getContainerAt:
the block's container, else a container entity (chest boat or raft, chest or
hopper minecart) whose box meets the space in front, picked at random. Horizon's
dropper only asked for a block container, so items fell out next to a chest boat
instead of going in. It now falls back to the entity lookup hoppers already use.
The dispenser harness gains 48 dropper cases with chest boats, rafts and chest
and hopper minecarts centred in front, reaching in from beyond, just past the
space, two at once, behind a chest block and non-container vehicles. The oracle
and the fixture print the container entity's items (Tinv lines).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`tools/port_map/game2-002.tsv` recorded `gap: minor: anchor_pos is not read` and
`not written` for `Phantom.readAdditionalSaveData` and
`Phantom.addAdditionalSaveData`. Both now carry the anchor and say how: the key
only for a phantom that has an anchor, absent or unreadable as no anchor.
`mob_from_nbt` and `mob_to_nbt` are the pair `Game::load_mob` and
`Game::mob_tree_nbt` go through, so the test loads and saves a phantom by its
compound. A phantom at (0.5, 75, 0.5) whose save names `anchor_pos: [100, 64,
-40]` loads with that anchor, circles to (109.66, 62, -37.41) - the anchor,
`distance*cos(angle)` across and `-4 + height` up - and a phantom that moved its
anchor to (9, 70, -3) saves that position, not the one its compound came in
with. A phantom with no anchor loads with none and saves no `anchor_pos`.
Without reading the anchor the load gives the phantom (0, 80, 0), its own
position five blocks up, and the circle point (9.66, 78, 2.59); without writing
it the save keeps the stale (100, 64, -40).
`Phantom.addAdditionalSaveData` writes the anchor the phantom circles around
beside its size, and `readAdditionalSaveData` takes it back:
protected void readAdditionalSaveData(final ValueInput input) {
super.readAdditionalSaveData(input);
this.anchorPoint = input.<BlockPos>read("anchor_pos", BlockPos.CODEC).orElse(null);
this.setPhantomSize(input.getIntOr("size", 0));
}
protected void addAdditionalSaveData(final ValueOutput output) {
super.addAdditionalSaveData(output);
output.storeNullable("anchor_pos", BlockPos.CODEC, this.anchorPoint);
output.putInt("size", this.getPhantomSize());
}
Horizon models that anchor in `extra.home`, which
`PhantomCircleAroundAnchorGoal.selectNext` centres the circle on
(`anchor + (distance*cos(angle), -4 + height, distance*sin(angle))`), but
`mob_to_nbt` wrote only `size` and `mob_from_nbt` read only `size`. A phantom
saved and reloaded therefore kept the anchor `register` gives every new phantom
- its own position five blocks up, vanilla's `finalizeSpawn` value - instead of
the one in its save: a phantom at (0.5, 75, 0.5) loaded with
`anchor_pos: [100, 64, -40]` came back anchored at (0, 80, 0), and its first
circle point was (9.66, 78, 2.59) rather than (109.66, 62, -37.41).
`TagValueOutput.storeNullable` writes the key only for a non-null value, and
`read(...).orElse(null)` gives null for a save without one, so a phantom with no
anchor now writes no `anchor_pos` and loads back with none - the circle then
fills it with its own `blockPosition()`, as vanilla's does.
Write back live menu slots before spilling chest boats, chest rafts and
container carts. Reuse the container split/motion algorithm through its
actual double-origin path, preserving integer callers and empty-slot draws.
Keep ordinary hurt, hull, lead and pending-loot policies separate.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
NaturalSpawner.getRandomPosWithin samples up to chunk.getHeight(WORLD_SURFACE)+1,
and ChunkAccess.getHeight is firstAvailable-1, so the bound is firstAvailable.
Horizon already stored firstAvailable and added one more. Also correct the
oracle's fake LevelChunk.getHeight.
Fast lava, water evaporation, bed and anchor rules, the raid-bright check, the
day timeline, ambient light and the default clock of /time read the level's
dimension type facts instead of matching on the level, as vanilla reads
DimensionType and its attributes. No behaviour change for the three built-in
levels.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The fight tick, end crystal fire and respawn, the boss bar, the fight dragon
check, the exit portal and the DRAGON chunk ticket read the dimension type's
has_ender_dragon_fight, and the End flashes read its End skybox. No behaviour
change for the three built-in levels.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MobWorld carries the level key and its dimension type facts instead of nether
and end flags, and the spawn light test samples the type's
monster_spawn_light_level as vanilla's DimensionType.MonsterSettings does.
Fast lava, snow golem melting, piglin zombification, fixed-time and day
timeline checks read the type's facts. No behaviour change for the three
built-in levels; random draws are unchanged.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Part of #11126.
Load complete jigsaw starts from the owning chunk's structures.starts into
its shared generator cache, retaining regeneration for unsupported or
undecodable starts. Read pending saves in every dimension and preserve
reference counts alongside the original piece layout.
Cover resource-free round trips, generated layouts across two seeds and
pending-record loading from a different referencing chunk.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8KX3vDMZy53y1vXqeT
Follow vanilla's eager chunk queue, cooldown and per-tick/time/write limits.
Keep save state with its level and respect disabled saves and server pauses.
Save only lit (full) chunks eagerly and only once a world is open; an unlit
one keeps its place in the queue.
Move proto NBT encoding to the region writer without changing its encoder.
Cover selection regressions and repeated edits reaching real region files.
Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Level.updateSkyBrightness is `(int)(15.0F - skyLightLevel)`: the float
truncated. Horizon's environment::sky_darken (and the End copy in
Game::sky_darken) rounded, stepping the darkening up half a level early
at dusk and late at dawn, which moves bed eligibility and the light-
dependent spawns with it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
LivingEntity.travelFallFlying stops the glide on a climbable, and a
server simulates its players' travel (AUTHORITATIVE_SIDE_AND_SERVER).
Horizon's server travel cleared the flag only on the guest's temporary
body; as_guest now copies it back, so the glide ends on the server and
the client, which takes the flag from the server, stops gliding too.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
handleClientCommand(PERFORM_RESPAWN) for a player who won the game
calls restartClientLoadTimerAfterRespawn, as the death respawn does: the
player's movement and actions wait, and isInvulnerableTo spares it, until
its client reports the level loaded or 60 ticks pass. Horizon restarted
the timer only after a death.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Leashable.setLeashedTo goes through LeashData.setLeashHolder, which
clears the delayed holder a save named. Horizon kept it, so 100 ticks
after loading the forgotten holder still dropped a lead item next to a
mob the player was leading, or a holder that loaded late took the mob
from the player. Mobs and boats both.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Piglin.finishConversion cancels the admire, then drops every non-empty
stack of its inventory with spawnAtLocation (whole stacks, components
kept), then converts; the converted sound comes before all of it. Horizon
dropped only the admired off-hand item, before the sound, and the
inventory was lost with the piglin.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds gcc, glibc.dev, pkg-config, alsa-lib.dev, and udev.dev to the FHS
environment so native mod compilation works on NixOS without external shell.
Co-Authored-By: Pluto <[email protected]>
Provides a Nix flake packaging the Horizon launcher in an FHS environment
along with a development shell containing dependencies for building the project.
Co-Authored-By: Pluto <[email protected]>
AbstractMinecart, MinecartTNT, MinecartFurnace and MinecartHopper read
their fields through ValueInput's numeric getters, which take any
NumericTag: getIntOr (fuse, DisplayOffset), getShortOr (Fuel, the low 16
bits), getDoubleOr (PushX, PushZ) and getBooleanOr (FlippedRotation,
HasTicked, Enabled: byteValue() != 0). Cart::load took only byte, short
and int, and only double for the pushes, so {fuse:20L} left a TNT
minecart unprimed and {PushX:1} gave a furnace minecart no push.
Command harness group "minecart numeric fields": 3/18 -> 18/18.
Fixes#11146
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Match vanilla's column order and fallback when choosing a breathing destination.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
entity_chunks(remove = true) is the unload path: every other kind it
writes is also taken out of the level, and the potion clouds were not,
so a chunk loaded again got a second cloud on the same spot. The new
test unloads and reloads a cloud and expects one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
JukeboxSongPlayer.stop posts the event inside `if (this.song != null)`:
taking a disc out (by hand or by a hopper) after its song ended is silent.
The test takes the finished disc out and expects only the end's event.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The merchant fixture compares strict command and menu records, so the raw
packet trace (net/command_trace.rs in the client receive loop, the Java
trace reader, the oracle's serialized channel) and the per-operation
receipts were diagnostics only. The production client no longer writes or
joins anything for them; tools/test_merchant_gate_parity.py is a thin
wrapper like the other command_exec fixtures.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrownTrident inherits AbstractArrow.lerpMotion, which restarts life even
when the motion is zero. The remote motion test compiles remote_motion out
of remote.rs, so its fixture needs the arrow fields; it now also checks
that zero motion keeps a stuck arrow in place and non-zero motion frees it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the completed ordered request list for the current atlas so an
unchanged overflow does not regenerate and upload every trim each frame.
Preserve final LRU residents, retry failures, and invalidate the cache
when equipment, atlas contents or the graphics device changes.
Fixes#10539
Co-Authored-By: Codex GPT-6 <[email protected]>
`build_surface_shared` applies the compiled rule at every solid block of a
column. In the Overworld most bands specialise to "the ore veins, then one
block" (deepslate), so 1.7M rule applications per 64 chunks produce ~1M block
writes, each walking the rule tree from the top and going through
`set_block`/`set_block_worldgen_column`.
A band variant that is a block rule, or ore veins followed by one, gives that
block at every position where no vein places one. The walk now writes the whole
run of solid blocks at once: the top y through the single-block path, the rest
through `TerrainChunk::fill_solid_run`, stopping at the first y where a vein
can place a block (each vein's first test, its density), at the band's bottom
and at the write bounds. Only blocks that are opaque for the stage's heightmaps
and carry no fluid take this path, so one update at the run's top leaves the
maps as a write per block does.
`CompiledRule::apply` on a sequence also tests its conditional children in the
loop instead of through a recursive call.
Instructions (callgrind, proto phase, 64 chunks, seed 12345): Overworld
6.49 G -> 6.03 G (-7.0%), surface stage 1.98 G -> 1.53 G (-23%), rule
applications 599 M -> 329 M; Nether 2.97 G -> 2.73 G (-8.1%).
A first draft by DeepSeek V4.1 Flash, via pi, reviewed and reworked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Skip only the Nautilus custom air handler under NoAI, preserving the existing Breath/drown path and normal custom behavior. Cover actual Game and full Mob ticks against fresh Java.
Co-Authored-By: Codex <[email protected]>
A ledger build from today's upstream/main reports seven map gaps, all rows
whose where cell names a function that no longer exists: PrimedTnt.explode
points at Game::tick_tnt (it is tick_one_tnt), FlatLevelSource.createState at
FlatTerrain::structure_settings (from_settings), PacketProcessor's hand-off at
conn::read_loop (io_pool::Pool::push), three ServerCommonPacketListenerImpl
keep-alive rows at conn::write_loop (that loop is wait_alive), and
PackSelectionScreen.Watcher is a class row written as a port.
AreaEffectCloud.addAdditionalSaveData was listed missing, which was right: only
the reader existed. game/potions.rs::potion_cloud_to_nbt writes it now, from
entity_chunks.
A cloud with a poison and a slowness effect, one of them hidden and one ambient,
written with potion_cloud_to_nbt and read back with potion_cloud_from_nbt: the
position, age, duration, radius, the four use settings, the duration scale,
RadiusPerTick, the effect list and the colour all come back. The test also
checks that the compound carries the field names vanilla's
readAdditionalSaveData looks for, and that it is saved as
minecraft:area_effect_cloud.
AreaEffectCloud.addAdditionalSaveData writes the cloud's age, duration, radius,
the four use settings, RadiusPerTick, potion_duration_scale and its
potion_contents, and the client already read every one of them back
(game/potions.rs::load_potion_cloud): a cloud that was loaded from a save came
back whole. Nothing ever wrote one, though. entity_chunks writes the dragon, the
crystals, the item frames, the fangs and the rest kind by kind, and the
lingering clouds -- which live in breath_clouds, not in entity_lists! -- were in
none of those, so every replacement pass dropped the cloud a thrown potion or a
creeper left behind.
The save side now writes them: game/potions.rs::potion_cloud_to_nbt builds the
compound on the entity fields entity_nbt::entity_base_tag gives it, and
entity_chunks adds one tag per potion-bearing cloud of the chunk. A dragon's own
breath carries no potion and is not written.
The reader moved out of the Game method into potion_cloud_from_nbt, so the pair
can be tested against itself. The client keeps no potion id, so the effects are
written as custom_effects with the colour the cloud was drawn in, and the custom
particle and owner reference the vanilla cloud also saves have no counterpart
here.
A windowless game with a jukebox at (0, 64, 0): a disc put in and popped back
out posts one G 0,64,0 minecraft:jukebox_stop_play, a song ticked to its end
posts one, a broken jukebox posts none from this path (the world's spill posts
it) and a jukebox that never held a disc posts nothing.
JukeboxSongPlayer.stop posts GameEvent.JUKEBOX_STOP_PLAY at the block (and level
event 1011), and both ways a song ends reach it: popOutTheItem, when the disc is
taken out or a hopper pulls it, and tick's own hasFinished branch.
Horizon posted that event only from the world's removal path, so a broken
jukebox was heard but a jukebox whose disc was pulled out by hand, or whose song
ran out, was silent to a sculk sensor beside it. The paths that keep the block
now go through stop_song_heard; jukebox_removed keeps the plain stop_song,
because the world's spill already posts the same event once for it.
Resolve an absent saved show_icon from show_particles while preserving
explicit icon bytes and the remaining potion fields. Keep this in the
saved item encoder, without changing the general component view.
Add resource-free decoded/modelled codec coverage and a guarded,
zero-tick chest command regression against the pinned Java behavior.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Retain the real last trader on the villager when a trade is accepted,
then deliver the coalesced reputation event after Brain.tick. Keep it
across menu close and live NoAI edits, and refresh current offer prices
when the event actually runs.
Add an actual-owner merchant regression with complete raw capture and
separate strict-all byte-order diagnostics.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Preserve the existing adapter and maintainer history while including the
upstream removal of stale Menu Navigation translation keys.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Select attribute and carpet registry names from the actual server release.
Upgrade incoming pre-component block entities on both update and chunk paths,
retaining tag identity and vanilla typed-field remainders.
Add independent registry and vanilla DFU regression fixtures, including
missing IDs, removed types and unchanged current-version tags.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Preserve published compatibility commits and newer maintainer changes before addressing the protocol-764 review. This merge alone claims no new compatibility or parity result.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Preserve the published older-server contribution and current upstream changes as separate parents before the next tested extension. No new compatibility or parity result is claimed by this merge.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate protocol 766 attributes, food, horse inventories, projectile power,
chat holders and item use into the pinned protocol. Supply the old static
enchantment and painting names, and normalize legacy wolf variant holders.
Reuse verified recipe and boat layouts and cover changed codecs with
independent vanilla wire fixtures, including full chat packet translations.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate protocol 767 packets, recipes, item components and generic boat
variants into the pinned protocol. Preserve old movement and interaction
layouts, and correct simple spell particles across the older adapters.
Add independent vanilla wire fixtures and document the experimental scope.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate protocol 768 particles, player-list defaults, vehicle movement and
integer model data through the existing older-server pipeline. Preserve old
creaking heart states and transient entities through explicit registry aliases,
and keep legacy trail lifetimes in the client particle provider.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate legacy chunk framing, item patches, recipe previews, chat and
entity metadata into the pinned client representation. Preserve old click
operations through authoritative server resynchronization and cover older
stack/profile layouts with independent vanilla wire fixtures.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Map protocol 770's packet and registry ids, remove vanilla's unused chunk
padding, renumber command parsers before reading their properties, and
translate player actions, crouch transitions and hanging-entity metadata.
Convert pre-1.21.6 attribute and equippable components in both directions,
with independent vanilla wire fixtures and protocol regression coverage.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Reuse the packet layouts shared with 1.21.7/1.21.8, map the item and sound
registry ids before the lava chicken insertion, and generate inherited tables
only where the vanilla reports differ. Cover shifted discs and sound holders
and exercise the shared metadata and packet conversion cases on both protocols.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Generate vanilla packet and registry mappings, translate the changed layouts
and player metadata, preserve renamed chain states, and extend the offline
protocol probe to fail on decode errors and follow dimension changes.
Co-Authored-By: Codex (GPT-6) <[email protected]>
2026-11-03 22:18:41 +00:00
1742 changed files with 239874 additions and 40297 deletions
| `horizon-anticheat` | The dedicated server's anticheat, off by default: `[anticheat]` settings, violation levels, the checks' rules; the hub runs them (`lan/anticheat.rs`) ([docs/anticheat.md](docs/anticheat.md)) |
| `horizon-cosmetics` | Cosmetic items Horizon clients draw on players (off by default): the item format, bones and animations ([docs/cosmetics.md](docs/cosmetics.md)) |
| `horizon-addresses` | Every built-in address of an outside service vanilla doesn't contact (Horizon layer; [docs/layers.md](docs/layers.md#built-in-addresses)) |
| `horizon-friends`, `horizon-relay` | Play with Friends: invite codes, encrypted tunnel, the relay ([docs/play-with-friends.md](docs/play-with-friends.md)) |
| `horizon-mods`, `cargo-horizon-mod` | Content mods (`.hmod`: manifest, content fence, no code), the registry index and `.hpack`, and `cargo horizon-mod` (`new`, `build`, `run`, `check`, `registry`) ([docs/mods.md](docs/mods.md)). Code mods are native, built by the launcher ([docs/native-mods.md](docs/native-mods.md)) |
@@ -70,6 +73,8 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| `horizon-shaders` | Shader packs (the `shaders/` folder format), rebuilt clean-room from a written spec of the format; no wgpu |
| `horizon-theme` | Design tokens and fonts for the launcher, the game's own menus and the website |
| `horizon-telemetry`, `horizon-telemetry-server`, `horizon-bench` | Opt-in data collection, its ingest server, the benchmark report format |
| `horizon-system` | The machine the game runs on (OS, CPU, memory, drive, power, GPU maker and memory), for the crash report, the renderer's GPU info and telemetry |
| `horizon-platform` | The platform's public API: what the Horizon layer may use of the engine ([docs/layers.md](docs/layers.md)); re-exports `horizon-system` today |
| UI screens | `Screen` enum in game.rs; L: `title.rs` + `title/`, `options_ui.rs`, `multiplayer.rs`, `pack_screen.rs`, `settings_shell.rs` (Horizon's settings screens on it: `extras_ui.rs`, `graphics_ui.rs`, `controller_ui.rs`, `shader_ui.rs`; a page list too long for its column collapses into one row), `screen_draw.rs`, `ui.rs` (widgets), `hud.rs`, `hud_layout.rs`, `hud_overlays.rs`, `hud_kit/` (Horizon overlays' `Room` and `Plate`, the collapsed page strip `tabs` the shell's page list uses, [docs/ui-kit.md](docs/ui-kit.md)), `pad_glyph.rs` (controller glyphs, by what the button does), `chat.rs`, `tab_list.rs`; G: `startup_menu.rs`, `loading_screen.rs` (the loading screen, the pre-game `App`), `resource_loader.rs` (what a start loads, `run_loading`), `screens.rs` (pause, death), `options.rs`, `lan_screen.rs`, `chat.rs`, `sign_edit.rs`, `books.rs`, `creative_screen.rs` |
@@ -138,13 +137,13 @@ It runs rustfmt, clippy, the touched crates' tests, the Python tests, the public
## Project status
**Player-visible gaps: 0 open** of the 833 the [port ledger](tools/port_ledger.py) found, reading every vanilla 26.3 method against Horizon's code: vanilla behaviour a player would notice that Horizon doesn't have yet. By area: Animals [0](https://github.com/horizon-rust/horizon/issues/3512); Monsters and bosses [0](https://github.com/horizon-rust/horizon/issues/3513); Entities, projectiles, vehicles and decorations [0](https://github.com/horizon-rust/horizon/issues/3514); Items, enchantments and inventories [0](https://github.com/horizon-rust/horizon/issues/3515); Blocks, redstone and fluids [0](https://github.com/horizon-rust/horizon/issues/3516); World, world generation and saves [0](https://github.com/horizon-rust/horizon/issues/3517); Screens, HUD and client packets [0](https://github.com/horizon-rust/horizon/issues/3518); Entity and block rendering, models [0](https://github.com/horizon-rust/horizon/issues/3519); Server and players on the server [0](https://github.com/horizon-rust/horizon/issues/3520); Mob AI, villagers and raids [0](https://github.com/horizon-rust/horizon/issues/3521). This number goes down as gaps close (`tools/parity_gaps.py`); the harness results below say that what is tested matches.
**Player-visible gaps: 0 open** of the 833 the [port ledger](tools/port_ledger.py) found, reading every vanilla 26.3 method against Horizon's code: vanilla behaviour a player would notice that Horizon doesn't have yet. By area: Animals [0](https://github.com/horizon-rust/horizon/issues/3512); Monsters and bosses [0](https://github.com/horizon-rust/horizon/issues/3513); Entities, projectiles, vehicles and decorations [0](https://github.com/horizon-rust/horizon/issues/3514); Items, enchantments and inventories [0](https://github.com/horizon-rust/horizon/issues/3515); Blocks, redstone and fluids [0](https://github.com/horizon-rust/horizon/issues/3516); World, world generation and saves [0](https://github.com/horizon-rust/horizon/issues/3517); Screens, HUD and client packets [0](https://github.com/horizon-rust/horizon/issues/3518); Entity and block rendering, models [0](https://github.com/horizon-rust/horizon/issues/3519); Server and players on the server [0](https://github.com/horizon-rust/horizon/issues/3520); Mob AI, villagers and raids [0](https://github.com/horizon-rust/horizon/issues/3521). This number goes down as gaps close (`tools/parity_gaps.py`); the table below says what the harnesses compare.
The table is the project's own audit: every subsystem matches vanilla in every harness that compares it. 100% means 100% of what the harnesses compare, not that nothing else differs. What players still run into: [known issues](docs/known-issues.md).
The table is the project's own audit of what the harnesses compare, subsystem by subsystem: a score says how much of that subsystem the harnesses cover and pass, not that nothing else differs from vanilla. What players still run into: [known issues](docs/known-issues.md).
### Java parity: 100%
### What the harnesses compare
Scores: 0 (absent), 0.25 (started), 0.5 (playable, major features missing), 0.75 (mostly complete) or 1 (complete and verified by differential tests); the percentage is their average. Last audited 2026-10-13: the harnesses pass on Linux and macOS. The [port ledger](tools/port_ledger.py) adds a method-by-method view of what is ported.
Scores: 0 (absent), 0.25 (started), 0.5 (playable, major features missing), 0.75 (mostly complete) or 1 (its harnesses cover the subsystem and pass). Last audited 2026-10-13: the harnesses pass on Linux and macOS. The [port ledger](tools/port_ledger.py) adds a method-by-method view of what is ported.
One item for all modding work ([#3911](https://github.com/horizon-rust/horizon/issues/3911)), led by levzzz5154 with vladislav_hagen; the order between the parts is theirs. Native mods on stable hook points (game/native_hooks.rs, [#3345](https://github.com/horizon-rust/horizon/issues/3345), [#3346](https://github.com/horizon-rust/horizon/issues/3346), [#3434](https://github.com/horizon-rust/horizon/issues/3434), [#3435](https://github.com/horizon-rust/horizon/issues/3435)) built by the launcher ([#3388](https://github.com/horizon-rust/horizon/issues/3388), [#3855](https://github.com/horizon-rust/horizon/issues/3855)); server-side hooks ([#3878](https://github.com/horizon-rust/horizon/issues/3878), [#3706](https://github.com/horizon-rust/horizon/issues/3706)); mod-owned screens, input and drawing; tools for mod makers (templates, `native.py check`); the registry ([#1634](https://github.com/horizon-rust/horizon/issues/1634), [#1642](https://github.com/horizon-rust/horizon/issues/1642), [#1659](https://github.com/horizon-rust/horizon/issues/1659)); machines with inventories and menus ([#1189](https://github.com/horizon-rust/horizon/issues/1189)); mods that read and change the world through the server. [docs/native-mods.md](docs/native-mods.md).
One item for all modding work ([#3911](https://github.com/horizon-rust/horizon/issues/3911)), led by levzzz5154; the order between the parts is theirs. Native mods on stable hook points (game/native_hooks.rs, [#3345](https://github.com/horizon-rust/horizon/issues/3345), [#3346](https://github.com/horizon-rust/horizon/issues/3346), [#3434](https://github.com/horizon-rust/horizon/issues/3434), [#3435](https://github.com/horizon-rust/horizon/issues/3435)) built by the launcher ([#3388](https://github.com/horizon-rust/horizon/issues/3388), [#3855](https://github.com/horizon-rust/horizon/issues/3855)); server-side hooks ([#3878](https://github.com/horizon-rust/horizon/issues/3878), [#3706](https://github.com/horizon-rust/horizon/issues/3706)); mod-owned screens, input and drawing; tools for mod makers (templates, `native.py check`); the registry ([#1634](https://github.com/horizon-rust/horizon/issues/1634), [#1642](https://github.com/horizon-rust/horizon/issues/1642), [#1659](https://github.com/horizon-rust/horizon/issues/1659)); machines with inventories and menus ([#1189](https://github.com/horizon-rust/horizon/issues/1189)); mods that read and change the world through the server. [docs/native-mods.md](docs/native-mods.md).
[#8513](https://github.com/horizon-rust/horizon/issues/8513)); every other GPU compiles with FXC, part of Windows.
Copyright (c) Microsoft Corporation. It is not covered by Horizon's MIT license. It is distributed under the
"Microsoft Software License Terms, Microsoft DirectX Shader Compiler" (`LICENSE-MS.txt` of that release). Its
Distributable Code terms are the Windows SDK's: end users get terms that protect the code and Microsoft at least as
much, and the distributor indemnifies Microsoft. The project accepted them on 2026-11-08. Its open-source part is also under the University of Illinois/NCSA license (`LICENSE-LLVM.txt`). Both
files ship beside the DLL as `DXC-LICENSE-MS.txt` and `DXC-LICENSE-LLVM.txt`, with `DXC-NOTICE.txt`
(`packaging/windows/`), which tells players those terms apply and that deleting the DLL brings back FXC.
- Only x64: the Windows SDK's list of distributable files
("shaders.properties".into(),"particles.ordering=mixed\nseparateEntityDraws=true\nblend.gbuffers_block=off\nblend.gbuffers_particles=off\nblend.gbuffers_block_translucent=SRC_ALPHA ONE_MINUS_SRC_ALPHA ZERO ZERO\nblend.gbuffers_particles_translucent=SRC_ALPHA ONE_MINUS_SRC_ALPHA ZERO ZERO\n".into()),
Loaded 100 of 1742 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.