🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union R1 bb29ce68e (union/cg19-r1: main c6c2ec5f9 + 83 PRs) Mac mini: 98 se.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union R1 bb29ce68e (union/cg19-r1: main c6c2ec5f9 + 83 PRs) Mac mini: 98 se.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union A3 0c0206714 (main d9b6483aa + 2503 2521 2513 2555 2476) Mac mini 95/.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union A 5fb64f0d6 (main c1ebf5d1b + 50 PRs) Mac mini 352/354 harnesses (fin.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union A 5fb64f0d6 (main c1ebf5d1b + 50 PRs) Mac mini 352/354 harnesses (fin.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union A 5fb64f0d6 (main c1ebf5d1b + 50 PRs) Mac mini 352/354 harnesses (fin.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union R1 bb29ce68e (union/cg19-r1: main c6c2ec5f9 + 83 PRs) Mac mini: 98 se.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union R1 bb29ce68e (union/cg19-r1: main c6c2ec5f9 + 83 PRs) Mac mini: 98 se.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union R1 bb29ce68e (union/cg19-r1: main c6c2ec5f9 + 83 PRs) Mac mini: 98 se.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked hours ago, with no new objection since. Proven in cg19 union R1 bb29ce68e (union/cg19-r1: main c6c2ec5f9 + 83 PRs) Mac mini: 98 se.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: Proven in union R1 (branch union/cg19-r1, main + 83 of kacpherek's PRs): the 98 harnesses it can affect, clippy -D warnings, tests, lan_e2e in-frame and on the server thread.
🤖 Agent: move only: the Opt enum (option ids) goes from options_ui.rs to settings.rs (re-exported from options_ui), cutting graphics_ui -> options_ui (step B of the cycle plan). pr_ready --quick green (1323 tests). Queued once pr_ready was green, as the orchestrator asked; merge after verify and parity pass.
🤖 Agent: move only: HealthRules (and its Default) go from health.rs to world.rs (re-exported from health), cutting world -> health (step B of the cycle plan). pr_ready --quick green (1323 tests). Queued once pr_ready was green, as the orchestrator asked; merge after verify and parity pass.
🤖 Agent: move only: the MobEffects/Potions tables go from brewing.rs to horizon-client-core::potions (re-exported from brewing), cutting effects -> brewing and hud_overlays -> brewing (step B of the cycle plan). pr_ready --quick green (1361 tests). Queued at the orchestrator's request once pr_ready was green; merge after verify and parity pass.
🤖 Agent: merging the Sculk sidebar card (Ludovic approved the screenshots); fmt, clippy -D warnings and launcher tests pass on the head merged with main.
🤖 Agent: verify passed at 84c631a88. Move only: the villager Poi enum goes from mobs/villager_brain.rs to poi_sections.rs (re-exported), cutting world -> mobs (step B of the cycle plan). pr_ready --quick green (1323 tests). Ready to merge once parity is green.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21); main merged in since, CI green at this head.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21); main merged in since, CI green at this head.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21); main merged in since, CI green at this head.
🤖 Agent: verify passed at 83bdb31ee. Move only: SavedProto goes from save.rs to chunk.rs (re-exported from save), cutting streaming -> save (step B of the cycle plan). pr_ready --quick green (1323 tests). Ready to merge once parity is green.
effects.rs and hud_overlays.rs read the effect and potion tables from
brewing, which made them depend on brewing (and through it on containers
and items), edges that keep the client lib in a single cycle. The tables
have no dependencies, so they move to horizon-client-core; brewing
re-exports them. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg20 union C 5900ab387, Mac mini); main merged in since, CI green at this head.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg20 union C 5900ab387, Mac mini); main merged in since, CI green at this head.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (72 kacpherek PRs incl. this one) playhorizon 94/95 + Mac mini 33/33, lan_e2e 23/23 both ).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (72 kacpherek PRs incl. this one) playhorizon 94/95 + Mac mini 33/33, lan_e2e 23/23 both ).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (72 kacpherek PRs incl. this one) playhorizon 94/95 + Mac mini 33/33, lan_e2e 23/23 both ).
Keeps main's Decreased and Minimal particle options. As in
ClientLevel.tickWeatherEffects, the rain position is set before Minimal's
break, so the rain sound still plays with Minimal particles.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (72 kacpherek PRs incl. this one) playhorizon 94/95 + Mac mini 33/33, lan_e2e 23/23 both ).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg20 union C 5900ab387, Mac mini); main merged in since, CI green at this head.
The sidebar showed Sculk's wide banner shrunk to 228 px, and its baked-in text could not be read.
It now shows a card made for the sidebar: Sculk's characters and logo cropped from its banner, and
the headline, "Ready in under a minute", a "Get my server" button and the HORIZON15 code drawn by
the launcher, so they stay sharp and follow its language (French and Russian added). Where the
window is too short for the tall card, a compact one puts the characters beside the text. The
Partner label, the 180 s before Hide, the link and the hover lift stay; the Servers page keeps
the wide banner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Read against the vanilla 26.3 sources (Server players see villager hearts, taming hearts and smoke, spawn poofs and snowball crumbs) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
entity.wgsl: the vanilla glint path also takes main's overlay 33 (a translucent item with the glint).
gfx.rs: main's energy swirl pipeline; lightning's offset stays on attribute 7 (6 is the glint UV).
item_render.rs: main's translucent item overlays, chosen with the glint UV the branch adds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg21 union 65b0cb252 (main c1ebf5d1b + 72 kacpherek PRs) playhorizon 94/95, use_item_on fixed (#2126523280e78).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg20 union C 5900ab3879 (main b6f18dc8f + 57 PRs) Mac mini 122/122 delta harness).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg20 union C 5900ab3879 (main b6f18dc8f + 57 PRs) Mac mini 122/122 delta harness).
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head; a re-review was asked, with no new objection since. Proven in wave 5's union run (cg20 union C 5900ab3879 (main b6f18dc8f + 57 PRs) Mac mini 122/122 delta harness).
graphics_ui named options_ui::Opt for its rows, which made it depend on
options_ui, one of the edges that keep the client lib in a single cycle.
The enum (the option ids) moves to settings.rs; options_ui re-exports it
and keeps its methods. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
World holds the health rules but they were defined in health.rs, which made
world depend on health, one of the edges that keep the client lib in a
single cycle. The plain data type (and its Default) moves to world.rs and
health re-exports it. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
World's POI sections are typed by mobs::villager_brain::Poi, which made
world depend on mobs, one of the edges that keep the client lib in a single
cycle. The enum moves to poi_sections.rs; villager_brain re-exports it and
keeps its methods. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
streaming's ChunkGenerator trait named save::SavedProto, which made
streaming depend on save, one of the edges that keep the client lib in a
single cycle. The plain data type moves beside ChunkExtras in chunk.rs and
save re-exports it. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Scroll and add the energy swirl of charged creepers and powered withers. I checked it against vanilla 26.3, merged main in, and it passed every harness it reaches in a union with your other PRs.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Make the Particles option thin all particles, explosion debris and rain splashes. I checked it against vanilla 26.3, merged main in, and it passed every harness it reaches in a union with your other PRs.
Brings main in so the branch has one merge base with it (GitHub saw a
conflict through the criss-cross bases; the merge itself is clean).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Bubble columns lift and drag mobs, items and ender pearls. I checked it against vanilla 26.3, merged main in, and it passed every harness it reaches in a union with your other PRs.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Play the sand idle and poplar leaves ambient sounds. I checked it against vanilla 26.3, merged main in, and it passed every harness it reaches in a union with your other PRs.
throw_from_eyes takes thrown_from_hand now (main): the cursor stack is
dropped with drop(carried, false).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passed (after merging main for the rabbit-arm clippy fix). Move only: the pack.mcmeta filter regex goes from packs/pattern.rs to horizon-client-core (re-exported), cutting atlas_sources -> packs (step A). pr_ready --quick green (1352 tests). Ready to merge once parity is green.
🤖 Agent: verify passed. Move only: ChunkExtras and RolledLoot go from save.rs to chunk.rs (re-exported from save), cutting chunk -> save (step A of the cycle plan). pr_ready --quick green (1315 tests). Ready to merge once parity is green.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head (checked against the head by contrib-gate-20). Re-review was asked about 07:30Z with no new objection. Proven in wave 5's union run (main + 54 PRs, 332/332 selected harnesses; lan_e2e 18/18 in-frame and on the server thread). Thank you!
🤖 Agent: verify passed. A test move only: the item_render error-order check goes from model_library's test to item_render's tests, cutting model_library -> item_render (step A of the cycle plan). pr_ready --quick green (1316 tests). Ready to merge.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw entity outlines for the Glowing effect. I checked it against vanilla 26.3, merged main in, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Mobs are pushed by flowing water and lava. I checked it against vanilla 26.3, merged main in, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head (checked against the head by contrib-gate-20). Re-review was asked about 07:30Z with no new objection. Proven in wave 5's union run (main + 54 PRs, 332/332 selected harnesses; lan_e2e 18/18 in-frame and on the server thread). Thank you!
🤖 Agent: Read against the vanilla 26.3 sources (Firework rockets: smooth flight and spark trail on a server) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Give the Ender Dragon its ambient sound) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Walk, follow and flee at the camel's real AI speeds) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
The rain sound stays where the PR moved it (tickRainParticles, from the
splash loop's random); main's copy in the tick is dropped. Main's Horizon
Weather gate covers the whole block, so the option still silences the rain.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Read against the vanilla 26.3 sources (Fire item_used_on_block for block transforms, waxing, jukebox discs and vault keys) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Lightning takes a copper golem back one oxidation step) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Use vanilla ambient sound intervals for guardians, hoglins, shulkers and allays) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (End the Absorption effect once the absorption is used up) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Count cake, cauldron, note block, bell, ender chest and jukebox statistics) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Merged by hand by Ludovic's AI agent (orchestrator) after the contribution gate. rdbtCVS's change request is addressed at this head (checked against the head by contrib-gate-20). Re-review was asked about 07:30Z with no new objection. Proven in wave 5's union run (main + 54 PRs, 332/332 selected harnesses; lan_e2e 18/18 in-frame and on the server thread). Thank you!
🤖 Agent: Read against the vanilla 26.3 sources (Frogs walk at vanilla speed (smooth swimming move control)) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Make allays drop what they carry on death and use the default pickup delay when throwing) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Let frogs take fall damage five points below the base amount) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Bees let out of a sedated hive stay out of hives for 20 seconds) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Bee stings only poison on Normal and Hard) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Merged by Ludovic's AI agent (orchestrator). This fixes main's clippy failure: save_mob's second minecraft:rabbit arm is unreachable, and the first one already writes RabbitType and MoreCarrotTicks. Verify is green. Thanks @FusselTV for catching it first!
🤖 Agent: Read against the vanilla 26.3 sources (Let goats breed, follow wheat and be milked) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Make breeze shots follow the world difficulty and aim higher at riders) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Give a partial item pickup the random pitch of a full pickup) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Make a detector rail's comparator read the minecart on it) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Let tadpoles be fed slime balls to grow up faster) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
remote.rs keeps both the PR's entity-event particles (20, 6/7/12/13/14/18, 3)
and main's drowning event.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Read against the vanilla 26.3 sources (Let a brush take a scute off an armadillo) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Count talking to villagers under vanilla's statistic name) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Pull the third-person camera back further when riding a happy ghast) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Signs reopen their editor on right click and take dyes, ink sacs and honeycomb) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Count fall, riding and flying statistics as vanilla does) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Fire the villager_trade advancement trigger on trades) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Step out of a minecart in vanilla's order) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Update comparators when a copper golem moves items in or out of a chest) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Furnaces: drying a wet sponge fills the bucket in the fuel slot) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Infested blocks release their silverfish when broken) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Make the fall, drowning, fire and freeze damage game rules work) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Apply suspicious stew effects when the stew is eaten) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Server players see sweep attacks, damage indicators and fishing particles) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Draw the level events the client dropped: lava and torch smoke, drips, chorus, sniffer eggs, dragon fireball burst) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Give a server player's new body its effects at once after a level change) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
main moved the cloud's client tick into cloud_client_tick; its particles
stay always visible (addAlwaysVisibleParticle).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Read against the vanilla 26.3 sources (Let a sneaking player open chest and hopper minecarts) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Charge melee exhaustion only when the hit hurts) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
The shulker's teleport particles (event 2016, from main) take the
engine's own particle level like the other block_teleportation calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Read against the vanilla 26.3 sources (End crystal explosion shows its particles on the server thread) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Save the world on SIGTERM, Ctrl+C and SIGHUP) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Lingering potions leave an effect cloud) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
🤖 Agent: Read against the vanilla 26.3 sources (Show a hit-back fireball turning at once) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
The chunk stores them, but they were defined in save.rs, which made chunk.rs
depend on save, one of the edges that keep the client lib in a single cycle.
They move to chunk.rs and save re-exports them. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#2585 and 0d496fdc8 both added a "minecraft:rabbit" arm to mob_to_nbt;
the first writes RabbitType and MoreCarrotTicks, so the second is never
reached and clippy -D warnings fails on main (unreachable_patterns).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passed at 42c653be0. Move only: sprite_mipmap_limit and its test go from gfx.rs to sprites.rs, cutting sprites -> gfx (step A of the cycle plan). pr_ready --quick green (1315 tests). Ready to merge once parity is green.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw Arabic, Persian and Hebrew text joined and right to left. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes. I merged main in (no conflicts).
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Send body armour, carpets and saddles to the single player window. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Create New World: edit the game rules before creating the world. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw mob variants: rabbit, parrot, axolotl, salmon size and baby models. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Save and load a mob's active effects. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw F3+B hitboxes for every entity. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Size players' boxes by pose, including sleeping. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
packs/pattern.rs is self-contained; atlas_sources reached into packs for it,
one of the edges that keep the client lib in a single cycle. It moves to
horizon-client-core and packs re-exports it under its old path. No
behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Freeze mobs in powder snow and shake frozen mobs and cold striders. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw the guardian's swimming bubbles and the bubble trail along its beam. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Follow vanilla's block outline rules: high contrast, F1 and adventure. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Send the drowning entity event (67) and draw the bubbles on clients. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Play the amethyst chime when walking on amethyst blocks. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Wet sponges drip water and suspicious sand and gravel shed dust. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw the smoke trail behind wither skulls and fireballs, and the wither's head smoke on a client. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Open the command block screen on a server's client and send its edits. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Show water through dropped stained glass items. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
model_library's test ended with a check of ItemRenderer::load_with_library,
which made model_library depend on item_render, one of the edges that keep
the client lib in a single cycle. The check moves to item_render's tests
with its own fixture. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Villagers in an ongoing raid splash for guests too (entity event 42). I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Check rain at the player's and arrow's position. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Check rain at the mob's position, not the global weather. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Save and load the difficulty through level.dat's difficulty_settings. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Use vanilla's head turn limits for some mobs and show a parrot in water as flying. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Use the Weather Radius for rain splashes and let the Weather option stop them. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Hide player arrows on a map in an item frame. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Fix turtle step timing and camel sand step sounds. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Fix the baby zombie villager, happy ghast and player hurt voice pitch. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Play the level-up sound for each level a gain crosses. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Play player step sounds while crawling under a low ceiling. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Stop End flashes lighting the scene when Hide Lightning Flashes is on. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Ease the night vision pulse with the partial tick. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Set the preset to Custom when Chunk Builder changes. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
sprites.rs called back into gfx for this one function, one of the edges that
keep the client lib's modules in a single cycle. The function and its test
move to sprites.rs; gfx calls it there. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Stop vexes suffocating inside blocks. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
main's jumpOutOfFluid (isFree with containsAnyLiquid and the shulker
collision) replaces this branch's is_free.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the vex holds its item at 0.55 scale with VexModel's offset (includes @kacpherek's #2560). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the #2141 regression test. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): HDR light sources under the vignette (off by default; no HDR display here). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @senjinthedragon!
LevelEventHandler plays 1501, 1502, 1046 and 1047 with
ClientLevel.getRandom(), not the server level's random: drawing them from
the world's random moved the level random in use_item_on's respawn
anchor cases (12 of 4000 differed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the shadow depth bias: shadows look the same as before on Metal and no rim flicker showed before or after (0 flipped pixels over 7 sub-pixel yaw steps). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @sifi11!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): unbounded selectors reach the other loaded levels; includes #2559 (End crystals and the dragon) and a main merge with the score-holder and execute origin arms. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): frogs walk at vanilla's speed. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the benchmark's load gate waits for its chunks. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the dig stop is covered in the LAN harness (lan_e2e dig_stop passes; includes #2563). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): a villager that loses its job site offers its new trades. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): thrown items fly between server and client (lan_e2e thrown passes). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): creative potions keep their contents (includes #2561). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the container press state ends with the screen (lan_e2e chest, containers pass). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): sprint-state regression tests. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @levzzz5154!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): items and mobs go through End portals. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @ASOwnerYT!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): Windows thread CPU time: checked and clippy-clean for x86_64-pc-windows-msvc from the Mac mini; not run on Windows. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @DanielGodriNeto!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): a client's effects are forgotten on join; the client keeps ticking effects until the server removes them, as vanilla (lan_e2e effects passes). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @socksprox!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): foxes, rabbits, goats and horses keep their state (includes #2541). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @FusselTV!
🤖 Agent: verify passed at 80751276f (7 min 38 s, in the range of the last merges: 5-7.5 min). Parity scope selected no harness. Test-only change: the 5 full-content-root mod_content tests are #[ignore]d, and pr_ready runs them when the diff touches what they load. Ready to merge.
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): movement, offhand swap, eat gating and farmland push (includes #2564; lan_e2e eating and helduse pass). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @socksprox!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): Wayland window controls: launcher clippy and tests pass on macOS and the Linux build type-checks; playhorizon lacks the xcb/xkbcommon libraries to link and run it. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @Murilinho145SG!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): held-slot answer as vanilla. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @mahirsn!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): server books: page indicator and component-list pages. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @pqhaz3925!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): an occupied horse gives no inventory, feeding or ride (lan_e2e occupied_horse, riding, mounts pass). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @rdbtCVS!
🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: mods can now turn on the game's own distant terrain, and Far View is an official mod. I merged main into the branch. render.set-far-terrain is now API level 7, because main added levels 4 to 6 (world, recipes, browser) in the meantime. The horizon-mods sandbox tests (32), the far-view wasm build, and clippy with the mods feature all pass. Without the mods feature, the LOD path is unchanged.
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): release notes keep wrapped known issues. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @rdbtCVS!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): recipe book settings persist on the server. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @rdbtCVS!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): HDR marks only the sun and moon discs (off by default; no HDR display here). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @senjinthedragon!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): HDR output builds and validates; HDR stays off by default (no HDR display here to see it). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @senjinthedragon!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): server logging survives a closed stdout. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @rdbtCVS!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the relight cache: light parity harnesses and the randomized equivalence test pass; the speed-up is the author's measurement (relight -70% in a microbenchmark, no FPS change), not re-measured here. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @DanielGodriNeto!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): jumpOutOfFluid needs a dry box and no collidable entity (Entity.isFree). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @socksprox!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): shulker placement, push and look rules, with no_collision counting only canBeCollidedWith entities. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @socksprox!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): turtles walk at their own pace. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @Louistuis!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): USE_ITEM_COMPLETE finishes the use; eating on the client (lan_e2e eat_full, eating, helduse, consume-release pass). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @Louistuis!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): the last finished ring is lit against decorated neighbours (our follow-up #2699 carries a neighbour-check fix). It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @FusselTV!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): minecarts save HasTicked and Fire. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @FusselTV!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): farm animals and frogs keep their variant; sound variants saved. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @FusselTV!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): vanilla view scale for entity render distance. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @socksprox!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): pause menu server links. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @socksprox!
🤖 Agent: verified by Ludovic's AI agent (contrib gate 20): cushion break particles reach multiplayer players. It passed in this wave's union with main on the Mac mini: the selected parity harnesses (332/332, then 122/122 on the final union), clippy -D warnings and the tests, and lan_e2e with the host on its server thread and in-frame (18/18 each). Thanks @ESHAYAT102!
The five mod_content tests that load the whole content root took minutes of
every `cargo test -p horizon-client --lib` on prepared data. They are now
#[ignore]d; pr_ready runs them when the diff touches what they load, and CI's
verify job runs them filtered with --ignored on its existing build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
render.set-far-terrain moves to API level 7: main added levels 4 to 6
(world, recipes, browser) since this branch started.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#2239 adds the same LocalDifficulty field as `id`; with the same lines
here the two branches merge without a conflict.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MinecartTNT.tick calls explode(getDeltaMovement().horizontalDistanceSqr())
when the fuse runs out; the vertical-speed branch has no vanilla source.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`VexModel.translateToHand` draws the held item at 0.55 scale, so the
vex's sword is the tiny one vanilla shows. The test checks the scale of
the item's transform; it fails without the cull-scale (it was 1).
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
InfestedMobEffect.onMobHurt, WeavingMobEffect.spawnCobwebsRandomlyAround
and WindChargedMobEffect.onMobRemoved all use mob.getRandom(): a mob's
own random, the world's for the player as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's ProjectileKind::Trident (a mob's trident) is the trident's 0.5 box with
its 0.13 eye height, as the player's tridents below.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Infested event stays immediate (onMobHurt runs in hurtServer); the
other events a hit raises are left in the outbox for the end of the
tick, as on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A blast's attacker is the projectile's owner, else main's exploding mob
(getIndirectSourceEntity); both CombatTracker fields kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#2492 takes overlay code 32 for translucent item quads (33 with glint): the
energy swirl moves to 34 and is the fifth of eight entity materials.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The guest's health goes through the same totem path as the host's
(applied on the next tick_totem), so the kill check skips a fired totem
for both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps this branch's add_effect (infinite durations, isShorterDurationThan)
over main's similar fix, and main's hidden_effects cleanup with infinite
effects kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both change Animate::Falling: suspicious sand and gravel take the new flag
false (BrushableBlock.animateTick drops dust only; the desert sound is
SandBlock's).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps the hooked trigger with main's item-entity damage (3) and main's
fish_caught per #fishes catch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps this branch's ShedScute event and main's GiftLoot (with its mob
and the turtle's mob_drops check).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.applyEffectsFromBlocks plays playEntityOnFireExtinguishedSound for any
player whose fire the rain puts out: a LAN guest or the server thread's player
(the default single player) heard nothing before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HangingSignBlockEntity.getSignInteractionFailedSoundEvent is
block.hanging_sign.waxed_interact_fail, not the standing sign's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified against vanilla 26.3 (mining hit sounds now come from the level events, once per hit as MultiPlayerGameMode/LevelRenderer do). I merged main into the branch (one conflict in level_particles.rs, both functions kept). Tested on a tree of main + this PR + #2279: the 33 harnesses its files reach passed, and two real games over LAN (host in-frame and on its server thread) heard 5-7 stone hits per block, with digging, cracks, instant digging, death-while-mining, respawn and hurt checks passing. Thanks @kacpherek!
On playhorizon's runner (CPUs 4-7, 4 harness slots, frames drawn on the CPU)
remote_fireworks still failed in #2215's job after #2589 and #2629: a
screenshot took ~1.5 s (58 ticks for two) beside three other harnesses. The
harnesses in parity_hosts.json's new solo list run alone there, after the
others; bigger machines are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`VexModel.translateToHand` draws the held item at 0.55 scale with the
vex's own offset, so the vex's sword is the tiny one vanilla shows.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
Keeps main's ExplosionSource event and charged_exploders, and this
branch's Species on Explode and hive_emptying_blast.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main added a mob's hidden_effects (effects given with hideParticles): a saved
mob effect now writes show_particles and show_icon false for them and reads
show_particles back (MobEffectInstance.Details.CODEC), so a reload keeps them
hidden.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
indicateDamage now uses main's knockback direction, which is zero when
the hit has no source position (as in dealDefaultKnockback).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps both sets of level-event sounds: this branch's chorus flower and
dripstone-into-cauldron, and main's zombie door and potion splashes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps both entity-event handlers in remote.rs: this branch's events 20,
6/7/12/13/14/18 and 3, and main's honey slide (53).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified against vanilla 26.3: LivingEntity.baseTick counts hurtTime down for a dead or riding player too, so the hurt tilt no longer restarts every frame and shakes the camera on the server-thread path. Tested on a tree of main + #2350 + this PR: the 33 harnesses its files reach passed, and the LAN hurt, death-while-mining and respawn checks passed with the host in-frame and on its server thread. Thanks @kacpherek!
Resolves potions.rs against main's thrower tracking: splashes keep
their indirectMagic source, and a lingering cloud keeps the thrower too
(AreaEffectCloud.getOwner), so its instant damage names them, else the
cloud.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
block_entity_nbt moved into horizon-client-core (crate split): save.rs
reads gateway_exit across the crate boundary, so it is public. The
generated block entities now load through decode_block_entity, which
reads the gateway's exit with this PR's decode_end_gateway.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Level.noCollision(shulker, box) keeps the entities whose
canBeCollidedWith is true for the shulker: living shulkers and adult
happy ghasts (boats and minecarts too, outside the mob world). Players
and other mobs never stop a shulker from opening or landing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The health layer spends the last of the player's health and raises the dead
flag, so Game::tick can show the death screen.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
MobEffectInstance's show_icon defaults to show_particles: an effect given with
hidden particles hides its icon too, and keeps doing so after a reload.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AgeableMob has two entries in 26.3 (DATA_BABY_ID 16, AGE_LOCKED 17), so
Strider's DATA_BOOST_TIME is 18 and DATA_SUFFOCATING 19. A boolean at 18 is
the wrong type for that slot on a vanilla client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LightmapRenderStateExtractor adds a third of the flash's intensity to the sky
factor while the boss overlay creates world fog (the dragon fight).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
acting_uuid() falls back to [0, 0, 0, 0] while level.player_uuid is unset (a new
world before its first save); the owner saved then never matches the UUID the
world gets, so the player could no longer command the pet after a reload.
acting_player_uuid() assigns the world's player UUID first, as dropped items do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractSignEditScreen.setMessage writes messages[line] and the sign;
removed() sends messages. The editor keeps its lines up to date, so a
sign removed while it is open still gets the player's text (review by
rdbt's agent).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractHorse.mobInteract (and Horse/ZombieHorse/AbstractChestedHorse)
hand an isVehicle horse to Mob.mobInteract before the inventory, the
feeding and the ride, so the passenger check goes before all three.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityGetter.getEntityCollisions keeps the entities that canBeCollidedWith
the mob: among mobs that is a living shulker, not every mob nearby.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
26.3's client LivingEntity.tickEffects only runs tickClient on each effect;
the server's ClientboundRemoveMobEffectPacket removes one. The join reset
stays. The AGENTS.md wording is the maintainer's to change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (orchestrator). Reviewed and approved; verify passed. The two parity failures (server_player_sculk, remote_fireworks) are the load flakes fixed by #2629, which merged after this run started (03:30Z); both pass on the Mac mini at this head. Thanks @kacpherek!
Both pull requests reshape Game::select in game/commands.rs, so the
cross-level branch now carries the End crystal and dragon candidates as
well: the crystals and the dragon are listed by level_candidates, and
level_holds knows them, so an unbounded selector finds them in the level
that holds them.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
The second part of the launcher's translation: Home's banner, rows, crash notice and
Join a friend, the instance row and its actions, the sidebar's headings and instance
states, the partner label, play times and "last played" dates, and the resources line.
Play's label clears its icon in wider languages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Checked against the decompiled 26.3 sources: EndPortalBlock's transition
out of the End is Relative.ROTATION plus the respawn data's yaw and pitch,
so a mob or item turns by the world spawn's angles instead of keeping its
own. Into the End it faces west and keeps its pitch, and calculateAbsolute
clamps the pitch to +-90 either way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Settings > Launcher picks an accent (violet by default, blue, teal, green, amber, red, pink)
and the player's own picture behind Home. An accent turns the violet tokens to its hue and
adjusts their lightness until text on the Play button and the accent on every background read
as well as the violet's. The picture is checked, copied under a name of its hash and shown
cropped under the banner's veils. CLI: settings accent NAME, settings background PATH|default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `/damage` builds `DamageSource(type, cause, by)` with the location or the direct entity's position for the knockback, and the causing mob gets the kill credit. It touched the same code as #2452, so the merged tree uses your `hurt_player_from` for both. Main merged in at f1f587e7c after #2452 landed (world_cmds.rs and creatures.rs: one `hurt_player_from` path that also records the direct entity, as in the union): clippy and the horizon-client lib and bin tests pass on it, and CI parity runs on this head. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
The lift's threshold read the frame, which the vignette darkens toward
the screen's edges, so a jack o'lantern lit up fully in the middle of
the screen and only at its eyes and mouth further out. Read it from the
world copy instead; the gain still applies to the frame, so the vignette
darkens highlights as before.
Telling the vignetted world from UI also failed on pixels with a channel
at 0 (a pumpkin's stem): each channel's ratio was compared to their
mean, and a 0 channel's ratio is 0. Hold the channels to one factor, the
ratio of their sums, allowing a step of 8-bit rounding.
Part of #1441.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A string table per language (launcher/lang/<code>.json, English text to its translation)
and tr()/trf() in i18n.rs. The window follows the system's language unless Settings >
Launcher > Language picks one; a missing string shows in English. This first part
translates the top bar, the account switch and the Settings page's account, update and
launcher rows; buttons, segments, section titles and page headers translate through their
widgets. CLI: settings language system|en|fr|ru.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LivingEntity.tickEffects` draws the swirls from `DATA_EFFECT_PARTICLES` and `DATA_EFFECT_AMBIENCE_ID`, only for visible effects, and your follow-up keeps `hideParticles` out of both, which matches. Main merged in at 0ab24d080 after #2432 landed (sprint dust and effect swirls both kept in client_ambient.rs and lan.rs, as in the union): clippy and the horizon-client lib and bin tests pass on it, and CI parity runs on this head. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
Settings > Account's Skin... shows the front of an offline name's skin and cape, takes a
skin PNG (64x64 or 64x32) and a cape PNG (64x32 or 22x17), and picks classic or slim arms.
The launcher keeps copies in skins/ and gives the game HORIZON_OFFLINE_SKIN,
HORIZON_OFFLINE_SKIN_MODEL and HORIZON_OFFLINE_CAPE when that name plays. CLI and MCP:
account skin.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The non-player portal path only knew Nether portals, so a cow or a dropped
item standing in an end_portal block never travelled (#1515). It now also
finds End portal blocks (EndPortalBlock.entityInside, its 6/16..12/16
entity-inside shape met along the move), and an entity through one waits as
an arrival with a fixed destination (getPortalDestination): the End's spawn
point above a rebuilt obsidian platform, facing west, or out of the End the
top of the Overworld's shared spawn column. Motion and pitch are kept, the
portal cooldown applies, and saved arrivals keep their End plans.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The top bar's account chip opens a menu of every Microsoft account, the
offline names the player keeps and the game's own name; picking one plays
as it. Settings > Account adds and forgets offline names, which reach the
game as --name. CLI and MCP: account offline [NAME], account select
(accounts or offline names), account forget-offline. Share log strips the
offline names too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
playhorizon's CI runner has CPUs 4-7, and remote.sh pinned agents' runs there
too: CPUs 4-7 sat at 100% while 0-3 idled, and window harnesses in pull
requests' parity jobs lost ticks. Runs share a lock per CPU set; an
--exclusive run (a benchmark) holds it alone. world_reopen also waits for
the world to load before its first count, as after each reopen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Launch panel shows, per instance, a switch for each one this computer
has: GameMode when gamemoderun is on the PATH, MangoHud when mangohud is,
and the dedicated graphics card with two cards (DRI_PRIME, or NVIDIA's
render offload variables with its driver). They wrap the game like the
wrapper does, outermost first, and do nothing where the tool is gone.
CLI and MCP: instance launch --gamemode/--mangohud/--dedicated-gpu.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: launcher-features-1 checked this: base main after #2602 merged, clean merge with main, launcher CI green; fmt, clippy -D warnings and the launcher tests pass locally. Queued for merge.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `DamageSource.getLocalizedDeathMessage` (the causing entity, else the direct one: the potion or cloud) and `indirectMagic`. I pushed one fix: with no entity at all, vanilla sends the one-argument message and `TranslatableContents.decompose` shows a template it can't fill as written, so I removed the fallback to `magic` / `generic`. Main merged in at e338690bf after #2442 landed (potions.rs: `splash` keeps both the owner and the block): clippy and the horizon-client lib and bin tests pass on it, and CI parity runs on this head. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
Home's Instance row gets Launch...: a shell command run in the instance's
folder before the game (which starts only if it succeeds), a wrapper the
game runs through, and environment variables, kept in instances.json.
The game stays one process: /bin/sh runs the command and execs the
wrapper and game through "$@" on Unix, cmd /C on Windows. The window's
Play and `play` both use them. CLI and MCP: instance launch (confirm
needed over MCP). The Instance row's actions move under its name, and
editing an instance that shares its name with an import works again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: launcher-features-1 checked this: base main after #2601 merged, clean merge with main, launcher CI green; fmt, clippy -D warnings and the launcher tests pass locally. Queued for merge.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: breaking the block under you no longer sends a position correction the client didn't need. Fixes#2463. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the block outline uses the shape for the held item (light, scaffolding) and the shulker box's lid, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `PolarBear` (its goals, `PolarBearMeleeAttackGoal`'s rearing and warning sound, `PolarBearHurtByTargetGoal`, `PolarBearAttackPlayersGoal`, the stand animation and `DATA_STANDING_ID`). Left for later: the height doesn't grow while standing, the anger isn't saved, and anger at LAN guests isn't tracked. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `execute on origin` (the thrower or shooter) and `on leasher`; your follow-up records the thrower of a guest's drops and the player behind a lead. Left for later: a fence knot leasher isn't an entity in Horizon, so it finds nothing. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Gui.renderSavingIndicator` (the fade in and out around the autosave), as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the baby leg animation uses the interpolated `walkAnimation` position, not the tick value, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `/tellraw`, `/title` and `/playsound` reach a server's players as their packets, and your follow-up plays a remote sound in the packet's category (`handleSoundEvent`). Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `BonusChestFeature.place` (shuffled columns, the chest with the `spawn_bonus_chest` loot table, a torch on each side that survives). I pushed one fix: `setInitialSpawn` passes the level random and the feature sets the blocks with flags 2 (`UPDATE_CLIENTS`). Left for later: it is skipped when the spawn chunk is not lit yet. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `JigsawStructure`'s codec defaults (`max_distance_from_center` 80, the default `dimension_padding`), as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `ThrownTrident` (8 damage, `dealtDamage`, the 0.99 water inertia, the hit and ground sounds, `deflect(REVERSE, ..., (0.02, 0.2, 0.02))`) and the drowned's `TridentAttackGoal`. I pushed one fix: the damage goes through `EnchantmentHelper.modifyDamage` of the trident, so Impaling adds only for `#sensitive_to_impaling` (its `minecraft:damage` requirement), not for every mob that swims. Fixes#2447. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `StructureTemplate.placeInWorld` loads each block's template NBT into its block entity, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ClientPacketListener.handleParticleEvent` with count 0 makes one particle moving at max speed times the delta. I pushed one fix: both are floats in the packet, multiplied as floats before the widening. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `AbstractHorse.aiStep`'s 1-in-200 `moveTail` and its 8-tick `tailCounter`, with the model's tail swing, match. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `ScoreContents` resolution in `/tellraw` and `/title`; your follow-up gives armor stands, frames, paintings, boats, carts and others their UUID name, as `getScoreboardName` does. Left for later: the `*` holder resolves to the acting player, not to each recipient. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the Entity Shadows option gates `EntityRenderDispatcher`'s shadow pass, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the client's `AbstractContainerMenu.clicked` predicts the sort and tweak clicks like any other, so the last sorted item stays under the cursor until the server answers. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
On playhorizon (frames drawn on the CPU, control requests answered slowly)
world_reopen read the minecarts before the mineshaft's last one arrived (3
then 4 on every reopen), and the sculk walk polled the sensor with separate
requests that missed its 30-tick active window. The minecart list is read
once it stops changing; both clients sample the sensor every tick from one
ctl script, alongside the walk.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Player.attack` sends the enchanted hit (animate action 2, `MAGIC_CRITICAL_HIT`) when the weapon's enchantments add damage, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LivingEntity.tickDeath` sends entity event 60 at 20 ticks and the client's `makePoofParticles` matches. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `BundleItem` / `ClientBundleTooltip` mouse-wheel selection (`ServerboundSelectBundleItemPacket`, cleared when the cursor leaves the slot, now on a server's client too). Left for later: the selected item's name line in the tooltip. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LivingEntity.checkFallDamage` sends the landing `block` particles (`150 * scale` of them, scaled by the fall distance, at speed 0.15) where the fall ends, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LlamaSpit.recreateFromPacket` adds seven spit particles at 0.4 + 0.1 i of its movement, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ServerExplosion.isSmall` (radius under 2, or the blocks kept) picks the small particle, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `ItemStack.addDetailsToTooltip`: the order here (instrument, bees, container, banner patterns, written book, charged projectiles with their indented details, fireworks, potion with `When Applied:`, jukebox, trim, stored and plain enchantments, dyed, lore, attributes, unbreakable, block state, spawner, then the advanced durability, id and component count) is vanilla's, and the crossbow lines match `ChargedProjectiles.addProjectileTooltip`. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `Entity.DATA_SHARED_FLAGS_ID` bits (on fire, crouching, swimming, invisible, glowing, fall flying) and the pose for LAN players; your follow-up also sends the crouching pose under a low ceiling, which matches `Player.updatePlayerPose`. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `CreakingHeartBlock` / `CreakingHeartBlockEntity.removeProtector` on an explosion kills the bound creaking. Left for later: no XP for a heart broken by a player's TNT (Horizon doesn't track the TNT's owner yet). Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `NoteBlock.triggerEvent` adds the note particle 1.2 above the block with note / 24 as its colour, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
Home's Instance row gets Shortcut / Remove shortcut, and the CLI and MCP
instance shortcut [instance] [--remove]. The shortcut runs
horizon-launcher play --instance <id>: a .desktop file on Linux (and in
the applications menu), a script app with Horizon's icon on macOS, and a
Shell Link written from Microsoft's published MS-SHLLINK layout on
Windows. HORIZON_LAUNCHER_DESKTOP points it at a scratch folder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ChunkMap.TrackedEntity.getEffectiveRange` ends in `scaledRange`, which applies `entity-broadcast-range-percentage`, as done here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `HoneyBlock` (`isSlidingDown`, `doSlideMovement`, `maybeDoSlideEffects` with its 1-in-5 sound and particle rolls, entity event 53). Your follow-up runs it for a server's player too, with the sound to the others and event 53 to everyone, which is how vanilla splits it. Left for later: mobs don't slide yet, and there is no `honey_block_slide` criterion. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `WitherRoseBlock`'s effect (wither for 40 ticks on the mobs inside it) and the eyeblossom's poison on bees. Since then you added `MobEffectInstance.update`'s rule (a stronger effect replaces, an equal one keeps the longer duration, a weaker one changes nothing), which matches. Left for later: a weaker, longer effect is not kept hidden to resume afterwards (`hiddenEffect`). Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's particle providers and `animateTick`s: honey drips (`DripParticle`), the poplar and azalea leaf particles, falling dust under gravity blocks and the campfire food smoke match vanilla's rates, sprites and velocities. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `SnowGolem.DATA_PUMPKIN_ID` and `MushroomCow.DATA_TYPE` with their layers (`SnowGolemHeadLayer`, `MushroomCowMushroomLayer`), as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: launcher-features-1 checked this: base main, clean merge with main, launcher CI green; fmt, clippy -D warnings and the launcher tests pass locally. Queued for merge.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LevelEvent` 2002 / 2007 (`LevelEventHandler`: the item crumbs, then the coloured spell or instant effect particles) and the 1053 / 1054 break sounds. It conflicted with main in `game.rs` (the zombie door sounds 1019 / 1021 landed there), so I merged main in, keeping both lists. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Entity.canSpawnSprintParticle` and `spawnSprintParticle` (the block under `getOnPosLegacy`, clamped into it, fences and walls taken from the supporting block) match. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: the parity stop at c5534f0d7 was friends_integration, which main broke through #2501 and #2598 fixes, plus remote_fireworks and server_player_sculk (load on the old runner; both pass on main on the Mac mini). None of the three is from this PR. Merged main (fde2dfffe, a clean merge); pr_ready --quick passed: 1357 tests, clippy, launcher.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the server sends entity event 4 once its warmup ends (`EvokerFangs.tick`), `handleEntityEvent(4)` starts the client's attack and plays the sound locally, and the client only counts `lifeTicks` down for the animation, while the server deals the damage and discards. Moving the host's sound to `play_unshared` matches that, since each client plays its own. It conflicted with #2094 (both hook entity events) once that merged, so I merged main into it, keeping both calls. `lan_e2e.py --check fangs` passes in both modes. It conflicted again with main in `tools/lan_e2e.py` (new checks on both sides), so I merged main in once more, keeping every check; clippy is clean. Fixes#1482. Thanks @rdbtCVS! Gate: this batch merged with wave 1 in one tree: clippy `-D warnings` clean, 1164 horizon-client lib tests pass, and of the 88 harnesses `parity.py --changed-files` picked, 87 pass (`block_entity_render` fails from #2099, which is held).
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `EnderDragonFight.tick` scans the state and places the exit portal only when `dragonEvent.getPlayers()` is not empty, so a force-loaded End with nobody in it gets no portal, as here; the new `forced_end` harness compares both servers. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `WorldGenRegion.setBlock` gives a block with a block entity a `DUMMY` entry that becomes `newBlockEntity`'s default, so the deep dark's sensors, catalysts and shriekers and potent sulfur get theirs, while placed data is kept. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `SpikeFeature` places the end crystals during decoration, so they are saved with the chunk that generates them, as here. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `FallingBlockEntity` with a `BrushableBlock` breaks it where it lands (`onBrokenAfterFall`'s sound and level event). Left for later: the game event uses the block position, not the bounding-box centre. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ForceLoadCommand` adds its FORCED tickets x-outer, z-inner, all at once, so the decoration order follows from that; the new `forced_order` reproduces it with the same ticket simulation as `vanilla_order`. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
Settings > While playing: Stay (the default), Minimise, or Hide (macOS).
The window goes away when the game starts and comes back to the front
when it closes, with the run's play time, errors and log. CLI and MCP:
settings while-playing stay|minimize|hide.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: a chunk's `STRUCTURE_STARTS` are its `getAllStarts`, whether or not a piece reaches it; `starts_at` now gives those, as vanilla saves them. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `StructureTemplate.placeEntities` and the pieces' markers call `finalizeSpawn(level, difficulty, STRUCTURE, null)` inside the `WorldGenRegion` with its random, and the save keys now follow `addAdditionalSaveData`. Your rebased head is the reviewed change plus the comment cleanup, on current main. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @FusselTV!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: these three options have no system in Horizon yet, so they are drawn inactive with the same notice the other unavailable options use, and clicks do nothing. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `MusicManager.MusicFrequency` and the option's listener (`setMinutesBetweenSongs`). I pushed one fix: in `SoundOptionsScreen` Music Frequency starts its own row (beside Music Toast), not beside Show Subtitles. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3's `EntityAnchorArgument` and `execute positioned as` (it keeps the anchor). I pushed one fix: a small armor stand's eyes are at 0.9875 (`BABY_DIMENSIONS.withEyeHeight`), not half the type's, and the types' eye heights are floats widened as `getEyeY` does. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the High Contrast option adds or removes the `high_contrast` pack (`Options.updateResourcePacks`), and your follow-up keeps the pack screen's selection in step. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `HumanoidModel.setupAttackAnimation` and `AnimationUtils.swingWeaponDown` use the main arm of a left-handed mob, and `PiglinModel`'s crossbow and admiring poses mirror with it. Gate: this wave's 50 PRs merged on main in one tree (union 9033a9c3b): fmt and clippy `-D warnings` clean, the horizon-client (1136 lib plus the integration tests), horizon-client-core (37), horizon-world (165) and horizon-protocol (100) tests pass, the fast build links, and the 140 harnesses `parity.py` picks for the tree's changed files pass on playhorizon (remote_fireworks, a known load flake there, passed on a rerun on a quiet Mac mini). `lan_e2e.py` passes 14 checks with the host on its server thread and in-frame (chest, xp, damage, kills, projectiles, mobs, drops, body, stopsound, digging, instant_digging, books, knockback, barter); `hurt` fails on main too when run after `damage` (check-order dependency, not this wave). Thanks @kacpherek!
Home's Console shows the game's output of this run as it comes, with a
search, Follow, Copy and Open logs folder. Share log uploads it to mclo.gs
only after the player confirms, with the home folder, user name and
account names taken out first, and can delete the paste again. CLI:
share-log and unshare-log (MCP tools, the second needs confirm).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A file dropped on the window goes where it belongs:
- a shared instance becomes a new instance, which gets selected on Home;
- a Java modpack goes to the Modpacks import;
- a resource or shader pack goes into the selected instance;
- a data pack points to the world rows.
A note in the corner says what happened, until it is closed. The Packs
section and the world rows still take their own drops, and now stop the
drop there so it isn't imported twice.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`horizon_launcher::import` works out what a file is from its contents:
- a shared instance: a .mrpack needing no mod loader (as `instance
export` writes) or a game-folder zip;
- a Java modpack: a .mrpack with a loader, or a CurseForge zip;
- a resource or shader pack;
- a data pack.
A shared instance becomes a new instance named after the pack, with
"(2)" if the name is taken. Its overrides/ (or the zip's entries) are
copied in, but only the files an instance keeps, and only paths that
stay inside its folder. Its .mrpack files are downloaded from Modrinth
and checked against their SHA-512. If anything fails, the half-made
folder is removed.
CLI and MCP: `horizon-launcher instance import <file> [--instance <i>]`
does what dropping the file on the window will do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Instance row gets Export…, which opens a panel to pick .mrpack or
.zip and switch on the worlds to include. The file goes to exports/ in
the launcher's data folder under a free name, and the panel offers Show
in folder. The panel closes when another instance is picked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`horizon-launcher instance export [<instance>] [--format mrpack|zip]
[--world <w>]... [--out <file>]` (and its MCP tool) writes what makes an
instance, or a port: options.txt, horizon-*.txt, servers.dat, resource
and shader packs, horizon-mods/ and config/, plus the worlds named. It
never includes logs, screenshots, backups, caches or account files. A
world leaves out playerdata/, stats/ and advancements/, and its level.dat
is written without the player's UUID.
A .mrpack follows Modrinth's published format: modrinth.index.json
lists the resource packs the browser installed from Modrinth that are
unchanged, by their download with SHA-1 and SHA-512 asked from
Modrinth's API. The rest goes under overrides/. An instance with nothing
to share is refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mods page gains a Data packs tab: Modrinth's data packs for 26.3,
each marked Partly with what Horizon runs of a data pack today
(functions, function tags, predicates, item modifiers). Install puts the
project's data pack file (not its mod build), checksum checked, into the
datapacks/ of a world picked on the tab, and says what of that zip runs
and what isn't read yet. A pack with nothing Horizon runs (worldgen
only) is refused. The world's list on Home shows it.
CLI and MCP: `mods search --kind datapack`, `mods install <pack> --world
<world>`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The shadow quads lie exactly on block tops like vanilla's, but terrain
and shadows are separate draws: their fp32 depth interpolations disagree
by a few dozen ulps of the fragment depth, and at a constant bias of
16 ulps the rim re-rolled the fight with the block top as the camera
drifted a subpixel - the visible speckle. Subpixel yaw glides at three
camera pitches (70, 45, 20 down) measured ~88/~60/~26 strong flip
pixels a frame with the shadow, all on the rim, none without it.
A constant bias of 128 ulps covers the spread at any resolution while
staying far below what the 0.001 a model's feet sit higher amounts to,
so shadows never bleed onto the feet.
Co-Authored-By: GLM 5.3-Flash <[email protected]>
A chunk was lit only once all eight neighbours were finished, so the
outermost finished ring of a loaded area (a /forceload square, a /pregen
band's edge, a player's last ring) stayed unlit: saved as FULL with no
light at all, and with no generation animals, which wait for light.
Vanilla's LIGHT step needs its neighbours only at INITIALIZE_LIGHT
(ChunkPyramid), so its last FULL ring is lit against decorated chunks.
A light job now reads a neighbour that will not finish (past the area)
from its decorated proto chunk (ChunkGenerator::light_view). A chunk
saved unlit is saved again once lit, and one whose light changes when a
neighbour finishes later is saved again. `execute if loaded` needs the
chunk lit, as vanilla's needs it ENTITY_TICKING.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mods page and `mods` commands installed everything into the default
game folder whichever instance was picked. Mods, resource packs and
shader packs now go into the selected instance's folder, and the
Installed list, updates, Remove and the mod switches are that
instance's; picking another instance reads its list. The page says which
instance it installs into when there are several. `mods list|install|
update|remove|enable|disable` take `--instance` (default: the selected
one). Java mods' checks stay shared in the default folder for the
search results.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A remote client no longer tramples farmland or turtle eggs locally:
BlockLandingWorld.is_server_level/is_client_side now reflect the
player's client_side flag, as vanilla's fallOn gates trampling and
entity events on `level instanceof ServerLevel`. The server's block
change and teleport drive the change, so a player no longer ends up
inside the grown block (phasing through when farmland turns to dirt).
- key.swapOffhand, key.drop and a mount's inventory key on a server now
wait for the tick's input phase instead of sending a packet straight
from the window event, like the click queue: no action packet leaks
between CLIENT_TICK_END and a later pong.
- remote_use_item treats a refused consume like vanilla's
InteractionResult.FAIL: a full player who right-clicks food no longer
starts a use the server will refuse (the eat/noslow desync), the off
hand gets its turn, and no use cooldown starts.
- The host's "moved too quickly" check subtracts the guest's
server-simulated velocity like vanilla's expectedDist term, so a
fast legitimate arrival (e.g. landing with elytra) isn't flagged.
Co-Authored-By: Devin <[email protected]>
🤖 Agent: play into a world / join a server + servers.dat list (item 4 of launcher-features-1), on main after #2593: verify and launcher CI passed on this head, merges cleanly with main; launcher fmt, clippy -D warnings and lib tests pass locally (servers.dat round trip keeps every field and hidden entries); screenshots in the PR.
datapacks.rs lists a world's datapacks/ (zips and folders with
pack.mcmeta, as the game discovers them; off when datapacks.json lists them
disabled), adds a data pack zip under a free name (refusing resource packs:
no data/ folder) and removes one to the trash, forgetting it in
datapacks.json. The game turns a new pack on when the world opens.
Home: each world gets "Data packs", which opens its packs under the row
with Add data pack… and Remove; a zip dropped on a world goes into it.
CLI: `worlds datapack-list|datapack-add|datapack-remove <world>`;
datapack-remove needs confirm over MCP.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: worlds in the launcher (item 3 of launcher-features-1), on main after #2591: verify and launcher CI passed on this head, merges cleanly with main; launcher fmt, clippy -D warnings and lib tests pass locally (vanilla-style backup layout, copy naming); screenshots in the PR.
The Mods page's Installed list gets "Update all (N)" when newer versions
were found: every pack with one is updated in turn on a thread (each
download checked as an install is), with "1 of 2: …" progress, and one that
fails is named without stopping the others. The CLI already does this with
`mods update` and no pack name (MCP: mods_update).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
thread_cpu() returned None on Windows. Implement it from thread cycle counts,
inside horizon-threads so horizon-client keeps forbid(unsafe_code).
Co-Authored-By: Claude <[email protected]>
Entity.isFree is noCollision(this, box) && !containsAnyLiquid(box); the
check only looked at block collisions, so a mob pressed against a wall by
water flow hopped out where vanilla refuses, and it ignored other
collidable entities entirely.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
desktop_seams.rs holds the launcher's two hard-to-undo desktop effects.
HORIZON_LAUNCHER_TRASH=<folder> makes Delete (worlds, screenshots, packs)
move into that folder instead of the system's trash, and
HORIZON_LAUNCHER_CLIPBOARD=<file> makes every Copy in the window write into
that file instead of the clipboard. Test runs set both inside their scratch
folders.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Relight now runs over a Region that clones each touched section's Arc once,
copies its light on first write and writes back only sections whose light
changed. The increase loop reads state and light in one lookup. Output is
identical; a randomized border test checks it against a full relight.
Co-Authored-By: Claude <[email protected]>
A rejoining player kept the previous session's effect list, so the HUD
could show effects the server no longer had (nothing applied in combat,
no icon was earned). Vanilla's new LocalPlayer starts empty and gets its
effects from UpdateMobEffect; match that, and let the client-side tick
drop effects whose duration ran out, as its removeIf does.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Agent: version per instance (item 2 of launcher-features-1), now on main after #2590: verify and launcher CI passed on this head, merges cleanly with main; launcher fmt, clippy -D warnings and lib tests pass locally (pin/unpin test); screenshots in the PR.
Home lists the selected instance's resource and shader packs with a switch
each (disabled while the game runs: it rewrites options.txt itself), Remove
after a confirmation, and Add pack… or a .zip/.hshader dropped on the
section. switch() now takes any id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
packs.rs lists an instance's resource packs (resourcepacks/, a .zip or a
folder with pack.mcmeta) and shader packs (shaderpacks/: Horizon's own and
the shaders/ format, as the game's Shader Packs page lists them), and turns
them on or off the game's own way: resourcePacks in options.txt (a pack
turned on goes on top) and pickedShaderPack in horizon-graphics.txt (one at
a time); every other line of both files stays. add() copies a dropped file
into the right folder under a free name; remove() turns it off and moves it
to the trash.
CLI: `packs [list|add|on|off|remove] [--kind] [--instance]`; `packs remove`
needs confirm over MCP.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: fixes parity-main's friends_integration (red since 4be5c8eb5). Cause: #2501 spawns new players at a random spot near the spawn, as vanilla does, and test_friends.py assumed the guest stood on the spawn itself. Main failed 4/4 on the Mac mini, main with #2501 reverted passed 2/2, and this branch passed 4/4 on the server thread and 1/1 in-frame. Python only; harness-only, skip-release.
🤖 Agent: launcher instances in the window (item 1 UI of launcher-features-1), now on main after #2587: verify and launcher CI passed on this head, merges cleanly with main; launcher fmt, clippy -D warnings and lib tests pass locally; screenshots in the PR.
Pages kept as components for their click events wrote a plain text page as
{text:"p"}; vanilla's codec writes a component with no style or siblings
as its string (the hotbar harness).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A shulker used to check only that its cell was air and the support
block had a full collision box. canStayAt now mirrors vanilla: a moving
piston in the shell's own cell does not force a teleport, the support's
face toward the shell must be sturdy, and the fully opened shell's box
must be clear of blocks and entities. teleportSomewhere likewise checks
the target cell with noCollision and refuses faces tagged
minecraft:shulker_does_not_teleport_to, emits level event 2016 (whose
particles now render), and forces a position sync.
The shell easing toward its peek now pushes collidable entities out of
the swept box (onPeekAmountChange / MoverType.SHULKER) through a new
MobEvent::Shift, so standing on a shulker lifts you instead of wedging
you inside the lid.
ShulkerLookControl is ported too: the head yaw is computed in the
attach face's plane (new LookKind::Shulker and look_yaw) instead of the
generic world-yaw, and hurtByTarget now ignores damage from other
shulkers as toIgnoreDamage does.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Agent: launcher instances core (item 1 of launcher-features-1). Verify and the three-platform launcher build passed; fmt, clippy -D warnings and all launcher tests pass locally, including the migration test (no existing file moves); CLI and play checked in scratch homes.
Home shows the selected instance's newest screenshots (what F2 saves in its
screenshots/ folder), all of them on request, and opens one over the window
with Newer/Older (and the arrow keys), Copy (the picture to the clipboard),
Open, Show in folder and Delete (to the trash, after a confirmation).
CLI: `screenshots [list|delete] [--instance]`; `screenshots delete` needs
confirm over MCP. worlds::to_trash is shared by worlds and screenshots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: game quick-play flags (item 4 of launcher-features-1): pr_ready --quick passed (1281 horizon-client tests, clippy, fmt); verify passed and CI's parity scope selected no harness for this argument-parsing change; checked by hand with the real game (missing world → title screen, existing world opens).
Every run of the game started from the window or `horizon-launcher play`
adds its length to its instance's play time in instances.json (by id, so a
renamed instance keeps it), with when it ended. Home's Instance row shows
"3 h 06 min in total · last played today"; `instance list` shows it too,
and its JSON gives played_secs and last_played_ms.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each world on Home gets Play (the game starts with --world <folder>), and
Home lists the selected instance's servers from the game's own servers.dat:
Join starts the game with --server <address>, Add server and Remove change
the file. server_list reads and writes it as vanilla's ServerList does
(uncompressed NBT, a new file then servers.dat_old) and writes every entry
back with all its fields, the hidden Direct Connection ones too.
CLI: `multiplayer [list|add|remove] [--instance]`, `play --server`;
MCP: play takes `server`; `multiplayer remove` needs confirm.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging. Formatting only: Cargo.toml's workspace members one per line (38, including #2160's), so new crates stop conflicting. tomllib parses it identical to main and Cargo.lock is unchanged, so no parity run is needed; verify and launcher pass.
The "names its own person" rule was ambiguous about which identity an
agent should attribute a post to; handles are the public identity.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The launcher starts the game straight into a world or a server. Vanilla's
names for that (client Main, QuickPlay) now work too: --quickPlayMultiplayer
is --server; --quickPlaySingleplayer opens a world of saves/ only if it
exists, as QuickPlay.joinSingleplayerWorld does, and shows the title screen
otherwise (--world also creates one).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Home lists the selected instance's worlds from their level.dat (name, mode,
version, last played) with their size and picture. Back up zips one into
backups/ as vanilla's Make Backup does; Copy makes "<name> (1)" with that
name in its level.dat; Delete moves it to the system's trash after a
confirmation (the file manager call on macOS, no Finder permission);
Open folder. `horizon-launcher worlds list|backup|copy|delete`, and their
MCP tools (delete needs confirm). level.dat is read with horizon-world's NBT.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging the first crate split (horizon-command + horizon-client-core, pure move, re-exported under the old paths). CI parity passed at 17f8419ac (head merged into main), verify and launcher pass, approved by rdbt's review agent; landed by hand while the REST pool refills.
streaming.radius reverts to the client setting in apply_settings, so
an options change on a server with a lower view distance raised every
entity's render range past vanilla's. min(render_distance,
remote.view_distance) is Options.getEffectiveRenderDistance.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
An instance plays the launcher's version, which follows updates, unless it is
kept on another installed one: the Play page's picker pins the selected
instance (picking the version marked "follows updates" unpins it), and
`instance version <instance> <tag|latest>` does it from the CLI. play,
`play --instance` and MCP play use the pin; a pack's Play uses its instance's.
The sidebar shows a pinned instance's version, or that it is not installed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The sidebar lists the instances by group with their icons (a texture from the
player's prepared files, drawn as pixels, or a colour); clicking one selects it
for Play. + makes one, empty or a copy of the selected one (copied in the
background); Home's Instance row edits its name, group and icon, copies it, and
opens its folder. The dialog's name and group are the launcher's shared text
fields.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #2501 a player new to the world spawns at a random free spot near the
world spawn, as vanilla's PlayerSpawnFinder does. The guest often stood against
a hill, so walking forward moved it less than a block, or terrain hid the block
it had to aim at: friends_integration failed on every run on the Mac mini.
The host now clears a floor beside itself and teleports the guest there before
the movement checks, and the block check waits for the guest to stand still.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The game's folder stays the default instance, "Horizon", where it is; imported
and registry modpacks are listed as instances in their own folders; new ones
go to instances/<slug>/ in the game's folder, empty (the player's options and
offline name) or a copy of another (never sessions, logs or telemetry). Names,
icons and groups live in the launcher's instances.json, so nothing moves.
Play, `play --instance` and MCP play start the selected instance, which
follows the launcher's data collection answer and No AI. Crash reports are
read from the run's own folder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A manifest line defining or turning on a workspace crate's feature no longer
makes the manifests count, if every source naming the feature (or one it turns
on) changed too, as in a split, where the new crate's files are all new.
#2215 (horizon-items with a test-support feature) now selects 209 harnesses
instead of 354.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On playhorizon (frames drawn on the CPU) a screenshot takes ~0.6 s: with a
16-tick wait the later shots came after the sparks' lifetime, and the setup's
own red chat lines, gone by then, made the host's count negative.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A piston pushing a slime block moves the body in front of it and bounces
it: the block's swept collision shape pushes the body out of the way, and
the slime block sets the body's velocity along the push axis
(`PistonMovingBlockEntity.moveCollidedEntities`, the `causeBounce` branch).
The new `piston-slime` LAN case checks both for the host and the guest, with
a plain block as the control: a plain block only pushes the body 0.81
blocks, the slime block pushes and bounces it 2.5.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
Vanilla's frog uses a SmoothSwimmingMoveControl(85, 10, 0.02, 0.1, true), so
a walking frog covers 0.1 blocks a tick (its movement speed attribute is 1.0
and the controller's outside-water speed is 0.1). Horizon gave every generic
animal the plain ground controller, which drives the mob at the attribute
itself: a frog walked ten times too fast, jittered, teleported and flew
(#2213).
The regression test fails without the change.
Each new crate then adds one line instead of rewriting the whole list, so
PRs that add crates at the same time stop conflicting (the crate split's
#2160 conflicted on it three times).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Game::select` gathered its candidates from the level in place only, so
`@e[tag=...]` never found an entity in another dimension the server
still holds: vanilla's `EntitySelector` searches every loaded level
unless the selector carries a spatial constraint.
The selector now searches each loaded level in turn, and the commands
that act on the entity it found (`/data get entity`, `/kill`, `/tp`)
act on it in the level that holds it. The experience orbs and the
projectiles are named by their entity id, like the other entities, so
the entity a command acts on is the one the selector found.
Fixes#1524
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
A block the client breaks at once (a creative break, a dead bush) sends no STOP or ABORT, so the server has to forget the dig itself. Without it the server keeps mining the emptied cell: the block put back there is destroyed again (#1340) and the mining sound keeps playing at it (#1908, the cell's sound group is stone).
The new dig_stop check breaks a stone block in creative and a dead bush in survival for the host and the guest, then puts the block back and waits: it must stay, and no mining sound may play.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
HORIZON_E2E_HEADLESS launches the games with --offscreen --no-pause, so the suite runs on a machine with no focused window: without it the games pause on the focus loss and wait_ready never completes. macOS keeps its offscreen launch.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
The benchmark's readiness rule waits for the resident chunk count to reach
the view's target before "steady" means loaded, so a scene that starts with
an empty view (the integrated server has not sent its first chunk yet) is
measured after the world is really there. The rule had no test, so a change
could silently drop it. This adds one: a fake control that keeps the counts
steady and the queue empty for a second and a half, and only then lets the
chunks arrive, must not end the wait before they do. Without the gate the
wait ends after the quiet second and the test fails.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
finalizeSpawn picks a fox's type (red or snow), a rabbit's type, a goat's
screaming flag and horns and a horse's max health, but the save and the
load did not keep them: a snow fox came back red, every rabbit brown, a
goat that had lost a horn grew it back, and a new horse was saved with
the type's health (53) above the max health it rolled.
- Fox, Rabbit and Goat.addAdditionalSaveData: Type, RabbitType,
IsScreamingGoat, HasLeftHorn and HasRightHorn are saved from the model
and read back (an unknown rabbit type is brown, horns default to
present, as vanilla reads them).
- The fields Horizon does not model (a fox's Trusted, Sleeping, Sitting,
Crouching, a rabbit's MoreCarrotTicks, a horse's EatingHaystack and
Bred) keep a loaded value or get vanilla's default.
- A loaded horse or llama gets its health clamped to its max health, as
LivingEntity.setHealth does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reported sequence in #1639: a villager works at a bench, the bench is
destroyed and it takes another bench's job, and then it does not open a
trading menu.
`Villager.setVillagerData` drops the offers (`offers = null`) when the
profession changes, so the next `AbstractVillager.getOffers` rolls the new
profession's trade set. The brain reaches that through `set_profession`,
which now also marks the offers as not made (#2009); this test covers the
whole reported sequence, from the lost job site to the new profession's
trades, so a villager that keeps the old profession's offers marked as made
cannot pass it.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
`move_pushed` resolved the pushed movement with `SceneCollisions`, which
supplies no world-border collision, so a piston next to the border could push
a player through it even though ordinary movement stops at the border.
`Entity.move` collects the border's walls in
`collectCollidersIgnoringWorldBorder` whenever
`WorldBorder.isInsideCloseToBorder` holds, and `CollisionGetter.borderCollision`
adds `WorldBorder.getCollisionShape` to them. The push now resolves through
`EntityCollisions::with_border`, which applies that same rule to the pushed
body's own x and z, so a body pushed into the border's wall stops there.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
Arrows now ask whether it rains at their spot (biome precipitation and
blocks above, Level.isRainingAt) instead of just the sky light, and a
Riptide trident needs rain at the player's position. Rain also puts out
a burning player (Entity.baseTick), in the window and for guests on the
server thread.
Water sensitive mobs, endermen, dolphins, wolves, sun burn and the happy
ghast's healing now ask the rain at their own spot (Entity.isInRain,
Level.precipitationAt) instead of the world raining flag. Rain also puts
out a burning mob, and striders are hurt by water and rain.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Axolotl.handleAirSupply` (6000 air, refilled by water or rain, `dry_out` damage at -20) and `Squid.hurtServer`'s `spawnInk`. I pushed two fixes: `makeSound`'s voice pitch is drawn even when the squid is silent, so the ink's random draws stay in vanilla's order; and rain now uses the biome's height-adjusted temperature (`Biome.getTemperature` with the frozen modifier and the drop above sea level, `Level.isRainingAt`), not the base temperature, so an axolotl high up a mountain is not refilled by snow. On main with this head: clippy `-D warnings` clean and the 1186 horizon-client lib tests pass. CI parity runs the harnesses this head reaches. Thanks @kacpherek!
A server's client ticks a projectile itself between the entity tracker's
updates, as vanilla's Entity no-op interpolation handler does; before #2086 it
only eased towards the server's position, so a thrown egg, ender pearl or wind
charge hung in the air for half a second. The client tick is now a named
function with unit tests for that flight, and the parity harness keeps the
reproduction the issue asked for: a connected client throws each of them and
samples their position twice as often as the tracker's interval. The LAN
harness can also run headless now (HORIZON_E2E_HEADLESS), so the games render
offscreen and never pause on the focus loss on a machine with no focused
window.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
#1832 reported that a potion taken from the creative inventory and put in
the hotbar turned into the uncraftable potion. The hand-over keeps the
stack as it is on `main` (#1796 and #1874), so this adds the test that
keeps it that way: the item tab's stack is the real one, the creative
screen hands it over unchanged, and the hotbar slot holds the brew.
Fixes#1832
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
AbstractContainerScreen keeps `isQuickCrafting` and the drag it belongs to
on the screen, so every screen starts with its own state. Horizon keeps
both on `Game`, and #2052 only dropped them when the pointer was grabbed
again, so a press whose release never arrived (the pointer left the
window) kept the drag running on the next screen: `Clicks::press` answers
`None` for every later press, so the Shift quick move and the clicks on
the inventory went dead.
The press state now goes with the pointer: `Game::capture` drops it
whichever way the pointer changes hands (a screen opens or closes, the
window loses focus), and `Clicks::cancel_drag` names the drop.
Fixes#1737
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
On a server client (single player's server thread, LAN guests, dedicated
servers) the window ended a consumable's use on its own timer and never ate
the food: its FoodData waited for the server's next ClientboundSetHealth.
With the button still held, the next startUseItem found the food bar below
20 and began another bite, so the eating animation went on after the bar
was full (#2471).
Vanilla's client never completes a use by itself: LivingEntity.updateUsingItem
completes it only on the server, and ServerPlayer.completeUsingItem first
sends the player entity event 9 (USE_ITEM_COMPLETE). Player.handleEntityEvent(9)
then runs completeUsingItem on the client, where Consumable.onConsume calls
the FoodProperties listener on both sides: the client's FoodData eats at once.
- The server sends event 9 to the acting guest when a consumable, a potion or
a spyglass use completes. Like the guest's packets, the events are queued
during their turn (kept across nested turns) and sent with their entity id.
- The client keeps using until that event, then shows the last crumbs, eats
the food (the next ClientboundSetHealth replaces it) and starts the item's
use_cooldown, which ItemStack.use only starts at once for an item with no
use time.
- What vanilla's client checks so that a use can't outlive the server's:
MultiPlayerGameMode.useItem passes on an item cooling down (chorus fruit
held through its cooldown), LivingEntity.updatingUsingItem stops when the
hand no longer holds the item the use began with (ItemStack.isSameItem),
a respawn is a new player with no use, and Consumable.startConsuming
starts none for a consumable with no use time.
- ItemCooldowns tick where Player.tick ticks them: after LivingEntity.tick's
use on the server and after Minecraft.handleKeybinds on the client. Both
sides ticked them first, so a guest's entry ended a tick later on the
server than on their client, whose held retry was refused and never ended.
lan_e2e's new eat_full check eats melon slices up to a full bar with the
button still held: before, a new bite had started (use ticks 26, 2, 9, 15);
now nothing is in use. Held on two chorus fruits, both are eaten across the
cooldown. consume-release holds its completed use a little longer, as the
use now ends with the server's event.
Fixes#2471
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Horizon's command targets enumerated the ordinary entity collections
only, so `@e[type=end_crystal]` and `@e[type=ender_dragon]` matched
nothing: the End crystals and the dragon live in their own lists, and
neither carried an entity id a command could name.
The crystals and the dragon now take an id from the entity counter when
they are created, as EndCrystal's and EnderDragon's constructors do; the
selector offers them as candidates; `/data get entity` reads them and
`/kill` removes them the way `Entity.kill` does. `/summon
minecraft:end_crystal` creates one, as vanilla does.
Fixes#1531
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
Turtles used the default MoveControl, which sets the speed straight to
speedModifier * movement speed (0.25 on a stroll). Vanilla gives them
TurtleMoveControl: updateSpeed halves the speed every tick on land (never
below 0.06), and in water lifts the turtle 0.005 and cuts the speed away
from home (0.08) or as a baby (0.06); tick then eases the speed an eighth
of the way to the wanted one, faces the wanted point with the body, lifts
towards it and never jumps. On land that settles at 0.08375, so a
strolling turtle covered about nine times the ground per tick that
vanilla's does (the input is speed squared). TurtleModel swings the legs
by 8 * walkSpeed at walkPos * 5, so the walk animation fed by that motion
spun the feet erratically.
This only fixes the move control. Swimming still differs from vanilla:
Turtle.travelInWater (moveRelative(0.1F), 0.9 drag, sinking 0.005 without
a target unless heading home within 20 blocks of it), TurtlePathNavigation
and the turtle goal set are not ported yet; turtles keep the generic
fluid travel and goals for now.
Vanilla: Turtle$TurtleMoveControl.tick / updateSpeed, Turtle.<init>,
TurtleModel.setupAnim, LivingEntity.updateWalkAnimation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The maintainer asked that CI stop using his desktop (2026-10-26). playhorizon's
runner now carries horizon-game and the desktop's runner none of the job labels;
full-parity jobs go to playhorizon only, with a 6 h timeout on its 4 CPUs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mob's effects were never written to its NBT and never read back, so
they were gone after a reload. LivingEntity.addAdditionalSaveData and
readAdditionalSaveData store them as active_effects.
A player's effect saved show_particles as always on and read it back
from show_icon, so an effect given with hideParticles came back with
particles.
Cargo.lock is a shared input and every harness depends on its crates'
Cargo.toml, so a crate split (#2160) selected all 352 harnesses although
its moved code reaches 123. With --changed, Cargo.lock and Cargo.toml
changes that only add, remove or move workspace crates (no registry or git
package, version, feature, dependency or build setting changed across them,
compared with the merge base) are left out; the moved code counts through
its own sources. Any other manifest change still counts, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under parity.py the release and fast profiles have identical settings
(PROFILE: no LTO, 16 codegen units, incremental), yet 38 harnesses built
horizon-client in release and 16 others in fast: every run where the client
changed compiled it twice. They all use fast now. Release stays for
horizon-world's probes, which no fast harness builds.
Measured on playhorizon (4 CPUs), all 13 probe builds after 78 merges:
1578 s, of which the release client (bin + client_probe) took 485 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sprint is a state, not a held key: LocalPlayer.aiStep starts it with the
sprint key and stops it only on a vanilla stop condition, so releasing the
key keeps it while forward is held. The server-side overwrite that rewrote
the flag from the held keys is gone, so the underwater swim, the sprint
jump and the flight speed follow the state on main. This adds the
end-to-end regression tests that were missing for those three paths: the
swim speed with the key released, the jump impulse with the key released,
and the flight speed that follows the sprint state and not the sprint key.
Co-Authored-By: minimax-m3.1-flash (Claude Code) <[email protected]>
The body slot (horse armour, llama carpet, harness, nautilus armour) was sent as the head slot and the client ignored slots 6 and 7. Send the body item as slot 6 and apply slots 6 and 7 to the fields the renderer reads.
ArmorStandRenderer draws worn armour through the same
HumanoidArmorLayer as a living entity, so its pieces should use the
armour glint when ItemStack.hasFoil() is true. The stand's equipment
loop now checks the stack's enchantment_glint_override component, then
the item's default override, then its enchantments component (vanilla's
Item.isFoil), and draws both the armour and its trim layer with overlay
13. The existing ARMOR_GLINT path supplies the model UV and samples
enchanted_glint_armor through ARMOR_ENTITY_GLINT_TEXTURING.
Co-Authored-By: ZCode <[email protected]>
RabbitRenderer, ParrotRenderer, AxolotlRenderer and SalmonRenderer pick
their texture or model from the variant the mob already carries, and the
baby models and textures of the other AgeableMobRenderer species are used.
A baby zombie villager uses its own texture.
Structure mobs (bastion piglins, brutes and hoglins, ocean ruin drowned,
village and mansion mobs, witch huts, monuments) were tagged and finalized
at load by the cut-down spawning routine: no STRUCTURE reason (piglin
weapons rerolled), no Mob step (follow range bonus, left-handedness), no
baby hoglins, no brain memories, no drowned nautilus mounts, and draws
from each mob's own random. The decoration region now calls a hook the
game installs in the generator workers, which runs mobs::finalize with
Reason::Structure, the region random in placement order and the level's
difficulty with no inhabited time; jockeys come back as the vehicle with
its Passengers. A mob the hook cannot finalize keeps its tag and is
finalized on load as before, as are mobs of older saves; other entities
drop the tag. The generator gets the level's difficulty as soon as the
level opens, so the spawn area's mobs use it too. mob_to_nbt writes Invulnerable and LeftHanded always, IsBaby
for zombies and piglins, Age for hoglins, the finalized attributes and
memories, and drops the template keys vanilla never saves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging. Ludovic asked for the Sculk banner visible on open without scrolling, hideable after a while. Launcher run with isolated homes: banner at the sidebar's foot, HIDE after 3 min seen; clippy -D warnings and 137 launcher tests pass.
Glowing entities are drawn as team-coloured silhouettes into an outline
target with no depth test, then vanilla's entity_outline chain (sobel,
box blur across and up) is blended over the frame after the hand.
Mobs, players, item entities and projectiles get outlines. The server
sends shared flag 6 for mobs and items, and the remote client reads it.
A "Hide" shows beside its label after the banner has been on screen for
3 minutes, counted over every start (settings: partner_seen_secs, saved
each minute). Hiding it is kept (partner_hidden); the Servers page keeps
its own banner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Home it sat under every row, so nobody saw it without scrolling. It now
sits small (228 px wide) above the Discord and Website links on every page
but Servers, which keeps the large one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Enchanted worn armour lost its animation: mobs/render.rs gave every
armour vertex [0, 0] for glint_uv, while the entity shader samples the
glint at that coordinate for overlay 7, so the armour showed a single
texel instead of the scrolling pattern.
Armour layers now carry overlay 13 (ARMOR_GLINT) and the layer's model
UV. entity.wgsl applies ARMOR_ENTITY_GLINT_TEXTURING (scale 0.16) and
samples misc/enchanted_glint_armor through the same repeating, linear
GlintSampler as items, matching ARMOR_CUTOUT_NO_CULL_GLINT. The scroll
clock (millis * glintSpeed * 8 modulo the offsets' common period) rides
in globals.time.w, so armour and items move together.
Addresses the review comment on #2296.
Co-Authored-By: ZCode <[email protected]>
A client of a server (single player's window included) never got the
animation state these mobs compute on the server: no pose, no sniffer
state, no shulker peek or wall face, no camel sit or dash, no warden
tendril, attack or sonic boom events.
Send Entity.DATA_POSE and the species' synched data from mob_meta,
broadcast the warden's entity events 4, 61 and 62, read both in the
remote mob code, and run the client animation steps in
mobs::sync::client_tick.
Wet sponges now drip water from their sides and bottom like
WetSpongeBlock.animateTick, and suspicious sand and gravel now shed
falling dust in their map colour when nothing is below them.
The last 10 seconds of Night Vision pulse in brightness. Vanilla reads the pulse at (duration - partial tick), so it eases between ticks. Horizon used the whole tick count, so the brightness stepped once per tick.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3 Soul Speed: the speed on soul blocks, no soul sand slowdown, the particles and sound, and boot wear on 4% of ticks, now also for each player on a server (`guest_server_tick`), with the particle sent to the players around. I pushed one fix: the random draws come in vanilla's order (the 0.04 wear roll first, then the particles, then the 0.35 sound roll and its pitch). It then conflicted with main (#2190 added the elytra glide wear in the same tick), so I merged main in: Soul Speed comes first, as `LivingEntity.baseTick` runs before `aiStep`, then the glide wear, so both draw from the level random in vanilla order. On that merge: clippy `-D warnings` clean, horizon-client tests pass, the fast build links, and `lan_e2e.py` elytra, damage and breath pass with the host in-frame and on its server thread. CI parity runs the harnesses this head reaches. Thanks @kacpherek!
🤖 Agent: #2318 has merged and this PR's base is main. The official Recipes mod (needs API level 6): pr_ready --quick green; the e2e passes 25/25 in-frame and on a dedicated server on this head (61 of 63 in five modes before; the 2 failures were #2501's bug, now merged). Queued at 92e3f496f.
🤖 Agent: #2306 has merged and this PR's base is main. recipes.place and recipes.give at API level 6, give cut to one stack. Sandbox (31), api_reference and clippy -D warnings pass; the actions e2e passed 58/58 in five modes. Queued at bd1436f16.
🤖 Agent: #2305 has merged and this PR's base is main. Recipe pages with station tabs and an ellipsis on a long title; clippy -D warnings with --features mods and --no-default-features is clean, and the browser e2e passed 98/98 in five modes. Queued at 0cba2edbd.
Splashes always used a radius of 10 blocks; vanilla uses the Weather Radius
option. The Horizon Weather toggle now also stops the splashes and the rain
sound, not only the falling rain.
🤖 Agent: verify passed (incl. Linux clippy for the glibc malloc_trim path); chunk_generation, worldgen, worldgen_random, save_roundtrip and world_reopen pass on this head; pr_ready --quick green. Preload starts on world select per the orchestrator's decision. Perf fix, no label.
The Particles option only changed two effects. Follow ClientLevel.doAddParticle
and calculateParticleLevel in the one add path: Decreased drops a third, Minimal
drops all but the always-visible and limiter-overriding particles. Explosion
block debris is thrown only on All, and rain splashes halve on Decreased and
stop on Minimal.
🤖 Agent: #2301 has merged and this PR's base is main. browser.set-recipes (with station tabs) at API level 6; sandbox (31), api_reference and clippy -D warnings pass. Queued at a373eea18.
A crate split reaches every harness (#2160: 352 harnesses, 71 min on a Mac
mini runner) and held the Mac mini's runners while other pull requests'
parity jobs queued for over an hour. With the full-parity label, the job
runs on the desktop's runner only (8 at a time) and waits for it while the
desktop is in use; its timeout is 300 min, since a job under way when
someone comes back goes on with 2 threads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #2300 has merged and this PR's base is main. Merged up the renumbered stack (browser at API level 6); clippy -D warnings with --features mods and --no-default-features is clean, and the browser e2e passed 98/98 in five modes before. Queued at a2cd70e8e.
AbstractMinecart saves Entity.firstTick as HasTicked, and a cart's fire
stays 0: Entity.move sets it to -getFireImmuneTicks(), which is 0 for
everything but players. A mineshaft's chest minecart was saved without
HasTicked and with Fire -1; vanilla's generated chunk saves it with
HasTicked 1 and Fire 0, and a cart that has ticked with HasTicked 0 and
Fire 0.
The mineshaft writes both as the proto chunk saves a new cart, and a
cart keeps first_tick from its save until its first tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
finalizeSpawn picks the variant of a pig, cow, chicken or frog and the
sound variant of a pig, cow, chicken, cat or wolf, but the save wrote
neither for these: pigs, cows, chickens and frogs came back temperate
after a reload, and every cat and wolf was saved with the classic sounds
whatever it rolled. Chickens also lost their egg timer.
Every mob with a variant component now saves and loads `variant`
through finalize::species_variant_component / variant_index (the cat and
wolf code used its own copy). The sound variant is kept as an index into
the species' registry (None is the default, classic) and is saved and
read back; Chicken also saves `EggLayTime`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #2284 has merged and this PR's base is main. browser is at API level 6 (recipes took 5). Sandbox (31, the UI guest at level 6), api_reference and clippy -D warnings pass. Queued at b836eea06.
🤖 Agent: #1895 has merged and this PR's base is main. Merged main: recipes moves to API level 5 (world took 4). Sandbox (30, including the level assertion), api_reference and clippy -D warnings on horizon-mods pass. Queued at b360e3c26.
🤖 Agent: LAN guests (host on its server thread) and a dedicated server's new players joined inside each other on the exact spawn, so they clicked the other player instead of a table or chest. New players now get PlayerSpawnFinder.findSpawn as in vanilla. lan_e2e spawned/crafting/chest pass in both host modes, a dedicated two-guest check passes, and the 32 affected harnesses pass.
glibc keeps the pages the preload thread freed in that thread's arena, and
macOS keeps them in its zones until memory runs short, so dropping the
preload didn't lower the footprint: horizon-threads now asks the allocator to
return free pages (malloc_trim, malloc_zone_pressure_relief).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The command ran on after remote.sh was stopped or its connection dropped,
holding the name's lock, and a game or daemon it left behind outlived the
run. It now runs in a scope named after the run (remote-<name>.scope),
stopped when the command ends, when the ssh session ends first, and before
the next run under that name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The harness sampled the fireball from a new horizon-ctl process each time,
10-16 ticks apart on a loaded runner: the fireball (~2 blocks a tick) left
the cleared corridor or the guest's tracking range before the sixth sample,
and the harness failed. It now flies slowly (acceleration_power 0.02, under
0.4 blocks a tick) down a longer corridor, and the guest takes 16 samples
in one ctl script (lan_e2e's Game.script returns every reply with every=True).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DamageSource.getLocalizedDeathMessage sends the one-argument message, and
TranslatableContents.decompose shows a template it cannot fill as written.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
wantsToDance draws from RandomSource.createThreadLocalInstance(gameTime),
not the piglin's random, and wasHurtBy ignores another piglin's hit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrownTrident.onHitEntity runs EnchantmentHelper.modifyDamage on the
trident, so Impaling adds to the 8 for the entity types in
#sensitive_to_impaling (its minecraft:damage requirement), not for every
mob that swims or breathes water.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MinecraftServer.setInitialSpawn passes level.getRandom() to
BonusChestFeature.place, which sets the chest and torches with flags 2.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The side file kept an entry for a gateway after Horizon created its exit,
so removing the exit with /data remove block ... exit_portal, or placing
a different gateway there, sent the player to the old exit again.
Once the gateway's chunk is loaded, gateway_job moves an entry the side
file still holds into the block entity (unless the block entity has an
exit of its own) and drops the entry. set_gateway_exit writes only the
block entity when the chunk is loaded, and leaves an entry in the side
file only when the chunk is already unloaded.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ArmorStand.BABY_DIMENSIONS keeps its own eye height rather than half the
type's, and EntityDimensions.eyeHeight is a float widened by getEyeY.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The parity client's chat, villager and furnace steps went on 1.5 s after
sending, so a busy server's late reply landed in the next step (or a click
carried a stale state id). Each step now waits until the server answered:
its chat line, both slot packets of a shift-click, and the furnace totals
the click changes (up to the same 30 s patience). The respawn window keeps
only the player's own entity packets: a squid's swim event or a mob coming
into view falls into it as each server's ticks fall.
Off the known-flaky list: it passes under 2.5 s server stalls every 5 s,
where main's client fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sitting, standing, dyeing and healing a cat or wolf now check the saved
owner. The follow, teleport and owner-hurt goals use the owner, not the
nearest player, and a new tame wolf or cat is owned by whoever tamed it.
Horizon had no End gateway block entity: game/end.rs kept each gateway's
exit in its own side file and gateway_job only looked there, so a
gateway set with /setblock end_gateway{exit_portal:[I;..],ExactTeleport:1b}
(or the exit worldgen writes) was ignored, and /data get block said the
block was not a block entity.
TheEndGatewayBlockEntity stores Age, exit_portal and ExactTeleport.
BlockEntity::EndGateway now holds them with vanilla's NBT and the
spawnable-bounds check. gateway_job reads the block entity's exit first
and keeps the side file and the 512-block End-spawn rule as fallbacks,
so older saves and gateways without data behave as before. The exits
Horizon creates are stored in the block entity as well, a new exit keeps
the gateway's own ExactTeleport (setExitPosition), and a command that
only changes a block entity's NBT now marks its chunk modified so the
change is saved.
Fixes#1517.
Implementation by Codex (GPT-6.1 Sol), reviewed and extended by Claude.
Co-Authored-By: Codex (GPT-6.1 Sol) <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: verify passed; chunk_generation, worldgen, worldgen_random, save_roundtrip and world_reopen pass locally on this head plus #2481; pr_ready --quick green. Perf fix, no label.
The worldgen bundle, the loot tables and the registries a server sends are
read on a helper thread from the moment the player opens Singleplayer or
Create World, so a world opening finds them ready. A world opened meanwhile
waits for the part being read. If the player goes back to the title or to
Multiplayer and opens no world for a minute, the preload is dropped: the three
caches can now let go of what they hold.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The host fetches the commit from GitHub (nothing else is sent), keeps one
checkout and target per worktree, runs the command niced in its CI slice on
CPUs 4-7 with scratch homes, prepares vanilla data for harness commands as
CI does, and streams the output back. One run per name at a time; no lock
leaks into daemons the run starts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The server put every player new to the world exactly on the world spawn.
On the server thread the host's own player joins that way too, so the host
and each new guest stood inside each other: the guest's client targets
the host's player (its eye inside that box, as vanilla's getEntityHitResult
does), so it could open no crafting table, chest or furnace, and Entity.push
never parts two players with the same centre. A dedicated server stacked
its new players the same way.
Vanilla places each new player with ServerPlayer.adjustSpawnLocation:
PlayerSpawnFinder.findSpawn, a free column at a random offset within the
respawn radius. Game::server_player_spawn does it for a server's new
players with the window's own finder, passing over the candidates whose
chunk isn't loaded yet (vanilla waits for them; a server tick can't).
lan_e2e's new check `spawned` asserts that the host and the guest join on
different spots, then has the guest open a crafting table, a chest, a
furnace and a stonecutter where it joined.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A title wider than the panel ended at its edge mid-word ("Recipes: Oak P");
it now ends with vanilla's "..." (CommonComponents.ELLIPSIS).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nine vanilla Java servers from harness runs had been orphaned on the Mac mini for 2 to 5 days. Each harness runs in
its own session (killpg on a timeout), so when a CI job was cancelled, its harnesses and their servers outlived
parity.py.
- parity.py: SIGTERM and SIGHUP exit through the finally blocks, which now stop the local executor's harnesses
too, not only the remote hosts'. The worker (--worker) stops its harnesses the same way.
- runner-gc.sh (every 6 h from launchd): stops Java processes whose parent is gone (launchd, init or
systemd --user) and whose command line names a runner's checkout or a worktree's .local/, once ORPHAN_HOURS
(default 3) old.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The client breaks the block under its feet and falls in the same tick. The
hub checked that move against the block that was still there and sent the
player back. Moves queued behind a dig packet now wait for the next tick,
after the break.
Move players, mobs and common objects when piston block entities advance, applying collision and the per-tick piston limit. Exercise host, guest, inventory-open and mob pushes in both single-player paths, and compare the push geometry with vanilla.
Co-Authored-By: Codex (GPT-6) <[email protected]>
pr_ready --quick passed #1895 while CI's clippy failed on an unused import in horizon-mods' tests: clippy on the
touched crates and their dependents gives them other features than CI's workspace-wide run (Cargo unifies
features over the packages it builds).
- clippy: CI's two commands as they are. The whole workspace runs with the game's code mods, and the server build
(horizon-client --no-default-features) runs when horizon-client is affected. Warm on the Mac mini, a change to
hud.rs took 14-16 s against 17 s before, and food.rs 22-32 s against 14 s. Each worktree's first run costs
about 3 min once.
- tests stay on the touched crates, with the features CI's workspace run gives them: horizon-client's code mods,
and horizon-mods' runtime, pack and index.
- The Python tests always run, as in CI: some read the crates, the roadmap or the workflows.
- The launcher steps run when CI's launcher job would (tools/ci/rust_changes.py's rule, which also covers the
root manifests and the check itself).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main for #2316 (the campfire fix: this head had #2015 without it, so dispenser and block_removal would fail parity). Clippy -D warnings on horizon-mods (all targets, all features) and on horizon-client --features mods --bins is clean. Requeued at 8ec47d641.
Level.isRainingAt takes Biome.getHeightAdjustedTemperature, which applies
the frozen oceans' temperature modifier and the height noise above the snow
level; reuse horizon-world's version instead of a simplified copy.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Polar bears only panicked and strolled. They now follow vanilla's goals: melee attack that rears up and growls before each strike, hurt-by and cub-defending targets, anger at the player, and fox hunting. A hurt cub calls the adults. The standing flag is synched and the client animates it.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: turtles lay (`TurtleLayEggGoal` with the digging effect), `TurtleEggBlock` hatches, and a growing baby drops a scute. I pushed one fix: the home is set at spawn and kept on load (`finalizeSpawn`, `home_pos`), and breeding drops one orb of 1 to 7, drawn from the mother's random and only with `mob_drops`. After #2137 the gift loot event names its mob. I merged main into your branch (6ef61ac13): main's `Reinforcements` stays beside your `BlockBreakEffect`, and the grown scute now goes through #2137's `drop_gift_loot` (the turtle's id added to both `GiftLoot` events) behind your mob_drops check; clippy clean, same code as the tested tree. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Merged by Ludovic's AI agent (Claude). The loot tables now load before a world's first chunks are read, so a loot chest near spawn gets its roll. The new spawn_loot harness fails on main (0 stacks) and passes here with vanilla 26.3's exact roll, on the server thread and in-frame. I merged main after #2316; dispenser and block_removal pass again. Follows @FusselTV's #2073.
Jobs queued while the loading screen was up carried ring-order priorities,
far above the plain distances used once it closed, so they waited behind
every later job. The proto chunks they generated held back the decoration
of the chunks around the player: at render distance 32 in-frame, nothing
was decorated until every proto chunk had been generated. Queued jobs now
keep their column and bias and are ranked again from the current centre,
extra areas, band and loading screen when those change, as vanilla's
chunk task queue follows the current ticket levels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod can now turn on the game's distant terrain (LOD) with
render.set-far-terrain: a distance of 64 to 1024 chunks and Far Detail's
Reduced. The game builds and draws the tiles as it does for the LOD
Distance setting, which wins while it is on. One far terrain shows at a
time, the first loaded mod's. API level 4.
mods/official/far-view is the official mod on it: Distance (256 chunks by
default) and Far detail on its settings page, kept in its storage.
Keeps main's Reinforcements beside BlockBreakEffect, and the turtle's grown
scute goes through #2137's drop_gift_loot (the mob's id added to its two
GiftLoot events) behind the mob_drops check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `BreakDoorGoal` (the difficulty gate, 240 ticks, the crack progress that your last commit now sends to nearby players) and the vindicators' door rules in raids. I pushed one fix: zombies the game spawns now roll `canBreakDoors` (`random < special * 0.1`), as `Zombie.finalizeSpawn` does. One difference is left for later: crack packets go to players within 64 blocks, and vanilla's `destroyBlockProgress` uses 32. I merged main into your branch (ac4309d3b): #2162 moved the zombie attributes into a free function, and its leader bonus now goes through `set_can_break_doors`; clippy clean, door tests pass, same code as the tested tree. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
tools/parity_hosts.json's new `known_flaky` lists the harnesses that still flake for a cause not fixed yet,
each with its reason: dedicated_server (its Java client's 1.5 s step timer), remote_fireball (wall-clock
sampling) and world_reopen. One of them that fails runs once more from scratch, and a pass then counts.
Every retry prints a RETRIED line at the end of the run and in the CI job's summary, so a harness that
passes only on its retry stays visible on PR parity and parity-main alike. The existing fresh rerun of a
failure that reused oracle results is reported the same way. A known flaky harness skips that rerun and keeps
reusing oracle results, because flaking doesn't mean its cached oracle runs were wrong. Repeated runs
(--repeat, --flake-check) are never retried.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps #2184's glide wear with Unbreaking after Soul Speed's tick, the order
of LivingEntity.baseTick and aiStep (both draw from the level random), and
both the can_glide_through and soul_speed_blocks tags.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FlatSettings::from_tag rejected layers: [], so a world with every layer removed fell back to the classic bedrock/dirt/grass settings when re-created. An empty saved list is a valid void flat generator; a non-empty list whose entries all fail to parse is still rejected.
Co-Authored-By: ZCode Agent <[email protected]>
Create New World greyed out Customize for a Superflat world because Horizon
had no preset editor. Bring vanilla's CreateFlatWorldScreen and
PresetFlatWorldScreen:
- the layer list (top first), Remove Layer and the layer heights;
- the preset chooser with the nine visible flat_world_preset entries, their
display items and names;
- the share box and Select, parsing layers;biome and validating block and
biome ids against the prepared registries;
- Done keeps the layers in WorldOptions, Cancel and Escape discard them.
world_loading writes them with write_world_gen_settings_with, so a new
world's world_gen_settings keeps the custom generator; Re-Create reads them
back for the editor.
Co-Authored-By: ZCode Agent <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Shulker.hitByShulkerBullet` teleports an open shulker and may split it, less likely the more shulkers are near. I pushed one fix: the split roll uses the level's random, as vanilla's `level.getRandom()` does. Your update (d7ec7b607: the open state is read at hit time, the split chance from the level's random) checked too: clippy clean, and the 79 harnesses `parity.py --changed-files` picks pass apart from the campfire cases #2316 fixes. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `PiglinAi.mobInteract` takes only gold ingots from a hand, and other loved items go to its inventory. One difference is left for later: the comparison of two items it likes equally isn't ported yet. Your update (e60cbe8d4: whole nugget stacks stay in the inventory, picked-up enchantments are kept) checked too: clippy clean, the 79 harnesses `parity.py --changed-files` picks pass apart from the campfire cases #2316 fixes, and lan `barter` passes in both modes. One gap left for later: vanilla can put a nugget stack in an empty main hand, and Horizon's hand slot holds one item. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LivingEntity.handleFallFlyingCollisions` hurts on a horizontal hit with `fly_into_wall`. Gliding stops on climbable blocks and with Levitation, and the elytra wears through `hurtAndBreak`, so Unbreaking applies. Your update (a4407a4bc: Unbreaking for guests, wall hits kept on the server copy) checked too: clippy clean, the 79 harnesses `parity.py --changed-files` picks pass apart from the campfire cases in `dispenser` and `block_removal` that #2316 fixes, and lan `elytra` passes in both modes. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A head tested alone keeps failing harnesses that main already fixed. Since 10-25 evening, 9 of 21 failed PR
parity jobs failed only on such fixes: server_knockback (heads without #2114, 7 times), mob_ai's oracle heap
(without #2270), save_roundtrip (without #2212). #2160 needed 4 parity runs this way.
The job now merges main's current tip into the head and selects harnesses against that main commit, so only
the pull request's own changes count. A branch that doesn't change the runner scripts or the selection gets
main's, which replaces the two steps that copied main's vanilla-data.sh and parity.py in by hand. A head that
conflicts with main is tested alone, with a warning. The job summary names the main commit, and the check is
still `parity / parity` on the head.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Witch.aiStep` stands still while drinking (the -0.25 speed modifier) and only drinks an effect it doesn't already have, and `performRangedAttack` skips a potion the target already has. It merges after #2138, since both add fields to `Seen`. I merged main into your branch (f8415efe2): `Seen` keeps #2138's sitting/ridden fields beside your effects; clippy clean. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: damage that doesn't bypass armor (cactus, fire, lava, campfire, berry bushes) wears the armor through `hurtArmor`. Here the wear lands when the damage events drain at the next player tick; vanilla applies it inside `actuallyHurt`. That is close enough for now. #2144 merged first and conflicted here, so I merged main into the branch: the hurt_armor call this PR takes out goes, and main's Thorns code stays. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the `ExperienceOrb` pickup box, merging by count, and the lava bounce. Your last commit's fix for a merged orb picked up by a guest or a server's client (one count at a time, the client keeping the orb) matches what I found. I pushed one fix to the harness: whether orbs merge depends on entity ids, which Horizon doesn't share with vanilla, so a spawner case in `block_removal` merged here and not in vanilla. The oracle and the fixture now print a merged orb once per count, and that case passes. (`block_removal`'s campfire cases fail on main until #2316 lands.) Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `PiglinSpecificSensor` finds the nearest `#piglin_repellents` block (a soul campfire only while lit), and the piglin walks away from it. I pushed one rename: this PR and #2191 each added a `find_repellent` to piglin.rs, so this one is now `find_piglin_repellent`. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `BlockItem.place` plays the place sound only when a block item places a block; an empty hand or another item plays none. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ConduitBlockEntity.serverTick` (the frame every 40 ticks, Conduit Power in range, the attack on the nearest enemy), and your last commit's server ticker, which runs without a renderer. One difference is left for later: the conduit's target isn't synced to remote clients yet. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the illagers' target goals skip baby villagers. One difference is left for later, older than this PR: the illagers' shared hurt-by list still includes Vex. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the evoker's vex summon and cooldowns, the fang lines and circle, the vex limits, and the wololo spell (blue sheep only, with `mob_griefing`). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
Player.attack shows magic crit particles whenever the enchantments added
damage. Horizon showed only the critical hit, for the window's player and
for a guest's attack.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Player.drop` throws leftovers from the eyes, and `ArmorStand.swapItem` lets a creative player keep their stack. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `CactusBlock.entityInside` hurts any entity with `cactus` damage, and that destroys item entities. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ChestBlock.isChestBlockedAt`: a block above that is a redstone conductor, or a sitting cat on top. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the guardian's beam deals 2 more on Hard and 2 more from an elder, and squid and axolotl targets must be over 3 blocks away (`GuardianAttackSelector`). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Chicken.aiStep` lays through `dropFromGiftLootTable(gameplay/chicken_lay)` with the chicken as `this`, which the table's conditions need, and snow and chorus flowers drop their loot when a player breaks them. One difference is left for later: an explosion's drops don't pass the exploding entity as `this` yet (snow broken by TNT). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A mob removes its body twenty ticks after dying and tells everyone with
entity event 60, which draws twenty poofs about it. Horizon removed dead
mobs silently.
Vanilla's GlintSampler repeats enchanted_glint_item per pixel, and the
GLINT transform's scale 8 spans about 8 repeats across an item. Folding
the UV per vertex into the shared atlas could only interpolate between
the four corners, so the pattern rendered as one wide diagonal band
instead of the texture's fine weave. The glint now binds as its own
128x128 texture with a repeat, linear, mip-free sampler (the texture's
mcmeta: clamp false, blur true) in the HUD and entity pipelines, and
`Glint` passes the unwrapped `GLINT_TEXTURING` UV through.
Co-Authored-By: ZCode <[email protected]>
A thrown potion or experience bottle that breaks sends level events 2002 or
2007 with its colour and the splash sound (1053, 1054). Horizon spawned only
eight terrain crumbs, on the server's own side, so a server player saw
nothing.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: the turtle helmet gives Water Breathing for 200 ticks while the eyes are out of water (`Player.turtleHelmetTick`). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Bucketable.bucketMobPickup` for cod, salmon, pufferfish, tropical fish, tadpoles and axolotls, with the data each bucket carries (salmon size, tropical fish pattern and colours, the age only for axolotls and tadpoles) and the pattern names in enum order. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ServerLevel.findLightningRod` takes the closest lightning rod POI within 128 blocks, at the top of its column. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked: the hotbar now draws its stacks by reference. The output is the same, without a clone per stack each frame. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @Domanffe!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: a charged creeper's explosion makes a mob it kills drop its head, once per creeper (`Creeper.canDropMobsSkull` / `increaseDroppedSkulls`). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A client draws the particles of an entity's visible effects itself, from
the synched effect particle list (LivingEntity.tickEffects). Horizon drew
none and sent no such list: now the window's own player, a server's mobs
and players, and the mobs and guests of a game in the frame all swirl.
The retained bolt offset and the entity vertex's new glint UV were both
declared at attribute location 6, and the lightning pipeline builds its
vertex buffers from the entity layout plus the bolt-offset instance
buffer, so Device::create_render_pipeline rejected the location clash
and the client crashed at startup.
Co-Authored-By: ZCode <[email protected]>
Horizon invented the glint: the GUI drew sine bands in screen space and
the entity shader layered the same over held and dropped items. Vanilla
26.3 samples `misc/enchanted_glint_item` through `GLINT_TEXTURING`
(`item.vsh`/`item.fsh`, `TextureTransform.setupGlintTexturing`), so
items now carry the real texture:
- `ItemQuad.foil_uv` keeps the model UV the glint samples; `Glint` turns
it into the atlas UV with the 26.3 transform (translation(-o0, o1) *
rotateZ(PI/18) * scale) and `WorldSink` gives foil quads overlay 7 and
the per-corner glint UV.
- The GUI glint is a separate additive HUD pass masked by the item's
texture alpha (`item.fsh`'s ALPHA_CUTOUT 0.1 and GLINT path).
- `entity.wgsl` samples the glint at overlay 7/8 like vanilla's
`color.rgb += glintColor.rgb * glintColor.rgb`.
- The Accessibility options Glint Speed and Glint Strength work: both
sliders persist to `options.txt` (they were hidden placeholders), the
GUI pass takes them per draw, and `Frame.glint_alpha` feeds the entity
shader every frame instead of whatever the last HUD item set.
- GUI items use `GLINT_TEXTURING`'s scale 8, world items
`ENTITY_GLINT_TEXTURING`'s scale 0.5.
Co-Authored-By: ZCode <[email protected]>
#2162 moved the zombie attributes into a free function; its leader bonus now
goes through set_can_break_doors like this branch's other door flags.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Zombie.hurtServer` on Hard rolls `spawnReinforcementsChance` and tries 50 spots around the target (`checkSpawnRules`, no player close, unobstructed), and both zombies lose 0.05 of the chance. Two small differences are left for later: the roll is drawn at the end of the tick rather than inside the hurt, and the new zombie draws one extra yaw. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A landing that hurts sends the block's crumbs from the server
(LivingEntity.checkFallDamage). Horizon built the event but dropped it, for
the window's player and for every server player.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: Infinity's `ammo_use` effect only covers `minecraft:arrow`, so tipped and spectral arrows are used up. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
Every item sits in the item panel beside the inventory and container
screens, with a search box (words in the name or id, @namespace, #tag).
R or a click shows an item's recipes, U or a right click its uses, with a
tab per station; Back returns through the pages. + beside a crafting
recipe the player knows fills the grid as the recipe book does. In
creative mode, with "Click gives items" on, a click gives one item and a
right click a stack. The keys are rebindable in Controls.
Written from vanilla 26.3's recipe book and screens and Horizon's design
system only. On servers that are not Horizon it shows the game's own
26.3 recipes; known recipes and + follow the server's recipe book.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sprinting player kicks up dust of the block under their feet every tick
(Entity.spawnSprintParticle). Horizon drew none, for the window's player
or for the other players it sees.
Custom names are italic. Unbreakable, dyed, written book author and
generation, music disc songs, goat horn instruments, loaded crossbow
projectiles, hive bees and honey, the potion "When Applied" block and
enchantment attribute modifiers (Efficiency V gives +26 Mining Efficiency)
now appear, and advanced tooltips (F3+H) end with the item id and the
component count. Attribute lines use the attribute's sentiment colour.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `FishingHook.canHitEntity` hooks item entities too, its luck is the owner's luck attribute plus Luck of the Sea, and `fish_caught` counts the catch. One difference is left for later: the hook takes the first overlap rather than the closest one on its path. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
The property was parsed and ignored. The tracking range of each entity type is now scaled by it on a dedicated server, like DedicatedServer.getScaledTrackingDistance.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ButtonBlock.checkPressed` presses a wooden button while an arrow is inside its shape and keeps it pressed as long as one is there. One difference is left for later: wooden pressure plates and tripwires don't count arrows yet. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `TargetBlock.onProjectileHit` gives a signal of 1 to 15 from how close the hit is to the face's centre, for 20 ticks from an arrow and 8 from anything else, with the `target_hit` trigger and stat. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A new editor or a removed sign closes the open editor (Minecraft.setScreen, AbstractSignEditScreen.tick), and closing sends ServerboundSignUpdatePacket with the lines it opened with when the sign is gone. Sign lines resolve key binds and translations (Component.getString) instead of keeping only plain text.
ClientboundSetHeldSlotPacket only changes the selected hotbar slot (and ignores slots outside the hotbar), so the next tick sends ServerboundSetCarriedItemPacket as vanilla does. Each login starts again from slot 0, as handleLogin's new MultiPlayerGameMode and LocalPlayer do.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `DaylightDetectorBlock.updateSignalStrength` (sky light minus darkening, the sun-angle curve, the inverted mode) and its update when used. Main conflicted with its new campfire block entity, so I merged main into the branch, keeping both block entities. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: Piercing sets the arrow's `pierceLevel`, and `AbstractArrow.onHitEntity` remembers the entities it went through and goes on until the level runs out. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: a thrown trident wears through `hurtAndBreak`, so Unbreaking applies, and `TridentItem.releaseUsing` doesn't launch a riding player with Riptide. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
As the creative inventory does: a count over the item's stack size (64, 16
or 1) is cut to it, so a mod can ask for a full stack without knowing it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: channeling.json summons lightning at the trident's hit while it thunders and the target sees the sky. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `VillagerMakeLove.takeVacantBed` asks the POI manager for a free home within 48 blocks (a sphere, closest first) and keeps only a bed a path reaches. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A chunk's STRUCTURE_STARTS are the starts made in it
(StructureManager.getAllStarts), whether or not one of their pieces
reaches it. own_starts filtered the chunk's references, which only hold
starts with a piece in the chunk, so a start whose pieces all lie next
door (seed 8675309's giant ruined portal in chunk -26, 23) was not saved.
The proto chunk now keeps the starts made at its position.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A captainless raider walked towards the nearest fallen ominous banner
even when no path led to it. Check that a path reaches the banner before
going for it, and skip a banner without one for 600 ticks, like vanilla.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `WorkAtComposter` composts the farmer's seeds through `ComposterBlock.insertItem` and makes bread from wheat. I pushed one small fix: the `ComposterBlock.addLayer` doc comment had moved off its function. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
WorldGenRegion.setBlock gives every block that has a block entity a new
one, so vanilla saves the sculk sensors, catalysts and shriekers of the
deep dark and potent sulfur with their block entities. Horizon only
saved block entities that a structure or feature placed data for, and
these kinds have none, so a Horizon-made world had none of them (3,035
sculk sensors, 753 catalysts, 329 shriekers and 21 potent sulfur over
three 1,024-block seeds). A finished column now adds vanilla's defaults
(block_entity_nbt::canonical) for every such block: as the modelled
block entity where Horizon has one (sculk), as raw NBT otherwise.
Potent sulfur gets its block entity type.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With force-loaded levels ticking without players (#1525), Horizon placed
the exit portal as soon as the End's main island was loaded. Vanilla
only places it from scanState and the dragon's death, both with players
in the fight, so a pre-generated End has no portal until someone
arrives.
test_forced_end_parity force-loads the End's arena on vanilla's and
Horizon's dedicated servers with nobody online and compares what is
around 0, 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SpikeFeature.placeSpike adds each crystal to the WorldGenRegion, so a
pre-generated End holds its ten crystals before anyone ticks it.
Horizon's decoration dropped spawn_end_crystal and the game placed the
crystals later, once the arena was loaded and ticking. An End generated
with ticks frozen, as a seed generator does, had none. The decoration
region now writes the crystal like any placed entity; the game's own
placement still skips a spike that has its crystal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BrushableBlock.tick makes the falling block entity with disableDrop, so
vanilla breaks it on landing (onBrokenAfterFall: level event 2001 and
the block_destroy game event) and
places nothing. Horizon landed it as a plain block without its
archaeology block entity. Seen in pre-generated worlds: suspicious gravel
of cold ocean ruins that sits over water fell during the first ticks and
landed one block lower without its block entity, so with no loot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chunks no player ticket reached were decorated nearest the centre first.
Vanilla's ForceLoadCommand adds a FORCED ticket per chunk, x-outer and
z-inner, and the FULL tasks run in that order, so a chunk decorates
before its neighbour at higher x or z. Both orders agree only in the +x,
+z quarter of the map. Elsewhere features crossing chunk borders
overwrote each other the other way round: a geode next door ate a trial
chamber's dispensers, sculk or a clay pool closed a dungeon so it was
never placed, and ore, stone and tree blobs swapped.
The forceload command now ranks the chunks it adds in vanilla's order
(decoration_order::forced_order, which shares its scheduler with
vanilla_order); chunks without a rank still go last, nearest first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Creeper.mobInteract` ignites with any `#creeper_igniters` item. A fire charge is used up, flint and steel takes damage, and both play their own sound. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `AbstractSkeleton.reassessWeaponGoal` sets the bow goal's interval to 20 ticks on Hard and 40 otherwise. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
Brings #2316 (the campfire block entity fix): CI's parity checks out this
branch's head, so dispenser and block_removal need it here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ServerLevel.explode` turns `ExplosionInteraction.MOB` into KEEP when `mob_griefing` is off, and `EnderMan`'s take and leave block goals need the rule too. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Player.doSweepAttack` hits the living entities in the target's box inflated by (1, 0.25, 1), not the player, the target or allies, and not marker armor stands. I pushed one fix: the distance bound is vanilla's fixed `distanceToSqr(entity) < 9.0`, not the player's reach. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A row of tabs above the recipe pages shows `set-recipes`' stations (their
item in a slot, a white frame on the selected one). A click sends `tab(i)`
to the mod, R/U over a tab works like over any slot, and horizon-ctl lists
them as "<name> tab". Changing tab resets the page.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: rabbits breed (`Rabbit.isFood`), damage ends love (`Animal.hurtServer` resets `inLove`), the horse and wolf `canMate` checks, and the breeding orb only with `mob_drops` (`Animal.finalizeSpawnChildFromBreeding`). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `Player.hurtServer` scales damage that `scalesWithDifficulty` from a living non-player: none on Peaceful, `min(d / 2 + 1, d)` on Easy, `d * 3 / 2` on Hard (so a creeper's blast scales, and TNT a player lit doesn't). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `SetStewEffectFunction` keeps the effect it rolled, with the duration times 20 unless the effect is instant. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Merging ahead of the queue. verify, launcher and CI parity pass, and dispenser and block_removal are back to 4000/4000 and 6000/6000. This turns main green again after #2015.
The server now sends level events 2019 and 2020 every tick a player digs
(ServerPlayerGameMode.tick), and clients handle them like LevelEventHandler:
a crack on the face, plus the hit sound for 2020. A remote client plays only
the first hit itself and no longer adds its own sound every fourth tick, so
the hit was heard twice, out of phase, for every block.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `SlimeBlock.stepOn` slows a walker that isn't sneaking when `|dy| < 0.1`, by `0.4 + |dy| * 0.2`. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `/weather` sets the weather whether or not `advance_weather` is on. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ServerPlayer.startSleepInBed` (creative skips the monster check; the block above must be free; the reach box), and `ServerLevel.tick`'s morning skip with the weather reset. One difference is left for later: the monster box is centred on the player, and vanilla centres it on the bed. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `FireBlock.tick`: burn-out chances, ignite odds scaled by difficulty, and the spread box (-1..1 sideways, -1..4 up). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `LightningBolt.spawnFire` only on Normal and Hard and with fire spread on, as here. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
Main's thorns (#2144) sit where this branch took out hurt_armor; the armor
now wears through the damage pipeline and main's thorns stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `EnderDragon.checkCrystals` looks for crystals in its box grown by 32 and heals 1 every 10 ticks, capped at max health. Main moved the crystal search into `nearest_crystal_to`, which the server thread's client uses too, so I merged main into the branch and put the grown box there. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
Mob.requiresCustomPersistence also covers a leashed mob and an enderman holding a block, and the spawn caps skip such mobs. Gather the existing cases in one helper, add the missing ones, and use it for both the despawn check and the cap count.
A cow dropped onto farmland left it untouched. A landing mob now turns
farmland to dirt with chance fall distance - 0.5, when mob griefing is on and
the mob is big enough, and is lifted onto the dirt.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `KeyMapping.click` and `set` act on every mapping bound to the key, as here, and your second commit stops a key from replaying a press when two mappings share it by default. #2207 fixes the same issue; I kept this one because it came first and changes less. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
A warden holds no weapon, so its blows never put the shield on cooldown. Warden.getSecondsToDisableBlocking is a flat 5 seconds, so use that when the attacker is a warden.
The bonus went to every swimming or water-breathing mob, including undead,
blazes, ghasts and iron golems. Read the damage from the enchantment data so
only #sensitive_to_impaling mobs take it.
Post-attack effects only handled ignite, so the apply_mob_effect effect of
Bane of Arthropods never did anything. Handle apply_mob_effect like vanilla.
The page indicator only replaced bare %s, but 26.3's en_us template is
"Page %1$s of %2$s"; it now goes through format_java like other
translations (BookViewScreen.getPageNumberMessage).
Pages sent as a component list (Hypixel through ViaVersion) were dropped
by item_data::read_text, which only took a string or a compound, so the
book opened blank. ComponentSerialization.CODEC also takes a list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ShovelItem.useOn` puts out a lit campfire (`CampfireBlock.dowse`) with its sound and smoke. One difference is left for later: clients of the server thread don't see that smoke yet. Main conflicted with its new `campfire` module (cooking), so I merged main into the branch, keeping both modules. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ServerLevel.tick` keeps ticking a level that has forced chunks, even with no players and past the 300-tick empty timeout. `lan_e2e.py --server-thread --check forced-levels` passes. Main conflicted in lan_e2e.py, so I merged main into the branch, keeping both checks. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @rdbtCVS!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `ServerLevel.runBlockEvents` broadcasts each handled block event, and `ClientLevel.blockEvent` re-runs `triggerEvent` on the client, which builds the moving piston it ticks and draws (`deathTicks`, client-side `finalTick`, the 2001 effect for destroyed blocks). I pushed one fix with a test. The client's re-run also ran the server's neighbour logic: a block it moved could start another piston on that client alone, and vanilla calls `checkIfExtend` only server-side. Its `playSound(null, ...)` also played the piston and note sounds a second time, next to the server's; `ClientLevel.playSeededSound` plays only when the excluded player is the local one. In game on the server thread, a piston that pushes a redstone block under a second piston leaves both clients' blocks as the server has them. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @socksprox!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: Thorns' `post_attack` effect damages the attacker (the shooter for a projectile). Main conflicted in `lan_pvp.rs` because it already had `Strike.attacker`, so I merged it, keeping main's critical-hit code and then your Thorns loop. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
Brings #2212 (namespaced furnace RecipesUsed), which save_roundtrip needs:
CI's parity checks out this branch's head, not a merge with main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `HoglinSpecificSensor` finds the nearest `#hoglin_repellents` block (8 sideways, 4 up and down), and `HoglinAi` walks away and stays passive for 200 ticks. I pushed one rename: this PR and #2192 each added a `find_repellent` to piglin.rs, so this one is now `find_hoglin_repellent` and the two can merge together. Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @kacpherek!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked: the crack overlay keeps its UVs aligned from one stage to the next. Its ignored GPU test passes here (macOS, Metal). Gate: this wave's 55 PRs merged on main in one tree: clippy `-D warnings` clean, 1186 horizon-client and 163 horizon-world tests pass, and of the 101 harnesses `parity.py --changed-files` picked, 100 pass (`block_removal` failed on one spawner case from #2199's orb merging, fixed there). `lan_e2e.py` passes 24 checks with the host on its server thread and in-frame (chest, xp, pvp, doors, villagers, beds, frames, projectiles, arrows, fishing, damage, mobs, blast, drops, elytra, breath, predict, cracks, barter, kills, hurt, contacts, underwater, plates), plus forced-levels on the server thread. Thanks @rdbtCVS!
lan_crafting_left removed the guest's GuestPlay from host.play before
the leave_game award and save_guest_records ran, so a normal guest
disconnect awarded nothing and wrote no statistics. It now takes the
crafting table and workstation state out of the entry and leaves the
entry for HostEvent::Left to remove after saving.
New lan_e2e check `leave`: a guest leaves twice and the host's saved
statistics show leave_game 1, then 2. It fails on the previous commit
(0, then [0, 0]) and passes here.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Since campfires got their block entity (#2015), the container lookup took
it for a zero-slot container: a dropper facing one kept its item instead of
dropping it, as vanilla's HopperBlockEntity.getContainerAt finds no Container
there. The dispense fixture also filled campfires through the generic slots,
which a campfire doesn't have, so block_removal saw them empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.shouldRenderAtSqrDistance scales the bounding box's mean edge
by 64 * viewScale, and LevelExtractor sets viewScale to the entity
distance option times clamp(renderDistance / 8, 1, 2.5). Horizon used
the option alone, so above render distance 8 entities vanished sooner
than vanilla: a chicken at 32 blocks instead of 48 at the default 12.
RemotePlayer's box also counts ten times.
Fixes#2273
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The completed-download push into ready never marked the location queued,
so an again() before the next take_ready() put the same skin in twice:
store_skin allocated two slots, and evicting one removed the sprite while
show_skin still found the other entry — the skin never uploaded again.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
recipes.place(book-id, all) is the recipe book's click: in the open
inventory's or crafting table's grid it sends ServerboundPlaceRecipePacket
on a server, or places it in this world, refused as handlePlaceRecipe
refuses it (no crafting screen, a recipe the player's book doesn't hold, an
impossible one); a recipe missing items shows as a ghost. recipes.give(stack)
gives a creative player the stack as the creative inventory does; a server
hears it as SetCreativeModeSlot through the inventory's sync. Both answer at
once from the tick's snapshot (placing, creative, known) and take at most 36
calls a hook call.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The progress overlay only changed drawing: a pack pushed while playing
left movement, attack and use live behind it. Input events are now
dropped while the staged reload runs (as vanilla's LoadingOverlay does),
and start_pack_reload releases held keys and mouse buttons up front so
nothing keeps acting across the frame it covers.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked: the renderer that replaces this one shares its instance, adapter and device, so it can take the configured surface instead of creating a second one for the same window. Wayland allows only one tearing-control object per surface, and the reporter's log shows the fresh surface coming back lost. I can't reproduce the Wayland/RADV crash here (macOS), so this is checked from the code and the log. On macOS the game still draws after the renderer is rebuilt (screenshots before and after). Fixes#1959. Main's HDR output conflicted in recreate_surface, so I merged main into the branch: the reused surface now goes through main's Renderer::configure too. Thanks @kacpherek! Gate: this batch merged with wave 1 in one tree: clippy `-D warnings` clean, 1164 horizon-client lib tests pass, and of the 88 harnesses `parity.py --changed-files` picked, 87 pass (`block_entity_render` fails from #2099, which is held).
Player heads whose profile carries only a name or only a UUID
(ResolvableProfile.Dynamic) resolved to nothing, so heads some servers
send that way kept the default skin. Adds skins::Resolver
(ProfileResolver.Cached: profiles/getByName then
session/getProfileById through the services discovery document, on
background threads) used by placed heads, head items and mannequins; the
profile-derived DefaultPlayerSkin stands in until the download lands,
with the name's offline UUID keying it for name-only profiles.
A skin evicted from the atlas's 128 slots could never return: the decoded
image was dropped and show_skin simply failed. The downloader now keeps
the images (and the disk cache backs them), and again queues the upload
again where the tab list, level players, mannequins and heads notice the
missing sprite.
profile_tag keeps id so id-only and name+id profiles survive into the
item data JSON, and the player-head item fallback matches vanilla's
slim/steve (was wide/steve).
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Full trial (run 40341431094): 342/348 in 96.9 min; save_roundtrip,
remote_fireball and server_player_sculk passed on a rerun with vscreen and
fewer jobs; these three did not.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Send open/filter changes to the authoritative player's recipe book and cover packet settings plus save/reopen in both single-player modes.
Co-Authored-By: OpenCode GPT-6.1 Sol <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude). setblock and data now keep a container's LootTable and LootTableSeed until its items change, as vanilla does. command_exec matches the 26.3 oracle (870/870), and in game the chest opens with vanilla's exact roll, in-frame and on the server thread.
The celestial pass wrote alpha 0 over each quad, so HDR output lifted a
square of bright daytime sky around the sun. It now draws premultiplied
(one, one), which looks the same, and its alpha is how much a texel adds;
the blend takes that off the frame's alpha, so only the discs shine. The
dim End sky, drawn by the same pass, stays unmarked. fs_celestial keeps
its own blend for lightning and additive entities.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
browser.set-recipes replaces the panel's items with the mod's recipes,
laid out with the game's slot, furnace arrow and flame sprites and small
buttons: a crafting grid, one input over a flame, one input, three inputs
or two, then an arrow and the result, a note under it and the action
button beside the result (under it when the panel is narrow). Slots cycle
through their stacks once a second. As many recipes as fit the panel's
height make a page, with the recipe book's arrows; a title row holds a
Back button and the title. The same title sent again keeps the page.
Clicks on the slots, the action button, the arrows and Back reach the mod;
tooltips and the mods' keys work on the recipes' stacks as on the items.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod gives the recipes to show (a layout: crafting grid, cooking, single
input, smithing or brewing; the slots, each cycling through its stacks;
the results; a note; and an optional action button, enabled or greyed)
under a title; an empty list shows the items again. The game pages them
and sends the slot-clicked, action, page and back events.
At most 256 recipes, 9 slots each and 64 stacks a slot. Same level as the
rest of browser (5): none of it has shipped yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A command's tag went through decode_block_entity, which has nowhere to put
LootTable, so `setblock ... chest{LootTable:...}` made an empty chest
(`data get` showed `Items: []`). An empty container given a LootTable is
now rolled as a loaded chunk's are, and the roll is kept with its chunk
(RolledLoot), so it saves, and `data get` shows, LootTable and
LootTableSeed until its items change (tryLoadLootTable / trySaveLootTable).
`data merge` and `data modify` go through the same path.
command_exec: setblock a seeded loot chest, then data get it whole and its
LootTableSeed (870/870 match the 26.3 oracle).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The download toast already matched vanilla, but once a server pack was
downloaded the whole resource reload ran synchronously on the render
thread: the frame froze on "Joining world" for seconds where vanilla's
Minecraft.reloadResourcePacks shows a LoadingOverlay with a progress bar.
The reload is split like the startup loader: a worker builds the overlay
root, models and sprites, the menu bundle and the pack's tables while a
progress screen (Preparing resources -> Loading assets -> Loading pack
tables -> Applying resources) covers the frame, then the renderer swap
and resource application land on the render thread and
ServerPacks.reloaded drives the feedback replies, including the recovery
reload on failure. The pack screen's own reload reuses the same staged
load, so its behaviour is unchanged.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). The client branch of Squid.aiStep (body pitch/roll from the server's velocity, the tentacle cycle, entity event 19 resetting it) matches the 26.3 bytecode. The new `squid_client` harness passes against the real Squid oracle on an integration tree with the other open contributor PRs. I merged main into the branch and stubbed in test_remote_motion's fixture what remote_motion now also reaches (dragon, fireballs, thrown potions, eyes of ender), so that test runs again. Fixes#1684 is right. Thanks @ESHAYAT102!
Main's HDR output configures the surface through Renderer::configure; the
reused spare surface goes through it too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Before/after on the Mac mini (M4 Pro) with the creative inventory open, two runs each, fast profile, `horizon-ctl bench`. Main: CPU frame 0.81 to 0.91 ms on the building/combat tabs and 1.32 to 1.47 ms while searching (664 to 742 fps). This PR: 0.44 to 0.49 ms on building/combat and 0.49 ms while searching (1122 to 1125 fps). The panel pixels match main. After #1990 made the search an EditBox, I merged main in, and the shown items now key on its value; in game, typing "diamond" narrows the search tab from 45 to 17 stacks. `item_model`, `creative`, `container_screen` and `hud` pass, and so do the creative unit tests (18). Fixes#1953 is right. Thanks @kacpherek!
🤖 Agent: Merged by Ludovic's AI agent (Claude). Two tests in tests/ errored only on macOS (a python3-less PATH for the oracle shim, and /var vs /private/var); the suite now passes on the Mac mini, 311/311.
ClientboundServerLinksPacket (configuration and play) decoded and kept,
the pause menu gains its "Server Links..." button when a server sends
links, opening the server_links dialog screen: scrollable link buttons,
ConfirmLinkScreen on click (chatLinks/chatLinksPrompt honoured), and the
custom-screens warning with its disconnect. A dedicated server sends its
`bug-report-link` property as the BUG_REPORT link, like vanilla.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Whether two orbs merge (ExperienceOrb.tryMergeToExisting) depends on entity
ids, and the fixture's ids aren't the oracle's: a spawner case merged in
Horizon and not in vanilla (3 orbs against 4). Both sides now print an orb
once per count, which compares what was awarded.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The panel sits right of the inventory or container window, from the top of
the screen to a search box at its foot, in up to 12 columns of the game's
own item slots, with the recipe book's page arrows. It shows on the
survival inventory and every container screen (not the creative one) while
a running mod has items in it, and hides when there is no room for two
columns. Clicks on it never throw the carried stack; the carried stack and
tooltips are drawn again above it.
The mod hears clicks on items, the search text as it changes, and its own
keys pressed with the pointer on an item, the panel's or a slot's. The
search box takes every key but Escape while focused, as the recipe book's.
Each set-items is turned into stacks once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new interface under capability ui. browser.set-items lists the items of
a panel the game shows right of the inventory and container screens (an
empty list hides it); browser.poll returns what the player did since the
last poll: a click on an item, the search text, or one of the mod's own
keys pressed over an item (a slot's or the panel's), with that stack. The
slot-clicked, action, page and back events are there now for the recipe
pages to come, since adding variant cases later would break mods.
At most 8192 items and 256 waiting events. The game side is the next PR.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
OracleReuseTests ran the shim through `#!/usr/bin/env python3` with a PATH
whose Python folder is Homebrew's, which names it only python3.13: the
shim wasn't found (127). The test now gives that PATH a python3 of its own.
test_report_rejects_sources_changed_during_a_run compared a resolved source
path with an unresolved temp root, and macOS's /var is /private/var.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
remote_motion now also hands motion to the dragon, its fireballs, thrown
potions and eyes of ender; the extracted fixture needs those to compile.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's PlayerList.remove awards Stats.LEAVE_GAME to a player before
saving them, and PlayerList.removeAll does it for everyone when the
server stops. Horizon only counted it in the in-frame path, so on the
default server thread the owner never got it, and guests who left or
were removed at shutdown did not either.
LanHost::award_leave_game counts it once per guest (a guest is saved
more than once on the way out) and runs where a guest leaves, when the
integrated server stops, and when LAN closes.
Part of #2095 (item 4 of the report). The signal handling and the
remote-world disconnect are #2116's.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Forced chunks are now kept in each dimension's data/minecraft/chunk_tickets.dat as 26.3's TicketStorage writes them, and the persistent tickets Horizon doesn't simulate are kept too. A world with /forceload areas round-trips with vanilla 26.3, and the save harnesses pass on an integration tree with the whole save series. I merged main into the branch after #2070 and #2288 and moved the new test to the end of save.rs's tests, so the other save PRs don't collide with it (save:: 27/27). Fixes#1527 is right. Thanks @rdbtCVS!
🤖 Agent: Merged by Ludovic's AI agent (Claude). /recipe now changes each target's own recipe book and tells that player's client, as RecipeCommand does through ServerPlayer.awardRecipes/resetRecipes. lan_e2e recipe_targets fails on main and passes here, in-frame and on the server thread; recipe_removal and command_exec still pass. Thanks to platform-2 for finding it.
🤖 Agent: Merged by Ludovic's AI agent (Claude). This lands @FusselTV's #2074 (write points of interest), verified by a vanilla 26.3 round trip; the save harnesses pass. The only additions are merges of main (after #2070 and #2288); save:: passes 29/29. Thanks @FusselTV!
🤖 Agent: Merging. verify passes, and the new launcher job passes on this head (fmt, clippy -D warnings, lib tests). On the earlier head it had caught main's E0061 before #2281. The Python tests show the same 2 environment errors as main.
cmd_recipe changed self.level.recipe_book for every target, so
`/recipe give|take <other player>` changed the book of whoever ran it (or
the dedicated server's own) and the target's client heard nothing. Each
target now runs with its state swapped in (for_player), as
ServerPlayer.awardRecipes/resetRecipes do on the player's own
ServerRecipeBook: the guest's book changes and its client gets
ClientboundRecipeBookAdd/RemovePacket.
lan_e2e recipe_targets: the host gives and takes the guest's recipes and,
on the server thread, the guest gives the host one; each open book must
show exactly its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged by Ludovic's AI agent (Claude). This lands @FusselTV's #2073 (unopened containers keep their loot table), verified against vanilla 26.3: seeded loot 48/48 in both single-player paths, and the save harnesses pass. The only addition is a merge of main after #2070. Thanks @FusselTV!
Prepending tools/ made unittest discovery import tools/test_decoration_parity.py
in place of tests/test_decoration_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main to resolve a conflict in game/remote.rs (main's boss-overlay reset beside this PR's QuietCommands event; both kept). Clippy with mods on is clean. Requeued at the new head.
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Item.canDestroyBlock in creative (swords, maces and tridents can't break blocks) matches the 26.3 bytecode, on the player's prediction and on the server. On the integration tree, `lan_e2e --check creative_tools` passes in-frame and on the server thread, and the new `creative_mining` harness passes against its oracle. I merged main into the branch twice; the second time, lan_e2e.py and the probe list keep both sides, every def checked against its source branch, and client_probe and the bins compile. Fixes#2035 is right. Thanks @ESHAYAT102!
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Checked against 26.3: `AreaEffectCloud` syncs `DATA_RADIUS` (8), `DATA_WAITING` (9) and `DATA_PARTICLE` (10), waits 20 ticks (dragon clouds too), and `clientTick` is ported draw for draw. `DragonFireball`'s trail is `dragon_breath`. One fix pushed: `AbstractHurtingProjectile.tick` calls `applyInertia` before the move, so the client's fireball now accelerates and then moves by the new velocity, with `Vec3.normalize`'s zero for a tiny vector. Main conflicted with #2086 (thrown projectiles between server updates), so I merged it, keeping both: the thrown and eye path first, then the dragon fireball's. `lan_e2e.py --check particles` and `objects` pass in both modes. Part of #2083, as it says. Thanks @kacpherek! Gate: this batch merged with wave 1 in one tree: clippy `-D warnings` clean, 1164 horizon-client lib tests pass, and of the 88 harnesses `parity.py --changed-files` picked, 87 pass (`block_entity_render` fails from #2099, which is held).
🤖 Agent: Gate check by Ludovic's AI agent (Claude). Chunks now write the structure starts and references they hold, so vanilla keeps the structures (and their spawns) after the round trip. Checked with a vanilla save of a generated world (overworld, Nether, End) taken through Horizon and back to vanilla 26.3 on the server thread and in-frame. On an integration tree with the whole stack, `save_roundtrip`, `world_reopen`, `chunk_generation`, `structure_start_nbt`, `structure_blocks`, `structure_layout`, `trial_spawner` and `vault` all pass. Thanks @FusselTV!
#2171 takes level 3 (command.run-quiet), so the world interface's functions
take level 4. The workspace members keep main's horizon-hdr and this branch's
beacons example.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HDR10 is what games usually present and what the display receives: a
compositor composes in it or converts to it anyway. The NVIDIA driver
offers it on Wayland next to scRGB. A 10-bit swapchain also halves the
last pass's bandwidth; the frame before it is 8-bit, so nothing is lost.
hdr.wgsl converts the same linear light to BT.2020 (BT.2087) and encodes
it with the ST 2084 curve. HORIZON_HDR_OUTPUT=pq or scrgb allows only
that one, for comparing them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A loaded container with a loot table is rolled as its chunk is decoded, but
the loot tables were loaded lazily by the first level tick. The spawn chunks
are decoded while the world opens, so a loot chest in them came out empty and
lost its table on the next save. WorldRequest::prepare, which every world
opens through (in-frame, server thread, dedicated server), now loads them
first.
spawn_loot: vanilla writes a flat world with a seeded loot chest by the
spawn and rolls it; Horizon opens it in a fresh process, in-frame and on the
server thread, and must hold the same stacks in the same slots. Without the
fix both modes hold nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
verify's cargo runs never build launcher/ (its own workspace), so #2059 and the
registry ratings commit each passed alone and left main's launcher failing to
compile (E0061). The new launcher job runs its rustfmt, clippy -D warnings and lib
tests when a pull request changes the launcher, a crate it builds or the root
manifests, and on every push to main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main moved the crystal search into nearest_crystal_to, which the server
thread's client uses too; the dragon's grown box now lives there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#2191 and #2192 each add a `find_repellent` to piglin.rs, one for
hoglins and one for piglins; merged together they would collide.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#2191 and #2192 each add a `find_repellent` to piglin.rs, one for
hoglins and one for piglins; merged together they would collide.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a server-thread client an elytra boost rocket drew no spark trail and
showed up as a static item sprite. The client now follows the entity the
rocket is attached to, spawns the trail each tick like
FireworkRocketEntity.tick, and does not draw an attached rocket.
Zombie.finalizeSpawn calls setCanBreakDoors(nextFloat() < special * 0.1)
after the baby roll; the game's spawn finalize never rolled it, so only
the finalize probe's zombies could break doors.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client's copy of a block event (`client_block_event`) ran the server's
neighbour logic: a block it moved could start another piston on the client
alone (`PistonBaseBlock.checkIfExtend` is only called server-side), and the
re-run's `playSound(null, ...)` played the piston and note sounds a second
time next to the server's (`ClientLevel.playSeededSound` plays only for the
local player as the excluded one).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
recipes.items() lists every item in registry order, with its name in the
player's language and its tags. recipes.all() lists every recipe: crafting
and furnace ones from the recipe book's own entries (vanilla's displays and
display ids), then campfire, stonecutting, smithing and brewing (display
only). Stacks point into the item table by index. recipes.known() gives the
book ids the player's recipe book holds, from the client book in every mode,
so on a server it follows the server's book.
The game builds the tables once, the first time a running mod reads them.
Experimental, new functions only. Smithing::Transform now keeps its recipe
id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mob.checkAndHandleImportantInteractions: a spawn egg of the clicked mob's
kind spawns a baby at the mob's position (SpawnEggItem.spawnOffspringFromSpawnEgg),
inheriting the parent's variant, and uses one egg outside creative.
Fixes#2269.
Main's critical-hit change added the same Strike.attacker field; keep
one, then the crit particles before the victim's thorns.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Turtle.finalizeSpawn sets homePos to where it spawns, and loading falls
back to the block it stands on; the home no longer moves with a turtle
that had none, which let it lay eggs anywhere. TurtleBreedGoal.breed adds
one ExperienceOrb of 1 to 7 drawn from the mother's random.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mob.makeSound calls getVoicePitch before Entity.playSound checks
isSilent, so the squid's random advances either way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both random_chance terms come first in their all_of conditions, so the
level random is drawn before the block and movement checks: the boots'
4% on every location change, and the sound's 35% on every tick the
boots are worn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A remote client (single player's window on its server thread, a LAN
guest, a dedicated server's client) never ran the client side of a
piston's move: the block event reached block_entity_event, which has no
piston ticker, so moving_piston blocks arrived with no moving block
entity and pushed blocks teleported (#1704).
Vanilla's runBlockEvents broadcasts a handled event to the clients, whose
own level re-runs triggerEvent (PistonBaseBlock.moveBlocks) to build the
PistonMovingBlockEntity it then ticks and renders interpolated. Do the
same:
- run_block_events emits a BlockEntityEvent for a handled event; the LAN
and integrated hubs send it as ClientboundBlockEventPacket, before the
tick's block changes, as vanilla's broadcast precedes
ChunkHolder.broadcastChanges.
- The remote client re-runs triggerEvent for pistons, note blocks and
bells on its own level (dropResources stays server-side, as
popResource checks !isClientSide), and ticks the resulting moving
pistons each client tick so they animate and collide between the
server's states. Block events a client-side move queues run right
away, as ClientLevel.blockEvent does.
Note blocks and bell rings reach the guests through the same packet,
which they did not get before.
Co-Authored-By: Devin <[email protected]>
Shulker.hitByShulkerBullet tests level().getRandom().nextFloat() against
the crowding chance, not the shulker's own random.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player.doSweepAttack keeps nearby entities with distanceToSqr < 9.0; it
doesn't use the entity interaction range.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These four 26.x effects only existed as potion entries. Oozing spawns two slimes, Weaving places cobwebs and Wind Charged makes a gust when the bearer dies. Infested sometimes releases silverfish when the bearer is hurt.
damage_resisted, damage_absorbed and damage_blocked_by_shield now add up when
Resistance, absorption hearts or a shield take damage, and a player who lands
the killing blow on another player gets player_kills.
A struck copper block goes back to unoxidized, and 3 to 5 random walks
take one step of oxidation off the weathering copper around it. Waxed
blocks stay as they are but still start the walks.
Eating a cake slice, using a cauldron (fill, use, clean armor, banner or shulker
box), tuning a note block, ringing a bell, opening an ender chest and putting a
record in a jukebox now add to their statistics, which stayed at 0 before.
Goats only strolled and panicked, so wheat did nothing after the love
hearts and a bucket gave no milk. Give them the GoatAi core and idle
behaviours that exist here (panic 2.0, breed, tempt 1.25, baby follow
1.25) and Goat.mobInteract's milking with the goat milk sounds.
Right-clicking an adult armadillo with a brush did nothing. Follow
Armadillo.mobInteract: drop a scute from the brush loot table, play the
brush sound, post the entity_interact game event and wear the brush by 16.
The statistic was written as talk_to_villager, but vanilla's key is
talked_to_villager, so the statistics screen and scoreboard criteria always
showed 0. Wandering traders already count through the same code.
Armadillos never dropped scutes because nothing ran their shed timer.
Follow Armadillo.customServerAiStep: count down 5 to 10 minutes, then
drop a scute, play the sound and post the entity_place game event.
Right-clicking a placed sign opened an empty inventory screen. It now
opens the editor for the side the player faces, or plays the waxed-sign
fail sound. Dyes, glow ink sacs, ink sacs and honeycomb change the
colour, glow or wax of the side the player faces and are used up.
Mobs now scale their detection range by 0.7 times the worn armor fraction (at least 0.1) when the player has Invisibility, like vanilla's getVisibilityPercent.
Fall distance is counted once per landing from 2 blocks or more instead of on
every downward tick. Riding counts only for the mounts vanilla lists (pig,
horse family by full distance, strider, happy ghast, nautilus, boat, minecart),
and airborne sideways moves over 25 cm count as flying.
A target block never changed its power when something hit it. Every
projectile that hits a block now asks the block how to react, as vanilla
does: a target block takes its power from where the projectile landed
(1 to 15 by the distance from the face centre) and resets after 20
ticks for an arrow or 8 for any other projectile. A player's hit also
counts the target_hit statistic.
Restore integer atlas origins and sample local texel centers before nearest filtering. Cover the packed stage origins with a GPU sampling regression.
Fixes#1882.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
A bred turtle now carries an egg instead of the baby appearing at once.
She walks home, digs into sand for a while and lays a turtle egg there.
A hatching egg gives one baby turtle per egg with the egg as its home,
and a baby that grows up drops a scute.
Mobs no longer open or break doors on their own except villagers and copper
golems. Zombies that rolled canBreakDoors break doors on hard, and
vindicators break doors on normal and hard and open them during a raid, as
BreakDoorGoal, VindicatorBreakDoorGoal and RaiderOpenDoorGoal do. A zombie's
CanBreakDoors value is now saved and loaded.
Piglins now sense the nearest block from the piglin repellents tag within 8 blocks sideways and 4 up or down (a soul campfire only while lit). When one is closer than 8 blocks and the piglin is idle with nowhere to go, it walks away from it.
Hoglins now sense the nearest block from the hoglin repellents tag within 8 blocks sideways and 4 up or down. While one is near they become passive for 200 ticks (the attack target is dropped and no new one is picked), walk away from it, and avoid picking random walk spots within 8 blocks of it. Being hit ends the passive state.
The enchantment's location_changed effects now run for the player: the
movement_speed and movement_efficiency modifiers turn on over soul sand and
soul soil and stay on while airborne, the boots wear with a 4% chance per
block change, and walking on those blocks spawns soul particles and the
soul escape sound.
A conduit with enough prismarine around it now gives conduit power to
players in water or rain in range, hurts an enemy in water or rain when
it has 42 frame blocks, and plays its activate, deactivate and ambient
sounds, as ConduitBlockEntity.serverTick does. Its target also feeds the
client's attack particles.
A mod hands `run-quiet` a list of lines (at most 256 a call). In single
player they run as the player typing them would, with the player's source
made silent; on a Horizon server (it announces horizon:quiet_commands) they
go as one payload that the server runs the same way; on any other server
they fall back to chat commands, whose feedback shows. command.run is
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Right-clicking a piglin with any loved item used to consume it and the piglin threw it back. Now only piglin currency (gold ingots) is accepted from the hand, and anything else a piglin admires is worn if it can be or stored in its inventory, which saves, and drops when it dies. Piglins also pick up gold nuggets when they have room, food when they have not eaten lately, and gear that fits an empty slot.
AbstractIllager.canAttack refuses a baby villager or wandering trader.
Skip them when an illager picks a nearest target and drop them from an
illager's current target.
An open shulker hit by a shulker bullet teleports and may leave a new
shulker of its colour behind, like Shulker.hitByShulkerBullet. Levitation
from the bullet now needs the hit to have hurt the player.
Count vexes and look for blue sheep within a 16 block radius, spare only
illagers and their vexes with evoker fangs, and let vexes ignore raiders
(including ravagers) instead of other vexes.
Gliding into a wall now hurts like LivingEntity.handleFallFlyingCollisions
(fly_into_wall, big or small fall sound). A glide no longer starts on a
climbable or under Levitation, ends there, and a climbable (not vines) ends a
glide in progress. The glide's elytra wear goes through Unbreaking, and the
glide uses the effective gravity instead of 0.08.
`draw_item` and `draw_item_decorations` only read the game, so they take
`&self`; the hotbar loop can then hand them `self.inventory[slot].as_ref()`
and `self.offhand.as_ref()` instead of cloning the stack once per slot per
frame.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
A drinking witch now gets the -0.25 drinking speed modifier, so it does
not walk for the 32 ticks. When it throws a potion, slowness, poison and
weakness are only picked if the target (player or mob) does not already
have that effect, as Witch.performRangedAttack does.
A creeper's explosion hurt mobs with no attacker, and the creeper is
already gone from the mob list when its blast is applied, so the
charged_creeper loot never matched. The creeper now announces itself
(ExplosionSource) before it explodes, its victims name it as the damage
source, and it stays known until they have been looted. One head drops
per charged explosion.
A hurt zombie with a target now tries up to 50 spots 7 to 40 blocks away
for a copy of itself, as Zombie.hurtServer does. The copy takes the
target, and both zombies get the charge that lowers their chance.
New zombies also roll their reinforcement chance and a leader's bonus
(Zombie.handleAttributes); before this the chance stayed at 0.
Wooden buttons ignored arrows: a pressed button always released after
its delay. A button whose type allows arrows is now pressed when an
arrow (or a thrown trident) is inside its shape, and it stays pressed,
scheduling the release again, while one still is. Arrows now also run
the entityInside check on the blocks they pass through each tick.
The search only covered 8 blocks up and down. It now uses the POI index over
the whole sphere, and a bed only counts when the villager has a path to it,
as VillagerMakeLove.takeVacantBed does.
Crossbow arrows now get their pierce level from the Piercing enchantment, go through that many entities plus one without sticking, and are discarded at the next one. Arrows without Piercing behave as before. Pillager arrows now use the pierce level their weapon already carried.
A creeper is now lit by any item in the creeper_igniters tag, not only
flint and steel. A fire charge is used up and plays its own sound; flint
and steel still loses durability.
Thorns picked the nearest living mob in the world. They now hurt the mob
that caused the hit (or its projectile's owner), or the attacking player
in a player-versus-player hit.
A farmer working at its composter turns wheat into bread (three wheat for one
loaf, up to three loaves, while it holds at most 36 bread) and puts spare wheat
and beetroot seeds into the composter, emptying a full one first, as
WorkAtComposter does. Other professions are unchanged.
Mob explosions (creepers, ghast fireballs, wither skulls and the wither
spawn blast) now keep blocks when mobGriefing is off, and follow the
mob_explosion_drop_decay rule when it is on. Endermen no longer pick up
or put down blocks with mobGriefing off.
Throwing a trident now wears it through the same durability path as Riptide, so Unbreaking applies, and it still never breaks the trident. Riptide no longer launches a player who is riding something.
A thrown trident with Channeling now calls down a lightning bolt in a thunderstorm, both on a mob it hurts (if the mob can see the sky) and on a lightning rod it hits (if the rod can see the sky), with the thunder sound.
A daylight detector never changed its power: nothing ran its update.
It now has a block entity with the ticker vanilla has, and updates its
power every 20 game ticks from the sky light, the sky darkening and the
sun angle. Using it to flip inverted mode recomputes the power at once.
A pure move, the first slice of splitting the client lib into crates:
- horizon-command: the command/ module (Brigadier port, vanilla tree, argument types, selectors), as is.
- horizon-client-core: what command needs from the lib, with no other dependency inside it: text, lang, snbt,
nbt_path, component, scoreboard, bossbar, gamerules, block_entity_nbt, fastutil_order; plus items moved out of
lib modules that sit in the cycle, unchanged: ParticleType (particles.rs), the key display name (keybinds.rs),
SOUND_SOURCES (settings.rs), uuid_text/uuid_from_text (save.rs), Registries/RegistryIds and parse_identifier
(command/args.rs).
horizon-client re-exports every module and item under its old path (`horizon_client::text`,
`crate::command::Registries`, `particles::ParticleType`...), so no caller changes. Inside the moved files only
paths changed (`crate::snbt` -> `horizon_client_core::snbt` in command, `crate::command::args::parse_identifier`
-> `crate::identifier::parse_identifier`...), `sign_side` became pub, lang.rs includes the lang files from
horizon-client's assets, and gamerules' test that saves a Level moved to save.rs (it needs the client's save).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two changes so `parity.py --changed` keeps picking the right harnesses once code moves into a crate of its own:
- A harness recorded before the move names the old paths. A changed Rust file of a crate the recorded crates
link (their path dependencies, transitively) but don't name now counts, and the reach search decides.
Before, such a file matched no record and selected nothing.
- A library's root opens once reached code names the crate (`horizon_world::`, `pub use horizon_command as
command`), instead of always. Every crate root was open, so any item of a new crate whose name matched a
name used anywhere counted. On main the selection is the same for 15 of 16 sampled files; for
horizon-world/src/nbt.rs one harness that reached it only through a name match drops out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rabbits get BreedGoal and TemptGoal with rabbit food. Damage to an animal
resets love. Horses, donkeys and llamas need canParent (not ridden, tame,
adult, full health) for both parents, a sitting wolf is no partner, and the
breeding experience orb needs the mob_drops game rule.
Gift loot (chicken eggs, sniffer seeds) is now rolled with the mob as the
loot context's this entity, and chicken variants are known to loot
predicates, so each chicken lays its own egg colour. A block a player
breaks now has the player as this entity, so snow layers drop snowballs
and chorus flowers drop themselves.
The field only appended and popped the last character, and Space typed
nothing on Windows (it arrives without a character). A shared one-line
field, launcher/src/line_edit.rs, now holds the text, caret, selection
and undo history: arrows, Home/End, Ctrl+arrows by word, Shift to select,
mouse click, drag, double and triple click, Ctrl+A/C/X/V and Ctrl+Z / Ctrl+Y.
AbstractHurtingProjectile.tick calls applyInertia first and then moves by
the new delta movement; Vec3.normalize leaves a near-zero vector at zero.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla sends these with ServerLevel.sendParticles, so every player
nearby sees them. Horizon drew them only in the host window's own
particle engine, which a window on a server (and a guest next to the
host) never sees.
One helper now sends a ClientboundLevelParticlesPacket with the same
count, offset and speed to each player within 32 blocks. While the host
holds the players (a guest's turn, a host hit on a guest) the packet
waits and goes out when the tick ends.
The server never sent ClientboundHurtAnimationPacket, and the client
ignored the yaw it carried, so a player on a server got the flash but the
camera never tilted towards where the hit came from.
ServerPlayer.indicateDamage now runs where the hit's knockback is dealt
(and with no direction for hits that have none), the packet goes out when
the guest's turn ends, and the client keeps the yaw as Player.hurtDir for
GameRenderer.bobHurt.
Single player on the server thread drew these particles only into the server's own engine. The server now sends the entity event byte where vanilla does (20, 12/13/14, 6/7, 18 and 3), and the client draws it in handleEntityEvent's way.
An end crystal (or minecart) hit by a player on the server thread blew up
with sound and damage but no explosion particles. The blast runs inside the
guest's turn, while the host holds the players, so lan_explode had no hub to
send the explosion packet through and dropped it. Queue it like a wind burst
and send it when the turn is over.
A thrown lingering potion used to act as one weaker splash. It now leaves an area effect cloud like vanilla: radius 3, a 10 tick wait, 600 ticks, shrinking as it goes and by 0.5 each time it affects someone. Every 5 ticks it affects the mobs and players inside, with a 20 tick delay per entity, at a quarter of the duration (instant effects at half strength). Server players see it as an area_effect_cloud, like the dragon's breath clouds.
A ghast fireball or wind charge hit back by a player was turned on the server, but the player kept seeing it fly on until its next update ten ticks later. Mark the turn to be sent right away, as vanilla does with needsSync, and reset the acceleration power to 0.1 as an attack deflection does.
Dragon fireballs were a server-side list like the breath clouds, so a server's players never saw them. Each is now tracked as a minecraft:dragon_fireball; the client keeps it in the same list, flies it on as AbstractHurtingProjectile.tick does, snaps it to the server's moves and draws it with its dragon_breath trail.
The breath clouds are kept as a list, not entities, so the server thread drew their particles into its own copy and players never saw them. Each cloud is now tracked as an area_effect_cloud (radius, waiting, particle), and a server's client draws it as AreaEffectCloud.clientTick does.
Applying packs dropped the window surface and created a new one for the renderer built on the same device. On Wayland with Mesa (RADV) the new surface came back lost (ERROR_SURFACE_LOST_KHR), the new renderer failed, and restoring the old one failed too, which crashed the game.
Track fang entities and deliver their start event to the existing client renderer. Exercise warmup, animation, authoritative positions and removal in the LAN harness.
Co-Authored-By: OpenAI Codex GPT-6.1 Sol <[email protected]>
A resource pack can rename items without changing the Search label the cache was keyed on, so a search kept its old results until the query or tab changed.
Two costs a frame were repeated for every item a screen draws. ItemModels::components cloned the item's whole default component map (a JSON map of every default component) on every resolve, though most stacks carry no patch; it now borrows the defaults and copies only to apply a patch. And gui::draw submitted, culled, sorted and clipped every quad of a static item again each frame (hundreds of edge quads for a generated item, all facing sideways in the GUI), though the result for the same layers at the same position is the same; the HUD vertices of a static item are now kept per GUI position, keyed by its baked models, tints, transforms, the atlas and the exact position, and appended as they were. Items with a glint or a special model renderer are drawn as before. With the creative inventory open the HUD's share of a frame went from 0.66 ms to 0.23 ms.
draw_item_model built the client level state (dimension, game time, sun angle, moon phase, respawn) for every stack it drew, sampling the sky each time: about seventy times a frame with the creative inventory open, a fifth of the time spent on its items. Within a frame the level is the same for all of them, so it is kept for the frame (keyed by the frame, game time, day time and dimension).
With the creative inventory open, every frame cloned all the tabs with their stacks (twice), and items() cloned the selected tab's stacks again on each of its three or four calls a frame; on the search tab each call ran the whole search again, building every stack's localised name. That took the frame rate from about 1,900 to 980 with the inventory open and 340 while searching (#1953). Vanilla fills ItemPickerMenu.items when the tab or the search changes (selectTab, refreshSearchResults): the grid's stacks are now kept until the tab, the search text or the language changes, and the screen draws the tabs without copying their stacks.
Horizon finds villager POIs from the blocks and kept no poi folder, so a
world it made had none. Vanilla rebuilds a missing section on load, but
until a chunk is loaded nothing knows its beds, job sites, bells, nests,
portals or lodestones.
Saving a chunk now writes its POI records the way PoiManager saves its
sections: each section with POI blocks, valid, one record per block of
PoiTypes with its max tickets, free_tickets left out at 0. A chunk that
lost its last POI and had a record gets an empty one, since vanilla
trusts a valid section.
The records are built on the region writer thread, which reads the
saved record there, so unloading chunks reads no files on the tick
thread. A straw bed is no home (PoiTypes.BEDS is the sixteen colours);
/locate poi uses the same table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A generated chunk keeps the starts it holds and its save writes them
into structures.starts with their current state, as vanilla saves the
StructureStart objects its chunks share. Proto chunks get their
references and starts too, so vanilla and Paper find the structures of
the chunks they finish at the edge of a generated area.
A start with a piece that cannot be saved is left out, with a warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A container whose items no longer match its roll is saved without the
table, also when it was loaded with one, so a chest emptied after a
load does not fill again on the next one. A zero LootTableSeed is left
out, as trySaveLootTable does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Run the client branch of Squid.aiStep, preserve authoritative movement and synchronize tentacle-cycle resets with entity event 19.
Co-Authored-By: OpenAI GPT-6.1 Sol <[email protected]>
Check the tool component before server destruction, local mining and remote prediction. Cover vanilla item defaults, overrides and mode-switch cancellation.
Co-Authored-By: OpenAI GPT-6.1 Sol <[email protected]>
Respawning drops the guest's body, so the next swap builds a fresh one,
which now clears what the old body was given (equipment, effects and
armor protection) itself.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server's player gets a new body when it joins, respawns or changes
level, but what the old body had been given by its effects (the applied
attribute modifiers and the armor protection table) was kept in its
per-player record. apply_effect_status then saw nothing to change, so
after a trip to another dimension Speed kept its icon while the body's
movement speed fell back from 0.14 to 0.10, until the effect was
reapplied.
Vanilla keeps the same ServerPlayer through ServerPlayer.teleport into
another level, with its attributes and MobEffect modifiers. A new body
now starts with nothing applied, and its effects are applied as soon as
it is created, before the movement that brought it is handled.
A server mod's registration now lists each command's arguments: a name, a
kind (word, text, integer with bounds, number, boolean, player, block-pos)
and whether it is optional. The server adds them to the command tree every
player receives, after vanilla's commands, so a player's chat box completes
and colours them as vanilla's. When a player sends one, the server parses
the line as vanilla's parsers do (horizon-mods' commands.rs: ~ coordinates
from the player, @s/@p, integer bounds) and calls the mod's command hook
with the values by name, or answers with vanilla's error without calling
the mod.
The hub keeps the server mods' commands as a root in the commands report's
format and resends players' trees when they change (CommandTree::
packet_with); the game sets it when its mods start, stop or reload, or a
hub opens. In-frame, the window's own chat box lists them by name.
SDK: Registration::command_with(name, [Argument::...]), Argument builders,
Args (text(), integer(), player(), block_pos() ...); command(name) is free
text, an optional text argument. Examples: welcome's /mined [player],
beacons' /beacon [height] and /ground [pos], with scripts for values and
errors.
Docs: docs/mods.md (typed commands, kinds table), docs/api/mods.md and
the regenerated reference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Server mods get interface world: get-block(dimension, x, y, z) reads a
loaded chunk's block state as "minecraft:oak_log[axis=y]", get-biome its
biome ("minecraft:plains"), and
set-blocks(dimension, changes) sets up to 4096 blocks per call, checked at
once, made as /setblock makes them (place_state: neighbours, light, block
entities, every client told through the block log) after the call, before
that dimension's next tick.
Reads cost no copy: for a mod call the game lends Mods the levels' chunk
maps (moved out with mem::take and put back after), only while a running
mod has world. Writes for a level that isn't ticking (another dimension,
in-frame) wait for it, up to 65,536.
Test scripts get blocks (setup) and expect.blocks; cargo horizon-mod check
reports a trial run's block writes. Example beacons: /beacon builds a
glowstone pillar beside the player, /ground reads the block underfoot and the biome.
parse_state is now a free function so the mod path shares it.
Docs: docs/mods.md (the capability, script keys), docs/api/mods.md and the
regenerated reference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 18:55:29 +01:00
566 changed files with 51010 additions and 5249 deletions
@@ -10,7 +10,8 @@ Feature → files → harness for the most-changed areas: [docs/architecture.md]
`tools/check_public_tree.py` rejects them; `git config core.hooksPath .githooks` runs it on commit.
- **Keep your runs away from real player data.** Any game or launcher you start gets fresh `mktemp -d`
folders in `HORIZON_HOME` (the game's data), `HORIZON_LAUNCHER_HOME`, `XDG_DATA_HOME` and
`XDG_CONFIG_HOME`, plus `HORIZON_LAUNCHER_NO_SELF_UPDATE=1` and `HORIZON_NO_AUDIO=1`. Never write to
`XDG_CONFIG_HOME`, plus `HORIZON_LAUNCHER_NO_SELF_UPDATE=1` and `HORIZON_NO_AUDIO=1`, and, for the
launcher, `HORIZON_LAUNCHER_TRASH`, `HORIZON_LAUNCHER_CLIPBOARD` and `HORIZON_LAUNCHER_DESKTOP` pointing into the scratch home. Never write to
`~/.local/share/{applications,horizon,horizon-launcher}` or `~/.local/bin`.
- **Mark what an agent posts.** A PR body starts with a line naming whose agent opened it and with what.
Ludovic's agents write `> 🤖 Opened by Ludovic's AI agent (Claude), not by Ludovic himself.`, and a
@@ -34,7 +35,9 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| Crate | Owns |
|---|---|
| `horizon-client` | The game: a lib (`src/lib.rs`, ~190 flat modules) and the bin (`src/main.rs` → `game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) |
| `horizon-client` | The game: a lib (`src/lib.rs`, ~180 flat modules) and the bin (`src/main.rs` → `game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) |
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
| `horizon-world` | Vanilla world data with no gameplay loop: NBT, regions, chunks, `datafix/`, `worldgen/`, `level/` (server-side block behaviour, ticks, redstone), entity physics, items, effects, food, damage |
@@ -63,6 +66,7 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
python3 tools/pr_ready.py # the self-check: fmt, clippy -D warnings, touched crates' tests, harnesses; paste its block in the PR
python3 tools/pr_ready.py --quick # same without harnesses
cargo test -p horizon-client --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client --features horizon-client/mods --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo build --profile fast -p horizon-client # optimized, quick to link: use it to play or run lan_e2e
python3 tools/parity.py --changed origin/main # the harnesses your branch can affect (CI's parity job)
python3 tools/parity.py --changed-files paths.txt --list # which harnesses cover these files, without running them
@@ -118,16 +122,16 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
description="The client's lowest layer: chat text and translations, SNBT and NBT paths, the scoreboard, boss bars, game rules and the registry and particle tables commands name"
Loaded 100 of 566 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.