Compare commits

..
Author SHA1 Message Date
Ludovic Marie ebcfa2263a Merge pull request #11637 from horizon-rust/fix/lightning-fire-radius-11376
🤖 Agent: lightning now places fire (and draws its extra-source offsets) only when canSpreadFireAround passes at the bolt, so fire_spread_radius_around_player holds for strikes far from every player.
2026-11-07 07:40:30 +01:00
Ludovic Marie 7127fb8ed1 Merge pull request #10065 from ilguerro/fix/thorns-victim
🤖 Agent: merged main again (#10015's lan_e2e check landed beside this one's); the Thorns change itself is the head the mace harness passed on.
2026-11-07 07:19:55 +01:00
Horizon agent 9d35a0c0ad Merge remote-tracking branch 'origin/main' into n/10065
# Conflicts:
#	tools/lan_e2e.py
2026-11-07 07:18:08 +01:00
Ludovic Marie b6aa962829 Merge pull request #10035 from IsT3RiK/traduction-messages-mods
🤖 Agent: the Mods page's install and port messages go through tr/trf with named arguments, so they translate; lang files merged key by key onto main, i18n tests pass.
2026-11-07 07:05:28 +01:00
Ludovic Marie c1102d9227 Merge pull request #11628 from horizon-rust/fix/absorption-instant-shift-11386
🤖 Agent: a mob's Absorption now refills on every accepted add (AbsorptionMobEffect.onEffectStarted), and heal/harm and effect intervals shift like Java's int << / >> (distance mod 32, wrapping) instead of clamping or panicking at amplifier 32+.
2026-11-07 06:58:08 +01:00
Horizon agent 057869bdad Merge remote-tracking branch 'origin/main' into n/10035
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-07 06:43:57 +01:00
Ludovic Marie 84ec8516c6 Merge pull request #11622 from horizon-rust/fix/frame-map-limit-11383
🤖 Agent: an empty frame now refuses a map tracking more than 256 counted markers (ItemFrame.interact's isTrackedCountOverLimit FAIL): the map stays held, no swing; exactly 256 still goes in.
2026-11-07 06:40:06 +01:00
Ludovic Marie 4500b9e72b Merge pull request #10015 from repositorykreml1n/codex/fix-issue-9377
🤖 Agent: /say from a command block reaches every player under the block's name, and a captured guest command no longer echoes it back as feedback (kacpherek's point); lan_e2e command-block-chat not run here.
2026-11-07 06:35:42 +01:00
Horizon agentandClaude Opus 5.5 2710eae7cf Start a bolt's fire only near a player
LightningBolt.spawnFire places fire, and draws its extra sources, only when
ServerLevel.canSpreadFireAround passes at the bolt: the
fire_spread_radius_around_player rule now holds for lightning (#11376).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 06:30:22 +01:00
Ludovic Marie ac40181853 Merge pull request #11613 from horizon-rust/fix/omen-added-sound-11373
🤖 Agent: the omen sound now plays from the player's addEffect for any newly added omen (command, potion, mod) at the feet, as MobEffect.onEffectAdded does; the three special paths moved from the eye to the feet too.
2026-11-07 06:29:33 +01:00
Ludovic Marie 2a4a9a4616 Merge pull request #11601 from horizon-rust/fix/tnt-entity-collisions
🤖 Agent: TNT, falling blocks and XP orbs now collide with boats, shulkers and a held happy ghast (Entity.collide's canBeCollidedWith); sand landing on a boat over air breaks into an item like vanilla's wouldContinueFalling.
2026-11-07 06:29:29 +01:00
Horizon agent bec6e40518 Merge remote-tracking branch 'origin/main' into n/10065
# Conflicts:
#	tools/lan_e2e.py
2026-11-07 06:26:44 +01:00
Horizon agent 702adabb25 Merge remote-tracking branch 'origin/main' into n/10015
# Conflicts:
#	tools/lan_e2e.py
2026-11-07 06:24:27 +01:00
Ludovic Marie eb2d90fb10 Merge pull request #10040 from NikitaMGrimm/shader-api-02-serve
🤖 Agent: 0.2 shader packs get the float HDR frame only under HORIZON_HDR_FRAME=float; the default and 0.1 paths keep their formats (frame_format tests cover both), so the vanilla look is unchanged.
2026-11-07 06:18:07 +01:00
Ludovic Marie 5a83184c7e Merge pull request #10170 from TheMrZZ/fix/furnace-loaded-cooking-total
🤖 Agent: a loaded furnace keeps its saved cooking total until the item finishes, and a hopper or dropper filling the empty input slot restarts the cooking as setItem does (kacpherek's point); furnace_loaded_total and furnace_output_components pass.
2026-11-07 06:15:34 +01:00
Ludovic Marie 1082a55b0f Merge pull request #10013 from MSedgeMC/feat/chat-draft-italic
🤖 Agent: a restored chat draft draws grey italic until edited, as ChatScreen's isDraft does; text a mod opens chat with is not a draft. Merge with main builds and the chat tests pass.
2026-11-07 06:15:30 +01:00
Horizon agentandClaude Opus 5.5 aacb969efa Refuse a framed map that tracks more than 256 markers
ItemFrame.interact returns FAIL when MapItemSavedData.isTrackedCountOverLimit(256):
the map stays in the hand and the frame stays empty (part of #11383).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 06:15:02 +01:00
Horizon agentandClaude Opus 5.5 143f965f6f native_open_chat: mod text is not a draft (open_chat takes is_draft)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 06:12:51 +01:00
Ludovic Marie 318e263959 Merge pull request #10119 from DUzzL/fix/10049-frost-walker-equip
🤖 Agent: Frost Walker's speed modifier now follows a boots change made by a guest too (the guest's armor is swapped in before the comparison); armor_changes copies only changed slots. armor_stand_frost passes on a union with main.
2026-11-07 06:12:42 +01:00
Horizon agent 955cd0b289 Merge remote-tracking branch 'origin/main' into n/10013 2026-11-07 05:59:01 +01:00
Ludovic Marie 29082ca5d1 Merge pull request #10009 from DUzzL/fix/9959-spectator-portals
🤖 Agent: a spectator using a portal block travels to its destination, never builds a Nether exit, and gets spectator.cannot_teleport without one (ServerPlayerGameMode.useItemOn); nether_portal harness passes on a union with main.
2026-11-07 05:55:35 +01:00
Ludovic Marie 3b6d6b542c Merge pull request #9995 from DUzzL/fix/9963-projectile-absorbed
🤖 Agent: damage a mob's absorption takes is credited to the thrower (guest throws settle inside as_guest, kacpherek's point); mace and trident harnesses pass on a union with main.
2026-11-07 05:55:32 +01:00
Ludovic Marie 7a2f824043 Merge pull request #10701 from Detimes/perf-reuse-packet-deflater
🤖 Agent: region saves reuse one deflater per writer instead of building one per chunk; the test checks the compressed bytes are identical to a fresh deflater's. Union check (main + the gate's 9 heads): fmt, clippy -D warnings, 5666 tests green; verify green after the runner's full-disk rerun.
2026-11-07 05:46:01 +01:00
Ludovic Marie cca49a3f34 Merge pull request #11606 from horizon-rust/fix/boat-controller-11541
🤖 Agent: the second rider's PlayerInput/MoveVehicle is now ignored like vanilla's getControllingPassenger check; riders are listed in boarding order so the first one in keeps the tiller.
2026-11-07 05:43:37 +01:00
Ludovic Marie b03633436a Merge pull request #11597 from horizon-rust/fix/swing-echo-9887
🤖 Agent: only SUCCESS_SERVER block uses (beds, light, a charged anchor, signs) swing back to their own player; item and entity uses still echo, as the body says. use_item_on, flat_place and dedicated_server pass locally.
2026-11-07 05:39:22 +01:00
Ludovic Marie f6a188d8cd Merge pull request #11586 from horizon-rust/fix/piston-push-order-11399
🤖 Agent: each moving block's collision view now advances right after its own push, so later pushes in the same tick see it at its new progress; the cost is one lookup per moving piston. piston_push and piston_movement pass locally.
2026-11-07 05:39:19 +01:00
Horizon agentandClaude Opus 5.5 93d50e0318 Refill a mob's Absorption on every add; shift heal and harm like Java
LivingEntity.addEffect runs onEffectStarted for every effect it accepts,
so Absorption given again refills a mob's shield to 4 per level of the
effect given. Horizon refilled it only for a new or stronger effect.
HealOrHarmMobEffect and the periodic effects use Java's shifts, which take
the distance modulo 32 and wrap: amplifier 32 is amplifier 0 again, and a
ticked heal is at least 0. Horizon clamped the distance to 30, and an
amplifier of 32 or more panicked on Regeneration's interval in a debug
build (#11386).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 05:36:50 +01:00
Ludovic Marie 507e0ae6f4 Merge pull request #10180 from lightcodegit/fix/brush-horn-use-vibration
🤖 Agent: horn and brush uses end with stopUsingItem (the interaction vibration), and a mob in front of the brushed block stops the brush out to the block reach; item_use_catalog harness passes on a union with main.
2026-11-07 05:16:14 +01:00
Horizon agentandClaude Opus 5.5 1d0b591927 Play an omen's sound when it is added to an entity
MobEffect.onEffectAdded plays Bad Omen's, Trial Omen's and Raid Omen's
sound (withSoundOnAdded) whenever LivingEntity.addEffect adds one that
is new, at the entity's feet. Horizon played them only on the raid, bottle
and trial spawner paths, at the eye, so /effect give, a potion's custom
contents or a mod adding one was silent (#11373). The generic add now
plays it for players and, from /effect, for mobs; the three special paths
play at the feet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 05:16:06 +01:00
Ludovic Marie 348e296ffa Merge pull request #10039 from MSedgeMC/fix/hud-text-height
🤖 Agent: the item name and the action bar take fixed heights above the hotbar (Hud.extractSelectedItemName 59, extractOverlayMessage 68) instead of following the bars; hud harness passes on a union with main.
2026-11-07 05:15:29 +01:00
Ludovic Marie 28f081a242 Merge pull request #10118 from DUzzL/fix/10050-breeze-potion-deflect
🤖 Agent: a breeze turns a thrown potion back at half speed (Breeze.deflection) once in a row, stopping at the hit point that tick; the REVERSE draw uses the potion's own random.
2026-11-07 05:14:31 +01:00
Ludovic Marie 47672aefc6 Merge pull request #10193 from MSedgeMC/fix/automatic-advancements
🤖 Agent: advancements with no criteria get their rewards on load (checkForAutomaticTriggers) but stay not done, as AdvancementRequirements.test answers false; checked on a union with main.
2026-11-07 05:13:38 +01:00
Ludovic Marie d81408ef0f Merge pull request #10124 from MSedgeMC/feat/stats-tab-keys
🤖 Agent: Ctrl+Tab and Ctrl+1..0 switch the statistics tabs from the raw key, as TabNavigationBar.keyPressed does; checked on a union with main.
2026-11-07 05:12:51 +01:00
Ludovic Marie 0ce9091c37 Merge pull request #10002 from some-du6e/optifine/sky-layers
🤖 Agent: the fade out ending at startFadeIn now runs to the end of the day (since()==0 read as DAY), which was kacpherek's review point; checked on a union with main (fmt, clippy -D warnings, tests).
2026-11-07 05:01:28 +01:00
Ludovic Marie 6c45dc0561 Merge pull request #11596 from horizon-rust/perf/terrain-hdr-override
🤖 Agent: the solid terrain pipelines are built with HDR_LIGHTING off and the lit copy only exists when hdr_output is Some, so HDR players switch per frame by self.hdr; frame-identity label makes CI check HDR off, offscreen HDR and HDR toggled off in a running game. −16 to −22% GPU on M4 Pro, image identical in-process.
2026-11-07 04:57:22 +01:00
Ludovic Marie 7775a14526 Merge pull request #11562 from horizon-rust/perf/11024-parallel-world-convert
🤖 Agent: merging. Empty region files (13 303 of 13 620 in the test world) are no longer rewritten and synced on world open, and region files convert on all cores but one with byte-identical output; datafix harness 6/6.
2026-11-07 04:40:56 +01:00
Horizon agentandClaude Opus 5.5 e3b3159901 Build solid terrain without HDR lighting when HDR output is off
terrain.wgsl's solid shaders carry HDR output's lighting behind a runtime
`hdr_light.z` test. The branch is never taken without HDR, yet on Apple GPUs its
code alone costs ~15% of the solid pass (register pressure). A pipeline
constant `HDR_LIGHTING` now guards it: the solid pipelines (per-section, pulled
and flat) are built without it, and a copy with it only where the display
offers HDR output, picked per frame by whether HDR is on.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 04:35:44 +01:00
Ludovic Marie 77936bf4be Merge pull request #10196 from IanWorley/native-api/session-state
🤖 Agent: the session state a client HUD reads (world id, screen kind) comes from fields the window already keeps; merged with #10195 and checked with clippy, the native_client tests and the hook stamp test.
2026-11-07 04:33:57 +01:00
Horizon agent f47ac2cbad Merge remote-tracking branch 'origin/main' into conv-par 2026-11-07 04:24:38 +01:00
Horizon agentandClaude Opus 5.5 7f0778e4da Merge origin/main into #10196
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 04:12:16 +01:00
Ludovic Marie b0a8cbf6fb Merge pull request #11579 from horizon-rust/mods/client-combat-hooks
🤖 Agent: client_hurt reports the packet's causing entity (the shooter, not the arrow) and treats an untracked entity as not a player, so a mod never gets a false PvP signal. Main merged in (test conflict with #11512's open-chat test, both kept); verify green on this head.
2026-11-07 04:12:11 +01:00
Horizon agentandClaude Opus 5.5 6bcbf52250 Merge origin/main into mods/client-combat-hooks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:59:52 +01:00
Horizon agentandClaude Opus 5.5 d2c44db65e Primed TNT, falling blocks and orbs land on boats and shulkers
PrimedTnt, FallingBlockEntity and ExperienceOrb move with Entity.move,
whose Entity.collide includes EntityGetter.getEntityCollisions: a boat,
a living shulker and a happy ghast held still stop them, as they stop a
dropped item. Horizon resolved their moves against blocks and the world
border only (#11518). The ordered tick gathers those boxes once per run
(again after an explosion) and passes the nearby ones to each move.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:54:41 +01:00
Horizon agentandClaude Opus 5.5 2b1731508d Let only a boat's first rider drive it on a server
A boat's passengers went to the clients in the order the players joined
the server, so the player who got in second could hear it sits first and
take the boat over, and the server took MoveVehicle from either seat: with
two players in one boat it kept snapping back (#11541). The passengers now
go in the order they boarded, and handleMoveVehicle ignores a player who
isn't the vehicle's controlling passenger, as vanilla does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:54:07 +01:00
Ludovic Marie 026f014ff6 Merge pull request #11538 from horizon-rust/mods/chat-input-helpers
🤖 Agent: native_set_chat_input filters text to what typing allows (no control chars, DEL or §) and clamps the cursor, so a mod can't inject formatting codes into the chat box.
2026-11-07 03:50:58 +01:00
Ludovic Marie 2aa5716a6a Merge pull request #11528 from horizon-rust/fix/followup-10529
🤖 Agent: Launcher-only follow-up to #10529: the Home tab strip is flex_none on wide windows again (glass::tabbed keeps its notch at 1440 px) and only scrolls when compact; settings rows keep 40 px between name and control and wrap the control only when narrow. No new strings; fmt/clippy/tests green on the union of this wave's heads.
2026-11-07 03:50:53 +01:00
Ludovic Marie 3e96961f1e Merge pull request #11524 from horizon-rust/fix/cg66-followups
🤖 Agent: follow-ups to #11033/#11044/#11043/#11016 that missed the merges: death protection is one prototype lookup per hand per tick (was a full component map for every armed mob), checked against the component map for patched and stripped stacks. Union check: fmt, clippy -D warnings, 5666 tests green.
2026-11-07 03:50:48 +01:00
Ludovic Marie ed38625015 Merge pull request #11036 from ilguerro/perf/9996-interp2
🤖 Agent: the interpolation change is checked bit for bit against the old formula over the test's whole range, so no position moves; one multiply fewer per entity per frame. Union check (main + the gate's 9 heads): fmt, clippy -D warnings, 5666 tests green.
2026-11-07 03:50:43 +01:00
Ludovic Marie 31d0888da7 Merge pull request #10703 from Detimes/perf-region-inflate-in-place
🤖 Agent: inflates each region chunk in place instead of through a second buffer; the differential test decodes the same chunks with the old block decoder and compares every section. Union check (main + the gate's 9 heads): fmt, clippy -D warnings, 5666 tests green.
2026-11-07 03:50:39 +01:00
Ludovic Marie a5ea46ce3e Merge pull request #11049 from DUzzL/fix/11024-parallel-world-upgrade
🤖 Agent: Optimize World now upgrades region files in parallel with one sync per file instead of one per chunk (F_FULLFSYNC on macOS was the cost); the 1-vs-4-thread test checks the counts against the fixture's own layout, and a canceled recreate leaves every file untouched. Union check: fmt, clippy -D warnings, 5666 tests green.
2026-11-07 03:46:43 +01:00
Ludovic Marie 5088011241 Merge pull request #9566 from hildlabs/panel/glass-look
🤖 Agent: the server panel moves its dialogs into the page and follows the Glass look; launcher only, strings through tr().
2026-11-07 03:45:02 +01:00
Ludovic Marie 3be1aae4a1 Merge pull request #10099 from yahya-nusseibeh/fix/rsa-constant-time
🤖 Agent: private-key operations are now constant-time (no branch or table index on secret bits); no change to the key format or the public API.
2026-11-07 03:44:58 +01:00
Ludovic Marie 7f3bf11140 Merge pull request #10169 from NikitaMGrimm/shader-pack-identity-launcher
🤖 Agent: the launcher keeps each native shader's identity and options across the settings screen rewrite; its parent #10168 is merged, and the union with main passes clippy and tests.
2026-11-07 03:44:55 +01:00
Ludovic Marie afcfad914c Merge pull request #10195 from IanWorley/native-api/client-entities
🤖 Agent: client mods read entities and textures through the Host only, no new write path; #10196 conflicts with it in native_client_services.rs and gets main merged after this lands.
2026-11-07 03:44:51 +01:00
Ludovic Marie 6fefc220f9 Merge pull request #9968 from masa-masa-0314/feat/realms-invite-codes
🤖 Agent: the invite-code calls go through one frontend() helper with x-mojangauth, only to the Realms frontend URL; builds and tests clean on a union with current main.
2026-11-07 03:44:47 +01:00
Horizon agentandClaude Opus 5.5 fc8e14548a A pushed entity meets the other moving blocks at their progress then
PistonMovingBlockEntity.tick pushes entities from each block entity's own
tick, and getCollisionShape reads its progress: the moving blocks that
ticked before it have advanced, the ones after it have not. Horizon
pushed every entity against last tick's progress of all of them, so a
TNT duper's TNT was pushed through a block that was about to move
(#11399). Each piston's collision view now advances right after its own
push.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:42:55 +01:00
Horizon agentandClaude Opus 5.5 ae8bfba8c6 Fill the swing test's Joined like main's, rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:40:33 +01:00
Horizon agent 8703ad614c Merge remote-tracking branch 'origin/main' into fix/swing-echo-9887 2026-11-07 03:39:58 +01:00
Ludovic Marie fce784f752 Merge pull request #11575 from horizon-rust/mods/screenshot-helper
🤖 Agent: native_take_screenshot refuses while another screenshot waits for the frame, so a mod's call can't overwrite F2's or the world icon's pending grab.
2026-11-07 03:37:48 +01:00
Ludovic Marie a5b752f7cc Merge pull request #11555 from horizon-rust/fix/sound-harness-runner-defaults
🤖 Agent: parity.py starts harnesses with no arguments; these two required --binary/--work-dir and failed parity-main in 0 s. On the desktop both now reach their servers (the Java sides ran); the full runs there timed out only because the desktop's swap was full.
2026-11-07 03:37:45 +01:00
Horizon agentandClaude Opus 5.5 9834143df1 Fix clippy: is_player needs no mut; keep player_voice_pitch's doc on it
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:34:42 +01:00
Ludovic Marie 4774d07dd5 Merge pull request #11513 from horizon-rust/fix/items-fall-through-minecarts-11091
🤖 Agent: removes only the minecart boxes from the item's collision list; boats, shulkers and a held happy ghast still hold items, as AbstractMinecart doesn't override canBeCollidedWith. entity_collision, held_drop, shulker_space, boat_item_hit pass locally.
2026-11-07 03:22:31 +01:00
Horizon agent b9693b5716 Merge remote-tracking branch 'origin/main' into fix/swing-echo-9887 2026-11-07 03:20:04 +01:00
Ludovic Marie 1647c1b466 Merge pull request #11028 from Detimes/hooks-b-all
🤖 Agent: merged after #11019 and main (hook and doc blocks kept whole, quinn kept beside the new optional crates). Without a mod, PlayerSender::send pays one relaxed WATCHED load and sends the bytes as they were, and worldgen, LOD columns and redstone keep vanilla's values.
2026-11-07 03:19:45 +01:00
Horizon agentandClaude Opus 5.5 ac31802b58 Give #11019's guest test a Joined with its configuration
The zero-health test that #11019 added builds a Joined, which this branch
gave a configured field.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:01:53 +01:00
Horizon agentandClaude Opus 5.5 c2adc89cc7 Keep the invocation test's binary inside its scratch folder
The harnesses resolve --binary, and /bin is a link to /usr/bin on
Ubuntu runners.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:59:37 +01:00
Horizon agent e82a08ca4d Merge remote-tracking branch 'origin/main' into cg68/hooks-b-all
# Conflicts:
#	crates/horizon-mod-deps/Cargo.toml
#	docs/native-mods.md
2026-11-07 02:55:11 +01:00
Ludovic Marie 41944ed914 Merge pull request #11553 from horizon-rust/pvp-2/legacy-pvp-check
🤖 Agent: the check, not the 1.8 rules, was broken: the switch row sat half under the list's edge so the click missed it; verified 7/7 legacy, 9/3.6 modern on the Mac.
2026-11-07 02:49:45 +01:00
Ludovic Marie 87784fb3df Merge pull request #11543 from horizon-rust/pvp-2/bots-crits
🤖 Agent: the fight bots' swing carried a hand byte 26.3's swing packet doesn't have, so every server kicked them on the first hit; it now sends the same Punch as --attack.
2026-11-07 02:49:41 +01:00
Ludovic Marie 1b4a669936 Merge pull request #11019 from Detimes/hooks-all
🤖 Agent: twelve hook groups land together. Without a mod, the move, teleport and trample hooks are fn pointers over an empty mods() slice, so a player's move costs one indirect call and no allocation; a mod command's permission node falls back to its own level when no provider answers.
2026-11-07 02:49:38 +01:00
Horizon agent 230d414d62 Merge commit '97e3a74179cf3e6137908ea2c8ce36a489605a84' into cg68/hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	crates/horizon-client/src/game/lan_actions.rs
#	crates/horizon-client/src/game/native_hooks.rs
#	crates/horizon-client/src/game/native_net.rs
#	docs/native-mods.md
2026-11-07 02:45:45 +01:00
Ludovic Marie 0ad4f59af2 Merge pull request #11537 from horizon-rust/multiplayer/drag-servers
🤖 Agent: a press on the already-selected server still joins without holding it, and servers.dat is written only on a release after a real move.
2026-11-07 02:37:14 +01:00
Ludovic Marie 3e6e75d8f2 Merge pull request #11511 from horizon-rust/settings/finer-lod-steps
🤖 Agent: only the slider's step table changes; saved values between steps snap to the nearest step (test covers 3000 → 13/23), so existing options.txt files still load.
2026-11-07 02:37:10 +01:00
Ludovic Marie cef80f4fd5 Merge pull request #11510 from horizon-rust/mods/quinn-dep
🤖 Agent: quinn is optional behind the quic feature with default features off (runtime-tokio + rustls-ring only), so no extra TLS backend lands in Cargo.lock beyond the 7 packages.
2026-11-07 02:37:06 +01:00
Ludovic Marie 52a7c28daf Merge pull request #11512 from horizon-rust/mods/open-chat-helper
🤖 Agent: native_open_chat refuses unless the screen is Playing, so a mod can't push the chat over an open container or menu.
2026-11-07 02:37:02 +01:00
Horizon agent 97e3a74179 Merge remote-tracking branch 'origin/main' into cg68/hooks-all 2026-11-07 02:33:34 +01:00
Horizon agentandClaude Opus 5.5 2ba9bcf90d Fix the build: the path goes to mods as a string
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:30:55 +01:00
Horizon agentandClaude Opus 5.5 dd338cd642 Fix the build: FnMut lookup for the attacker
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:29:11 +01:00
Horizon agentandClaude Opus 5.5 17ee8c5f13 lan_e2e legacy-pvp: flip the switch once it is whole in view, and swap across ticks
The check failed on main with 1.8 rules that work: the switch row now sits
under the PvP page's HUD rows, the scroll stopped as soon as the row showed
at the list's lower edge, and the click at its centre landed outside the
list, so the rules never turned on (legacy hit 9, not 7). It now scrolls
until the row stops moving and asserts the click flipped it.

The swap and back went out in one tick: since the client sends a slot
change from its next tick, as vanilla does (#10084), that sends nothing,
and the modern hit kept its full charge. The stick is now held 3 ticks.

Mac mini: legacy (7, 7), modern (9, 3.6).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:27:08 +01:00
Ludovic Marie 77db38ec8c Merge pull request #11523 from horizon-rust/platforms/ci-arm64-launcher-version
🤖 Agent: .github only; the by-hand ARM64 job's launcher --version step now runs from bash as launcher.yml does, and passes on the ARM64 runner (run 42320852405), where the pwsh version failed on any code.
2026-11-07 02:23:08 +01:00
Horizon agentandClaude Opus 5.5 6858867f2a Convert older worlds on all cores and skip empty region files
Opening a world saved by an older version runs the converter in
datafix/files.rs (upgrade_world), which rewrote every region file on one
thread. Now:

- upgrade_regions spreads a folder's region files over worker_threads()
  workers (all cores but one). Each file is still read, fixed and written
  whole by one worker through the same staging folder, so the bytes do not
  depend on the thread count. A cancel or an error stops every worker
  before its next file; the error returned is the first file's in order.
- A region file without chunks is left as it is instead of being rewritten
  as an empty 8 KiB file and synced. The game leaves many of them: 13 303
  of the 13 620 overworld files of a 1.21.1 ATM10 test world.

Tests: 4 workers give byte-identical files to 1 and every chunk at the
current version, an empty file stays empty, and a cancel from the first
chunk converts at most one file per worker and leaves the rest old.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:17:31 +01:00
Ludovic Marie 00f6358e7a Merge pull request #11081 from horizon-rust/platforms/docs-intel-arm64
🤖 Agent: one line in docs/hosting.md (Intel Macs have had server builds since the 10-31 Nightly; install.sh already maps Darwin-x86_64); the README half is already on main via bf4657da26.
2026-11-07 02:10:26 +01:00
Ludovic Marie b33917f89b Merge pull request #11080 from horizon-rust/platforms/arm64-emulated-dx12
🤖 Agent: Auto's backend order now reads the machine's arch (IsWow64Process2), so the x64 build on a Snapdragon PC starts on DX12 like the ARM64 one; the ARM64 runner built and started the game on this head. Unverified on real Adreno hardware (asked #7513's reporter).
2026-11-07 02:10:21 +01:00
Ludovic Marie 3fd9db1568 Merge pull request #10501 from arslanarm/shaderpacks/same-size-buffer-declarations
🤖 Agent: merged main in again (only the test module list in pack_post.rs collided, with #11196's sky_disc_blend_tests). The last parity failure on 4fa3bf699 was command_exec's '/advancement grant @s everything' count (116 vs 115), which fails identically without this PR; this head reruns parity. sizes_differ is a few array reads per pass per frame.
2026-11-07 02:10:17 +01:00
Horizon agentandClaude Opus 5.5 f31f4d179a horizon-bots: fight bots swing as 26.3 does, and --crits for PvP benches
The fight behaviour's swing went out as a raw Punch with a hand byte
26.3's ServerboundSwingPacket doesn't have: every server kicked the bots
on their first hit ("packet 46 was larger than expected: 1 extra bytes").
It now sends the same Punch as --attack.

--crits makes fight bots swing at a sword's full strength (13 ticks) and
jump into every other swing, landing it on the way down: a critical hit,
then a sweep from the ground. tools/perf PvP scenes get crit and sweep
particles from real hits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:08:33 +01:00
Horizon agentandClaude Opus 5.5 88a372f808 Let mob_local_sound and ominous_warning_sound run with parity.py's defaults
tools/parity.py starts every harness with no arguments, and both scripts
required --binary and --work-dir, so parity-main failed them in 0 s
(argparse usage error) since #10524 and #10266. Like
hostile_sound_categories, they now build the game through net.sides and
pick a work folder under .local/ when none is given.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:06:56 +01:00
Horizon agentandClaude Opus 5.5 1f7515f5ee Item move comment: a minecart does not stop an item
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:04:23 +01:00
Horizon agentandClaude Opus 5.5 84f77ee9a5 Launcher: keep the Home tab notch and the settings rows on wide windows
Follow-up to #10529 (atsauban's responsive wrapping). The Home tab strip
took the full width at every size, so glass::tabbed drew one plate and
lost its notch even at 1440 px: it is full width and scrolls only when
compact. setting_row went from 40 px to 16 px with justify_between for
every row; it keeps 40 px between name and control and wraps the
control under the name (12 px) when the window is too narrow. The
breakpoints are named constants beside WIDE.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 02:01:34 +01:00
Horizon agentandClaude Opus 5.5 8ce49872e7 Follow-ups to #11033, #11044, #11043 and #11016
- Death protection: one prototype lookup per hand instead of a component
  map rebuilt from the stack's patch. update_totem_ready runs for both of
  the player's hands and sync_mob_death_protection for every armed mob,
  every tick; only a stack that patches the component takes the old path.
- Remote projectiles: the swept bubble-column cells are deduplicated
  without a HashSet per projectile per tick.
- Ident::read hands a namespaced identifier back without a second String.
- parked_worlds keeps its doc comment (#11016 put parked_border under it).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:59:14 +01:00
Horizon agentandClaude Opus 5.5 280ef828d6 A placed block's swing is no longer sent back to the player
handleUseItemOn sends the use's swing to the player's own client only when
the result is not SwingSource.PREDICTED. Horizon sent every swing back, so
on a slow connection the echo of a placement's swing arrived after the first
half of the client's own swing and swung the arm again (#9887). A placement
and a block's own SUCCESS now count as predicted; the beds, light block,
respawn anchor and sign stay SUCCESS_SERVER and are still sent back.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:59:07 +01:00
Horizon agentandClaude Opus 5.5 3a6410b63b Location enchantments: copy only the armor slots that changed
The check runs every tick for every player; cloning all four stacks each
time allocated their components for nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:57:52 +01:00
Horizon agentandClaude Opus 5.5 482cc7707c Merge origin/main into #10119
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:57:49 +01:00
Horizon agentandClaude Opus 5.5 e409b37078 Brush: a mob blocks the brush out to the block interaction range
BrushItem.calculateHitResult picks entities with blockInteractionRange, not
the entity range: a mob 3 to 4.5 blocks away in front of the block stops it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:57:43 +01:00
Horizon agentandClaude Opus 5.5 64c68064f6 Merge origin/main into #10180
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:57:37 +01:00
Ludovic Marie 80947472c1 Merge pull request #11087 from horizon-rust/hosting/amp-template
🤖 Agent: the AMP update stage runs the egg's own install script byte for byte (test pins it), so both panels install and SHA-256-check the same way; not imported into a live AMP.
2026-11-07 01:55:30 +01:00
Horizon agent cf93b66d14 Merge remote-tracking branch 'origin/main' into fix/items-fall-through-minecarts-11091 2026-11-07 01:53:29 +01:00
Horizon agentandClaude Opus 5.5 486dd3f058 rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:52:03 +01:00
Horizon agentandClaude Opus 5.5 53d0ea1320 Native mods: read and write the open chat box (native_chat_input, native_set_chat_input)
A client mod that converts text as it's typed (Emoji Type's :code: to emoji)
reads the chat box's text and cursor and writes the result back as an edit,
so completions refresh as for typing. Only what typing lets in reaches the
box. Stacked on native_open_chat.

Part of #10107

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:52:03 +01:00
Horizon agentandClaude Opus 5.5 403ecdbadb CI: the by-hand Windows ARM64 job reads the launcher's --version from bash
Its first runs failed at "horizon-launcher --version failed": pwsh read no
output from the GUI-subsystem launcher, while launcher.yml's same check, from
bash on the same runner, passes on every main push.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:51:38 +01:00
Horizon agentandClaude Opus 5.5 5d91256713 rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:50:47 +01:00
Horizon agentandClaude Opus 5.5 5a24802d6a rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:50:16 +01:00
Horizon agentandClaude Opus 5.5 ac8a09ef0a rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:49:41 +01:00
Horizon agentandClaude Opus 5.5 0a639d598e Merge main into #10501 (test module list in pack_post.rs)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 01:49:38 +01:00
rdbt 13f70ad482 Merge pull request #11250 from TheMrZZ/fix/h252-loaded-pig-tags
Normalize Mob Tags when adopting NBT
2026-11-06 22:09:57 +00:00
rdbt 6590656a9a Merge pull request #11393 from ilguerro/fix/10548-villager-memories
Save villager brain memories like vanilla
2026-11-06 22:09:51 +00:00
rdbt f2e7c1865f Merge pull request #11405 from ilguerro/fix/w076
Count collidable entities in auto-jump like vanilla
2026-11-06 22:09:45 +00:00
rdbt 876c817f7d Merge pull request #11397 from ilguerro/fix/w029
Keep wandering trader homes like vanilla
2026-11-06 22:09:40 +00:00
rdbt b465f24309 Merge pull request #11395 from ilguerro/fix/w024
Match vanilla hanging entity coexistence checks
2026-11-06 22:09:04 +00:00
DetimesandClaude Opus 5.5 611dfc244f Merge upstream/main into hooks-all
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 21:38:48 +00:00
DetimesandClaude Opus 5.5 2e01586b5e Merge upstream/main into hooks-b-all
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 21:35:45 +00:00
Nikita Maximilian GrimmandClaude Opus 5.5 d36a2c9e21 fix(shaders): give the scripted-clock test its shader API line
main's test predates NativeFrame::new's api argument; it runs a 0.1 frame, as the other tests do.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 22:33:28 +01:00
Nikita Maximilian Grimm 1ade310792 Merge remote-tracking branch 'origin/main' into shader-api-02-serve
# Conflicts:
#	crates/horizon-client/src/game/native_shaders.rs
#	crates/horizon-client/src/game/shader_packs.rs
2026-11-06 22:29:20 +01:00
rdbt 9d11d0c710 Merge pull request #11369 from Skr1zzzz/ride-any-tracked-entity
Multiplayer: the player rides any tracked entity, sitting where vanilla seats it
2026-11-06 21:27:13 +00:00
rdbt c90689fd74 Merge pull request #10168 from NikitaMGrimm/shader-pack-identity
Keep native shader selections and settings across updates
2026-11-06 21:27:07 +00:00
rdbt 22628d794c Merge pull request #10194 from IanWorley/native-api/block-appearance
Native mods: block state facts and the client's resource files, for maps and radars
2026-11-06 21:27:01 +00:00
rdbt a94b615c99 Merge pull request #11014 from senjinthedragon/entity-at-velocity
Shader packs: at_velocity for entities
2026-11-06 21:26:55 +00:00
rdbt 63c8c5bce2 Merge pull request #11181 from Slavik-85/f3-overlay-light
F3 overlay: paint at 20 Hz, no per-frame OS calls, less GPU timing
2026-11-06 21:26:49 +00:00
rdbt 13be1a9e4f Merge pull request #10529 from atsauban/fix/launcher-narrow-layout-6362
fix(launcher): support responsive wrapping on narrow windows
2026-11-06 21:26:42 +00:00
rdbt 6a978d3bef Merge pull request #11051 from Delete228/native-dropped-item-contact
Native mods: expose dropped-item block contact
2026-11-06 21:26:37 +00:00
rdbt a8055893a2 Merge pull request #11033 from DUzzL/fix/10587-death-protection-item
Death protection: use the death_protection item and its effects, for players and mobs
2026-11-06 21:26:31 +00:00
rdbt ef78519011 Merge pull request #9952 from Ragbie/fix/iron-golem-toss
Iron golems toss by knockback resistance and keep their anger like vanilla
2026-11-06 21:26:25 +00:00
rdbt c30775263f Merge pull request #10197 from IanWorley/native-api/client-world-snapshots
Native mods: client world snapshots a mod reads on its worker threads
2026-11-06 21:26:18 +00:00
rdbt 04176c2939 Merge pull request #10133 from Ragbie/fix/mounted-attack-box
A mounted mob reaches from its vehicle's width, and a rider is hit from its seat up
2026-11-06 21:26:10 +00:00
rdbt 96505f501d Merge pull request #11113 from Detimes/hooks-bots
Native mods: helpers for server-side player bots (one movement tick, block collision boxes)
2026-11-06 21:26:04 +00:00
rdbt c932d4bec1 Merge pull request #10166 from hildlabs/remote-servers-ssh
Launcher: a server on another computer, run over SSH
2026-11-06 21:25:59 +00:00
rdbt c87e44bf63 Merge pull request #11062 from voidsipp/fix/round4-7-10178
Add local particle spawning for native client mods
2026-11-06 21:25:53 +00:00
rdbt d8d0d5e832 Merge pull request #10554 from ilguerro/perf/9996-startup
Parse entity-models.json once at startup
2026-11-06 21:25:47 +00:00
rdbt 79d2b975f5 Merge pull request #11264 from senjinthedragon/hand-translucent
Shader packs: gbuffers_hand_water for translucent held items
2026-11-06 21:25:41 +00:00
rdbt 0b9487e6e9 Merge pull request #11090 from Delete228/native-fluid-provider-upstream
Native mods: let existing block fluid helpers reach native-owned tanks
2026-11-06 21:25:36 +00:00
rdbt e32b03ea27 Merge pull request #11064 from horizon-rust/roadmap/proposal-issues
Roadmap: attach 22 proposal issues to their items, two new Later items
2026-11-06 21:25:29 +00:00
rdbt c4d8c79e2b Merge pull request #11076 from voidsipp/fix/round4-5-10444
Add native client sound parameter interception
2026-11-06 21:21:31 +00:00
rdbt 8cc05f114c Merge pull request #11196 from arslanarm/shaderpacks/sky-part-blends
Shader packs: the sky's parts blend as vanilla draws them
2026-11-06 21:21:25 +00:00
rdbt b80e816a76 Merge pull request #10532 from PzmuV1517/shaders/perf-background
Shader packs: a pack builds on a thread of its own while the game draws
2026-11-06 21:21:20 +00:00
rdbt 9b8b755e4c Merge pull request #11183 from rison1337/perf/dynamic-light-selection
Stream dynamic-light sources and bound nearest-light selection
2026-11-06 21:21:14 +00:00
rdbt 5692713279 Merge pull request #10556 from ilguerro/perf/9996-light
Light a section from its palette's light info, not its state ids
2026-11-06 21:21:08 +00:00
rdbt ff31439c92 Merge pull request #9993 from MSedgeMC/fix/prediction-snap
A refused block change that comes back inside the player snaps it out
2026-11-06 21:20:30 +00:00
rdbt 02641599e8 Merge pull request #11221 from AlfaRojo/fix/player-boarding-cooldown
A player boards a vehicle again right after leaving it
2026-11-06 21:20:24 +00:00
rdbt 12030689f8 Merge pull request #11237 from NikitaMGrimm/pack-sequence-clock
Shader packs: a set frame clock for repeatable captures (screenshot-sequence 1/3)
2026-11-06 21:20:19 +00:00
rdbt ce68709f1f Merge pull request #11281 from ilguerro/perf/mesh-row-masks
Reduce repeated work in section meshing
2026-11-06 21:20:13 +00:00
rdbt 3e5b2ef194 Merge pull request #11268 from DUzzL/fix/11242-hanging-entity-data
Apply item custom data and entity data to placed frames, paintings and armor stands
2026-11-06 21:20:07 +00:00
rdbt 14bb1416d1 Merge pull request #11016 from DUzzL/fix/10549-per-level-border
Keep one world border per level
2026-11-06 21:20:02 +00:00
rdbt 1f666cfec4 Merge pull request #11044 from Scholiboi/fix/remote-pearl-bubble-column
Pearls on a server's client hover in bubble columns as vanilla's do
2026-11-06 21:19:56 +00:00
rdbt 8b578746ea Merge pull request #11205 from arslanarm/shaderpacks/float-into-integer-buffers
Shader packs: a float output into an integer buffer no longer refuses its program
2026-11-06 21:19:50 +00:00
rdbt af55aad580 Merge pull request #11204 from arslanarm/shaderpacks/r11f-images
Shader packs: an R11F_G11F_B10F image is stored into and read back
2026-11-06 21:19:44 +00:00
rdbt b11e8a54b6 Merge pull request #10571 from Skr1zzzz/text-display-cull
Text displays: keep their glyphs, and skip the ones whose text is off screen
2026-11-06 21:19:38 +00:00
rdbt 0ef93c96b9 Merge pull request #11092 from rison1337/perf/cull-behind-camera-falling-blocks
Skip falling-block mesh preparation behind the camera
2026-11-06 21:19:33 +00:00
rdbt 5fca014076 Merge pull request #11227 from daMpoff/fix/launcher-instance-menu-width
Fix overflow in translated launcher instance menus
2026-11-06 21:19:27 +00:00
rdbt c1c985ce5b Merge pull request #10606 from Hiuoy/feat/entity-interact-specific
Native hooks: PlayerInteractEvent.EntityInteractSpecific, the point a player right-clicks on an entity
2026-11-06 21:19:22 +00:00
rdbt af24ac05bb Merge pull request #10564 from Alysia42/ghast-fireball-pickable
Arrows and ender pearls now redirect ghast fireballs instead of passing through them
2026-11-06 21:19:16 +00:00
rdbt 1a92fe7767 Merge pull request #11042 from Vaspyyy/launcher-updates-in-updates
Launcher: update status and Check now move from About to Updates
2026-11-06 21:19:10 +00:00
rdbt 079ed1a010 Merge pull request #11276 from apeirs/fix/mob-block-layers-cull
Mob block layers cull back faces like BlockModelRenderState
2026-11-06 21:19:05 +00:00
rdbt e55180e7dd Merge pull request #10018 from ilguerro/fix/player-death-stats
A player's death makes a vibration and counts toward Killed By
2026-11-06 21:18:59 +00:00
rdbt da4fb17df7 Merge pull request #11099 from Delete228/native-living-item-use-upstream
Native mods: item use after a mob passes interaction
2026-11-06 21:18:53 +00:00
rdbt 114e9920ac Merge pull request #11275 from TheMrZZ/fix/command-enchantment-map-validation
Validate command enchantment map entries before execution
2026-11-06 21:18:47 +00:00
rdbt ad2c0d2ecd Merge pull request #11355 from TheMrZZ/fix/vehicle-damage-command
Route generic damage commands to boats and minecarts
2026-11-06 21:18:41 +00:00
rdbt 976afe97e2 Merge pull request #11065 from voidsipp/fix/11038-server-property-numbers
Match Java numeric server properties
2026-11-06 21:18:34 +00:00
rdbt 7c58203441 Merge pull request #9668 from GabrielNichols/codex/recipes-hmod-server-fill
Keep .hmod Recipes searchable and placeable on the server
2026-11-06 21:18:28 +00:00
rdbt cb201c59e4 Merge pull request #10527 from Akirabane/feat/transfer-status
dedicated: transfer-status, the counts a proxy reads before it sends players
2026-11-06 21:18:22 +00:00
rdbt d22ecb0b52 Merge pull request #11299 from pehe1/fix/3109-airborne-knockback
Apply a guest jump before knockback at takeoff
2026-11-06 21:18:16 +00:00
rdbt 62075a5b32 Merge pull request #11070 from Delete228/native-block-clone-stack-upstream
Native mods: select the stack returned by Pick Block
2026-11-06 21:18:10 +00:00
rdbt aea2bdadb1 Merge pull request #11114 from Delete228/content-item-block-alias-upstream
Native content: place a block through an item alias
2026-11-06 21:18:04 +00:00
rdbt f59035948c Merge pull request #10177 from anthra123x/fix/ultrawide-fullscreen-taskbar
fix(client): prevent taskbar overlap and window shift in borderless fullscreen
2026-11-06 21:17:58 +00:00
rdbt d05f889fe1 Merge pull request #10129 from xFedeT/xfedet-launcher-transparent-topbar
Make launcher titlebar match Glass UI
2026-11-06 21:17:19 +00:00
rdbt c96af77eb6 Merge pull request #11144 from xxaxio/fix/11082-linux-audio-buffer
audio: support HORIZON_AUDIO_BUFFER override for Linux buffer underruns
2026-11-06 21:17:13 +00:00
rdbt d4df5dbf59 Merge pull request #10572 from GIGABAIT93/feat/launch-api-content
Content API: --launch-api says what content a build loads, and docs/api/content.md (#10562)
2026-11-06 21:16:35 +00:00
rdbt 66e6b23fb8 Merge pull request #11243 from AlfaRojo/fix/lead-hold-old-rotation
The lead held in first person turns with the mouse, without jitter
2026-11-06 21:16:29 +00:00
rdbt 918a4c8434 Merge pull request #11247 from Joansitoh/fix/11178-wither-skull-save
Keep wither skulls in flight through a chunk save
2026-11-06 21:16:23 +00:00
rdbt a3c540e8c0 Merge pull request #11255 from AlfaRojo/fix/screen-over-playing-releases-keys
A book, sign editor or prompt over the world releases the held buttons
2026-11-06 21:16:17 +00:00
rdbt e243cbac06 Merge pull request #11046 from voidsipp/fix/round4-2-10408
Honor remapped function keys from XKB
2026-11-06 21:16:11 +00:00
rdbt 4c5f37de8d Merge pull request #11199 from arslanarm/shaderpacks/noise-texture-resolution
Shader packs: noiseTextureResolution sizes the default noisetex
2026-11-06 21:16:05 +00:00
rdbt ba7cdd4d8c Merge pull request #11188 from TheMrZZ/fix/entity-scores-predicate
Evaluate named entity_scores predicates for bound holders
2026-11-06 21:15:58 +00:00
rdbt b61394f396 Merge pull request #11206 from arslanarm/shaderpacks/runtime-array-length
Shader packs: the length of a buffer block's runtime array reads back
2026-11-06 21:15:53 +00:00
rdbt a13866fff3 Merge pull request #10537 from arslanarm/shaderpacks/end-boss-fog-sky
The dragon fight's world fog leaves out the sky, as vanilla does
2026-11-06 21:15:46 +00:00
rdbt f9302c0736 Merge pull request #11023 from ilguerro/fix/9776-item-vibrations
Casting and reeling a fishing rod and throwing an eye of ender make the vibrations vanilla's do
2026-11-06 21:15:40 +00:00
rdbt 44e4124142 Merge pull request #10005 from UMCEKO/fix/projectile-owner-any-entity
Projectiles owned by any non-player entity need mob_griefing
2026-11-06 21:15:34 +00:00
rdbt dc040a9dd7 Merge pull request #11127 from Delete228/discarded-mob-lookup-upstream
Hide discarded mobs from immediate lookup and passenger saves
2026-11-06 21:15:29 +00:00
rdbt d54d736546 Merge pull request #11030 from ilguerro/fix/illager-target-memory
Merge remote-tracking branch 'origin/main' into fix/illager-target-memory
2026-11-06 21:15:23 +00:00
rdbt e4928503ac Merge pull request #11224 from TheMrZZ/fix/text-floating-zero
Canonicalize floating zero when parsing text tags
2026-11-06 21:15:17 +00:00
rdbt 2a83a524fc Merge pull request #11343 from TheMrZZ/fix/tnt-live-data-adoption
Adopt live TNT command data without replacing its identity
2026-11-06 21:15:11 +00:00
rdbt cfd68c41cf Merge pull request #11155 from TheMrZZ/fix/h125-mob-name-predicates
Fix live mob custom-name component predicates
2026-11-06 21:15:04 +00:00
rdbt 2c149ab9e6 Merge pull request #10096 from yahya-nusseibeh/fix/rcon-one-guess
RCON: one password guess per connection
2026-11-06 21:14:59 +00:00
rdbt aebabfb5c6 Merge pull request #11139 from TheMrZZ/fix/pillager-trader-saved-inventory
Preserve Pillager and WanderingTrader saved inventories
2026-11-06 21:14:53 +00:00
rdbt 90a09f5b25 Merge pull request #11346 from AlfaRojo/fix/creeper-explosion-radius
A creeper blows with its ExplosionRadius
2026-11-06 21:14:48 +00:00
Nikita Maximilian Grimm 1d1f36972c Merge remote-tracking branch 'origin/main' into shader-api-02-serve
# Conflicts:
#	docs/api/shaders.md
2026-11-06 22:14:42 +01:00
rdbt ea2986d57b Merge pull request #11223 from AlfaRojo/fix/release-use-before-move
An arrow leaves from where the player was when the bow was released
2026-11-06 21:14:42 +00:00
rdbt 7093191256 Merge pull request #11043 from voidsipp/fix/11034-protocol-identifiers
Validate resource identifiers when reading packets
2026-11-06 21:14:36 +00:00
rdbt d774839f1f Merge pull request #11202 from arslanarm/shaderpacks/compute-shadow-reads
Shader packs: a compute stage reading a shadow buffer turns the shadow pass on
2026-11-06 21:14:30 +00:00
rdbt 6cfb046532 Merge pull request #11203 from arslanarm/shaderpacks/celestial-alpha-blend
Shader packs: the sun and moon leave their own alpha, as vanilla's celestial blend
2026-11-06 21:14:24 +00:00
rdbt fb0866ef3f Merge pull request #11278 from TheMrZZ/fix/h035-loot-mine-pot-components
Preserve pot decorations in loot mine results
2026-11-06 21:14:18 +00:00
rdbt eec2eb3b32 Merge pull request #11067 from voidsipp/fix/11039-nbt-path-reader
Match NBT path quoted escapes and index tokens
2026-11-06 21:14:12 +00:00
rdbt 4e8ed32e62 Merge pull request #10173 from MSedgeMC/fix/10071-auto-update-enabled
launcher: an automatic update keeps a disabled mod off
2026-11-06 21:14:06 +00:00
rdbt 8f55a6c245 Merge pull request #10542 from TheMrZZ/fix/player-default-effect-amplifier
Omit the default amplifier from saved player effects
2026-11-06 21:14:01 +00:00
rdbt 13664f3799 Merge pull request #11157 from TheMrZZ/fix/terminal-execute-fork-errors
Honor fork state for terminal execute condition errors
2026-11-06 21:13:55 +00:00
rdbt 1fb80a5d79 Merge pull request #11354 from MSedgeMC/fix/explosion-item-damage
fix: a blast takes its damage off a dropped item's health
2026-11-06 21:13:49 +00:00
rdbt 3d8521b429 Merge pull request #11209 from TheMrZZ/fix/inline-slot-range-admission
Reject unknown inline slot-range names before execution
2026-11-06 21:13:42 +00:00
rdbt 6181c89ed6 Merge pull request #11246 from TheMrZZ/fix/item-reference-uuid-codec
Decode Item Owner and Thrower with numeric UUID streams
2026-11-06 21:13:37 +00:00
rdbt 2018af984c Merge pull request #11284 from TheMrZZ/fix/mob-command-equipment-slots
Respect mob body-slot admission and saddle commands
2026-11-06 21:13:31 +00:00
rdbt f31d16a2c6 Merge pull request #11201 from TheMrZZ/fix/h154-unless-function-fallthrough
Fix unless-function continuation after fallthrough
2026-11-06 21:13:25 +00:00
rdbt c7d5881fdc Merge pull request #10996 from ilguerro/fix/sleeping-mob-push
A sleeping villager, fox or player isn't pushed by the mobs that walk into it
2026-11-06 21:13:19 +00:00
rdbt c815a0f81c Merge pull request #11162 from TheMrZZ/fix/modern-sign-literal-quotes
Preserve literal quotation characters in modern sign messages
2026-11-06 21:13:13 +00:00
rdbt 08c3b12345 Merge pull request #11032 from ilguerro/fix/piglin-admired-drop
Merge remote-tracking branch 'origin/main' into fix/piglin-admired-drop
2026-11-06 21:13:07 +00:00
rdbt 0925060e6f Merge pull request #11079 from horizon-rust/platforms/intel-mac-native-mods
Native mods on Intel Macs: the launcher brings Rust for x86_64 macOS
2026-11-06 21:13:01 +00:00
rdbt 08f1c4f6c6 Merge pull request #11244 from Gabbotampo/fix/11222-negative-holder-ids
Item components: refuse negative holder ids and holder set sizes
2026-11-06 21:12:56 +00:00
rdbt 838375d4c2 Merge pull request #11363 from Skr1zzzz/older-servers-unknown-types
Older servers: an entity or block entity type the pinned version lacks is dropped, not renumbered
2026-11-06 21:12:50 +00:00
rdbt 8bb2525e31 Merge pull request #11009 from ilguerro/fix/trade-display-drop-chance
A villager's held-up trade item never drops when it dies
2026-11-06 21:12:44 +00:00
rdbt ef6be13a1c Merge pull request #11056 from TheMrZZ/fix/interaction-saved-defaults
Save default Interaction dimensions and response
2026-11-06 21:12:39 +00:00
rdbt 014ad04d1c Merge pull request #11179 from TheMrZZ/fix/h113-command-damage-vibration
Emit mob command damage vibrations before returning
2026-11-06 21:12:33 +00:00
rdbt f2ec4e9c01 Merge pull request #11171 from TheMrZZ/fix/arrow-owner-command-nbt
Expose loaded arrow Owner references in command NBT
2026-11-06 21:12:27 +00:00
rdbt 0d840d54e3 Merge pull request #10219 from TheMrZZ/fix/selector-unknown-criterion
Reject missing advancement selector criteria
2026-11-06 21:12:21 +00:00
rdbt 43528d198c Merge pull request #10476 from TheMrZZ/fix/h220-macro-source-type
fix: report actual macro argument NBT type
2026-11-06 21:12:15 +00:00
rdbt 3d92e52a83 Merge pull request #10565 from TheMrZZ/fix/entity-nbt-boolean-byte
Match entity NBT boolean loads to numeric byte conversion
2026-11-06 21:12:09 +00:00
rdbt 0ef3df34d6 Merge pull request #10373 from xxaxio/fix/10317-narrate-incompatible-server
multiplayer: narrate incompatible server in server list
2026-11-06 21:12:02 +00:00
rdbt e4872aeba7 Merge pull request #11071 from TheMrZZ/fix/tnt-saved-default-power
Omit stale default power from saved TNT
2026-11-06 21:11:56 +00:00
rdbt d08f9ce9d5 Merge pull request #11108 from TheMrZZ/fix/animal-stale-love-cause
Remove stale Animal LoveCause when no reference is modeled
2026-11-06 21:11:51 +00:00
rdbt 162dd10147 Merge pull request #11305 from ilguerro/test/widen-harness-inputs
Widen four parity harnesses' inputs
2026-11-06 21:11:45 +00:00
rdbt 5919e28e0a Merge pull request #11341 from MSedgeMC/test/explosion-edge-ring
test: pin the bed explosion harness's blast edge to the item's own age
2026-11-06 21:11:08 +00:00
rdbt 8985543344 Merge pull request #10042 from NikitaMGrimm/hdr-note-serve-0-1-until-step-5
HDR design note: serve shader API 0.1 until 0.2 is on by default
2026-11-06 21:11:02 +00:00
rdbt 29f5c3504f Merge pull request #10540 from M0-kii/fix/changed-file-paths
Keep exact changed paths and rename sources in tooling checks
2026-11-06 21:10:57 +00:00
rdbt 06c3ba2173 Merge pull request #10019 from IsT3RiK/traduction-fr
Launcher French: translate the strings left in English, vous throughout
2026-11-06 21:10:51 +00:00
rdbt 57dcec2e2d Merge pull request #11173 from SamsVT/thai-translation
Launcher: rewrite the Thai translation (th.json)
2026-11-06 21:10:46 +00:00
rdbt a3b0891fc2 Merge pull request #11254 from MSedgeMC/fix/ghast-still-timeout
A happy ghast keeps its still timeout across a save
2026-11-06 21:10:40 +00:00
rdbt 68d5e2ec6c Merge pull request #11057 from DUzzL/fix/11018-marker-common-nbt
Save common Entity fields for registered Markers
2026-11-06 21:10:33 +00:00
rdbt 909a8123d0 Merge pull request #10546 from TheMrZZ/fix/h074-happy-ghast-ambient-position
Fix HappyGhast ambient sound position
2026-11-06 21:10:27 +00:00
rdbt da3a5aff7e Merge pull request #11265 from NikitaMGrimm/still-crystal-phase
HORIZON_STILL: end crystals start their spin from their position
2026-11-06 21:10:21 +00:00
rdbt e1d2186ee1 Merge pull request #11142 from voidsipp/fix/ci-shared-parity-invocation
Let dolphin and spell parity harnesses use runner defaults
2026-11-06 21:10:16 +00:00
rdbt e500989785 Merge pull request #11262 from MSedgeMC/test/enchant-gate
enchanting: the stack's damage and existing enchantments at the table
2026-11-06 21:10:10 +00:00
rdbt 57ad8718c3 Merge pull request #11198 from MSedgeMC/test/creative-removal
test: a creative player's break leaves the block, but no drops or statistic
2026-11-06 21:10:03 +00:00
rdbt c12fe63d33 Merge pull request #11109 from Detimes/native-refresh-commands
Native mods: native_refresh_commands rebuilds the players' command trees at run time
2026-11-06 21:09:57 +00:00
rdbt 71e18e8c0a Merge pull request #11054 from TaquitoSuabe/fix/haswell-vulkan-fallback
gfx: skip broken Haswell Vulkan driver (hasvk) and fall back to OpenGL
2026-11-06 21:09:51 +00:00
rdbt 880b690d6c Merge pull request #10086 from horizon-rust/pvp-safe/sneak-input
Multiplayer: sneaking for a use is the input of the last tick, as vanilla's isShiftKeyDown
2026-11-06 21:09:45 +00:00
rdbt d55b7f8594 Merge pull request #10551 from ilguerro/fix/9759-golem-peaceful
Peaceful drops a mob's player target and a golem's anger
2026-11-06 21:09:39 +00:00
rdbt b849bc3dab Merge pull request #11007 from ilguerro/fix/use-swing-attack-reset
A use that swings the hand resets the attack charge
2026-11-06 21:09:33 +00:00
rdbt 85d5ce37f0 Merge pull request #10908 from DUzzL/fix/10550-trident-hurts-players
A thrown trident hurts other players on a served world
2026-11-06 21:09:26 +00:00
rdbt 5395de1ab4 Merge pull request #10967 from ilguerro/fix/mob-block-fall-sound
A mob's damaging fall plays the fall sound of the block it lands on
2026-11-06 21:09:19 +00:00
rdbt 24459551e6 Merge pull request #11100 from Delete228/native-held-break-slot-upstream
Native mods: choose the break slot for held-item durability
2026-11-06 21:09:12 +00:00
rdbt 848ffed94d Merge pull request #11136 from DUzzL/fix/11088-slime-split-delay
Split slimes, magma cubes and sulfur cubes when the body is removed
2026-11-06 21:09:06 +00:00
rdbt 66ee01b737 Merge pull request #11143 from Delete228/frozen-sprint-tick-boundary-upstream
Restore the frozen tick decision before ending a sprint
2026-11-06 21:09:01 +00:00
rdbt 7227485ca2 Merge pull request #11211 from AlfaRojo/fix/elytra-glide-saved
A player saved while gliding comes back gliding
2026-11-06 21:08:55 +00:00
rdbt d914334acc Merge pull request #11226 from Horizon-is-vibe-coded/main
fix(checkerboard): clamp negative scale before shift to avoid pathological shift distance
2026-11-06 21:08:49 +00:00
rdbt 07b8f97138 Merge pull request #11229 from AlfaRojo/fix/own-model-freeze-shiver
The player's own third-person model shivers when fully frozen
2026-11-06 21:08:44 +00:00
rdbt 5e8d557b17 Merge pull request #11292 from Ragbie/fix/snbt-trailing-comma
SNBT: a compound, list or array may end with a comma, as vanilla's grammar allows
2026-11-06 21:08:38 +00:00
rdbt 07a162cb30 Merge pull request #11272 from echo-ilovech3ss/fix/tp-passenger-followups
game: cover passenger rotation through /tp in rider test
2026-11-06 21:08:32 +00:00
rdbt 8146a586da Merge pull request #11279 from DUzzL/fix/11271-smithing-trim
Draw trims on the smithing preview armour
2026-11-06 21:08:27 +00:00
rdbt dc8aba8bee Merge pull request #11259 from DUzzL/fix/11252-autosprint-sign
Keep a latched sprint toggle out of the text-field Ctrl shortcuts
2026-11-06 21:08:21 +00:00
rdbt f1f352044f Merge pull request #11348 from d3xc0/native-default-state
Native mods: a block default state by name (native_default_state)
2026-11-06 21:08:15 +00:00
rdbt 83feefe860 Merge pull request #11368 from Vaspyyy/docs/claimable-issues-query
AGENTS.md: one query for the issues free to claim
2026-11-06 21:08:09 +00:00
rdbt 2132e6126f Merge pull request #11313 from DUzzL/fix/11290-beacon-beam-view
Cull back faces of beacon beams
2026-11-06 21:08:03 +00:00
rdbt 97b36f8e1b Merge pull request #11362 from AlfaRojo/fix/mob-protection-order
A mob's Protection runs after Resistance and not against a sonic boom
2026-11-06 21:07:58 +00:00
rdbt 521c26d6ca Merge pull request #11357 from AlfaRojo/fix/bone-meal-spreader-particles
Bone meal on a spreading block sparks over the block above it
2026-11-06 21:07:52 +00:00
rdbt 218624479d Merge pull request #11352 from AlfaRojo/fix/damage-taken-stat-threshold
Damage Taken is not awarded for a huge hit
2026-11-06 21:07:46 +00:00
rdbt 4dc1282f65 Merge pull request #9953 from Hiuoy/feat/9921-port-pack-events
Port pack: `events`, a mod's NeoForge listeners against the hooks
2026-11-06 21:04:33 +00:00
rdbt b351455eae Merge pull request #10245 from Hiuoy/feat/anvil-update
Native hooks: AnvilUpdateEvent, a mod's say in an anvil's result
2026-11-06 21:04:28 +00:00
rdbt 217e87e8f3 Merge pull request #10240 from MSedgeMC/fix/item-cull-frame
Dropped items: cull at the box the frame draws, not the tick's
2026-11-06 21:04:22 +00:00
rdbt e3356386d0 Merge pull request #11153 from DUzzL/fix/11055-cushion-sit-remote
Seat other players on a cushion they sit on
2026-11-06 21:04:15 +00:00
rdbt bf17581664 Merge pull request #11152 from DUzzL/fix/11074-dismount-hint
Show the dismount hint when the player starts riding
2026-11-06 21:04:09 +00:00
rdbt 02e62fbedf Merge pull request #11182 from DUzzL/fix/11137-golem-target-mounts
Keep golems off zombie horses, camel husks and zombie nautilus
2026-11-06 21:04:03 +00:00
rdbt e0f9934254 Merge pull request #11240 from DUzzL/fix/10287-shulker-piston-align
Snap piston-pushed shulkers to the block centre
2026-11-06 21:03:57 +00:00
rdbt 76bda62793 Merge pull request #11169 from DUzzL/fix/11130-mod-worldgen-feature-dir
Read mod features from worldgen/feature/, 26.3's folder
2026-11-06 21:03:51 +00:00
rdbt b12c89bf04 Merge pull request #11129 from Delete228/native-container-provisioning-upstream
Avoid inventory notifications when provisioning empty native slots
2026-11-06 21:03:45 +00:00
rdbt 345e6efdc9 Merge pull request #11151 from voidsipp/fix/gateway-passenger-player-ticks
Run the mounted player tick in the gateway parity scene
2026-11-06 21:03:38 +00:00
rdbt 77ec563892 Merge pull request #11112 from ilguerro/tooling/use-item-on-interact-start
use_item_on's oracle posts item_interact_start when a use starts
2026-11-06 21:03:32 +00:00
rdbt 5bd038ed99 Merge pull request #11156 from xxaxio/docs/11154-index-design-notes
docs/README.md: index live mod reloading design note
2026-11-06 21:03:26 +00:00
rdbt 06e2041d43 Merge pull request #11149 from xxaxio/docs/11145-platform-scope
docs/native-client: update stale platform scope note
2026-11-06 21:03:19 +00:00
rdbt 2643b0e246 Merge pull request #11134 from xxaxio/docs/stale-notes-cleanup
docs/server-levels: remove stale End credits for server players note
2026-11-06 21:03:13 +00:00
rdbt f235e95518 Merge pull request #11131 from xxaxio/fix/11120-test-harness-args-windows
tests/test_harness_args: match system path separator for default work dir
2026-11-06 21:03:07 +00:00
rdbt 1e68c9db3d Merge pull request #11010 from Hiuoy/docs/neoforge-labels-tooltip
Native hook catalog: tooltip_render_pre is RenderTooltipEvent.Pre; the Next hooks have all landed
2026-11-06 21:03:00 +00:00
VaspyyyandClaude Opus 5.5 ee88e8c870 AGENTS.md: one query for the issues free to claim
Agents had to open issues one by one to see if they were taken, and the rule
did not mention Discord claims (Kira's `in progress` label). Give them the
ready-made search and name both claim signals.

Fixes #11367

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 21:48:25 +01:00
rdbtCVSandClaude Opus 5.5 18eb428ffb Drop the agent marker from a code comment
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:35:38 +00:00
rdbtCVSandClaude Opus 5.5 cd99e67d1c Native client doc: macOS is a target too
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:35:11 +00:00
Kevin RamirezandClaude Fable 5.1 71622aabb3 A mob's Protection runs after Resistance and not against a sonic boom (#11308)
LivingEntity.getDamageAfterMagicAbsorb applies Resistance, returns 0 when
nothing is left, returns the damage as is for #bypasses_enchantments (the
warden's sonic_boom), and only then the enchantment protection. Horizon's
Mob::hurt_server ran the protection first and never read the tag, so a
zombie in a Protection IV chestplate kept 1.6 of a 10-point sonic boom.

DamageKind::bypasses_enchantments (SonicBoom, or a NamedDamage with the
new BYPASSES_ENCHANTMENTS tag) gates the protection, which now follows
Resistance as vanilla orders them.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 14:33:17 -06:00
rdbtCVSandClaude Opus 5.5 e70f5347ba Realms: finish the merge of main (frontend helper, tier fields)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:33:13 +00:00
Skr1zzzzandClaude Opus 5.5 ad8a713d95 Multiplayer: the player rides any tracked entity, sitting where vanilla seats it
A server seating the player on a block display (sit mods, NPC plugins) took the vehicle for a minecart; none was
found, so riding was dropped and the player fell into the slab under the seat (y -61.0 where the server keeps
-60.1). The seat is the vehicle's riding position less the player's vehicle attachment (0.6, scaled).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 23:31:46 +03:00
rdbtCVSandClaude Opus 5.5 9884fc3af1 Keep #[cfg(test)] on the test module the merge of main took it from
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:30:45 +00:00
rdbtCVSandClaude Opus 5.5 b25dbdab4e Restore the #[test] the merge of main took from this test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:29:53 +00:00
rdbtCVSandClaude Opus 5.5 dc175fe838 Hoppers restart a furnace's cooking when they fill its empty input
AbstractFurnaceBlockEntity.setItem resets the time; without the early reset a hopper-fed furnace would smelt its first item in one tick. Review feedback on #10170.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:23:55 +00:00
rdbtCVSandClaude Opus 5.5 d447274d79 Merge origin/main into PR #10170
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:22:15 +00:00
rdbtCVSandClaude Opus 5.5 c99811b0a7 /say: no captured copy of the broadcast line
A guest's /execute run say and the dedicated console saw it twice; review feedback on #10015.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:21:48 +00:00
rdbtCVSandClaude Opus 5.5 883d53f647 Merge origin/main into PR #10015
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:21:48 +00:00
rdbtCVSandClaude Opus 5.5 1e137ff7f3 Launcher: DRAWN is only read off Linux now
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:19:06 +00:00
rdbtCVSandClaude Opus 5.5 dcc21109ac Say what the automatic grant does for an advancement with no criteria
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:18:41 +00:00
rdbtCVSandClaude Opus 5.5 daf2f4debc Statistics tabs read the raw key, as Screen.keyPressed does
Review feedback on #10124.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:18:16 +00:00
rdbtCVSandClaude Opus 5.5 c612f19826 Merge origin/main into PR #10124
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:17:59 +00:00
rdbtCVSandClaude Opus 5.5 8c820a5994 Chat: clearing a draft runs onEdited, so its old command info goes too
EditBox.setValue fires the responder; review feedback on #10013.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:16:17 +00:00
rdbtCVSandClaude Opus 5.5 377650f3ee Merge origin/main into PR #10013
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:16:16 +00:00
rdbtCVSandClaude Opus 5.5 7fbe88924e Keep AdvancementProgress.isDone false for an advancement with no criteria
AdvancementRequirements.test returns false when its list is empty; review feedback on #10193.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:15:51 +00:00
MSedgeMC bae2b17a0d fix: a blast takes its damage off a dropped item's health
`ServerExplosion.hurtEntities` damages every entity in reach, and
`ItemEntity.hurtServer` writes the result back:

    this.health = (int)((float)this.health - amount);
    if (this.health <= 0) { this.getItem().onDestroyed(this); this.discard(); }

Horizon's item branch only asked whether the damage reached 5 and destroyed the
item, so an item that lived through a blast kept all 5 points of health: a
power-5 bed blast that leaves vanilla's item at 1 left Horizon's at 5.

The damage is now subtracted in f32 and truncated after the subtraction, as the
cast does, and the item is destroyed at zero health as before. The nether star
stays immune through `ItemStack.canBeHurtBy`'s explosion exception, and a blast
whose source is an entity with `mobGriefing` off never reaches the branch
(`items_ignore`).
2026-11-06 20:15:40 +00:00
MSedgeMC 6f5be3bbf8 test: the blast edge ring reads the survivor's health too
A dropped item starts with 5 health and `ItemEntity.hurtServer` takes the
blast's damage off it, so a survivor's `Health` says how much of the blast it
took. The pinned compound the ring already reads carries it, so the compared
line grows one fact:

  data push -0.03 -0.00 -0.05 health 1 keep 0.9800

Red on main: Horizon reports `health 5` where vanilla reports `1`.
2026-11-06 20:15:40 +00:00
rdbtCVSandClaude Opus 5.5 42948e3f17 Merge origin/main into PR #10193
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:15:26 +00:00
Florian ERNSTandCodex 627617ee6b Route damage commands to actual boat and minecart damage
Co-Authored-By: Codex <[email protected]>
2026-11-06 21:15:20 +01:00
Kevin RamirezandClaude Fable 5.1 9343b3c357 Bone meal on a spreading block sparks over the block above it (#11310)
BoneMealItem.addGrowthParticles places a NEIGHBOR_SPREADER's happy
villager sparks at BonemealableBlock.getParticlePos, which is the block
above for grass, nylium, netherrack and moss; a GROWER keeps the block
itself, rooted dirt and mangrove leaves the one below. Horizon used the
bone-mealed block for spreaders too, a block lower than vanilla.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 14:13:58 -06:00
rdbtCVSandClaude Opus 5.5 b85a07d423 Merge origin/main into PR #10129
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:11 +00:00
rdbtCVSandClaude Opus 5.5 8761658a18 Merge origin/main into PR #10180
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:10 +00:00
rdbtCVSandClaude Opus 5.5 9b788418c3 Merge origin/main into PR #10219
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:08 +00:00
rdbtCVSandClaude Opus 5.5 06a0f118e2 Merge origin/main into PR #10119
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:07 +00:00
rdbtCVSandClaude Opus 5.5 19eff5eb79 Merge origin/main into PR #10532
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:07 +00:00
rdbtCVSandClaude Opus 5.5 6218257d19 Merge origin/main into PR #10118
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:06 +00:00
rdbtCVSandClaude Opus 5.5 41d3a0cbb7 Merge origin/main into PR #10009
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:05 +00:00
rdbtCVSandClaude Opus 5.5 5393956e8a Merge origin/main into PR #9995
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:11:04 +00:00
rdbtCVSandClaude Opus 5.5 af5731d305 Merge origin/main into PR #9993
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:10:13 +00:00
Kevin RamirezandClaude Fable 5.1 53c13c0cd9 combat: no JDK method named in the damage_taken docs (the ledger reads them as citations)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 14:09:37 -06:00
rdbtCVSandClaude Opus 5.5 57a7b16d5a Merge origin/main into PR #9968
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:08:41 +00:00
rdbtCVSandClaude Opus 5.5 76c095290f Custom skies: a fade out ending at the fade in's start ramps down over the rest of the day
Review feedback on #10002.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:07:41 +00:00
rdbtCVSandClaude Opus 5.5 7581d1fbe3 Merge origin/main into PR #10002
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:06:52 +00:00
rdbtCVSandClaude Opus 5.5 33cd3992bf Merge origin/main into PR #10019
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:06:12 +00:00
Kevin RamirezandClaude Fable 5.1 a419cc37c7 Damage Taken is not awarded for a huge hit (#11339)
Player.actuallyHurt awards DAMAGE_TAKEN as Math.round(dmg * 10.0F) only
while dmg < 3.4028235E37F. Horizon awarded every hurt with Rust's round,
so /damage @s 3.4e38 added a saturated i32::MAX where vanilla adds
nothing. A damage_taken_award helper applies the threshold and
jmath::round_f32 (Math.round(float): ties up, saturating).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 14:06:03 -06:00
Skr1zzzzandClaude Opus 5.5 ec534edc6c Older servers: an entity or block entity type the pinned version lacks is dropped, not renumbered
A modded 1.21.11 server's own entity type (Sit's sit:entity_sit is 157) kept its number, which in 26.3 is
the zombie villager: every sitting player showed one under them. Chunks already dropped such block entities;
their updates now go too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 23:05:51 +03:00
rdbtCVSandClaude Opus 5.5 c25f5a1f49 Merge origin/main into PR #11081
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 20:05:45 +00:00
rdbtCVS 8c5025d3d9 Merge origin/main into PR #10476
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	tools/command_exec_corpus.py
#	tools/test_command_exec_parity.py
2026-11-06 20:00:41 +00:00
rdbtCVS 7b21af9519 Merge origin/main into PR #10196
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/src/game/native_client_services.rs
2026-11-06 19:59:43 +00:00
rdbtCVS 67fa3d1bb3 Merge origin/main into PR #10065
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	tools/lan_e2e.py
2026-11-06 19:59:37 +00:00
rdbtCVS d6113ce7e5 Merge origin/main into PR #10018
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	tools/lan_e2e.py
2026-11-06 19:59:36 +00:00
rdbtCVS 1c389c3758 Merge origin/main into PR #10606
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/tests/mod_api_hooks.rs
2026-11-06 19:59:35 +00:00
rdbtCVS 8d5e529b46 Merge origin/main into PR #10005
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/src/game/creatures.rs
2026-11-06 19:59:01 +00:00
rdbtCVS 6f82bf74b1 Merge origin/main into PR #10173
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	launcher/src/browser/horizon/auto/tests.rs
2026-11-06 19:59:00 +00:00
Kevin RamirezandClaude Fable 5.1 e7c082ee6f A creeper blows with its ExplosionRadius (#11332)
Creeper.readAdditionalSaveData reads ExplosionRadius as a byte (default
3), addAdditionalSaveData writes it back, and explodeCreeper blows with
it, twice it when powered. Horizon used a fixed 3 or 6 and never read the
key, so a summoned creeper with ExplosionRadius:12b blew with 3.

Extra.explosion_radius holds it (3 from Mob::new), entity_nbt reads any
numeric tag narrowed to a byte and writes the byte, and creeper_tick
scales it by the powered multiplier.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 13:56:10 -06:00
Florian ERNSTandCodex 5efccc59f0 Adopt live TNT command data without replacing its identity
Co-Authored-By: Codex <[email protected]>
2026-11-06 20:53:04 +01:00
MSedgeMC cdf61801bb test: pin the bed explosion harness's blast edge to the item's own age
The survivor the harness gained at the edge of the blast is read through the
item's own tick counter. A push on an item decays every tick
(`Entity.getAirDrag`, 0.98), and the two servers do not advance the same number
of game ticks between the same commands: at three script points of one case the
vanilla survivor was 31, 33 and 48 ticks old and Horizon's 38, 41 and 75, so a
`Motion` read there measures the pacing rather than the blast.

The edge item's `Age` is now read before the blast, and its whole NBT (`Age`,
`Pos` and `Motion` from one snapshot) at two pinned ages after it. The case
compares the push carried back over the ticks since the blast, to two decimals
because a tick of slack between the blast and the read is worth 2%, and the
decay the two snapshots show per tick, to four, which is `getAirDrag`'s 0.98.
Both agree with vanilla's in all twelve cases.
2026-11-06 19:50:58 +00:00
f7190cff67 Widen four parity harnesses' inputs
Cover all block states, registered enchantments, lore styles and
representative Lunge tiers using the existing vanilla oracles.

Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>

Claude-Session: https://claude.ai/code/session_01WZzq8KX3vDMZy53y1vXqeT
2026-11-06 20:32:02 +01:00
DUzzL 02896862e8 Cull back faces of beacon beams 2026-11-06 20:29:32 +01:00
Pedro CavalcanteandCodex 87761cd5b0 Apply a guest jump before subsequent combat packets
Co-Authored-By: Codex <[email protected]>
2026-11-06 16:14:13 -03:00
Arthur Black ff80bb806c Native mods: a block default state by name (native_default_state) 2026-11-06 11:08:00 -08:00
Evrim CicekandClaude Opus 5.5 d41641f85d SNBT: a compound, list or array may end with a comma
SnbtGrammar accepts one trailing comma before the closing brace or
bracket; a comma alone and two in a row stay errors. Both readers take it:
horizon-world's parse_snbt (NBT arguments, item components) and the text
component reader (tellraw and other component arguments).

Fixes #11285

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 22:06:09 +03:00
ed0780a03a Count collidable entities in auto-jump like vanilla
LocalPlayer.updateAutoJump reads Level.getCollisions, which returns the
collidable entities' boxes (EntityGetter.getEntityCollisions, query
inflated by 1e-7) before the block shapes. Horizon's auto-jump only saw
blocks, so walking into a boat, a shulker or a happy ghast never
auto-jumped onto it.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:48:41 +01:00
Florian ERNSTandCodex 9bc728f303 Respect mob body-slot admission and saddle commands
Co-Authored-By: Codex <[email protected]>
2026-11-06 19:46:24 +01:00
TheMrZZandCodex gpt-6.1-sol 58fc786742 Preserve pot decorations in loot mine results
Record the actual block entity or its existing pure save projection in a local Pending, then attach existing component copies to the unchanged block drops. Keep source blocks and loot replacement policy unchanged.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 19:22:40 +01:00
Ian WorleyandClaude Opus 5.5 cd86e8f45d docs(native-mods): entities a client sees
Document native_client_entities with its new fields,
native_client_items, native_client_texture and skins by UUID, which
entities the list leaves out, and an example loop.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:15:15 -06:00
Ian WorleyandClaude Opus 5.5 eecfdf2f7b feat(native-mods): list the client's entities and dropped items
Client mods such as radars, name tags and spectator ESP need the
entities the client shows, with what they look like, without reaching
into Game's internals. API 411's native_client_entities had ids,
positions and player profiles, but no type, babyhood or texture, and no
dropped items.

- NativeClientEntity gains kind, yaw, baby, spectator and the texture
  its body draws with (mobs::render::body), plus position_at, the
  renderer's interpolation. The struct is non_exhaustive, so existing
  mods keep building.
- native_client_items lists the dropped items the client shows.
  native_item_entities stays server-only, so server logic never acts on
  a client's copies.
- native_client_texture returns TexturePixels by the name texture and
  NativeClientProfile::skin give: a downloaded skin from memory, else
  native_client_resources().texture(name).

Pending fragment client_entity_list. Tests: position_at between two
ticks, and a client fed the server's play packets reading a zombie, a
calf, a spectating player with its default skin and a dropped item
through Host, plus texture pixels and a refused path out of the packs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:15:15 -06:00
Ian WorleyandClaude Opus 5.5 6d9ed21235 feat(native-api): read the client's resource files and texture pixels
Add the `native_client_resources()` helper. It returns `ClientResources`
over the resource tree the game loaded, which is vanilla's with the
resource packs turned on and the content mods' files over it. A client
mod fetches it on the game thread and can then use it on any thread.

`read(path)` returns a file's bytes for a plain path under `assets/`,
such as `assets/minecraft/textures/entity/cow/cow_temperate.png`.
`texture(name)` decodes a texture by name (`block/stone`,
`entity/cow/cow_temperate`) into `TexturePixels`. Paths outside
`assets/` return none, so Horizon's own prepared tables stay private.

Mods stop reading `options.resources` with `atlas_sources::texture_path`
and `prepared_files::read`. The tests build a folder resource pack over
a fake vanilla tree and check that the pack's texture wins, and that
paths outside `assets/` are refused.

Co-Authored-By: Claude Opus 5.5 <[邮箱]>
2026-11-06 12:15:01 -06:00
Ian WorleyandClaude Opus 5.5 908903a58e feat(native-api): block states' facts and looks for client mods
Add the `native_block_states()` helper. It returns `BlockStates`, a copy
of every block state keyed by its `BlockStateId`. A client mod fetches it
on the game thread and can then use it on any thread.

Each state's `BlockStateFacts` holds the block name, air, invisible,
`FluidState`, waterlogged, light emission, outline boxes, map colour and
whether the state can collide at all. `sprites(id)` returns what the
state's model draws on each side, as `native_block_sprites` does for a
block name. `texture(sprite)` names the texture behind a sprite, the
inverse of `native_texture_sprites`. `mod_api` now also exports
`FluidKind`, the type `FluidState::kind` returns.

The table copies the game's internal facts into stable public types.
Client mods that draw blocks their own way, such as minimaps, world maps
and radars, no longer need `res.facts()`, `res.collision()` or
`block_sprites::Lookup`, which change from commit to commit.

Tests run on real prepared resources (ignored without
HORIZON_TEST_RESOURCES): grass block facts, air and light, slab
outlines, and a grass block's face texture names.

Co-Authored-By: Claude Opus 5.5 <[邮箱]>
2026-11-06 12:15:00 -06:00
Florian ERNSTandCodex 3d2be9b1bd Validate command enchantment map entries against active definitions
Co-Authored-By: Codex <[email protected]>
2026-11-06 19:12:28 +01:00
DUzzL 258dee9c8d Generate the trim texture of the smithing preview's result 2026-11-06 19:12:15 +01:00
apeirs 24036af2fb Mob block layers cull back faces like BlockModelRenderState
A mooshroom's mushrooms, an iron golem's poppy, a snow golem's pumpkin and a
copper golem's antenna block are submitted through BlockModelRenderState.submit,
whose block-item sheets (ITEM_CUTOUT, ITEM_TRANSLUCENT) cull back faces. Only the
enderman's carried block culled, so the coplanar faces of the other layers' cross
models z-fought as the mob moved (#5301).

The cull now goes by each quad's winding, as the GPU's does, rather than the baked
facing, which is the nearest axis and 45 degrees off for a cross model's planes.

Fixes #5301
2026-11-06 19:02:50 +01:00
65ee18174a Reduce repeated work in section meshing
Precompute row and direction masks, speed adjacent palette reads, and bypass
random model selection for a single unweighted variant.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8KX3vDMZy53y1vXqeT
2026-11-06 18:53:25 +01:00
DUzzL 10e24b4f32 Apply an item's custom data and entity data to a placed frame, painting or armor stand 2026-11-06 18:43:23 +01:00
echo-ilovech3ss 5303bf09af game: cover passenger rotation through /tp in rider test
Root cause: #9335 follow-ups from #8733 left passenger rotation after /tp covered only by parity corpora (tools/passenger_teleport_corpus.py), which cannot run without vanilla data. The Game-level rider test exercised offset carry (sub-item 1) but not the rider keeping its angle to the vehicle through absolute and relative turns (sub-item 2).

What changed: extend teleport_rider_tests::a_vehicle_carries_its_riders_and_a_rider_leaves_its_vehicle with a second horse/villager pair: absolute turn 0->40 leaves rider at 70 (30-degree offset kept), relative turn to 50 shifts rider 70->80, and facing (turn_riders=false) leaves rider at 80. No runtime change; the teleport/passenger writers already implement Entity.teleportSameDimension/calculatePassengerTransition via vehicle_teleport::passenger_rotation.

Vanilla reference: 26.3 Entity.calculatePassengerTransition, PositionMoveRotation.calculateAbsolute, Entity.setXRot (pitch clamp).

Refs #9335
2026-11-06 23:08:34 +05:30
MSedgeMC e524b6a114 test: an enchanted or damaged stack at the enchanting table
tools/test_enchanting_parity.py always built a fresh ItemStack for its offer
corpus, so EnchantmentMenu.slotsChanged's ItemStack.isEnchantable gate (the
enchantable component and an empty minecraft:enchantments) was never
exercised, and the oracle skipped the gate entirely. Fixture lines now carry
damage and a pre-existing enchantments entry; the oracle applies the gate and
the probe builds the same stack. The default corpus is unchanged and
--no-components reproduces it.

68992/68992 enchanting cases match.
2026-11-06 17:22:55 +00:00
DUzzL 533dd084ae Keep a latched sprint or sneak toggle out of the edit shortcuts' modifiers 2026-11-06 18:11:35 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 f1fac7cc6f HORIZON_STILL: end crystals start their spin from their position
A crystal's spin and bob start from a random draw. Which crystal gets which
draw, and a server's entity ids that seed a client's copies, follow the order
chunks load in, so crystals stood at different angles in each launch of a
frozen scene. Under HORIZON_STILL the start now comes from the position; the
draw is still made, and normal play is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 18:05:51 +01:00
SenjinandClaude Opus 5.5 7b3be8d0b9 Shader packs: gbuffers_hand_water for translucent held items
The hand's quads from a translucent sheet (stained glass, ice...) go after the opaque ones in
its buffer and draw after deferred with gbuffers_hand_water (or its fallbacks: gbuffers_hand,
textured_lit, textured, basic), blended, as translucent terrain does; through gbuffers_hand
they came out solid.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 18:04:31 +01:00
Kevin RamirezandClaude Fable 5.1 408f38d62a tests: the key-release fixture stubs screen_over_playing, which mapped_key_event asks now
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 10:52:52 -06:00
Kevin RamirezandClaude Fable 5.1 e68a0653a0 A book, sign editor or prompt over the world releases the held buttons (#9729)
Gui.setScreen runs KeyMapping.releaseAll for every screen and
Minecraft.tick skips handleKeybinds while one is up, books, the sign
editor, dialogs and prompts included. Horizon keeps Screen::Playing under
those, and its release-all (track_toggle_screens) and the client input
gate (remote_handle_input) only knew the server dialog, while the book's
and sign's mouse arms eat both edges of a click: a dig held into a book
went on after the book closed, until the next click.

gameplay_has_keyboard now takes "a screen is over the world"
(screen_over_playing) in place of the dialog alone, and remote_handle_input
is not ready while one is up.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 10:46:37 -06:00
TheMrZZandCodex gpt-6.1-sol 686d897f59 Normalize Mob Tags when adopting NBT
Adopt the bounded String input before duplicate removal in the two Mob NBT load paths. Keep the canonical retained state visible to real tag commands and saves, and pin cap, duplicate, scalar and live rewrite behavior against Java.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 17:38:11 +01:00
Florian ERNSTandCodex a8201731a1 Decode Item Owner and Thrower through numeric UUID streams
Co-Authored-By: Codex <[email protected]>
2026-11-06 17:27:10 +01:00
Kevin RamirezandClaude Fable 5.1 ff4fad66c9 The lead held in first person turns with the mouse, no jitter (#10276)
LocalPlayer.getRopeHoldPosition eases the lead's hand end from the view
to yRotO/xRotO by half the partial tick. Entity.turn moves yRotO and xRotO
by the mouse's delta too, and AbstractBoat.clampRotation moves yRotO with
the clamp, so only what the tick turned (a boat's deltaRotation) eases in.
Horizon's rope_rot_old was a once-a-tick snapshot the turn never moved:
while the mouse moved, the hand end lagged by half the turn since the
tick began and snapped back at the next tick, 20 times a second.

turn_player now moves rope_rot_old through the same Rotation::turn as the
view, and both boat clamps (the turn's and the tick's) move it with the
view. A test pins that a mouse turn leaves no gap, a boat turn eases, and
xRotO clamps at 90.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 10:22:24 -06:00
MSedgeMC a13fa8c13c port_map: HappyGhast's two save methods
`HappyGhast.addAdditionalSaveData` writes `still_timeout`, the timer
`still_tick` keeps in `counter`; `readAdditionalSaveData` reads it back and
a save without it is 0.
2026-11-06 16:17:03 +00:00
MSedgeMC 5c86d98a9b test: a happy ghast's still timeout survives the save
0, 7 and 10 through `mob_to_nbt` and `mob_from_nbt`: the Int vanilla writes,
the timer back on the mob, and `on_still_timeout` following it. Without the
writer the saved tag has no `still_timeout` (the test fails on `left: None,
right: Some(0)`).
2026-11-06 16:17:03 +00:00
MSedgeMC 9004c6c8d6 fix: a happy ghast keeps its still timeout in the world save
`HappyGhast.addAdditionalSaveData` writes `still_timeout`, the
`serverStillTimeout` behind `staysStill`/`isOnStillTimeout`, and
`readAdditionalSaveData` reads it back:

    protected void addAdditionalSaveData(ValueOutput out) {
        super.addAdditionalSaveData(out);
        out.putInt("still_timeout", this.serverStillTimeout);
    }

Horizon keeps that timer in `Mob.extra.counter` (`happy_ghast::still_tick`
counts it down after the first 60 ticks, `player_above` refreshes it to 10)
but neither `mob_to_nbt` nor `mob_from_nbt` touched it, so a ghast saved
while it was held still came back with 0. For the ticks vanilla restores, a
dropped item resting on its back had no box to rest on
(`EntityGetter.getEntityCollisions` takes a happy ghast only while
`isOnStillTimeout`), and a rider had no control (`getControllingPassenger`
asks `isOnStillTimeout`).
2026-11-06 16:17:03 +00:00
GabbotampoandClaude Opus 5.5 5fe1090401 Item components: refuse negative holder ids and holder set sizes
ByteBufCodecs.holder in vanilla 26.3 reads a VarInt and, when it isn't 0,
looks the entry up with `byIdOrThrow(id - 1)`, which no negative id passes.
skip.rs's `holder` accepted any nonzero id, so a slot whose kinetic_weapon
sound holder was negative was kept as a raw component, and hashing it for an
inventory click computed `id - 1` on i32::MIN: a panic with overflow checks
on, a wrapped id otherwise.

ByteBufCodecs.holderSet reads the size plus one and builds
`new ArrayList(size)`, which refuses a negative size. `holder_set` computed
`n - 1` before checking the sign, the same overflow for i32::MIN.

Both readers now refuse a negative value with a protocol error, so every
component that reads a holder or a holder set gets the same boundary.
Valid direct and registry holders read as before (their vanilla hashes in
hash/raw.rs's tests are unchanged).

Fixes #11222

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 17:07:33 +01:00
daMpoffandOpenAI Codex 0ab59f64d4 Fix overflow in translated launcher instance menus
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-06 19:02:25 +03:00
Kevin RamirezandClaude Fable 5.1 b0d94ed2c0 The player's own third-person model shivers when fully frozen (#11217)
LivingEntityRenderer.isShaking is isFullyFrozen (DATA_TICKS_FROZEN at or
over 140), and setupRotations wobbles the body while it is. Mobs and
other players already shiver, but the window's own model, rebuilt each
tick by tick_player_model, never got the player's frozen ticks, and the
window ignored the DATA_TICKS_FROZEN its server sends it, so a player in
powder snow stood still in third person.

The own model takes ticks_frozen from the player's body, and own entity
data index 7 sets it on the window's player as LocalPlayer reads it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 10:01:55 -06:00
Horizon-is-vibe-coded a4c8dc296d fix(checkerboard): clamp negative scale before shift
Guard against negative scale values producing excessive shift
distances in CheckerboardBiomeSource::noise_biome by clamping
scale to a non-negative lower bound before adding the +2 offset.

This keeps the shift distance within a sane range and avoids
surprising behavior for out-of-range runtime scale inputs.
2026-11-06 17:00:42 +01:00
Florian ERNSTandCodex 1d800a7655 Canonicalize floating zero when parsing text tags
Co-Authored-By: Codex <[email protected]>
2026-11-06 16:57:32 +01:00
DUzzL 776bb4f4d5 Snap pistons-pushed shulkers to the block centre (Shulker.setPos) 2026-11-06 16:53:06 +01:00
Kevin RamirezandClaude Fable 5.1 9fb818013c An arrow leaves from where the player was when the bow was released (#8423)
ServerGamePacketListenerImpl handles a client's packets in order: the
RELEASE_USE_ITEM a client sends from handleKeybinds comes before the move
LocalPlayer.tick sends in the same client tick, so the server shoots from
the position of the move it handled the tick before, one tick of strafe
behind the camera. Hub::process_inbox applies moves at once and hands a
PlayerAction to the game for after the pass; it held a queued move behind
a dig, UseItem, UseItemOn and Interact, not behind the release, the drops
or the stab. The move ran first, and the arrow left a tick ahead of the
camera: strafing left, it seemed to come from the left instead of the
right.

Every PlayerAction now holds the queued move for the next tick. A test
drains a release followed by a move and checks the game gets the release
alone with the guest's position and known movement unchanged.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 09:53:03 -06:00
Kevin RamirezandClaude Fable 5.1 efc682f297 A player boards a vehicle again right after leaving it (#11158)
Entity.removePassenger sets the leaving passenger's boardingCooldown to
60, but Player.removeVehicle overrides Entity.removeVehicle and zeroes it
right after super.removeVehicle(), so a player never waits to board again;
only mobs do. #8733 (from #8586, which missed that override) gave the host
and every guest the 60 ticks, checked by may_board before boarding a boat,
cart, cushion, ghast, llama, mount or nautilus: the delay the issue
reports.

The player's cooldown goes: the boarding_cooldown and riding_at_tick
fields of Game and Guest, their swap for a guest's turn, the per-tick
count-down and may_board at its eight call sites. Each interaction still
refuses a sneaking player, the other half of Entity.canRide. Mobs keep
extra.boarding_cooldown.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 09:44:13 -06:00
Nikita Maximilian GrimmandClaude Opus 5.5 0bd8f4c89c Shader packs: the scripted clock's step in decimal milliseconds
A 60 fps clock steps 16.666667 ms a frame, which a whole number of
milliseconds can't give. FrameTiming::nth takes the step as f64.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 16:28:13 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 e173055d5d Shader packs: a scripted clock that comes before STILL's ticks
PackInputs::frame_timing gives the frame number, the seconds since frame 1
and the seconds since the frame before. Iris-format packs take it for
frameCounter, frameTimeCounter and frameTime and smooth wetness,
eyeBrightnessSmooth and centerDepthSmooth by its delta; custom uniforms
start over on its frame 1 and carry their state after. Horizon packs take
it for time, frame_index and delta_time, ease by it, and do not treat a
slow frame as a gap. Nothing sets it yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 16:28:13 +01:00
Florian ERNSTandCodex 5b44880bfb Reject unknown inline slot-range names before execution
Co-Authored-By: Codex <[email protected]>
2026-11-06 16:26:05 +01:00
Kevin RamirezandClaude Fable 5.1 b2a6c25a6e A player saved while gliding comes back gliding (#11193)
LivingEntity.addAdditionalSaveData writes FallFlying as isFallFlying(), and
readAdditionalSaveData sets shared flag 7 from it, so a player who saves
mid-glide resumes the glide on the next load. Horizon wrote a fixed
FallFlying of 0 and never read it back: the player came back standing in
mid-air.

Level now keeps fall_flying, read from any numeric FallFlying tag (as
getBooleanOr does) and written from the guest's glide; playerdata's store
and apply carry it between the Guest and the file. The server's next
sync_player_state sends the 0x80 shared flag to the client, and
updateFallFlying's port ends the glide if it can't go on.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 09:24:52 -06:00
TheMrZZandCodex gpt-6.1-sol 942e212ca7 Fix unless-function continuation after fallthrough
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 16:20:21 +01:00
JoansitohandGrok <grok-4.7> e380e88edb Keep wither skulls in flight through a chunk save
Entity.shouldBeSaved is true for every projectile in 26.3, and
WitherSkull adds `dangerous` on top of the hurting projectile's
fields. Horizon listed WitherSkull as not saved, so a skull still in
the air when its chunk unloaded was gone when the chunk came back,
and a blue skull came back as nothing.

Write and read the skull the same way as the fireballs: `dangerous`
as a byte (absent means a black skull), and no `Fire` since
WitherSkull.shouldBurn is false. The fire rule now keys on the
fireballs proper (is_item_fireball) instead of "not blast-immune",
which would have lit a saved skull.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-06 16:18:52 +01:00
Florian ERNSTandCodex 5201202a30 Evaluate named entity_scores predicates for bound holders
Co-Authored-By: Codex <[email protected]>
2026-11-06 15:59:55 +01:00
rison1337andCodex GPT-6 f5c1c04245 Stream dynamic light sources and bound nearest-light selection
Avoid owning held-item names and intermediate lists on every frame.
Keep stable nearest-16 lighting output and document FPS and image checks.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 19:48:30 +05:00
TheMrZZandCodex gpt-6.1-sol fcb378d2dd Emit mob command damage vibrations before returning
Post the existing damage event for accepted health-changing mob command hits so a same-tick teleport cannot move its origin. Preserve the existing duplicate marker and reinforcement handling; pin the real command/listener route against Java.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 15:42:35 +01:00
MSedgeMC 4062a09766 test: mine a block in creative too, where nothing drops
tools/test_block_removal_parity.py only ever mined as a survival player:
every mine case ran ServerPlayerGameMode.destroyBlock in SURVIVAL, so the
creative branch (Player.preventsBlockDrops, no mined statistic, no tool
wear) was never compared even though game.rs::break_block_as_player splits
on it. Half the mine cases now run in creative, with a tool that can destroy
blocks in creative (a sword and a mace cannot, which the creative-mining
predicate corpus already owns).

VanillaDispenserOracle's Miner gains a creative flag (isCreative and
hasInfiniteMaterials) and the real CREATIVE game mode; the game's dispense
fixture sets the same mode for the case. --survival-only reproduces the
corpus as it was before this change.

4000/4000 cases match on the new corpus.
2026-11-06 14:29:14 +00:00
Slavik-85andClaude Opus 5.5 791e721843 F3 overlay: paint at 20 Hz, no per-frame OS calls, less GPU timing
The overlay formatted its lines and laid out its glyphs every frame. It now
paints at most 20 times a second and copies those vertices in between, and
repaints at once for another window size, GUI scale or font epoch.

- The refresh rate is read at most once a second: on Windows, winit calls
  EnumDisplaySettingsExW each time. The Window line uses the surface size.
- Passes are timed only while the overlay lists their times, at most 10 frames
  a second. gpu-passes and HORIZON_SHADER_PACK_TIMING keep every sample.
- The CPU name is read in the background when the window opens: on Windows it
  starts reg.exe, which the first F3 frame waited for.
- DebugEntries::any_enabled and status no longer build a String per entry; the
  game asks every frame, F3 closed or not.
- Closing the overlay stops the server thread computing its lines each tick.
- The Mem, Allocation rate and Allocated lines now show on Windows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 16:28:44 +02:00
Florian ERNSTandCodex 48791d642e Expose loaded arrow Owner references in command NBT
Co-Authored-By: Codex <[email protected]>
2026-11-06 15:26:21 +01:00
DUzzL 7f87be741b Keep golems off zombie horses, camel husks and zombie nautilus 2026-11-06 15:22:15 +01:00
Arslan ArmanulyandClaude Opus 5.5 5a44d6dd7f Shader packs: the sky's parts blend as vanilla draws them
gbuffers_skybasic drew every part of the sky with the translucent blend.
Vanilla 26.3 draws the sky disc and the dark disc with RenderPipelines.SKY
(ColorTargetState.DEFAULT, no blending), the sunset fan translucent and
the stars with RenderPipelines.STARS (BlendFunction.OVERLAY: colour
SRC_ALPHA ONE, alpha ONE ZERO). Iris 1.11.7 drew them so with probe packs
of ours: the sky disc kept exactly what the program wrote, (0.3, 0.6,
0.9, 0.25) in buffers of four formats, and the stars added 0.25 x 0.5 to
the disc's 1.0 and left their own alpha 0.5. Without a
blend.gbuffers_skybasic of the pack's own, the disc and the void, and
the stars, now draw with pipelines of the same program that blend so,
keeping a blend.gbuffers_skybasic.<buffer> the pack sets (Iris blended
the disc into that buffer only).

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:20:28 +05:00
Arslan ArmanulyandClaude Opus 5.5 54c4a07e61 Shader packs: a float output into an integer buffer no longer refuses its program
A program writing a float vec4 into an integer colortex (RGBA32UI...) was
refused: wgpu has no float output into an integer target, so the whole
program, and every buffer it writes, was lost. Iris 1.11.7 drew such a
program with a probe pack of ours: its other buffers kept what it wrote,
and the integer buffer read the floats' bits back (GL leaves that value
undefined; these are NVIDIA 615.78.08's). The output now writes the
float's bits as the buffer's integer type.

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:19:41 +05:00
Arslan ArmanulyandClaude Opus 5.5 1fd49b9082 Shader packs: noiseTextureResolution sizes the default noisetex
Horizon always made its default noise texture 256 wide. Iris 1.11.7
made it the size a pack's `const int noiseTextureResolution` gives, when
declared in a fragment shader (composite, deferred, gbuffers, final, or
shadow.fsh with no shadow pass), and ignored it in a vertex shader, in
probe packs of ours. The setting is now read like a colortex format.

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:19:10 +05:00
Arslan ArmanulyandClaude Opus 5.5 892f24b15d Shader packs: the length of a buffer block's runtime array reads back
`.length()` of a runtime-sized array in a named buffer block made naga's
validator refuse the program: its GLSL frontend loads the array and takes
the length of the loaded value. After parsing, the length is now taken of
the array's pointer. Iris 1.11.7 ran such a program in a probe pack of
ours and read the buffer's length (bufferObject size / stride).

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:19:00 +05:00
Arslan ArmanulyandClaude Opus 5.5 dde8c502ca Shader packs: a compute stage reading a shadow buffer turns the shadow pass on
Horizon decided whether to run the shadow pass from the samplers of the
programs' vertex and fragment stages only. A pack whose only reader of
shadowtex0 or shadowcolor0 was a compute stage (prepare.csh alone, or a
composite.csh beside a composite.fsh that reads none) got an empty map
(1.0), while Iris 1.11.7 drew it for probe packs of ours (0.3956 depth,
0.251 shadowcolor0 at our noon scene). Compute stages now count as the
other stages do.

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:18:41 +05:00
Arslan ArmanulyandClaude Opus 5.5 b31e63adc8 Shader packs: an R11F_G11F_B10F image is stored into and read back
Horizon made an R11F_G11F_B10F custom image as RGBA16F, then refused
every program that declared it layout(r11f_g11f_b10f), since the shader's
storage format no longer matched the texture. Iris 1.11.7 stored into such
an image and read it back, with a probe pack of ours: (0.25, 1.5, 3.0)
read back as written, alpha 1, also filtered between texels. The image is
now Rg11b10Ufloat where the device stores into that format; elsewhere it
stays the RGBA16F stand-in, and the shader's image declares that format.

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:18:33 +05:00
Arslan ArmanulyandClaude Opus 5.5 e4bb7bf7ae Shader packs: the sun and moon leave their own alpha, as vanilla's celestial blend
The default blend of gbuffers_skytextured added the sun's alpha over the
sky's (One, OneMinusSrcAlpha). Vanilla 26.3 draws the sun and moon with
RenderPipelines.CELESTIAL, BlendFunction.OVERLAY: colour (SRC_ALPHA, ONE),
alpha (ONE, ZERO). Iris 1.11.7 left the sun's own alpha (2.0) in an
RGBA32F buffer the sky had written 1.0 to, with a probe pack of ours;
Horizon left 1.0.

Part of #10490

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 19:18:12 +05:00
DUzzL f7b204a231 Read mod features from worldgen/feature/, 26.3's folder 2026-11-06 15:17:45 +01:00
SamSu 6635e7a46a Launcher: rewrite the Thai translation (th.json)
Thank you for adding Thai to the launcher in #8306. We were really
happy to see it. When we used it, though, many lines were hard to
understand for us as Thai players. The translation looks
machine-made, and many words have their everyday meaning instead of
their computer meaning. We rewrote it so that Thai players can use the
launcher comfortably. 731 of the 1225 strings changed. The others
were already good, so we kept them as they were.

Below is each word we changed, what it is now, and how a Thai reader
understands the old word. We hope this makes our reasons clear, even
if you don't read Thai:

- build: อาคาร / โครงสร้าง -> บิลด์
  อาคาร means a building (a house, an office). โครงสร้าง means the
  structure of something. "Native build" read as "the original
  building". Thai developers and players say บิลด์, written as it
  sounds.
- instance: ตัวอย่าง -> อินสแตนซ์
  ตัวอย่าง means "example" or "sample", so "Instance" looked like a
  demo copy of the game, not a separate game setup. อินสแตนซ์ is the
  English word written in Thai, as Thai players say it.
- native (mod, build): ดั้งเดิม -> เนทีฟ
  ดั้งเดิม means traditional or original. "Native mod" read as "the
  original mod", which says nothing about compiled code. เนทีฟ is the
  usual Thai programming word.
- launcher: ตัวเรียกใช้งาน / ตัวเปิด / (left in English) -> ลันเชอร์
  The same thing had three names, so a player could not tell they
  were one program. ลันเชอร์ is what Thai players call it.
- jar: ขวด / โหล -> ไฟล์ jar
  ขวด is a bottle and โหล a glass jar for food. "Port gets the jars"
  read as "Port gets the bottles". It is a file type, so ไฟล์ jar.
- Loader: รถตักดิน -> ตัวโหลด
  รถตักดิน is a wheel loader, the construction vehicle.
- Client: ลูกค้า -> ไคลเอนต์
  ลูกค้า is a shop customer. Here it means the game client.
- Running / run: วิ่ง -> กำลังทำงาน / รัน
  วิ่ง is running on foot. "Running" read as "jogging". A program
  that runs is ทำงาน (working) or รัน.
- Proxy: หนังสือมอบฉันทะ -> พร็อกซี
  หนังสือมอบฉันทะ is a legal power-of-attorney letter.
- port: ท่าเรือ -> พอร์ต
  ท่าเรือ is a harbour for ships. A network port is พอร์ต.
- cape: แหลม -> ผ้าคลุม
  แหลม is a headland on a coast (or "sharp"). A Minecraft cape is a
  cloak, ผ้าคลุม.
- cosmetics: เครื่องสำอาง -> คอสเมติก
  เครื่องสำอาง is make-up (lipstick, powder). Thai gamers call
  in-game items to wear คอสเมติก.
- seed: เมล็ดพันธุ์ -> seed
  เมล็ดพันธุ์ is a plant seed for farming. Thai Minecraft players use
  the English word for a world seed.
- Home (page): บ้าน -> หน้าแรก
  บ้าน is a house. The Home page is the first page, หน้าแรก.
- Ban: บ้าน -> แบน
  Ban was also translated as บ้าน ("house"), so the ban button said
  "house". Thai players say แบน.
- founder (set): ผู้ก่อตั้ง -> ผู้เล่นรุ่นแรก
  ผู้ก่อตั้ง is the person who founded a company, so "Free for
  founders" read as "free for the people who started Horizon". The
  shop page says the set is for "the players who were here first", so
  it is ผู้เล่นรุ่นแรก, "early players". It is not "sponsors" either:
  the set is free.
- player counts: คน -> ผู้เล่น
  "{online} of {max} players" used คน (person). In a server list Thai
  players expect ผู้เล่น (player), as the game itself says.
- Java Edition: รุ่นจาวา -> Java Edition
  A product name, like Minecraft and Microsoft, which stay in
  English. รุ่นจาวา ("the Java version") reads as a Java release.
- 78 strings still in English -> translated
  They showed English in the middle of a Thai screen.
- Polite particle ครับ removed
  ครับ is the spoken "sir" that ends a sentence when a man talks. In
  UI text it sounds like a person talking, not a program.

We kept every {placeholder} and every middle dot of the English, and
we used one Thai word for each English term across the whole file.

The two of us, both native Thai speakers, read every changed line
(SamsVT and NotNiceKun). If anything still reads wrong,
please tell us and we will gladly fix it. Thank you for Horizon!
2026-11-06 21:11:40 +07:00
Florian ERNSTandCodex b127089760 Preserve literal modern sign string messages
Co-Authored-By: Codex <[email protected]>
2026-11-06 15:04:48 +01:00
Delete228andCodex e575a22cb2 Use the isolated game environment for mob discard parity
Co-authored-by: Codex <[email protected]>
2026-11-06 16:58:15 +03:00
AxxioandAntigravity 4e6b887535 multiplayer: narrate incompatible server version and motd
Narrate incompatible server list entries matching vanilla narration order: select -> incompatible -> version -> motd. Also remove the note about incompatible server status from docs/narrator.md known differences.

Closes #10317

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 14:49:21 +01:00
Florian ERNSTandCodex 4d3f9944eb Honor fork state for terminal execute condition errors
Co-Authored-By: Codex <[email protected]>
2026-11-06 14:44:04 +01:00
AxxioandAntigravity 4e36ae1e4e docs/README.md: index live mod reloading design note
Link docs/design/mod-hotload.md under the Design notes section.

Fixes #11154.

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 14:39:41 +01:00
TheMrZZandCodex gpt-6.1-sol 253a694871 Fix live mob custom-name component predicates
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 14:37:37 +01:00
AxxioandAntigravity bfdff7afb6 docs/native-client: update stale platform scope note
macOS and Windows are supported platforms with dedicated documentation under docs/platform/. Replace the stale 'macOS and Windows are out of scope' sentence with a reference to docs/platform/.

Fixes #11145.

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 14:36:19 +01:00
60742e9c49 Save villager brain memories like vanilla
A villager's Brain.memories now keep golem_detected_recently, last_woken
and potential_job_site, and each memory's remaining ttl, through a save
and load (Brain.pack, MemoryMap.CODEC, ExpirableValue.codec). The brain
built at the first tick starts from the loaded memories.
last_worked_at_poi stays unsaved: the villager brain does not register it.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:35:37 +01:00
0e0fa342b8 Keep wandering trader homes like vanilla
Use the shared mob home for spawn, movement restrictions and persistence.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:35:37 +01:00
fa76ed4d94 Match vanilla hanging entity coexistence checks
Reject conflicting overlaps between paintings and item frames.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:35:36 +01:00
voidsip ff416a1889 Tick the gateway passenger player in the command oracle scene 2026-11-06 07:33:50 -06:00
AxxioandAntigravity 89dc5664de audio: support HORIZON_AUDIO_BUFFER override for Linux buffer underruns
On Linux, Horizon asks ALSA for a 1024-frame fixed buffer to keep sound effects low-latency. When PipeWire is configured with min-quantum >= 1024 (common for Bluetooth audio or in VMs), the 1024-frame buffer repeatedly underruns with POLLERR and produces no sound.

Allow setting HORIZON_AUDIO_BUFFER=default or HORIZON_AUDIO_BUFFER=<frames> (e.g. 2048) to override the fixed buffer request and use a larger or system-default buffer. Document the environment variable in native-client.md.

Fixes #11082.

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 14:21:51 +01:00
voidsip a98ba02730 Allow parity runner defaults in dolphin and spell harnesses 2026-11-06 07:19:21 -06:00
Delete228andCodex 36232d8820 Restore the frozen tick decision before ending a sprint
Co-authored-by: Codex <[email protected]>
2026-11-06 16:17:27 +03:00
Florian ERNSTandCodex 85b299df9d Preserve Pillager and WanderingTrader saved inventories
Co-Authored-By: Codex <[email protected]>
2026-11-06 14:13:19 +01:00
Delete228andCodex c968fb0d7d Keep native compatibility checks runnable in the upstream test harness
Co-authored-by: Codex <[email protected]>
2026-11-06 16:11:31 +03:00
Delete228andCodex 1bb6912a4b Keep native compatibility checks runnable in the upstream test harness
Co-authored-by: Codex <[email protected]>
2026-11-06 16:11:31 +03:00
DUzzL 7a46ba1f27 Show the dismount hint when the player starts riding 2026-11-06 14:09:41 +01:00
DUzzL f3d842d846 Seat other players on a cushion they sit on 2026-11-06 14:08:47 +01:00
Delete228andCodex 8f73091827 Format the native compatibility changes
Co-authored-by: Codex <[email protected]>
2026-11-06 16:05:48 +03:00
AxxioandAntigravity 03d9b39fc4 tests/test_harness_args: match system path separator for default work dir
DEFAULT is a Path object that formats with backslashes on Windows, while harness_args.py appends str(default). Pinning against str(DEFAULT) instead of the hardcoded POSIX string lets the test pass across all platforms.

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 13:52:31 +01:00
Delete228 a5fa6f0d09 Keep empty native container provisioning out of inventory notifications 2026-11-06 15:51:42 +03:00
Delete228andCodex 04448803c8 Hide discarded mobs from immediate lookup and passenger saves
Keep removed mob entries out of command enumeration and UUID/ID lookup before
the normal sweep, and prevent a discarded passenger from saving inside its
vehicle. Preserve existing death-time and health rules.

Validate frozen Game commands/API37/save assembly against real26.3 discard
and entity-manager behavior with both client and server JARs.

Co-Authored-By: Codex <[email protected]>
2026-11-06 15:51:39 +03:00
AxxioandAntigravity 8f39c8afd2 docs: remove stale server End credits note
Co-Authored-By: Antigravity <[email protected]>
2026-11-06 13:48:49 +01:00
Horizon agentandClaude Opus 5.5 34af7009f5 Multiplayer: drag a saved server by its row to move it in the list
Reordering a long server list took many clicks on the icon's small arrows or
Shift+arrow presses. The press that selects a saved server now also holds it:
moving the pointer puts it on the saved server's row underneath, the others
close up, and the release saves the order. A press on the selected server
still joins, as in vanilla, and holds nothing.

Fixes #10060

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:38:53 +01:00
DUzzL 0f992292b7 Split slimes, magma cubes and sulfur cubes when the body is removed
Horizon split a dying cube at the moment of death. Vanilla
AbstractCubeMob.remove runs the split when the entity is removed, which
LivingEntity.tickDeath does after the death animation (deathTime 20),
so the smaller variants appear about one second later.
2026-11-06 13:37:46 +01:00
Delete228andCodex 21d66471d4 Bind content item aliases to ordinary block placement
Compile place_block declarations into the existing BlockItem binding table and load secondary item-to-block mappings for client prediction and server placement. Preserve alias item identity, component defaults and existing primary block-item mappings.

Validation: 49 compiler, 38 declaration and 63 item tests pass. Explicit prepared-resource tests verify original placement context IDs, real survival consumption, adventure refusal and dry client prediction. Strict clippy, formatting and public-tree checks pass.

Co-Authored-By: Codex <[email protected]>
2026-11-06 15:35:03 +03:00
Florian ERNSTandCodex 6e302fab48 Remove stale Animal LoveCause when no reference is modeled
Co-Authored-By: Codex <[email protected]>
2026-11-06 13:34:29 +01:00
Delete228andCodex b55e8616c1 Keep native entity data through creative Pick Block
Co-Authored-By: Codex <[email protected]>
2026-11-06 15:32:57 +03:00
Horizon agentandClaude Opus 5.5 3365d72927 Native mods: client_attack and client_hurt hooks for the local player's combat
A client mod (battle music, a combat HUD) couldn't hear its own player's
fights on a remote server: the attack and damage hooks run where Horizon is
the server. native_client_attack(target, player) fires when the attack packet
leaves; native_client_hurt(attacker, player) when the server's damage event
for this player arrives, with the causing entity it names and whether this
client knows it as a player.

Fixes #10064

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:31:31 +01:00
Riccardo GuerrieroandClaude Opus 5.5 f7af3f420f use_item_on's oracle posts item_interact_start when a use starts
The oracle's stand-in player overrode LivingEntity.startUsingItem and only recorded the item, so
the real vanilla vibration never ran: ItemStack.causeUseVibration posts ITEM_INTERACT_START when
the stack's use effects ask for interaction vibrations, which every item's UseEffects.DEFAULT
does (DataComponents.COMMON_ITEM_COMPONENTS). Since #10272 Horizon posts it, so the harness
reported 284 false mismatches (4916/5200). It now calls causeUseVibration under the same guard
as vanilla: 5200/5200.

A first draft by DeepSeek V4.1 Flash, via pi, reviewed by Claude.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:29:30 +01:00
Horizon agentandClaude Opus 5.5 ee55bb1e75 Dropped items fall through minecarts, as in vanilla
An item's move takes getEntityCollisions: the entities whose
canBeCollidedWith(item) holds. AbstractMinecart overrides only
canCollideWith (for its own moves), so a cart keeps Entity's false and
an item dropped on it falls through; the item list chained the carts in.
Boats, living shulkers and a held happy ghast still hold items (#11091).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:28:51 +01:00
Horizon agentandClaude Opus 5.5 1b221b01da Native mods: native_take_screenshot takes a screenshot as F2 does
A client mod (timed or repeated screenshots, as fabrishot does) asks for the
next frame's screenshot through F2's own path: screenshots/, vanilla's file
name, the chat line. It returns the file it will write; an error without a
window, or while another screenshot waits for the frame, which it would
otherwise replace.

Part of #9606

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:27:06 +01:00
Horizon agentandClaude Opus 5.5 3815013235 Native mods: native_open_chat opens the chat with text typed in
A client mod (a command macro such as CommandKeys) can open the chat screen
with text pre-filled for the player to finish, as T and / open it. Nothing is
sent. An error with no player in a world or while another screen is open.

Part of #10120

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:23:56 +01:00
Delete228andCodex ec74bd066b Add item fallback after native mob interactions pass
Keep entity actions and API 29 ahead of item fallback, reuse normal packet and crosshair selection, and let the first response consume with or without an acting-hand swing. Cover bounded native Mob entries with a pending fragment, generated dispatch and actual Game priority/filter tests; leave other living interaction tables explicit.

Co-Authored-By: Codex <[email protected]>
2026-11-06 15:21:46 +03:00
Delete228andCodex 3de12c0c7c Allow held-item break effects to name an explicit equipment slot
Keep ordinary wear, Unbreaking, durability criteria, stack removal and break handling in one path. Existing damage_held callers retain MainHand behavior; callers that model an offhand use may select OffHand without editing stack damage directly.

Validation: native prepared-resource Game test verifies offhand wear, break event48, unchanged main-hand stack, legacy event47 and creative immunity. Strict gameplay clippy, formatting and public-tree checks pass.

Co-Authored-By: Codex <[email protected]>
2026-11-06 15:21:44 +03:00
Horizon agentandClaude Opus 5.5 c960a329ce Detect the machine's architecture in horizon-telemetry
horizon-client-render forbids unsafe code, which the Windows ARM64 build
reported (IsWow64Process2 and sysctlbyname are FFI). The detection lives with
the other system probes in horizon_telemetry::system now.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:20:50 +01:00
Riccardo Guerriero 01954d87c5 Merge remote-tracking branch 'origin/main' into fix/illager-target-memory 2026-11-06 13:19:46 +01:00
Ludovic Marie 41f822b659 Merge pull request #10301 from foudretbr/feat/screen-transparent-background-hook
🤖 Agent: foudretbr's rebase onto main (a1ff75a4a) drops the stray `);` that broke `tests/mod_api_hooks.rs`, and the file-wide import reorder. The hook change is the same 9 files: with no mod loaded, every translucent screen background is still the vanilla gradient. The 10 hook tests and tests.test_stamp_native_hooks pass.
2026-11-06 13:19:07 +01:00
Ludovic Marie d6fd737e31 Merge pull request #10382 from voidsipp/fix/10255-slow-event-note
🤖 Agent: merged main so this builds again (its old base still called `mob_to_nbt` without the UUID lookup that #10544 fixed). Burst notes are unchanged: one group per packet kind, each naming the player who spent the most time; the 7 burst and event-timing tests pass on the merged head.
2026-11-06 13:19:03 +01:00
rison1337andCodex GPT-6 52766de1c4 Skip falling-block mesh preparation behind the camera
Cache conservative envelopes of every baked model alternative, invalidating
on mutable model access. Reject rear-origin falling blocks only when their
complete padded model is outside the posed camera's depth clip planes.
Depth-only clipping preserves TAA jitter and screen-edge geometry; shader
packs keep their original path.

Include geometry/cache regression tests and separate ordinary/TAA FPS and
image comparisons, with front/empty controls and an oversized model fixture.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 17:19:01 +05:00
Ludovic Marie ce25432200 Merge pull request #11086 from horizon-rust/docs/readme-refresh
🤖 Agent: README/docs-media only; keeps the Project status block and '### Java parity' table the site and tests read, so playhorizon.dev's parity number is unaffected.
2026-11-06 13:18:59 +01:00
Delete228andCodex 15405e4ccf Route standard block fluid helpers through native-owned storage
Co-Authored-By: Codex <[email protected]>
2026-11-06 15:16:38 +03:00
Horizon agentandClaude Opus 5.5 bf4657da26 README: a cleaner page with fresh screenshots of today's game and launcher
The README's pictures were from mid-October (launcher 0.2.30, the old
menus), so this retakes them and tidies the page around them.

- Pictures: Nightly 2026-11-05 (66f3981) windowed on the Mac mini's M4
  Pro (Metal) with seed 8675309: a river valley with a village and a long
  view, a cherry-grove sunset, a crimson forest with a piglin, survival
  with the HUD, and two players over LAN; the launcher 0.2.47's Home (with
  those screenshots in its instance) and Mods pages. WebP, 712 KB in all,
  under docs/media/readme/. The five README-only images they replace are
  removed; village/nether/underwater.webp stay (the launcher embeds them).
- Text: a short pitch, what works in the current release, a download
  table with each platform's file and requirements (Windows x64 and Arm,
  macOS Apple Silicon and Intel from 12, Linux glibc), the Microsoft
  sign-in requirement, mods and shader packs (Java mods don't run;
  modpacks import), hosting, agent control, build from source,
  contributing, and Mojang's "NOT AN OFFICIAL MINECRAFT PRODUCT" line.
- Fixed claims: macOS Intel and Windows on Arm are out (not "from the next
  Nightly"); the known-issues line points to docs/known-issues.md.
- Two badges instead of seven: the parity and gap badges the tests and
  tools/parity_gaps.py keep in sync. "Project status", its gap block and
  the "### Java parity: N%" table are unchanged, so the website still
  reads its number and #build-from-source/#project-status still resolve.

Builds on #11040 by Web-mc1, who first noticed the launcher pictures were
outdated and retook them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:14:09 +01:00
Horizon agentandClaude Opus 5.5 594552fec4 Hosting: an AMP template for horizon-server, beside the Pterodactyl egg
packaging/server/amp/ holds a template for CubeCoders AMP's generic module.
Its update stage runs the egg's own install script (the release asset checked
against its SHA-256), the start runs the same horizon-start.sh, and its
settings are the egg's variables with the same defaults. Tests keep both in
step and check the console patterns against the lines the server logs.

Fixes #8238

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:13:17 +01:00
Horizon agentandClaude Opus 5.5 3c6c890993 Drop the selector criterion run-log doc
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:11:20 +01:00
Horizon agentandClaude Opus 5.5 0efe1c2c13 Player effects also leave out ambient false and show_particles true
MobEffectInstance.Details.MAP_CODEC uses optionalFieldOf("ambient", false)
and ("show_particles", true), so like amplifier 0 those defaults are not
written (the mob writer already does this). The harness gets a default
work dir, so tools/parity.py can run it with no arguments; the run-log
doc is dropped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:11:18 +01:00
Horizon agentandClaude Opus 5.5 a2090d0b0f Entity boolean harness: default work dir; drop the run-log doc
tools/parity.py runs each harness with no arguments, and this one exited
asking for --work-dir; it now uses a fresh folder under .local.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:11:18 +01:00
Horizon agentandClaude Opus 5.5 5b12729b5f Happy ghast ambient goes through Mob::voice; drop the capture harness
Mob::voice already plays at the feet (Entity.playSound), stays quiet for a
Silent entity, uses getSoundVolume and HappyGhast's pitch 1.0, so the
special arm is not needed and a Silent happy ghast no longer sounds. The
test checks the Silent case too. The 238-line capture harness ran a Java
server, a native server and a client for 1200 ticks on every ai.rs
change; the resource test covers the position.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:11:14 +01:00
Horizon agentandClaude Opus 5.5 d3d24aca55 Optimize World: cancel within a chunk, one sync per region file
- Each worker looks at the cancel flag before every chunk, not only before
  every file, so Cancel stops within a chunk on large region files. A
  canceled file is never swapped in by recreate_region_files.
- The writes of a region file reach the disk once, when the file is done,
  instead of a sync_data after every chunk. On the ThinkPad (NVMe, ext4) the
  per-chunk sync was half of a 1.21.1 world upgrade (187 s -> 89 s, one
  thread); on macOS sync_data is F_FULLFSYNC and costs far more.
- The calling thread is worker 0 and helpers are named World Upgrader #N;
  a helper that fails to start leaves its files to the others.
- Tag modifiers are no longer cloned per chunk; the recreate folder is made
  once per file.
- Tests: the parallel/one-thread comparison also checks vanilla counts
  computed from the fixture; a new test cancels from the first chunk with 4
  workers and checks at most 4 chunks were looked at and that a canceled
  recreate leaves the world unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:08:06 +01:00
Horizon agentandClaude Opus 5.5 cf20ccc4f9 rustfmt the new toolchain test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:07:27 +01:00
Horizon agent 4f7fadfd9e Merge remote-tracking branch 'origin/main' into pr-11049 2026-11-06 13:06:39 +01:00
voidsip 58a5690ec6 Add native client sound parameter interception 2026-11-06 06:05:23 -06:00
Horizon agentandClaude Opus 5.5 11d40ff1b9 Native mods: quinn 0.11 (QUIC) in Cargo.lock through horizon-mod-deps
Mods that speak QUIC (e4mc's relay) couldn't build: their dependencies must
already be in Horizon's lock. quinn joins horizon-mod-deps as an optional
dependency (feature quic: runtime-tokio + rustls-ring, so TLS stays on the
rustls + ring already in the lock). Nothing in the game builds it.

Fixes #9602

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:04:20 +01:00
Horizon agentandClaude Opus 5.5 33c431e3ae Docs: Intel Macs and Windows ARM64 have builds now
The README and the hosting guide still said Intel Macs come "from the next
Nightly" and listed no Windows ARM64 launcher; both ship in every Nightly and
launcher release.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:02:53 +01:00
Florian ERNSTandCodex d2b61ade91 Omit stale default power from saved TNT
Co-Authored-By: Codex <[email protected]>
2026-11-06 13:01:50 +01:00
Horizon agentandClaude Opus 5.5 c19d97699d Windows ARM64: the x64 build emulated there also starts on DirectX 12
Auto already tries DirectX 12 before Vulkan on Windows ARM64, where Vulkan lost
the device (#7513, #6320 on Adreno), but it read the build's architecture: the
x64 game, which the x64 launcher installs on a Snapdragon PC, still started on
Vulkan. The order now follows the machine's architecture (IsWow64Process2), and
crash reports name the emulation ("x86_64 on aarch64"), on Windows and under
Rosetta 2 on macOS (sysctl.proc_translated).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:00:51 +01:00
DetimesandClaude Opus 5.5 cdd4ffe95d Format native_bots.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:00:29 +00:00
Horizon agent f217a34593 Merge main into #10240
# Conflicts:
#	tools/port_map/client-004.tsv
2026-11-06 13:00:17 +01:00
Horizon agent f15541c0fe Merge remote-tracking branch 'origin/main' into pvp-2/sneak-input
# Conflicts:
#	crates/horizon-client/src/game/remote.rs
2026-11-06 12:59:58 +01:00
Horizon agent 965c9d9aed Merge main into #10219
# Conflicts:
#	crates/horizon-client/src/game/scoreboard.rs
2026-11-06 12:59:46 +01:00
Horizon agent 97f85f4530 Merge main into #10540 2026-11-06 12:57:33 +01:00
Horizon agent 4ce74ca81c Merge main into #10565 2026-11-06 12:56:03 +01:00
Horizon agent 2dcb671e13 Merge main into #10546 2026-11-06 12:55:54 +01:00
Horizon agent 964d919d96 Merge main into #10542 2026-11-06 12:55:19 +01:00
Horizon agentandClaude Opus 5.5 904e1d5597 Video settings: LOD Distance gets fine stops from 16 to 256 chunks
A small render distance (8 chunks on a phone or an integrated GPU) could only
add far terrain out to 64 chunks, the slider's first stop. The slider now stops
at 16, 24, 32, 48, 64, 96, 128, 192 and 256 before doubling as before.

Fixes #9694

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:55:06 +01:00
Delete228andCodex fd61fa626e Expose block clone stacks to native mods
Co-Authored-By: Codex <[email protected]>
2026-11-06 14:53:55 +03:00
voidsip db25d1d092 Match NBT path quoted escapes and index tokens 2026-11-06 05:53:36 -06:00
Ludovic Marie 398da21689 Merge pull request #10372 from Skr1zzzz/tab-shown-10108
🤖 Agent: merged #10312 (with main) in. The cache compares the whole Scoreboard twice per frame (displayed_ids and layout) and clones it on every change, so a server ticking a sidebar score every tick rebuilds and clones each frame; still cheaper than the old double sort, and tab_list parity passes on a union holding this head.
2026-11-06 12:50:14 +01:00
Ludovic Marie 297a718908 Merge pull request #10312 from Skr1zzzz/mp-fps-4664
🤖 Agent: merged main in: #10328 (merged meanwhile) now caches the layouts of the tags your collect_name_tags keeps, and #10098's new hide_gui argument is passed in your early tab_list::visible check too, so F1 still hides the list on the skip path. Skipping a tag whose entity box (inflated 0.5) is off the frustum matches vanilla, where shouldRender gates the name tag with the entity. 194 selected harnesses run on a union holding this head: tab_list and hud pass, the 8 failures are identical without it.
2026-11-06 12:50:08 +01:00
DUzzL 8a78ad3315 Apply death_protection death_effects for players and mobs, read mob hands by component 2026-11-06 12:48:18 +01:00
voidsip 82dc682cbc Parse server properties numbers like Java 2026-11-06 05:48:05 -06:00
Evrim CicekandClaude Opus 5.5 09160dc1e2 Merge upstream/main into fix/mounted-attack-box
Minecarts and primed TNT now seat their riders from the riders map;
the branch's seated box is kept on that path.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:46:14 +03:00
voidsip f1ef5cb53a Expose local particle spawning to native client mods 2026-11-06 05:43:33 -06:00
Ludovic Marie 28a389b7a3 Merge pull request #10557 from Detimes/feat-10404-older-clients-1b
🤖 Agent: shelf mushroom stands in as brown_mushroom_block (solid, not the walk-through brown mushroom), explorer maps as filled_map; 26.2 refusal from #10477 still in place
2026-11-06 12:42:26 +01:00
Horizon agentandClaude Opus 5.5 8ead30efe3 Native mods on Intel Macs: the launcher brings Rust for x86_64 macOS
The x64 macOS launcher (#1161) installed and ran the game, but native mod builds
stopped with "the launcher has none for macos x86_64": its managed toolchain
had no x86_64-apple-darwin host. Add Rust 1.99.1's official rustc, cargo and
rust-std for it, pinned by the SHA-256 static.rust-lang.org publishes; the
linker is the system's, as on Apple Silicon.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:41:15 +01:00
atsauban 052d9ebf20 fix(launcher): support responsive wrapping on narrow windows
Prevent vertical letter-by-letter text stacking and control clipping
when running in narrow tiling or compact displays:
- Top bar switches to compact tab padding and hides long subtitle
- Home actions and subtabs adaptively scroll and wrap without overflowing
- Mods and Versions headers stack title and filters vertically
- Settings navigates via top scrollable pill bar on narrow displays
- About tiles and Account cards stack cleanly
- Setting rows wrap controls instead of shrinking labels to 0 width
2026-11-06 18:40:50 +07:00
Evrim CicekandClaude Opus 5.5 d1ce5ddfd9 Merge upstream/main into fix/iron-golem-toss
Upstream's UUID bridge (#10304) now writes a mob anger target's UUID
through mob_to_nbt's uuid_of. The branch keeps its two additions on top:
a target read from the save that is not in the world keeps the loaded
UUID, and the loaded angry_at goes once the anger is over.

A mob only gets its UUID when it is first saved, so an anger target
not saved yet came out with no angry_at (wolves too). mob_tree_nbt now
gives a target that is still in the world its UUID first; the new
anger_at_a_mob_not_saved_yet_names_it test failed without that.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:37:40 +03:00
Horizon agentandClaude Opus 5.5 25ce3945c8 Roadmap: link 22 proposal issues to their items; launcher accessibility and local builds as Later items
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:37:18 +01:00
Florian ERNSTandCodex 40b1fc9100 Save default Interaction dimensions and response
Co-Authored-By: Codex <[email protected]>
2026-11-06 12:35:47 +01:00
Detimes a61d6fa54f Merge remote-tracking branch 'upstream/main' into hooks-bots 2026-11-06 11:34:12 +00:00
TaquitoSuabe 38d23bc13c gfx: skip broken Haswell Vulkan driver (hasvk) and fall back to OpenGL
- Mesa's hasvk Vulkan driver for Intel Haswell (Gen 7.5, vendor 0x8086) reports conformance version 0.0.0.0 and suffers from severe vertex buffer corruption and polygon tearing across 3D terrain (#9912).
- Check is_broken_haswell_vulkan in open_with_retry to skip non-conformant hasvk on Haswell and fall back cleanly to Mesa crocus OpenGL 4.6 Core Profile.
- Preserve Ivy Bridge (IVB) on Vulkan (#4059).
2026-11-06 04:31:50 -07:00
Delete228andCodex c4c0e6fc6b Expose dropped-item block contact to native mods
Co-Authored-By: Codex <[email protected]>
2026-11-06 14:25:58 +03:00
DUzzL 1fc4fa2529 Upgrade region files in parallel in Optimize World 2026-11-06 12:21:55 +01:00
voidsip d2d046b1be Honor mapped function keys in keyboard input 2026-11-06 05:18:42 -06:00
DetimesandClaude Opus 5.5 964454e1f8 Test that a mod's health 0 kills a guest, in and out of their turn
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:18:32 +00:00
foudretbr ae2e74af51 Native mod API: transparent screen background hook and menu blur
Fixes #10131

# Conflicts:
#	crates/horizon-client/tests/mod_api_hooks.rs
2026-11-06 12:17:57 +01:00
DUzzL 62028656e0 Save common Entity fields for registered Markers 2026-11-06 12:15:42 +01:00
VaspyyyandClaude Opus 5.5 6f2b33ff95 Launcher Settings: update status and Check now move to Updates
Horizon's update status (Up to date, Install) and the launcher's version with
Check now / Update sat under About, while the Updates section held only the
two auto-update switches. They now open Updates, in both looks: the Glass
tiles above the switches, the classic rows at the top of its Updates group.
About keeps the data folder (now a row like Logs), logs and the applications
menu.

Fixes #11035

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:14:33 +01:00
voidsip 5fd9d40d4c Validate packet resource identifiers when decoding 2026-11-06 05:14:09 -06:00
DetimesandClaude Opus 5.5 270d067afa Keep the feedback of a captured vanilla command
A capturing server source is not silent: it takes every sendSuccess and
sendFailure line and informs no operator, so run it that way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:13:31 +00:00
Detimes d0a80d6eb8 Merge remote-tracking branch 'upstream/main' into native-refresh-commands 2026-11-06 11:06:23 +00:00
DUzzL 3a0d345f06 Use the death-protection item, not the totem id, for the totem animation
The integrated server matched the used item by id (minecraft:totem_of_undying)
and used it up before the client looked for it, so any other item with
death_protection showed a plain totem in the activation animation.

Vanilla LivingEntity.checkTotemDeathProtection takes the first hand whose
stack has DataComponents.DEATH_PROTECTION, keeps a copy before shrink(1), and
uses that copy for the stat, the used_totem trigger and the animation.

Part of #10587
2026-11-06 12:01:57 +01:00
ScholiboiandClaude Opus 5.5 09ac779466 Pearls on a server's client hover in bubble columns as vanilla's do
ThrowableProjectile.tick runs on the client too: handleFirstTickBubbleColumn
before gravity, then applyEffectsFromBlocks' BubbleColumnBlock.entityInside
after the move, with Projectile's unclamped pushes (+0.06 inside, +0.1 at the
surface, -0.03 dragging down). The client's remote_projectile_tick had none of
it, so a pearl held in a stasis chamber on a server sank between the tracker's
updates and snapped back on each one.

Projectile::tick_remote adds both for ThrowableProjectiles, through a new
ProjectileWorld::bubble_column hook (bubble_column_at for the game's world).
The integrated and dedicated servers keep pushing their pearls in the
creature tick, so their projectiles are not pushed twice.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 16:28:27 +05:30
Riccardo Guerriero cd7ce3024a Merge remote-tracking branch 'origin/main' into fix/piglin-admired-drop 2026-11-06 11:58:16 +01:00
DetimesandClaude Opus 5.5 962d7c6a22 Native helpers: per-player feedback, SNBT and map helpers move out of the hook points
They sat in the hook-point section, so the catalog test took them for hooks and hook_points_stay_hookable saw world_items!; now in the helpers' impl block of the same file (targets unchanged), the macro written out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:57:17 +00:00
Riccardo Guerriero 8eb1e7cf4f Merge remote-tracking branch 'origin/main' into fix/illager-target-memory 2026-11-06 11:55:42 +01:00
4e94325ba5 Evokers and illusioners keep an unseen target for 15 seconds
Their target goals call setUnseenMemoryTicks(300): the evoker's player
and villager goals, the illusioner's player, villager and iron golem
goals. Pillagers and vindicators keep the default 60.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:55:42 +01:00
Riccardo Guerriero 225bcde8b9 Merge remote-tracking branch 'origin/main' into fix/trade-display-drop-chance 2026-11-06 11:52:39 +01:00
1dd1969640 Tests skip when the vanilla data is missing, as CI has none
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:52:39 +01:00
Horizon agentandClaude Opus 5.5 7e13949bcf 26.2 stand-ins: a solid block for the shelf mushroom, filled_map for explorer maps
The shelf mushroom is a solid shelf players stand and bounce on (ShelfMushroomBlock's shape); the brown
mushroom it stood in as has no collision, so a 26.2 client would fall through what the server stands on and
be pulled back. Its block now shows as brown_mushroom_block, whose collision holds the shelf's; the item stays
brown_mushroom. The new explorer maps are filled maps (MapItem), so they show as filled_map, which draws their
map_id, not as an empty map. v26_2_ids.rs regenerated with --substitutes. TooltipDisplay's writer no longer
allocates a list on every write.

Review follow-up for #10557 (Detimes).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:51:51 +01:00
Ludovic Marie a1ff75a4a5 Merge pull request #11021 from horizon-rust/fix/audit-issue-flood
🤖 Agent: tooling only. When more than 20 harnesses newly fail at once, the audit now opens one issue instead of 421, and the open-issue lookup no longer stops at 200.
2026-11-06 11:51:49 +01:00
Ludovic Marie bedc6fbeda Merge pull request #11012 from Web-mc1/main
🤖 Agent: tooling only. ensure_work_dir now returns the folder argparse picks, so the --work-dir=PATH spelling no longer crashes four harnesses on sys.argv.index.
2026-11-06 11:51:46 +01:00
DetimesandClaude Opus 5.5 304340fa9f native_boss_bar: check the bar's ranges without a negated call in the hook points
hook_points_stay_hookable reads `!(` as a macro.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:50:36 +00:00
DetimesandClaude Opus 5.5 830ee7c7f7 Catalog test: chunk_generate and lod_column live in horizon_world::worldgen::native_generate
The test maps each free hook to its module; the two world generation hooks are free functions in horizon-world.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:50:36 +00:00
Riccardo Guerriero ad6e268383 Merge remote-tracking branch 'origin/main' into fix/sleeping-mob-push 2026-11-06 11:49:31 +01:00
445ba1168e Tests skip when the vanilla data is missing, as CI has none
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:49:31 +01:00
Horizon agentandClaude Opus 5.5 31310a69f6 harness_args: return the work dir the run uses
Four harnesses looked the directory up with sys.argv.index('--work-dir'), which raises
ValueError for the --work-dir=PATH spelling this PR keeps. ensure_work_dir now returns the
directory argparse will pick (the last one given) and they use that.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:48:41 +01:00
Horizon agentandClaude Opus 5.5 e517343add Nightly audit: one issue when many harnesses newly fail at once
The 2026-11-06 audit tested a main commit that did not compile (mob_to_nbt arity in raids.rs) and opened
421 "Parity regression" issues, one per harness. Above 20 new failures the audit now opens one issue with
the list and the first compiler error, and keeps those harnesses' previous results so the next audit
compares them again. The open-issue lookup no longer stops at 200.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:46:50 +01:00
Riccardo Guerriero d49f1a0444 Merge remote-tracking branch 'origin/main' into fix/mob-block-fall-sound 2026-11-06 11:46:33 +01:00
d2fbe234ba Tests skip when the vanilla data is missing, as CI has none
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:46:33 +01:00
DetimesandClaude Opus 5.5 ba4d3d471f Document the player bot helpers in native-mods.md
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:44:19 +00:00
DetimesandClaude Opus 5.5 a6a93455e8 Add native_simulate_player_move and native_collision_boxes for server-side player bots
One tick of a server player's own movement from a bot's input, played on a
copy of the body with the movement prediction's replay code, and the block
collision boxes in a box (Level.getBlockCollisions). Read-only; nothing runs
unless a mod calls them. Helper fragment player_bots, docs, unit tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:44:05 +00:00
DUzzL c545bd4cb1 Keep one world border per level 2026-11-06 11:34:26 +01:00
DetimesandClaude Opus 5.5 a0bb275b62 Native mods: native_refresh_commands rebuilds the players' command trees at run time (#10812)
A Game helper (Host::refresh_commands for trait mods): at the start of the
next server tick every mod's native_register_commands is asked again,
LanHost::native_commands and the hub's mod tree are rebuilt, and every
placed player is sent their tree again after native_player_commands_sent,
as PlayerList.sendPlayerPermissionLevel / Commands.sendCommands after
/reload. Hub::store_mod_commands keeps the new tree without sending it, so
each player gets one filtered tree. Servers whose mods never call it are
unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:28:45 +00:00
SenjinandClaude Opus 5.5 d8160a3ded Shader packs: at_velocity for entities
Each entity's vertices are matched with last frame's by entity (Frame::entity_motion, now with
the GPU-posed start at each mark): last frame's pack buffer stays bound at set 1, binding 2, and
binding 3 gives each vertex its index there. at_velocity is this frame's view position less last
frame's (gbufferPreviousModelView joins the block when a pack reads it); a vertex with no match
reads zero, as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:26:13 +01:00
Detimes fce1fb23d6 Merge remote-tracking branch 'upstream/main' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game/raids.rs
2026-11-06 10:26:00 +00:00
Detimes ef66a2db57 Merge remote-tracking branch 'upstream/main' into hooks-b-all 2026-11-06 10:26:00 +00:00
DetimesandClaude Opus 5.5 4198a4de7f Tests: dispatch-test stubs for the new hook types (EntityDataValue, NativeGenerate, NativeProtoChunk, NativeLodColumn, TickSpan)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:21:12 +00:00
Detimes d1288fc34b Catalog test: log_line's target is horizon_client_lib::native_client_hooks
The test maps each free hook to its module; log_line (hooks-10461-a) lives in native_client_hooks.rs.
2026-11-06 10:18:21 +00:00
HiuoyandClaude Opus 5.5 3eda4b1b14 Native hook catalog: tooltip_render_pre is RenderTooltipEvent.Pre; Next hooks have all landed
`tooltip_render_pre` (#10302) is labelled `RenderTooltipEvent.Pre`, so
`horizon-port-pack events` counts the 10 ATM10 mods that listen to it.
The "Next hooks" table in docs/native-hooks-neoforge.md listed
`player_tick_pre` and the client tick pair, which have now landed: the
section lists them with the rest.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:16:33 +04:00
DetimesandClaude Opus 5.5 7750a91768 Tests: the respawn guest test from main fills Joined.configured (player_channels)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:14:00 +00:00
DetimesandClaude Opus 5.5 0580048c9d Clippy: then_some for the closing native store's slots (menu_input)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:14:00 +00:00
DetimesandClaude Opus 5.5 82f9fbf1ad Clippy: no explicit deref and sort_by_key in the generation hooks (chunk_generate, lod_column)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:13:59 +00:00
Web-mc1 8ccf1404e7 harnesses: keep the --work-dir a caller gives
Five command-oracle harnesses looked for a bare `--work-dir` and appended
their default anyway when the caller wrote `--work-dir=PATH`. argparse takes
the last value, so the caller's directory was silently replaced and the run
wrote its scratch output somewhere they did not ask for. They now share the
guard test_boat_leasher_parity.py already used, and tests/test_harness_args.py
pins both spellings.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
2026-11-06 10:13:20 +00:00
1de40d7cd3 A use that swings the hand resets the attack charge
ServerPlayer.swingAndResetAttackStrength: the guest's block, item, leash
knot and entity uses reset the attack ticker when their swing starts.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:12:32 +01:00
Horizon agentandClaude Opus 5.5 4fa3bf699b Merge main into #10501 (main builds again after #10544)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:09:00 +01:00
e3ca63226c Casting and reeling a fishing rod and throwing an eye of ender make the vibrations vanilla's do
- FishingRodItem.use posts cast and retrieval vibrations using the stack's use effects.
- EnderEyeItem.use posts a projectile-shoot vibration.

Part of #9776.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:07:40 +01:00
77f00c606d A villager's held-up trade item never drops when it dies
ShowTradesToPlayer sets the main hand's drop chance to 0 when it shows
an offer (displayAsHeldItem) and back to 0.085 when it clears the hand
(clearHeldItem).

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:07:25 +01:00
5751bbd65f Native hooks: PlayerInteractEvent.EntityInteractSpecific, the point a player right-clicks on an entity
Add the native hook `entity_interact_specific` (NeoForge's `PlayerInteractEvent.EntityInteractSpecific`): a right-click on an entity hears the point clicked, measured from the entity's position, before `native_interact_entity`; a claim swings the hand and ends the click. Server-side.

Co-Authored-By: DeepSeek Pro via OpenRouter <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 13:58:31 +04:00
Detimes 624ac54cdc Dispatch tests: stub horizon_client_core::text and horizon_protocol::packets::login
player_pre_login and player_profile name those paths in their hook signatures; the generated
dispatch code compiled in the test crate needs them, as the existing horizon_client stubs.
2026-11-06 09:56:23 +00:00
Horizon agentandClaude Opus 5.5 1c0fabcd7e Merge main into this branch
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:56:05 +01:00
aba1b1eca4 Interpolate whole-cell volumes four columns at a time; skip air runs at once
Part of #9996.

A chunk's noise volume is made of whole cells, and InterpolatedSampler
filled it one cell at a time: per cell eight corner loads and four Z
lerps, per column two X lerps and a bounds-checked index, then a scalar
loop of stores and additions (fill_cell alone was 6.4% of the Overworld
proto phase's instructions). For a volume aligned to its cells,
fill_whole_cells now walks the four columns of one Z row of a cell from
the bottom of the volume to the top. The X lerps at a level of corners
are computed once (they are the top of one cell and the bottom of the
next: the same lerps of the same samples), the four columns' repeated
additions run as four lanes, and four Ys of the four columns are stored
as four vectors. Each value comes from the same operations on the same
operands in the same order as vanilla's InterpolatedFunction fillCell;
only the order between columns changes. The top cell may be cut short
(the 16x114x16 volumes); a volume that is not aligned, or whose top
corners would clamp, keeps the general path.

fill_noise_inner spent about ten instructions on each air block above
the aquifer's skip height. A downward run of non-positive densities that
stays above it is now skipped in one step: nothing was written for those
blocks and note_skipped only clears a flag, so one call covers the run.

Exact: chunkgen_bench --snapshot files (terrain and decorated) are
byte-identical to main in 16 cases: the Overworld at seeds 12345, -1,
987654321 and -7, the Nether at seeds 12345 and 42, the End at seeds
12345 and 7, sizes 3 to 12. A new test compares sample_volume and the
new path with a transcription of vanilla's sampleWithBlockStep and
fillCell, on an input with NaNs, infinities, signed zeros and
subnormals, for 4x8, 8x4, 4x4 and 8x8 cells, and checks which volumes
the new path takes.

Proto-phase instructions (callgrind, 64 chunks, one thread):
Overworld 5.970 G -> 5.615 G (-5.9%), the Nether 2.868 G -> 2.853 G
(-0.5%).

A first draft by GLM-5.3 and DeepSeek V4.1 Flash, via pi, reviewed and
reworked (the air-run skip).

Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: pi coding agent (GLM-5.3) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:56:01 +01:00
Detimes bce9527c43 player_client, server_player_state fragments: mod_api.rs is not a comment path
The stamp tool fills mod_api.rs's markers itself and refuses it in "comments".
2026-11-06 09:55:22 +00:00
Detimes 402534f462 run_command_as: run_guest_command_told takes the hub with ? (clippy::question_mark) 2026-11-06 09:55:15 +00:00
Detimes fb9ce454ed Build fix: one more entity_nbt test call of mob_to_nbt gets uuid_of
The same #10304 argument as in the earlier build fix; this call sits in a test only pr_ready's test run compiles.
2026-11-06 09:55:15 +00:00
Detimes f830672067 Merge remote-tracking branch 'upstream/main' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game/raids.rs
2026-11-06 09:50:14 +00:00
Detimes 0df8f65eda Merge remote-tracking branch 'upstream/main' into perf-region-inflate-in-place
# Conflicts:
#	crates/horizon-world/src/compression.rs
2026-11-06 09:49:51 +00:00
Nikita Maximilian GrimmandGPT 6.1 Sol c667277fcd chore: merge the updated shader identity core
Preserve the launcher layer while carrying main and the replacement shader screen from the game PR.

Co-Authored-By: GPT 6.1 Sol <[email protected]>
2026-11-06 10:49:49 +01:00
GIGABAIT93andClaude Opus 5.5 10ec872cc1 docs/api/content.md: where a launcher puts content and what it checks
The install side of the Launch API, experimental: what a build loads (the
new `content`), the folder and the on/off file of each kind under
--game-dir, when a change applies, native mods needing a rebuild, what to do
with a .hpack, and the checks before installing as mod-index.md specifies
them (where it comes from, size and SHA-256, the minisign key, revoked[] and
installed copies, the manifest). Listed in docs/api/README.md. Part of #10562.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:49:14 +02:00
a2b395d91a A piglin always drops the item it admires, and one that isn't an ingot stops it despawning
Piglin.holdInOffHand equips the item with setItemSlotAndDropWhenKilled,
a guaranteed drop even without a player's kill, and makes the piglin
persistent unless the item is a gold ingot.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:46:42 +01:00
ae31791c33 A sleeping villager, fox or player isn't pushed by the mobs that walk into it
LivingEntity.push(Entity) does nothing while the entity sleeps, and
doPush reaches the other entity through it. The mob snapshot carries
sleeping (a villager in bed, Fox.isSleeping) and push_entities skips the
push. The sleeper's own tick still pushes the others.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:46:41 +01:00
Horizon agent 1fa86c919f Merge remote-tracking branch 'origin/main' into review-10557 2026-11-06 10:46:37 +01:00
makscoder b7dbeda41a A brush stops on a mob in the crosshair
BrushItem.onUseTick aims with getHitResultOnViewVector, so a mob between the
player and the block is the nearer hit and the use releases without a stroke.
tick_brush used the block-only pick and brushed through the mob.
2026-11-06 12:45:11 +03:00
GIGABAIT93andClaude Opus 5.5 2710305be4 --launch-api says what content this build loads
`content` in horizon-client's reply, from the constants the build checks
content against: content mods' manifest api lines (APIS without the removed
WebAssembly 0.1), native mods' hooks level and that they need a rebuild, the
shader schema and the line it draws, the resource and data pack formats, the
modpack format and the index schema. A launcher offers only what works. The
generated reference shows the hooks level as a placeholder, since it rises
with every hook. Part of #10562.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:44:04 +02:00
makscoder c1de12ab86 A brush or goat horn that uses itself up sends the interaction vibration
LivingEntity.stopUsingItem, reached when a use runs out or when the brush hits
no block, causes the ITEM_INTERACT_FINISH vibration. The brush and instrument
paths cleared the use directly instead, so only letting go of the button made a
sculk sensor hear them.
2026-11-06 12:42:48 +03:00
Ludovic Marie f4dcf8a2fb Merge pull request #10545 from horizon-rust/fix/revert-10416
🤖 Agent: main merged in (the old base had the mob_to_nbt test breakage, since fixed on main); still only the revert of #10416.
2026-11-06 10:42:19 +01:00
Nikita Maximilian GrimmandGPT 6.1 Sol 1295602deb fix(shaders): integrate the new settings screen
Merge main at 0873f24d2, retain its replacement shader screen, and route native selections and options through stable manifest IDs.

Co-Authored-By: GPT 6.1 Sol <[email protected]>
2026-11-06 10:40:49 +01:00
Florian ERNSTandCodex c87e5a6893 Omit the default amplifier from saved player effects
Co-Authored-By: Codex <[email protected]>
2026-11-06 10:39:48 +01:00
Ludovic Marie 0b2f2fda73 Merge pull request #10475 from Hiuoy/feat/player-tick-pre
🤖 Agent: the pre-tick hook swapped each guest's whole state into Game every tick even with no mod loaded; I pushed a guard so `as_guest` runs only when `mod_api::mods()` isn't empty. The fragment passes tests.test_stamp_native_hooks.
2026-11-06 10:39:30 +01:00
DetimesandClaude Opus 5.5 f416696038 cargo fmt: native_npcs before native_packets in game.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:38:41 +00:00
Horizon agent ef64ea9d3e Merge remote-tracking branch 'origin/main' into fix/revert-10416 2026-11-06 10:38:23 +01:00
Florian ERNSTandCodex 54cdfe00fe Load entity boolean flags through NBT byte conversion
Co-Authored-By: Codex <[email protected]>
2026-11-06 10:37:37 +01:00
Detimes 03ded99473 Merge remote-tracking branch 'upstream/main' into feat-10404-older-clients-1b 2026-11-06 09:31:26 +00:00
Ludovic Marie 0873f24d20 Merge pull request #10525 from Web-mc1/main
🤖 Agent: verify failed because bake_bricolage.py imports fontTools before it reads its arguments, and CI has no fontTools. I pushed a change that checks the arguments first, so the new usage test passes without fontTools installed.
2026-11-06 10:30:01 +01:00
Ludovic Marie 823cf263ff Merge pull request #10510 from megabytesme/fix/windows-icon-required
🤖 Agent: #10398 asked that a local build in a folder with a space still builds, so I pushed a fallback for GNU windres when the volume has no 8.3 names: outside CI it warns and builds without the icon instead of failing. With the taskbar-icon commit, `cargo check --target x86_64-pc-windows-gnu` passes on the desktop for horizon-client and horizon-client-input.
2026-11-06 10:29:57 +01:00
Hiuoy e5d96bad14 Merge remote-tracking branch 'origin/main' into feat/player-tick-pre 2026-11-06 13:29:54 +04:00
Ludovic Marie 296ac7fbf6 Merge pull request #10379 from voidsipp/fix/10102-prune-region-transactions
🤖 Agent: a failed region already restores its originals, so blocking the server start on any prune error took a server down for one corrupt chunk record. I pushed a change that starts it when no journal is left, with a supervisor test for it. Note that delete mode now writes regions one at a time with fsync, so a large prune takes longer than the old parallel rewrite. 169 horizon-server tests pass on Linux.
2026-11-06 10:29:53 +01:00
Hiuoy a6661aab7e Merge remote-tracking branch 'origin/main' into feat/9921-port-pack-events 2026-11-06 13:29:26 +04:00
Hiuoy 2c92825807 Merge remote-tracking branch 'origin/main' into feat/anvil-update 2026-11-06 13:29:23 +04:00
Riccardo Guerriero ed739050ae Merge remote-tracking branch 'origin/main' into fix/9759-golem-peaceful 2026-11-06 10:27:42 +01:00
Riccardo Guerriero 1caa6c8931 Merge remote-tracking branch 'origin/main' into perf/9996-startup 2026-11-06 10:27:23 +01:00
Riccardo Guerriero ddb365e6b4 Merge remote-tracking branch 'origin/main' into perf/9996-light 2026-11-06 10:27:23 +01:00
Ludovic Marie 64f691f9f7 Merge pull request #10544 from martipops/fix/raids-mob-to-nbt
🤖 Agent: raids.rs now passes the same `persist.uuid_of` lookup persist.rs gives `mob_to_nbt`, and the four entity_nbt.rs test calls get `|_| None`, so main builds again after #10304 (verify green on this head).
2026-11-06 10:26:17 +01:00
Ludovic Marie 0167f90c29 Merge pull request #10347 from PzmuV1517/shaders/menu-3-translations
🤖 Agent: translations only; %s placeholders kept and every file parses. The languages you flagged as uncertain (ba_ru, cv_cu, sah_sah...) stay worth a native speaker's look.
2026-11-06 10:26:13 +01:00
Ludovic Marie 28d84f2078 Merge pull request #10346 from PzmuV1517/shaders/menu-2-drop-old-pages
🤖 Agent: removes the old pack pages, their two slider kinds and four lang keys in all 87 files; nothing else read option_label_within, checked with clippy -D warnings on the stack over current main.
2026-11-06 10:26:09 +01:00
Ludovic Marie 5b16680739 Merge pull request #10337 from PzmuV1517/shaders/compat-6-paste-flat-matrix-offset
🤖 Agent: The non-constant texel offset becomes offset / textureSize(level 0) added to the coordinate: right for the base level, but at a sampled lower mip the shift is in base-level texels, not that level's (GL moves by texels of the level read). Fine for Arc's FXAA at level 0; worth a note if a pack hits it with textureLodOffset.
2026-11-06 10:26:05 +01:00
Ludovic Marie 086633c199 Merge pull request #10336 from PzmuV1517/shaders/compat-5-iris-macros
🤖 Agent: CAT_* macros come from biomes::CATEGORIES, so they pick up #10499's CAT_MOUNTAIN 17 and CAT_UNDERGROUND 18 with no second table to keep in sync; your test comparing every macro with constants() covers that.
2026-11-06 10:26:00 +01:00
Ludovic Marie fb75cde631 Merge pull request #10335 from PzmuV1517/shaders/compat-4-glsl-leniency
🤖 Agent: The constant evaluator now refuses a non-finite F32 fold with Overflow, and only GLSL function bodies turn that into a run-time expression, so WGSL input still rejects 1.0/0.0 at compile time. Vendored naga's own 133 lib tests pass with the chain.
2026-11-06 10:25:56 +01:00
Ludovic Marie 869d1aa812 Merge pull request #10334 from PzmuV1517/shaders/compat-3-integer-samplers
🤖 Agent: The integer-sampler helpers texelFetch the texel under the point with the coordinate clamped to the edge, which is what GL's nearest read of an integer texture gives; a bias argument is accepted and ignored, as GL does for these. Stack 3/6, checked with the rest of the chain on current main (clippy -D warnings, horizon-shaders and render tests).
2026-11-06 10:25:52 +01:00
2af85dcc84 A mob's damaging fall plays the fall sound of the block it lands on
LivingEntity.playBlockFallSound: the block 0.2 below the feet, at half
its volume and three quarters of its pitch, not for a silent mob or air.
Most mobs play it after their own fall sound, horses and llamas after
the damage, and a llama's damaging fall under six blocks plays it alone.

Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:24:38 +01:00
DUzzL 6285cca37c A thrown trident hurts other players on a served world (ThrownTrident.onHitEntity) 2026-11-06 10:19:18 +01:00
Horizon agentandClaude Opus 5.5 44a12076d5 Swap a guest in for player_tick_pre only when a native mod is loaded
as_guest swaps a player's whole state into Game: running it for every
player every tick with no mod to call cost the server for nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:17:18 +01:00
DetimesandClaude Opus 5.5 fd2ec97a17 Native hooks: chunk_generate and lod_column reach mods through a callback table
horizon-world cannot call horizon-client's dispatch, so the two generation hooks
call a NativeGenerateHooks table that entry.rs installs (as native_load_hooks
does for the loot hooks). mod_api re-exports NativeGenerate, NativeProtoChunk
and NativeLodColumn for the generated events. Without the table, and without
mods, both hooks do nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:16:06 +00:00
DetimesandClaude Opus 5.5 9b08e99433 Native hooks: mod_api contracts for the 16 new hook fragments, dispatch calls filled
Since #6253 the mod API generator needs a mod_api entry for every pending hook
fragment. Cancel flags get {cancel, out: cancel}, menu_input_closed collects its
slots, every other hook listens. The placeholder bodies are filled by
`cargo run -p xtask -- mod-api`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:16:06 +00:00
Skr1zzzz 079d82f968 Text displays: keep their glyphs, and skip the ones whose text is off screen
Every frame each text display built a batch, emitted its styled glyphs and
transformed every vertex, and a display of width or height 0 (what plugins
summon for holograms) is noCulling, kept to the distance rule because its
transformation can put the text anywhere: 200 of them cost ~0.3 ms a frame
with none on screen (#4664).

Each display's local-space text quads are now kept, rebuilt when the text,
its style, opacity, background, shadow, see-through, the font epoch or the
GUI atlas revision change (obfuscated text every frame), and only
transformed per frame. A noCulling display is skipped when the box of its
quads, through this frame's full matrix, misses the frustum: it can't draw a
pixel. Displays with a size keep vanilla's box test; the view range stays.
2026-11-06 12:13:04 +03:00
Detimes a93e07da89 Merge branch 'b-bundle-18' into hooks-b-all 2026-11-06 09:12:35 +00:00
Detimes 37dfe7d880 Merge branch 'b-bundle-17' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	crates/horizon-client/src/mod_api.rs
#	docs/native-mods.md
2026-11-06 09:12:34 +00:00
Detimes 58ba651c7b Merge branch 'b-bundle-16' into hooks-b-all
# Conflicts:
#	docs/native-mods.md
2026-11-06 09:12:15 +00:00
Detimes 950cd1058f Merge branch 'b-bundle-15' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 09:12:08 +00:00
Detimes 93ff841913 Merge branch 'b-bundle-14' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game/commands/world_cmds.rs
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 09:11:56 +00:00
Arslan ArmanulyandClaude Opus 5.5 6199a21cbe Merge origin/main into shaderpacks/same-size-buffer-declarations
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 14:11:26 +05:00
Detimes 4cd44b1bfa Merge branch 'b-bundle-13' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	docs/native-mods.md
2026-11-06 09:11:11 +00:00
Detimes 3dc1eac9a1 Merge branch 'b-bundle-12' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	crates/horizon-client/src/lan/hub.rs
#	docs/native-mods.md
2026-11-06 09:10:59 +00:00
Detimes 1065005412 Merge branch 'b-bundle-11' into hooks-b-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 09:10:36 +00:00
PzmuV1517 f4146eea7c Shader packs: rustfmt the pack loading call 2026-11-06 11:10:17 +02:00
Detimes 71e2f6b0f5 Merge branch 'b-bundle-10' into hooks-b-all 2026-11-06 09:10:07 +00:00
Detimes 702055471b Build fix: mob_to_nbt's uuid_of argument in raids.rs and three entity_nbt tests
#10304 added the argument while these calls were merged; upstream/main does not compile without it.
2026-11-06 09:10:01 +00:00
Hiuoy 503727b594 Merge remote-tracking branch 'origin/main' into feat/anvil-update
# Conflicts:
#	crates/horizon-client/tests/mod_api_hooks.rs
2026-11-06 13:08:39 +04:00
Hiuoy 9f4b5e0a80 Merge remote-tracking branch 'origin/main' into feat/9921-port-pack-events
# Conflicts:
#	crates/horizon-port-pack/src/lib.rs
2026-11-06 13:08:29 +04:00
Hiuoy 41eec6978b Merge remote-tracking branch 'origin/main' into feat/player-tick-pre
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
2026-11-06 13:07:52 +04:00
Detimes bf7e616ebf hooks-all: integration fixes for the merged hook branches
- NativeEffect (player_effects) and NativeInventoryKind (set_inventory) are pub and re-exported in
  mod_api: the generated mod API refused both fragments.
- native_teleport_player's cross-level teleports fill LevelTeleport's new cause (hooks-move) with
  "unknown"; player_teleport's docs name that case.
- lan_vehicles: the vehicle_moved_v2 call closure is FnMut.
- cargo fmt (game.rs mod lines in order).
2026-11-06 09:00:30 +00:00
Detimes 8887870ec3 Build fix: mob_to_nbt's uuid_of argument in raids.rs and three entity_nbt tests
#10304 added the argument while these calls were merged; upstream/main does not compile without it.
2026-11-06 08:58:30 +00:00
martipopsandClaude Opus 5.5 88cddcec82 Tests: pass mob_to_nbt its UUID lookup in entity_nbt's tests too
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:55:48 -05:00
Alysia Zappavigna bc6b668820 Format the pickable projectile tests 2026-11-06 19:54:27 +11:00
Detimes 21d7853cc9 Merge branch 'hooks-pread' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	crates/horizon-client/src/game/native_server.rs
#	docs/native-mods.md
2026-11-06 08:51:10 +00:00
Detimes b2ec69a247 Merge branch 'hooks-vehicle' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 08:51:09 +00:00
Detimes 23bb236cfc Merge branch 'hooks-pstate' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	docs/native-mods.md
2026-11-06 08:51:08 +00:00
Detimes af12d6d443 Merge branch 'hooks-login' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game.rs
#	docs/native-mods.md
2026-11-06 08:51:07 +00:00
Detimes b74f04855d Merge branch 'hooks-inv' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 08:51:06 +00:00
Detimes 010504d207 Merge branch 'hooks-move' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 08:51:06 +00:00
Detimes 24876b536b Merge branch 'hooks-cmds' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game/lan.rs
#	crates/horizon-client/src/game/native_hooks.rs
#	crates/horizon-client/src/game/native_server.rs
#	docs/native-mods.md
2026-11-06 08:51:04 +00:00
Horizon agentandClaude Opus 5.5 aad7542283 Keep the local no-alias fallback for GNU windres
#10398 asked that a local build in a folder with a space still builds: a
volume without 8.3 names gives windres no usable path, so outside CI
such a build warns and goes on without the icon, as before. CI and
every other case still fail without it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:50:46 +01:00
2a931e3e63 Peaceful drops a mob's player target and a golem's anger
On Peaceful, vanilla never lets a player be a mob's target
(`LivingEntity.canAttack` refuses a player there), so a mob that already
held one gives it up. A neutral mob also stops being angry and forgets
who hurt it (`NeutralMob.stopBeingAngry` calls `setLastHurtByMob(null)`).
Horizon dropped a player target only for creative and spectator players,
so an iron golem that had been angered kept attacking after the
difficulty was set to Peaceful.

Fixes #9759.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fekd3xkgubE8VfXrALh2td
Co-Authored-By: Codex gpt-6.1-terra <[email protected]>
2026-11-06 09:50:35 +01:00
Detimes 5699b862d6 Merge branch 'hooks-perm' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 08:49:52 +00:00
Detimes 90a7a7d47b Native mods: a compound mods keep on a player (player_persistent_data)
native_player_persistent_data(uuid) reads and
native_set_player_persistent_data(uuid, data) replaces the compound mods
keep on a player, as NeoForge's Entity.getPersistentData() and Bukkit's
PersistentDataContainer. It is saved in the player's file as
NeoForgeData, beside the keys Horizon keeps without modelling them, and
kept over a death and a level change; an empty compound takes the key
away. Nothing calls them without a mod.

Unproven: not yet compiled or probed live (build queue); the integration branch tests it.
2026-11-06 08:49:26 +00:00
Detimes 66b3059251 Merge branch 'hooks-10461-d' into hooks-all
# Conflicts:
#	docs/native-mods.md
2026-11-06 08:49:13 +00:00
Detimes 341896762f Merge branch 'hooks-10461-c' into hooks-all
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
#	docs/native-mods.md
2026-11-06 08:49:11 +00:00
Detimes 513b1fe43d Merge branch 'hooks-10461-b' into hooks-all
# Conflicts:
#	crates/horizon-client/src/entry.rs
#	crates/horizon-client/src/game/mods.rs
#	crates/horizon-client/src/game/native_hooks.rs
#	crates/horizon-client/src/menu.rs
#	crates/horizon-client/src/native_client_hooks.rs
#	docs/native-mods.md
2026-11-06 08:48:54 +00:00
Alysia Zappavigna be4ce23d67 Let projectiles strike ghast fireballs, as Projectile.isPickable does (fixes #10547) 2026-11-06 19:48:44 +11:00
Detimes b99b9e13f7 Native hooks: item_dropped_v2 can cancel a throw (unproven on a server)
native_item_dropped_v2(player, stack, cancel) is now cancellable, as
NeoForge's ItemTossEvent is: a canceled throw puts the stack back into
the player's inventory (Inventory.add, the held slot first); only what
does not fit is thrown, unheard, and native_item_dropped hears nothing.
The drop statistics and the swing still happen, as in NeoForge. Wanted
by the combat log, freeze and inventory-lock ports.

Unproven: this commit was neither compiled nor run (builds were stopped for
the integration test); `cargo run -p xtask -- mod-api` filled the dispatch.
The listen-only v2 drop hook before it was proven on a server.

Part of #10461.
2026-11-06 08:48:11 +00:00
Detimes c2ec210fea Merge branch 'hooks-10461-a' into hooks-all 2026-11-06 08:47:51 +00:00
Detimes 9edbea0a6d Native hooks: thrown projectiles at the world border (projectile_world_border)
native_projectile_world_border(dimension, entity, kind, pos, velocity,
collide) is heard each tick a thrown projectile (pearl, snowball, egg,
potion...) inside the world border would end its next step outside it,
before 26.3 stops it on the border like a wall (MC-310476). A mod may
change its position and velocity, or set `collide` false: that tick's ray
is then clipped without the border, so it flies through as before 26.3.
Arrows and tridents, which Horizon does not stop at the border, are not
heard. Static (no self), called from tick_projectiles.

Unproven: not compiled or run on a server on this branch (handed to the
hooks-all integration build).

Part of #10461.
2026-11-06 08:47:25 +00:00
Detimes c6ed7f75a0 Native hooks: farmland trampling, cancellable (trample)
native_trample(dimension, entity, kind, pos, fall_distance, cancel) is
heard when a player or mob that fell onto farmland is about to turn it to
dirt, once FarmlandBlock.fallOn's chance and checks have let it (NeoForge's
FarmlandTrampleEvent, Bukkit's PHYSICAL PlayerInteractEvent and
EntityInteractEvent). Canceling keeps the farmland and its crop; the fall
damage is taken either way. The landing rules ask through a new
BlockLandingWorld::trample_allowed (true by default), which the player's
and the mobs' landing worlds answer from a function the game sets once
(block_contact::TRAMPLE); there is no self.

Unproven: not compiled or run on a server on this branch (handed to the
hooks-all integration build).

Part of #10461.
2026-11-06 08:47:25 +00:00
Detimes 5caff4ecb4 Native hooks: where a dead player respawns (player_respawn_position)
native_player_respawn_position(player, name, dimension, pos, yaw, pitch)
is heard once per respawn, after the Respawn click and before the player
is placed (NeoForge's PlayerRespawnPositionEvent, Bukkit's
PlayerRespawnEvent.setRespawnLocation), with their respawn point: a bed
or anchor block's top, a forced point, else the world spawn. Left alone,
the point is resolved as before. A changed dimension or position places
the player exactly there, with no bed or anchor used and no free column
looked for; yaw and pitch set their look, which Hub::respawn now takes
instead of always 0, 0. An unknown dimension or a non-finite value
leaves that part unchanged.

Unproven: not compiled or run on a server on this branch (handed to the
hooks-all integration build).

Part of #10461.
2026-11-06 08:47:25 +00:00
Detimes 893eb8d636 Native hooks: a player's teleport before it reaches their client (player_teleport)
native_player_teleport(player, name, cause, from_dimension, from,
to_dimension, to, relative, cancel) is heard before a server player's
teleport is sent (Bukkit's PlayerTeleportEvent, NeoForge's
EntityTeleportEvent): commands (/tp within a level and into another),
ender pearls, chorus fruit, the spectator teleport menu, a dismount and
other server moves of the player. A mod may move `to`, cancel (the
player and their body stay where they are), or set the look and motion
bits of the position packet's relative flags, so a pearl can keep the
player's speed (0x1f8, what 26.2 sent); the position bits are ignored.

The call sites name the cause in a thread-local (lan::hub::teleport_cause)
that Hub::teleport and Hub::teleport_keeping_motion read; a teleport
without a named cause (respawns, portals, the End gateway, vehicles
carrying riders, waking up, refused moves) is not heard. Command and
spectator teleports into another level carry their cause in
LevelTeleport. The hub reaches the hook through a function the game sets
once (lan::hub::PLAYER_TELEPORT); there is no self.

Unproven: not compiled or run on a server on this branch (handed to the
hooks-all integration build).

Part of #10461.
2026-11-06 08:47:25 +00:00
Detimes 8e83a2c898 Native hooks: a player's move before it is applied (player_move)
native_player_move(player, name, from, to, yaw, pitch, on_ground,
cancel) is heard for each position or look packet that changed either
and passed the server's checks (moved too quickly, moved wrongly, the
anticheat), before Hub::position_change applies it (Bukkit's
PlayerMoveEvent). Canceling sends the player back to `from`, as a
refused move does; a changed `to` puts them there and their client gets
a teleport. Riders, sleepers and moves while a teleport waits are not
heard. The hub reaches the hook through a function the game sets once
(lan::hub::PLAYER_MOVE), as the damage hooks do; there is no self.

Unproven: not compiled or run on a server on this branch (handed to the
hooks-all integration build).

Part of #10461.
2026-11-06 08:47:25 +00:00
Detimes 0c8fb31992 Native mods: respawn a dead server player without the Respawn click (respawn_player)
The helper native_respawn_player(uuid) respawns a dead server player as
their client's PERFORM_RESPAWN does (Spigot's player.spigot().respawn()),
after the players' packets of the server tick, through the same
guest_respawn. Nothing calls it without a mod.

Unproven: not yet compiled or run on a server (builds stopped for the hooks-all integration).
2026-11-06 08:47:13 +00:00
Detimes d117673fe9 Native mods: let a server player fly in any game mode (set_flight)
The helper native_set_flight(uuid, allow, flying) lets a server player fly
in any game mode or not and sets whether they fly now, as Bukkit's
Player.setAllowFlight and setFlying: the guest keeps the mod's mayfly
(allow_flight) until their game mode changes or they respawn, their
abilities packet and the server's body carry it, and the flying check does
not kick them. Without a mod allow_flight stays false and every check is
the game mode's, as before.

Unproven: not yet compiled or run on a server (builds stopped for the hooks-all integration).
2026-11-06 08:47:13 +00:00
Detimes 2efd4b8346 Native mods: seat an entity on a vehicle, take it off, empty a vehicle (mount)
The helpers native_mount(entity, vehicle), native_dismount(entity) and
native_eject_passengers(vehicle) seat a server player or mob on a boat,
minecart or mob, take it off, or take every rider off, as /ride does with
its checks and native_entity_mount (Bukkit's addPassenger, leaveVehicle,
eject). /ride's mount checks move into ride_mount, which both use; the
command's answers are unchanged. Nothing calls the helpers without a mod.

Unproven: not yet compiled or run on a server (builds stopped for the hooks-all integration).
2026-11-06 08:47:12 +00:00
Detimes a8c67d77aa Native hooks: a ridden vehicle's move, cancellable and with its id, mounts included (vehicle_moved_v2)
native_vehicle_moved_v2(player, dimension, vehicle, kind, from, to, cancel)
is heard right after native_vehicle_moved, with the same state swapped in,
and also for a mob a server player rides. A canceled move puts the vehicle
back at from with its riders, a changed to puts it there
(native_teleport_vehicle), as Bukkit's VehicleMoveEvent with a teleport.
v1 keeps its signature, firing point and vehicles. Without mods nothing
moves the vehicle.

Unproven: not yet compiled or run on a server (builds stopped for the hooks-all integration).
2026-11-06 08:47:12 +00:00
Detimes 052443e2a5 Native mods: teleport a vehicle with its riders (teleport_vehicle)
The helper native_teleport_vehicle(dimension, id, pos) teleports a boat,
minecart or ridden mob of the level in place with its riders, as /tp does
a vehicle (Bukkit's Vehicle.teleport): the riders stay seated and a server
player riding it is told, as their client drives it. Only where nobody's
state is swapped in. Nothing calls it without a mod.

Unproven: not yet compiled or run on a server (builds stopped for the hooks-all integration).
2026-11-06 08:47:12 +00:00
Detimes 38a9866a1f Native mods: teleport a server player from any server hook (teleport_player)
The helper native_teleport_player(uuid, dimension, pos, yaw, pitch)
teleports a server player as /tp does (Bukkit's Player.teleport): at once
when they are the acting player or nobody's state is swapped in, else
after the players' packets of the server tick, so a port no longer runs
execute in .. run tp from the console, which waits for the next tick and
does nothing inside a player's hook. Into another level it is a command's
level teleport. Nothing calls it without a mod.

Unproven: not yet compiled or run on a server (builds stopped for the hooks-all integration).
2026-11-06 08:47:12 +00:00
Detimes aa8f5fc12a Native mods: send a player's command tree again (resend_commands)
The helper native_resend_commands(player) sends a player their command
tree again, with native_player_commands_sent, its v2 and the permission
nodes of the mods' commands asked anew, as Bukkit's Player.updateCommands:
after a permissions mod changed what they may use. The tree goes out in
the next server tick, with no player's state swapped in, so the helper
works from any server hook, the player's own included. Nothing calls it
without a mod.

Unproven: not yet built or run (handed to the hooks-all integration branch for the build and proof).
2026-11-06 08:47:05 +00:00
Detimes 329acc6c53 Native mods: the level one typed command runs at (player_command_level)
native_player_command_level(player, name, command, level) is heard in
Game::guest_command after native_player_command, before the server checks
the command: level comes in as the player's own and becomes the level this
one command runs at (GuestCommand.permission), so a permissions mod can
grant /gamemode to a player of level 0 or refuse a command to an operator,
as Paper's VanillaCommandWrapper does with minecraft.command nodes. A mod
command with a permission node falls back to this level. Unchanged, the
command runs at the player's level, as before.

Unproven: not yet built or run (handed to the hooks-all integration branch for the build and proof).
2026-11-06 08:47:05 +00:00
Detimes 0bd927b7c1 Native mods: a permission node on a mod's command (NativeCommand::permission)
NativeCommand::new(usage).permission(node) gives a mod's command a node, as
Bukkit's plugin.yml permission: does. The server then asks
native_has_permission(player, node, level), the command's level as the
default, both before it puts the command in a player's command tree and
before a player's typed command reaches native_perform_command, so a
permissions mod can grant it below that level or refuse it to an operator.
Without a permissions mod the level decides, as for a command without a
node; a dedicated server's console runs every registered command as before.

Unproven: not yet built or run (handed to the hooks-all integration branch for the build and proof).
2026-11-06 08:47:05 +00:00
Detimes 3cc7e3c822 Native mods: put a command the level hides back into a player's tree (player_commands_sent_v2)
native_player_commands_sent_v2(player, name, names, names_hidden) is heard
right after native_player_commands_sent, at the same times, with the root
commands the player's level hides (those a level 4 operator would get) in
names_hidden. A root a mod moves into names is sent with its subtree, as
for an operator of its level, so a command a permissions mod grants
completes and highlights in the player's chat box (Paper's
PlayerCommandSendEvent after a passed permission check). v1 keeps its
signature and firing point. Without mods nothing is granted and the tree
is unchanged.

Unproven: not yet built or run (handed to the hooks-all integration branch for the build and proof).
2026-11-06 08:47:04 +00:00
Detimes 25ac7a9841 Native mods: permission nodes, answered by a permissions mod (permission_check)
The hook native_permission_check(player, node, default_level, result) lets a
permissions mod (a LuckPerms port) say whether a player has a permission
node; the first mod to set result decides. The helper
native_has_permission(player, node, default_level) asks it, for every mod
and for the server, and falls back to the player's operator level against
default_level when no mod answers, as Bukkit's Permissible.hasPermission and
NeoForge's PermissionAPI.getPermission do. Without a permissions mod every
answer is the operator level, so nothing changes for unmodded servers.

Unproven: not yet built or run (handed to the hooks-all integration branch for the build and proof).
2026-11-06 08:47:04 +00:00
MegaBytesMe cfdca1dd6e fix: set Windows taskbar icon 2026-11-06 08:45:12 +00:00
DUzzL 379850ac5c Never build an exit portal for a spectator using a nether portal 2026-11-06 09:41:40 +01:00
Horizon agentandClaude Opus 5.5 e096a156a1 Check bake_bricolage's arguments before importing fontTools
CI has no fontTools: the import failed before main() could print the
usage, so the new test saw a traceback (verify failed).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:41:09 +01:00
DUzzL 99b969686e Stop a deflected potion at the breeze and use its own random 2026-11-06 09:41:05 +01:00
TheMrZZandCodex gpt-6.1-sol 4a20680924 Fix HappyGhast ambient sound position
Use the entity position for the special ambient caller and cover adult and baby natural events through the real Mob tick and recording route.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 09:40:23 +01:00
Horizon agentandClaude Opus 5.5 85a5195411 Revert "Natural spawning: a start's height ends at the column's first empty block" (#10416)
parity-main: natural_spawn first fails at f6a9444de (#10416), of 182 merges
since 66f3981f5. The spawn start heights it produces differ from vanilla 26.3
by up to tens of blocks (chunk/10 monster: vanilla y 99, Horizon 62), so
players would see mobs spawn at the wrong heights. Reverted until the change
passes the natural_spawn harness.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:38:57 +01:00
Detimes fe7635e545 Cargo.lock: the 12 packages of the new horizon-mod-deps entries (unbuilt)
Written by hand from a scratch resolution seeded with this lock: rusqlite,
libsqlite3-sys, fallible-iterator, fallible-streaming-iterator, hashlink,
vcpkg, yaml-rust2, arraydeque, gif, weezl, color_quant, snap. Existing
entries keep their versions; chrono gains serde and mysql_common gains chrono.
To confirm: cargo metadata --locked must not want to change it.
2026-11-06 08:38:14 +00:00
Detimes 71b36cca26 Native mods: a store menu the hub closes on its own gives the cursor back too (removed) (unbuilt) 2026-11-06 08:37:33 +00:00
DUzzL ac4045d83f Keep the worn-armor snapshot per LAN guest 2026-11-06 09:37:03 +01:00
Detimes ca623aae34 Native mods: another player's inventory in a server menu with vanilla click rules, live or on their file (native_open_inventory_menu) (unbuilt) 2026-11-06 08:35:30 +00:00
martipopsandClaude Opus 5.5 3aaa70563e Raids: pass mob_to_nbt its UUID lookup, so main compiles
#10304 gave mob_to_nbt a fifth argument after the raid observation's call to it
was written, and the two merged without a conflict.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:35:09 -05:00
Detimes 8c7807e832 Native helpers: the name over a player's head (set_name_tag) (unbuilt) 2026-11-06 08:34:22 +00:00
DUzzL 98e1e2d67f Credit trident damage_dealt_absorbed to the throwing LAN guest 2026-11-06 09:33:17 +01:00
Detimes 296d765d78 Native helpers: a player's styled name in chat (set_display_name) (unbuilt) 2026-11-06 08:33:08 +00:00
Detimes ca660e8d1f Native mods: server menus with input slots, their contents, the click's cursor and the slots on close (native_open_menu_with_input, native_menu_input_closed) (unbuilt) 2026-11-06 08:32:15 +00:00
Detimes 450b6c8548 Native hooks: leave mod_api to the stamp step; cancel flags named cancel (unbuilt)
The bundle's fragments carry no mod_api entry: session A adds them in one
step (add_mod_api.py, which takes a cancel: &mut bool parameter for the
cancel contract). So interact_entity_v2's claimed and
right_click_block_v2's canceled are named cancel; they act as before.

Not built yet.
2026-11-06 08:32:11 +00:00
Detimes 4cf75b20d9 Native helpers: a player's styled Tab list name (set_list_name) (unbuilt) 2026-11-06 08:32:07 +00:00
Detimes 873480bddc Native hooks: damage with the players' UUIDs and the attacker (living_incoming_damage_v3) (unbuilt)
native_living_incoming_damage_v3(victim, victim_uuid, kind, attacker,
attacker_uuid, direct, damage_type, amount, cancel) fires everywhere v1
fires (health::HURT_SERVER), right after it with the amount v1 left and
the same cancel flag. It adds a player victim's UUID, the causing entity
where v1 leaves 0 (a mob's attacker, a player's melee, the synchronous
attack in progress as living_knockback_v2 falls back to), a player
attacker's UUID and the direct entity. Players are noted by entity id as
each guest's turn starts (game/native_damage.rs, only while a native mod
is loaded), since the hit carries ids only and no Game.

Not done here, as the plan asked: a &mut self host (the victim is
borrowed out of the game inside Mob/Player/Dragon hurtServer, behind the
HURT_SERVER fn pointer) and Bukkit's modifier breakdown (the reductions
run after the hook in horizon-world's living_hurt and Mob::hurt_server).

Sentinel (attacker matched within 500 ms), Wanted, HuskSync and
CombatLog (an id-to-player map each tick) need the names.

Not built yet.
2026-11-06 08:31:35 +00:00
Detimes 0072925aef Native mods: kick a player with a whole text as the reason (kick) (unbuilt)
native_kick(player, reason) disconnects a player with a Text reason in its
colours and translations (Adventure's Player.kick(Component)), through
Hub::kick as /kick does, so the log line and native_player_kicked follow.
It is queued and runs at the tick's end with the mod messages
(flush_native_net), so it works from any server hook, the kicked player's
own included; today a port sends a raw Disconnect and then runs /kick with
one plain line.

Part of #10461.
2026-11-06 08:31:11 +00:00
Detimes 342a45b57a Native helpers: entities shown to one player's client alone (client_entities) (unbuilt) 2026-11-06 08:31:04 +00:00
Detimes ca9437b79f Native hooks: resource pack answers with the pack's id, configuration included (resource_pack_status_v2) (unbuilt)
native_resource_pack_status_v2(player, name, pack, status) is an additive
version of native_resource_pack_status with the answered pack's id
(Bukkit's PlayerResourcePackStatusEvent.getID), so a mod's own pack's
answers are told apart from the server's. In play it is heard just before
v1, which keeps its signature and firing point. The answers to the
server's own pack given during configuration, which the connection thread
handled alone, are kept (lan::Configured::pack_answers) and heard once the
player has logged in, right after native_player_logged_in. The connection
answers the packets exactly as before.

Part of #10461.
2026-11-06 08:30:06 +00:00
Detimes 0c10886ec0 Native mods: horizon-mod-deps adds rusqlite, yaml-rust2, gif, snap and mysql/chrono (unbuilt)
rusqlite 0.41 (bundled + cache, no default features) reads SQLite files such
as a Bukkit plugin's .db; yaml-rust2 0.13 (no default features, safe Rust)
reads and writes config.yml; gif 0.14 and snap 1 are small and pure Rust;
mysql gains chrono for DATETIME columns. All optional, nothing builds them.
Cargo.lock not yet updated.
2026-11-06 08:29:39 +00:00
Horizon agentandClaude Opus 5.5 361766f621 Merge #10312 (with main) into #10372
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:29:35 +01:00
Detimes d7c1e71d45 Native mods: each server tick's time and spans, a shared span switch and mob costs a mod samples (native_server_tick_timed, native_spans, native_mob_cost_sampling, native_take_mob_costs) (unbuilt)
tick_server_spans calls native_server_tick_timed with the pass's wall and CPU time and merged spans (TickSpan, re-exported from spans::Span) before handing them to the report. Spans are switched through tick_profile::want_spans by who wants them (HORIZON_SPIKES, horizon-report, a mod), so a mod and a report no longer turn each other's spans off. tick_profile keeps a mod's mob cost sums apart from the report's (two bits, two maps), so both can sample at once; calibration stays the report's. Fragment server_tick_timed.
2026-11-06 08:29:28 +00:00
Detimes 36323e05ae Native mods: each player's play packets once a tick (native_server_packets, native_count_packets) (unbuilt)
While a mod turns counting on, the network threads keep each framed and read play packet's id and body size in the connection's Outbox (io_pool Conn::frame and Conn::read, behind one relaxed load of a static switch, capped at 65536 each way); the server tick's end hands them to native_server_packets per player and way. Off (the default) nothing is kept and the hook never fires. Fragment server_packets.
2026-11-06 08:29:28 +00:00
Detimes 8abf5298fc Native mods: hurt a mob or a player from an attacker (native_hurt_entity) (unbuilt)
native_hurt_entity(dimension, target, amount, damage_type, attacker)
hurts a mob or a player of the level as LivingEntity.hurtServer with
damageSources().mobAttack(attacker) does, the way /damage <target>
<amount> <type> by <attacker> goes: cmd_damage's hurt is moved unchanged
into damage_target, which both call. Returns whether it hurt; false for
another dimension, a target that is neither a mob nor a player of the
level, a missing attacker or a non-finite amount.

Sentinel's own damage (damage set, workaround damage, lightning weapon)
today runs a damage command by UUID.

Not built yet.
2026-11-06 08:29:25 +00:00
Detimes 7fbd43b6a9 Native mods: offer and take away a mod's own resource packs (resource_packs) (unbuilt)
native_send_resource_pack(player, id, url, sha1, prompt, required) sends
ClientboundResourcePackPushPacket with the mod's own pack id (Bukkit's
Player.addResourcePack), checked against the packet's limits (hash at most
40, URL at most 32767 characters); native_pop_resource_pack(player, id)
sends ClientboundResourcePackPopPacket for one pack or, with None, all
(removeResourcePack). Both go through native_send. A decline still kicks
only when the server's own pack is required, as vanilla's
handleResourcePackResponse does.

Part of #10461.
2026-11-06 08:29:23 +00:00
Detimes e5acbdde15 Native helpers: hide an entity from one player's client (hide_entity_from) (unbuilt) 2026-11-06 08:28:56 +00:00
Detimes b842df0899 Native hooks: a mob's target changing, cancellable (living_change_target) (unbuilt) 2026-11-06 08:28:56 +00:00
Detimes 7f91f765b2 Native hooks: the plugin channels a player registers, in configuration and play (player_channels) (unbuilt)
native_player_register_channels(player, name, channels, register) hears
the names a client registers (minecraft:register) or unregisters
(minecraft:unregister), NUL-separated as Bukkit's and Fabric's clients send
them; vanilla reads neither (Bukkit's PlayerRegisterChannelEvent). What a
client registered during configuration, which the connection thread
dropped, is kept (lan::Configured on Joined) and heard once right after
native_player_logged_in; a message in play is heard as it is handled, with
the player's state swapped in, and then goes on as before. The helper
native_player_channels(player) lists a guest's channels
(Player.getListeningPluginChannels). At most 128 names of at most 256
bytes are kept per client.

Part of #10461.
2026-11-06 08:28:35 +00:00
Horizon agentandClaude Opus 5.5 835f062e8e Merge main into #10312 (names.rs, scoreboard.rs)
names.rs: #10328 landed on main; its NameTagLayouts frame now wraps this
PR's frustum-culled collect_name_tags, both test sets kept.
scoreboard.rs: #10098 added the hide_gui argument to tab_list::visible;
the early visibility check here passes !self.ui.hide_gui too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:28:22 +01:00
Detimes 33e0f27a8d Native hooks: a button's, lever's or plate's signal change (block_redstone) (unbuilt)
native_block_redstone(dimension, pos, old, new) is asked, as Bukkit's
BlockRedstoneEvent, when a button is pressed (hand or arrow) or let go by
its release tick, a lever is pulled, or a pressure plate's weight check
changes its signal. A mod may change new; one equal to old keeps the
component as it is, and a held button or plate is asked again at its
next scheduled check. The seam is LevelAccess::redstone_change in
horizon-world (default: leaves the signal), which GameLevel forwards on
an authoritative level. Wire, torches, diodes, observers and the other
sources are not heard yet.

CoreSMPEssentials part B holds its power rods' component on without the
2-tick re-set that leaks a 1-tick off pulse. Test (not run):
a_level_can_keep_a_lever_as_it_is.

Not built yet.
2026-11-06 08:28:19 +00:00
Detimes ce01d5ffeb Native mods: native_set_compass_target, kept over respawns and level changes (unbuilt)
Bukkit's Player.setCompassTarget: Hub.compass_targets by UUID while the player is on; default_spawn sends the target in place of the world spawn wherever it is sent (join, respawn, level change). Without a target the packet is unchanged. Helper-only fragment compass_target.
2026-11-06 08:28:07 +00:00
Detimes 1ffc88650d Native mods: native_boss_bar, boss bars for one player (unbuilt)
Bukkit's BossBar.addPlayer/removePlayer: a NativeBossBar (mod_api) by player UUID in BossState, offered to that viewer by boss_events_for, so publish_boss_bars sends Add/Update/Remove as for the world's bars. Unmodded servers keep an empty map. Helper-only fragment boss_bar.
2026-11-06 08:28:07 +00:00
Detimes d66f3e02ea Native mods: native_title, a title and subtitle for one player (unbuilt)
Adventure's showTitle and /title: SetTitlesAnimation, SetSubtitleText, SetTitleText through native_send. Helper-only fragment title.
2026-11-06 08:28:06 +00:00
Detimes ebb19e976e Native mods: native_send_particles_to, particles for one player alone (unbuilt)
Vanilla's ServerLevel.sendParticles(player, ...) and Bukkit's Player.spawnParticle: within 32 blocks, 512 with force (overrideLimiter). Replaces the hand-built LevelParticles (dust id hard-coded) of plugin ports; same options and checks as GameLevel::native_send_particles. Helper-only fragment send_particles_to.
2026-11-06 08:28:06 +00:00
Detimes 6ad344bec9 Native mods: native_play_sound_to, a sound for one player alone (unbuilt)
Bukkit's Player.playSound for plugin ports, which sent a raw ClientboundSoundPacket through native_send. Queued in game/native_player_fx.rs and sent at the tick's end with the hub back, so it works from any server hook; helper-only fragment play_sound_to.
2026-11-06 08:27:51 +00:00
Detimes fe2ef8529c Native mods: native_mob_attack, a mob's own attack on a mob or player (unbuilt)
Sentinel's native attacks (its default) call Mob.doHurtTarget for the
body. Horizon's do_hurt_target needs the goal's world, and its player is
whichever guest the mob sees, so the port injected a goal and wrote
LanHost.mob_players to bind the body to its target. native_mob_attack
runs do_hurt_target from any server hook with the target guest's view as
the mob's player (a new lan_guest_view, the single-guest form of
lan_guest_views), then hands the hits on that player to the guest and the
rest to handle_mob_events, as a mob's tick does. Helper only: unmodded
servers never call it. Part of #10461.
2026-11-06 08:27:49 +00:00
Horizon agentandClaude Opus 5.5 1f00e33453 Group slow-tick bursts by packet kind and name the top player
The Team review (kacpherek) on #10382: groups filled in arrival order
under a 32 cap hid a late UseItemOn burst behind other players' moves;
count > 1 dropped 69 players sending one 6-9 ms use each; and one slow
event hid the rest of its group. Groups are now one per packet kind (a
bounded set) with the time of their short events only (the slow ones
are noted on their own), and each names the player who spent the most
of it: "213 x UseItemOn under 10 ms each; most from Steve: 140 of
them, 700.0 ms".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:26:36 +01:00
Detimes 21da267e5b Native mods: native_remove_entity takes projectiles and fishing bobbers (remove_projectiles) (unbuilt)
native_remove_entity(dimension, id) also removes projectiles (arrows,
tridents, snowballs, eggs, pearls, fireballs, wind charges, llama spit,
shulker bullets, firework rockets) as /kill does (kill_target), and a
server player's fishing bobber, as Entity.discard: the bobber of the
player in place, or outside a guest's turn any player's of the level
(remove_server_hook in fishing.rs). Displays, markers and interactions go
as before. Sentinel's arrow cleanup and CoreSMPEssentials part B's power
rods need it.

Not built yet.
2026-11-06 08:26:32 +00:00
Detimes 182345dc5e Native mods: mob navigation and look helpers for NPC ports (unbuilt)
Citizens' Navigator and Sentinel walk their bodies to points and
entities, stop them, ask whether they still walk and aim their heads.
Horizon offered this only to a goal (Mob::nav_move_to with the goal's
world), so ports injected a goal that read intents from a global map, or
moved bodies in straight /tp steps through walls. native_mob_navigate,
native_mob_follow, native_mob_stop, native_mob_navigating and
native_mob_look_at call the mob's own navigation and look control
(PathNavigation.moveTo, stop, isInProgress, LookControl.setLookAt) from
any server hook, for a mob of any loaded level, with the world its goals
see. Helpers only: unmodded servers never call them. Part of #10461.
2026-11-06 08:26:27 +00:00
Detimes a59c7468d1 Native mods: maps a server mod draws, kept by the world (native_new_map, native_paint_map) (unbuilt) 2026-11-06 08:26:00 +00:00
Detimes 07acadab21 Native mods: native_set_entity_rotation for mobs and mannequins (unbuilt)
native_move_entity turns only the entities Horizon keeps without
simulating them, and native_teleport_mob takes no rotation, so NPC ports
turned their bodies with execute .. rotate or tp .. facing every few
ticks. The helper is /rotate (Entity.forceSetRotation) for an entity of
any loaded level, with an optional head yaw (setYHeadRot) for a mob.
Helper only: unmodded servers never call it. Part of #10461.
2026-11-06 08:25:28 +00:00
Detimes 4855ac0767 Native hooks: the entity data one viewer is sent, changeable for them alone (entity_data_to) (unbuilt)
native_entity_data_to(viewer, entity, values) is a typed shortcut of
native_server_packet for ClientboundSetEntityDataPacket: the entries one
guest is about to get for one entity, changeable for that viewer alone, so
a mod can show one player glowing to one viewer without touching the
server's own flags (TAB's and Essentials' per-viewer glow, which resend
their own packets on a timer today). Heard just before native_server_packet
for the same packet. Switched on by native_watch_entity_data, which decodes
that packet only. EntityDataValue is horizon_protocol's entity_data::Value.

Part of #10461.
2026-11-06 08:25:27 +00:00
Detimes aad1954499 Native hooks: a server player's bobber on a block (fishing_hook_landed) (unbuilt)
native_fishing_hook_landed(player, hook, dimension, block, face) hears a
server player's flying fishing bobber meeting a block on its way, and
each tick it lies on one (on the ground as the tick began or ends: the
block under its centre, its top), as Bukkit's ProjectileHitEvent for a
FishHook. It is heard at the end of FishingHook.tick with the bobber back
in place, so a mod can remove it there. hook_block_hit keeps its result;
a sibling hook_block_hit_at also gives the block and face.

CoreSMPEssentials part B starts its power rods from the plate, button or
lever the bobber lands on; native_projectile_impact leaves bobbers out.

Not built yet.
2026-11-06 08:25:18 +00:00
Detimes e55c423833 Native mods: what each open server level holds (native_level_stats, native_level_entities, native_level_block_entities) (unbuilt)
NativeLevelStats counts a level's loaded and forced chunks, block entities and ticking ones (BlockEntities::ticking_len), and its entities (players included) and block entities by type. The entity list reuses the selector walk (level_candidates, now pub(in crate::game)) with a parked level swapped in as /execute in does; block entity types come from the block (lan::chunks::block_entity_type), which the enum's variants cannot tell apart, so this replaces the planned BlockEntity::type_id(). Fragment level_stats.
2026-11-06 08:25:07 +00:00
Detimes db26ac1679 Native mods: native_entity_by_uuid and native_merge_entity_nbt for any loaded level (unbuilt)
A stage plugin keeps its actors by UUID and moves, changes or removes them
in whatever level they are in; Horizon's entity handles reach the level in
place only, so ports ran tp/data merge entity by UUID on the console.
native_entity_by_uuid finds the entity (players included) in any loaded
level and returns its dimension, id and type; native_merge_entity_nbt is
native_entity_merge_nbt for a named loaded level. Helpers only: unmodded
servers never call them. Part of #10461.
2026-11-06 08:24:56 +00:00
Detimes 9bf6fb8323 Native mods: an item stack from SNBT (native_item_from_snbt, ItemStack::from_nbt, mod_api::parse_snbt) (unbuilt) 2026-11-06 08:24:47 +00:00
Detimes 4b1319096e Native hooks: a mod's terrain in the far terrain (lod_column) (unbuilt)
native_lod_column(dimension, column, seed, flat, column_out) is asked
for each column GeneratorWorker::lod_grid, surface_sample and
surface_column sample. A mod whose native_chunk_generate builds that
chunk answers the column's blocks (and its biome) with
column_out.replace, and the far terrain shows them, with the top, the
water over it and the spans derived as vanilla's sample derives them,
instead of vanilla's noise until the chunk generates.

Vanilla's far sample is computed first and only replaced when a mod
answers: without a mod, one empty call per column and the same columns
(#105).
2026-11-06 08:24:15 +00:00
Detimes 6c3dd1247f Native hooks: a player's fishing rod with its hand (player_fish_v2) (unbuilt)
native_player_fish_v2(player, name, state, hand, cancel) is
native_player_fish with the rod's hand: the hand the cast or reel-in uses
(Bukkit's PlayerFishEvent.getHand), and for a bite the hand holding the
rod, main hand first as FishingHook.shouldStopFishing looks. Asked right
after the unchanged v1 hook with the same cancel flag. CoreSMPEssentials
part B sees off-hand stasis and power rod casts.

Not built yet.
2026-11-06 08:24:02 +00:00
Detimes f4ccc08720 Native hooks: every play packet a player is sent, changeable and cancellable (server_packet) (unbuilt)
native_server_packet(player, packet, cancel) hears each play packet the
server is about to send one guest, decoded, a broadcast once for each
player, where PlayerSender::send hands it over (ProtocolLib's and
PacketEvents' outgoing listeners; NeoForge and Bukkit have no event).
A changed packet is encoded again, a canceled one is not sent. Nothing is
decoded or copied until a mod calls the helper native_watch_server_packets:
until then each packet costs one relaxed atomic load. The hook is a free
function the game sets once (lan::conn::SERVER_PACKET), as player_move is.

Part of #10461.
2026-11-06 08:23:51 +00:00
Detimes 91a7c31b12 Native mods: native_spawn_mob and native_summon reach any loaded level (unbuilt)
Both helpers were refused for any level but the one in place, so a mod
spawning from native_server_tick (where no level is defined) or into
the Nether from the Overworld's tick could not, and Stage-style ports ran
`execute in <dimension> run summon` instead. The named level is now put
in place for the call and put back afterwards, through the same
enter_command_level that `execute in` uses (npc_in_level in the new
native_npcs.rs). A level not loaded is still refused. Unmodded servers
never call these helpers, so nothing changes for them. Part of #10461.
2026-11-06 08:23:46 +00:00
Kacper d3f799f5d5 Merge pull request #10368 from TheMrZZ/fix/h331-arrow-hit-category
Play ordinary arrow hits in the neutral sound category
2026-11-06 09:23:40 +01:00
Detimes 14e39641c2 Native hooks: a right click on a block with the hand (right_click_block_v2) (unbuilt)
native_right_click_block_v2(pos, face, hand, canceled) is
native_right_click_block with the hand that clicks (NeoForge's
PlayerInteractEvent.RightClickBlock.getHand), asked right after the
unchanged v1 hook with the same flag, once per hand tried. A cancel does
what a v1 cancel does. CoreWG's guard refuses an off-hand flint and steel
or bone meal; today it sees the main hand only.

Not built yet.
2026-11-06 08:23:26 +00:00
Horizon agentandClaude Opus 5.5 0a1a3c6ffe Merge #10502 into #10501 (both edit pack_post.rs)
Keeps both test modules (mixed_size_tests, undrawn_terrain_tests) and
has_terrain with #10502's doc and undrawn flag after sizes_differ.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:23:24 +01:00
Detimes ff07b57b27 Native mods: the server's tick times and ticks a second (native_tick_times, native_tps) (unbuilt)
native_tick_times() returns the last 100 tick durations at vanilla's getTickTimesNanos indices (tick n at n % 100); native_tps() the ticks a second over one, five and fifteen minutes (Bukkit's getTPS), measured between recorded tick ends in FixedStep::record. Helpers only, fragment tick_times.
2026-11-06 08:22:55 +00:00
Detimes 9553a05bb8 Native hooks: a right click on an entity with the hand (interact_entity_v2) (unbuilt)
native_interact_entity_v2(entity, kind, hand, stack, claimed) is
native_interact_entity with the hand that clicks (NeoForge's
PlayerInteractEvent.EntityInteract.getHand), asked right after the
unchanged v1 hook with the same claimed flag: a click v1 claimed is not
asked again. Once per hand vanilla tries, so a mod claiming the main hand
hears one event per click (Citizens' click events and commandtrait
hands, CoreWG, CoreSMPEssentials part B).

Not built yet.
2026-11-06 08:22:47 +00:00
Detimes 6847ec0b7b Native hooks: a chunk's terrain from a mod (chunk_generate) (unbuilt)
native_chunk_generate(dimension, chunk, seed, flat, chunk_out,
generate) is asked on the generation workers for every chunk, after its
biomes and structure starts and before the noise (Bukkit's
ChunkGenerator.generateNoise). A mod sets the chunk's blocks through
NativeProtoChunk::set_block and answers NativeGenerate::Replaced with
the shouldGenerate* flags: vanilla then runs only the surface rule, the
carvers (on a superflat level too), the structures and the features it
asks for. set_biomes and clear_structures work with either answer.

Left at Vanilla, nothing changes: the hook is one empty call per chunk
and every vanilla step runs as before. Replaces the cancellable HEAD on
VanillaTerrain::proto that generated the vanilla chunk and overwrote it,
and the coregen_glue module (#104).
2026-11-06 08:22:18 +00:00
Kacper 918767ac08 Merge pull request #10286 from Ragbie/fix/dragon-damage-command
/damage hurts the Ender Dragon and End crystals as vanilla does
2026-11-06 09:21:37 +01:00
Kacper 089a7c1242 Merge pull request #10259 from Ragbie/fix/port-pack-old-layout
Port pack: a mod made for 1.20 is not carried or counted as come over
2026-11-06 09:20:28 +01:00
Kacper 6cfb512e1e Merge pull request #10311 from rison1337/perf/cull-offscreen-blob-shadows
Skip block scans for off-screen entity shadows
2026-11-06 09:20:12 +01:00
Kacper 6f6042da38 Merge pull request #10474 from martipops/fix/pad-hid-layout
Controllers: read Bluetooth Xbox pads from their layout, on any firmware
2026-11-06 09:20:06 +01:00
Kacper 345a66c710 Merge pull request #10014 from Hiuoy/feat/client-logging-hooks
Native hooks: ClientPlayerNetworkEvent.LoggingIn / .LoggingOut (client_player_network_logging_in, client_disconnecting's label)
2026-11-06 09:19:55 +01:00
Kacper 2cc12be695 Merge pull request #10295 from foudretbr/fix/launcher-native-cache-busy
launcher: retry native cache fixture exec on ETXTBSY and copy via temp rename
2026-11-06 09:19:49 +01:00
Kacper 8126b95fb6 Merge pull request #10264 from ilguerro/perf/9996-aquifer
Aquifers keep their 12 nearby cells unpacked and reuse 8 of them a cell down
2026-11-06 09:19:43 +01:00
Kacper a36ce4ef76 Merge pull request #10495 from voidsipp/fix/boat-second-passenger-authority
Use the first passenger for remote boat control
2026-11-06 09:19:36 +01:00
Kacper d53d7bf02a Merge pull request #10087 from yahya-nusseibeh/fix/port-pack-capped-reads
port-pack: cap every jar and pack entry it inflates (launcher OOM from a mod search)
2026-11-06 09:19:27 +01:00
Kacper e42f12db85 Merge pull request #10232 from KBroneZ/claude/fix-10162-pack-templates
Read structure templates from data packs
2026-11-06 09:19:18 +01:00
Detimes a58cb67f76 Native world seed read helper (native_world_seed) (unbuilt)
The world's seed as /seed prints it (Bukkit's World.getSeed), for a
generator mod's preview that must draw the terrain the chunks get.
None off a server, where only a hash of the seed is known. No
behaviour change. Part of the world generation bundle (#98).
2026-11-06 08:19:15 +00:00
Kacper 988f7d1ade Merge pull request #10362 from CocofireHD/fix/10273-reoccuring-freezes-on-diamondfire-server-mcd
Multiplayer: a guest server's resent columns keep the packet's light
2026-11-06 09:19:12 +01:00
Kacper 8e3cade985 Merge pull request #10307 from Skr1zzzz/fix-9190
Friends: Cancel and Escape go back to the screen that opened it
2026-11-06 09:19:07 +01:00
Kacper 8ca4e1d985 Merge pull request #9975 from Hiuoy/feat/client-tick-hooks
Native hooks: ClientTickEvent.Pre / .Post (client_tick_pre, client_tick_post)
2026-11-06 09:19:01 +01:00
Kacper b5c12d0f76 Merge pull request #10185 from degepie/fix/pack-lang-lenient-merge
Resource packs: read a lang file leniently when merging the layers
2026-11-06 09:18:54 +01:00
Kacper 2251cfb716 Merge pull request #10209 from ilguerro/perf/9997-climate-search
Climate searches pass distances along and stop a box's sum once it is too far
2026-11-06 09:18:49 +01:00
Kacper e5b2d2ea71 Merge pull request #10320 from TheMrZZ/fix/squid-damage-result
Preserve inherited squid damage acceptance result
2026-11-06 09:18:42 +01:00
Kacper c1a7b0d35e Merge pull request #10484 from voidsipp/fix/10445-leashed-happy-ghast
Match Happy Ghast leash movement and distances
2026-11-06 09:18:30 +01:00
Kacper b502fe41ac Merge pull request #9973 from MSedgeMC/fix/trample-hook
Landing: a trample is cancellable, so a claims mod can stop it
2026-11-06 09:18:24 +01:00
Kacper a8c5f833e4 Merge pull request #9924 from Hiuoy/feat/9921-hook-neoforge-labels
Native hook catalog: NeoForge labels for 39 hooks
2026-11-06 09:18:18 +01:00
Kacper 8ca4e05005 Merge pull request #10333 from PzmuV1517/shaders/compat-2-constant-array-crash
Shader packs: a global constant reads an element of a constant array (Ebin no longer crashes)
2026-11-06 09:18:08 +01:00
Kacper adcc59b920 Merge pull request #10332 from PzmuV1517/shaders/compat-1-real-packs-summary
Shader packs: real_packs keeps going past a panicking pack and sums up each pack
2026-11-06 09:18:02 +01:00
Kacper f42833e146 Merge pull request #10489 from TheMrZZ/fix/noai-dolphin-air-reset
Restore NoAI Dolphin air after its superclass tick
2026-11-06 09:17:55 +01:00
Kacper 1bd640668a Merge pull request #10304 from MSedgeMC/fix/uuid-bridge
Entity saves: a UUID bridge for references to a mob
2026-11-06 09:17:49 +01:00
Kacper cc5f7ff239 Merge pull request #10235 from MSedgeMC/fix/armor-stand-light-probe
Armour stands: probe the marker's own box and every other stand's eye, at 15 while burning
2026-11-06 09:17:38 +01:00
Kacper f7a5a3809f Merge pull request #10200 from Hiuoy/feat/break-speed
Native hooks: PlayerEvent.BreakSpeed, how fast a player digs a block
2026-11-06 09:17:32 +01:00
Kacper 1faeccc439 Merge pull request #10293 from IanWorley/fix/tests-on-apple-silicon
Tests: the drop-motion and install.sh signature tests pass on Apple silicon
2026-11-06 09:17:20 +01:00
Kacper 57512281ba Merge pull request #10266 from TheMrZZ/fix/ominous-warning-sound-source
Use neutral source for ominous item warning
2026-11-06 09:17:10 +01:00
Kacper ebafaf775a Merge pull request #10478 from TheMrZZ/fix/spellcaster-spell-ticks
Bind Evoker and Illusioner NBT to their live spell counter
2026-11-06 09:17:04 +01:00
Kacper 2f890f7df9 Merge pull request #10524 from TheMrZZ/fix/mob-local-sounds
Keep local mob sounds out of shared server capture
2026-11-06 09:16:57 +01:00
Kacper 62a7855892 Merge pull request #10499 from arslanarm/shaderpacks/lush-caves-biome-category
Shader packs: biome_category matches Iris's readings, fitted to vanilla's biome tags
2026-11-06 09:16:46 +01:00
Kacper cc3714e6ad Merge pull request #10358 from CocofireHD/fix/8713-no-zoom-menu-in-extras-on-mac
Extras: a Zoom row that reaches the mod that replaced the extra
2026-11-06 09:16:40 +01:00
Kacper d08bf63777 Merge pull request #10127 from CocofireHD/fix/9273-stuck-button-highlight
UI: a button's highlight goes when its screen leaves
2026-11-06 09:16:34 +01:00
Kacper 92dbf8406e Merge pull request #10134 from Hiuoy/feat/9921-more-labels
Native hook catalog: NeoForge labels for five more hooks
2026-11-06 09:16:28 +01:00
Kacper 77ac54a216 Merge pull request #10000 from TheMrZZ/fix/raid-live-village-pois
Validate loaded bed claims for raid villages
2026-11-06 09:16:21 +01:00
Kacper 8815f97746 Merge pull request #10141 from ilguerro/perf/9996-perlin
Perlin volumes skip the Ys a lerp does not read, and batch Ys across cells
2026-11-06 09:16:15 +01:00
Kacper 7e74759055 Merge pull request #10309 from Skr1zzzz/fix-9570
Shader packs: material maps share the stitched colour atlas's layout
2026-11-06 09:16:08 +01:00
Kacper 6c0d2781e6 Merge pull request #10297 from CocofireHD/fix/10037-directional-audio-bug-3d-positional-sound-an
Audio: play multi-channel buffers direct like OpenAL
2026-11-06 09:15:57 +01:00
Kacper 45a678e309 Merge pull request #10221 from MSedgeMC/fix/item-light-probe
Dropped items: light the block at the item's eye, and 15 for one on fire
2026-11-06 09:15:51 +01:00
Kacper efaa2a0e3f Merge pull request #10238 from MSedgeMC/fix/entity-light-probe-frame
Mobs and projectiles: light the block at the eye of the frame, not of the tick
2026-11-06 09:15:46 +01:00
Kacper ed5ac7df1f Merge pull request #10483 from voidsipp/fix/10468-finite-native-options
Keep native shader float options finite
2026-11-06 09:15:39 +01:00
Kacper 3e92f5e6fd Merge pull request #10076 from yahya-nusseibeh/fix/launcher-rustls
Launcher: rustls 0.23.46 for four RustSec advisories
2026-11-06 09:15:33 +01:00
Kacper a0e3c383a2 Merge pull request #10012 from some-du6e/optifine/color-properties
OptiFine custom colours: read optifine/color.properties
2026-11-06 09:15:26 +01:00
Kacper 1c9379c29e Merge pull request #10247 from TheMrZZ/fix/noai-glow-squid-countdown
Keep glow squid dark countdown running with NoAI
2026-11-06 09:15:20 +01:00
Kacper 46e88c47b1 Merge pull request #10075 from Ragbie/fix/nautilus-sight-armadillo-panic
A nautilus only goes for a pufferfish it sees, and frost panics armadillos
2026-11-06 09:15:15 +01:00
Kacper 8671d460bf Merge pull request #10480 from voidsipp/fix/snbt-unicode-key-order
Keep UTF-16 key order in compound SNBT
2026-11-06 09:15:09 +01:00
Kacper 9f282e42ea Merge pull request #9992 from UMCEKO/fix/tnt-owner-saved
Primed TNT saves and loads its owner
2026-11-06 09:15:03 +01:00
Kacper 7b0e4fee81 Merge pull request #10310 from xxaxio/fix/launcher-account-skin-overflow
launcher: fix account skin arm-type segmented control overflow and caption layout
2026-11-06 09:14:57 +01:00
Kacper c76e42d88d Merge pull request #10066 from Hiuoy/feat/render-level-stage
Native hook catalog: RenderLevelStageEvent labels for the world drawing hooks
2026-11-06 09:14:51 +01:00
Kacper fe8b0c622f Merge pull request #10328 from rison1337/perf/cache-name-tag-layout
Cache unchanged entity name-tag glyph layouts
2026-11-06 09:14:45 +01:00
Kacper c8137fbec9 Merge pull request #10302 from foudretbr/feat/tooltip-render-surface
Native mod API: item tooltip render surface and container item drawing
2026-11-06 09:14:33 +01:00
Kacper a4d6ba2781 Merge pull request #10062 from MSedgeMC/feat/scissor-intersect
A nested GUI scissor clips to the intersection and restores the outer one
2026-11-06 09:14:27 +01:00
Kacper 84cc6b5d64 Merge pull request #10089 from yahya-nusseibeh/fix/telemetry-store-off-workers
Telemetry server: hub, intake and export off the async workers too
2026-11-06 09:14:21 +01:00
Kacper 5258de5549 Merge pull request #10038 from BnCatt/older-servers/1.21.11-either-holders
Older servers: read 1.21.11's EitherHolder item components
2026-11-06 09:14:15 +01:00
Kacper c939630a79 Merge pull request #10135 from TaquitoSuabe/perf/gl-occlusion-culling
gfx: enable early depth occlusion culling on OpenGL
2026-11-06 09:14:09 +01:00
Kacper 932d351e11 Merge pull request #10306 from TheMrZZ/fix/lodestone-tracker-default
Omit the saved lodestone tracker true default
2026-11-06 09:11:39 +01:00
M0-kiiandCodex bd0c8cb4fd Preserve changed filenames and rename sources in tooling checks
Read Git's NUL-delimited UTF-8 output without newline conversion, and
include deleted source paths when files move between check scopes.
Keep pr_ready's line accounting consistent with parity selection.

Fixes #10535

Co-Authored-By: Codex (GPT-6) <[email protected]>
2026-11-06 11:41:32 +03:30
Kacper 8504efabb8 Merge pull request #10001 from Ragbie/fix/enderman-teleport-danger
Endermen teleport only where vanilla lets them
2026-11-06 09:11:31 +01:00
Kacper 63db515796 Merge pull request #10502 from arslanarm/shaderpacks/dimension-folder-gbuffers
Shader packs: terrain without a program in the pack is drawn by nothing
2026-11-06 09:11:25 +01:00
Kacper d629a4f6f6 Merge pull request #10497 from arslanarm/shaderpacks/terrain-block-id-mid
Shader packs: terrain's mc_Entity.y reads 0 off fluids, as in Iris
2026-11-06 09:11:18 +01:00
Kacper 77f2a09212 Merge pull request #10341 from Skr1zzzz/prepare-spans
Spans: the frame's prepare steps
2026-11-06 09:11:12 +01:00
Kacper aa1a7c0c42 Merge pull request #10156 from Hiuoy/feat/10132-living-damage
Native hooks: LivingDamageEvent.Pre and .Post, the damage a living entity's health takes
2026-11-06 09:11:06 +01:00
Kacper 36e6c2dc3f Merge pull request #10231 from TheMrZZ/fix/selector-empty-tags
Correct empty tag selector predicates
2026-11-06 09:10:58 +01:00
Kacper aca06a277b Merge pull request #10282 from TheMrZZ/fix/item-frame-missing-facing
Default loaded item frames to DOWN when Facing is absent
2026-11-06 09:10:52 +01:00
Kacper fedc1c7313 Merge pull request #10175 from vnsmoreira/fix-translucent-color-feedback
Fix shader-pack color-buffer reads in translucent draws
2026-11-06 09:10:41 +01:00
Kacper df6139520e Merge pull request #10034 from Ragbie/fix/mob-spear-reach
A mob's melee reach follows the spear it holds
2026-11-06 09:10:35 +01:00
Kacper 1a57f3660d Merge pull request #10070 from yahya-nusseibeh/fix/launcher-no-remove-var
Launcher: open HORIZON_LAUNCHER_PROJECT once without remove_var
2026-11-06 09:10:29 +01:00
Kacper 1e030fd397 Merge pull request #10348 from TheMrZZ/fix/guest-respawn-world-point
Keep guest respawn world information separate from personal landing
2026-11-06 09:10:23 +01:00
Kacper 4c1c81539f Merge pull request #10360 from TheMrZZ/fix/ageable-integer-load
Load animal age and love with NumericTag integer conversion
2026-11-06 09:10:17 +01:00
Kacper bf6bb5d470 Merge pull request #10343 from rison1337/perf/cull-offscreen-entity-flames
Skip offscreen burning-entity flame geometry
2026-11-06 09:10:11 +01:00
Kacper f4cc8528ae Merge pull request #10508 from Skr1zzzz/head-skin-cache
Player heads: parse a profile once, not on every draw
2026-11-06 09:10:04 +01:00
Kacper c12ad4d2cf Merge pull request #10207 from MSedgeMC/fix/pack-update-keeps-mod-choice
launcher: a pack update keeps the player's mod choices
2026-11-06 09:09:52 +01:00
Kacper b8dccdaa55 Merge pull request #10376 from TheMrZZ/fix/hive-default-saved-patch
Omit default hive block state from saved item patches
2026-11-06 09:09:45 +01:00
Kacper 50ac3abcda Merge pull request #10202 from TheMrZZ/fix/furnace-output-components
Furnaces: require output component identity before cooking
2026-11-06 09:09:38 +01:00
Kacper f4eb3234a8 Merge pull request #10496 from voidsipp/fix/10465-server-address-ports
Match vanilla server-address port syntax
2026-11-06 09:09:30 +01:00
Horizon agentandClaude Opus 5.5 3728c4b7d1 Play every arrow and trident tick sound on NEUTRAL
The crossbow hit, trident hit and trident hit_ground sounds come from the same
Entity.playSound as the arrow hit (AbstractArrow.onHitBlock / onHitEntity,
ThrownTrident.onHitEntity), so they take getSoundSource(), NEUTRAL, too; the
name-based guess put them on PLAYERS.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:03:29 +01:00
Detimes 304276d8dc Region chunks: inflate zlib straight into the chunk's buffer
A saved chunk's zlib stream was inflated by a new Decompress, 8 KiB at a
time into a stack block, each block then copied onto the output, which
grew by doubling from empty. Now each thread keeps one inflater, reset
for every chunk, and a whole stream that fits is inflated straight into
the output, reserved at about eight times the stream (a chunk's usual
ratio) and never past one byte over the limit. Any stream that fails
(damaged, cut short, over the limit) is decoded again the old way, so the
error and what the output keeps are exactly as before. A new test
compares both on whole, cut and damaged streams around every limit.
2026-11-06 08:03:25 +00:00
Horizon agent 899d87e22a Merge remote-tracking branch 'origin/main' into w10368 2026-11-06 09:03:00 +01:00
Horizon agent 1fd348869c Merge branch 'w10339' into w10286
# Conflicts:
#	crates/horizon-client/src/game/dragon.rs
2026-11-06 09:02:54 +01:00
Horizon agentandClaude Opus 5.5 ec6b1817a5 Start the server after a prune that rolled itself back
A failed region restores its originals before the error comes back, so
the world is whole: blocking the start then took a server down for a
corrupt chunk record. Only a recovery that cannot finish stops it now.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:02:36 +01:00
Horizon agentandClaude Opus 5.5 07c3ef02d0 Dragon scanning turn: vanilla degree conversion in double
DragonSittingScanningPhase.doServerTick computes Math.acos(dot) * 180.0F / (float) Math.PI
and Mth.atan2(..) * 180.0F / (float) Math.PI in double, not a multiply by the float
RAD_TO_DEG constant. Also moves the Silent test doc comment back onto its test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:01:37 +01:00
Kacper a02fdabe6f Merge pull request #10061 from MSedgeMC/fix/region-file-compression
Region files are written with the compression server.properties names
2026-11-06 09:00:51 +01:00
Kacper d16b78e715 Merge pull request #10022 from DUzzL/fix/9964-burning-powder-snow
Burning players and mobs destroy powder snow they stand in
2026-11-06 09:00:43 +01:00
Kacper e5cc020034 Merge pull request #10272 from MSedgeMC/fix/item-interact-start
Starting an item use posts item_interact_start
2026-11-06 09:00:36 +01:00
Kacper 799fcccba4 Merge pull request #10279 from MSedgeMC/fix/jukebox-play-vibration
A playing jukebox posts jukebox_play every twenty ticks
2026-11-06 09:00:28 +01:00
Kacper 54feca80d1 Merge pull request #10246 from MSedgeMC/fix/disable-mob-generation
The End's no-creatures rule comes from the data, not the dimension
2026-11-06 09:00:22 +01:00
Kacper c1f8fee513 Merge pull request #10226 from MSedgeMC/fix/creative-search-focus
The creative search box loses the focus with its tab
2026-11-06 09:00:15 +01:00
Kacper 8ca476e489 Merge pull request #10172 from MSedgeMC/feat/creative-chat-key
The chat key opens the creative inventory's search tab
2026-11-06 09:00:07 +01:00
Kacper efabb913d0 Merge pull request #10098 from MSedgeMC/fix/f1-tab-list
F1 takes the player list away
2026-11-06 08:59:58 +01:00
Kacper bed239a2cd Merge pull request #10154 from Vaspyyy/fix/stonecutter-take-sound-once
Play a station's take sound once per shift-click
2026-11-06 08:59:51 +01:00
Kacper 63ddd76a15 Merge pull request #10100 from yahya-nusseibeh/fix/nbt-heap-quota
Network NBT: no more tags than vanilla's quota admits
2026-11-06 08:59:44 +01:00
Kacper 208f1c010f Merge pull request #10101 from DUzzL/fix/10051-frame-item-count
Keep one item in item frames filled by /item or NBT
2026-11-06 08:59:35 +01:00
Kacper 935e3927ab Merge pull request #10004 from DUzzL/fix/9960-allay-aim-eyes
Allay: aim deliveries at the player's eyes
2026-11-06 08:59:28 +01:00
Kacper 412594e68d Merge pull request #10016 from lightcodegit/fix/loot-guest-and-double-chest
Loot: guests roll a container's table on open; double chests roll both halves
2026-11-06 08:59:21 +01:00
Kacper 63d25cbd63 Merge pull request #10155 from lightcodegit/fix/push-entities-up
Snow, dripstone clay and spreading sculk lift what stands on them
2026-11-06 08:58:57 +01:00
Kacper 43aba8dbe5 Merge pull request #10126 from lightcodegit/fix/spectator-cant-open
A refused spectator opening gets the red action bar
2026-11-06 08:58:50 +01:00
Kacper 9ee9bb4d0e Merge pull request #10105 from DUzzL/fix/10041-adventure-can-place-on
Adventure mode: honor can_place_on for buckets and flint and steel
2026-11-06 08:58:42 +01:00
Kacper 4d5f0a965f Merge pull request #10010 from DUzzL/fix/9976-scaffolding-projectiles
Fix projectiles hitting the underside of scaffolding
2026-11-06 08:58:35 +01:00
Kacper 585e28d1fb Merge pull request #10186 from degepie/fix/enter-block-trigger
Fire the enter_block trigger from the server's block pass
2026-11-06 08:58:28 +01:00
Kacper 966a63278e Merge pull request #10094 from DUzzL/fix/10045-enderman-anger-harming
Splash of harming makes endermen teleport instead of hurting them
2026-11-06 08:58:21 +01:00
PzmuV1517 aff26bf9f9 Shader packs: a pack builds on a thread of its own while the game draws
Loading a shaders/ pack made its pipelines on the main thread, and the driver
compiles each program there: the game froze for 5 to 50 s on a big pack
(Complementary Reimagined, Photon), with no frame drawn.

The renderer now builds the pack on a "Shader pack" thread (load_shader_pack)
with its own handles to the device and the inputs, and the game keeps drawing
without a pack meanwhile; each frame poll_shader_pack checks the thread and
runs the pack once it is built, logging its programs and notes as before. A
pack the thread refuses or panics on is turned off as before. Picking another
pack, or none, drops one that is still building.
2026-11-06 09:58:20 +02:00
Kacper cf8a5a51b4 Merge pull request #10097 from DUzzL/fix/10052-hotbar-q-same-tick
Send the carried slot before a drop
2026-11-06 08:58:13 +01:00
Detimes 3dd74dcc64 Native read-only chunk snapshots (native_chunk_snapshot)
A NativeChunkSnapshot of a loaded chunk of an open level (block states,
WORLD_SURFACE heights, noise biomes, the fuzzy biome zoom's cell) that
shares the chunk's sections copy-on-write and can be read on another
thread, with native_block_map_color, native_biome_name and
native_biome_tint to draw it. Replaces the engine internals a web map
reads today. No behaviour change. Part of #10461.
2026-11-06 07:57:06 +00:00
Kacper 22462182e3 Merge pull request #10494 from voidsipp/fix/10466-final-live-config-values
Apply only final config values to the running server
2026-11-06 08:56:41 +01:00
Kacper c8c4a29367 Merge pull request #10363 from CocofireHD/fix/8668-container-to-hopper-minecart-transfer-speed
Pin the hopper minecart's one-item-a-tick transfer (#8668)
2026-11-06 08:56:34 +01:00
Horizon agent 0c47273286 Merge remote-tracking branch 'origin/main' into w10339 2026-11-06 08:56:14 +01:00
Kacper f24cc569db Merge pull request #10515 from Alysia42/tnt-first-tick
Tick TNT that joined during a tick in that tick's late pass
2026-11-06 08:56:09 +01:00
Kacper c3140e1bae Merge pull request #10192 from KBroneZ/claude/fix-10160-state-provider
upgrade-datapack: a simple state provider becomes a state object
2026-11-06 08:56:03 +01:00
Kacper 10b0942e4f Merge pull request #10191 from KBroneZ/claude/fix-10159-empty-noise
upgrade-datapack: a noise with no octaves becomes a silent 26.3 noise
2026-11-06 08:55:56 +01:00
Kacper 81aeeacf55 Merge pull request #10522 from kacpherek/perf/smp-server
Server: boats and minecarts stop walking every mob, and a Perfetto trace of every thread
2026-11-06 08:54:22 +01:00
Kacper a0261a708a Merge pull request #10190 from KBroneZ/claude/fix-10158-default-states
Read default_block and default_fluid in 26.3's block state form
2026-11-06 08:52:19 +01:00
Kacper 894f7a0684 Merge pull request #10167 from DUzzL/fix/10113-f3-keys-main-menu
Handle F3 debug keys on the title screen
2026-11-06 08:52:13 +01:00
Kacper d5b1c59f67 Merge pull request #10507 from DUzzL/fix/10447-lead-creative-spectator
Keep the lead in creative and drop mob leads when the holder spectates
2026-11-06 08:52:07 +01:00
Kacper b0ea840d13 Merge pull request #10128 from DUzzL/fix/10048-ladder-mob-push
Mobs on a ladder or vine are not pushable; no crit while climbing
2026-11-06 08:52:00 +01:00
Kacper 479d2082a8 Merge pull request #10187 from DUzzL/fix/10073-horse-jump-anim
Fix horse rear ending before a donkey or mule lands
2026-11-06 08:51:52 +01:00
Kacper d4b24f27d6 Merge pull request #10206 from DUzzL/fix/10157-shulker-tooltip
Fix numbered translation args in container item tooltips
2026-11-06 08:51:46 +01:00
Kacper 3ad30a2021 Merge pull request #10488 from AlfaRojo/fix/crossbow-flame-ignite
Crossbows: Flame ignites the arrows they shoot
2026-11-06 08:51:38 +01:00
MegaBytesMe 017caac28c fix: track Windows resource tool changes 2026-11-06 07:46:17 +00:00
Florian ERNSTandCodex 061648308e Keep local mob sounds out of shared server capture
Co-Authored-By: Codex <[email protected]>
2026-11-06 08:41:33 +01:00
Kacpherek f80bf5d7f3 Vehicles: test a mob's position before working out its box
A boat's or minecart's box query (boat_contacts, boat_obstacles,
cart_entity_collisions) asked every mob of the level for its box, and
Mob::bounds reads the mob's whole size (dims: sleeping, baby, species, scale)
across several cache lines. While boats and carts tick, the widest half width
and tallest height of the mobs (BoxReach) are worked out once, and a mob whose
position is farther than that from the query is skipped without its box. The
reach is worked out again when a mob joins or leaves, and nothing a vehicle
does resizes a mob, so the same mobs are found.
2026-11-06 08:36:08 +01:00
GabrielNichols ce90330576 Sync Recipes content scenario with official main 2026-11-06 04:32:30 -03:00
Detimes 3a20f23ee6 Native mods: read and set a player's attributes (player_attributes)
native_attribute(uuid, attribute) gives an attribute's base value and its
value with every modifier (Bukkit's AttributeInstance.getBaseValue and
getValue); native_set_attribute_base sets the base as
/attribute .. base set does; native_set_attributes loads a saved
attributes list onto the player as vanilla's AttributeMap.load reads a
player's file. A lower max_health caps the health, and the client gets
UpdateAttributes from the tick's sync. Nothing calls them without a mod.

Unproven: not yet compiled or probed live (build queue); the integration branch tests it.
2026-11-06 07:31:33 +00:00
Detimes 865e30ecb6 Native mods: read, give, take and replace a player's effects (player_effects)
native_effects(uuid) reads a player's effects as NativeEffect { id,
amplifier, duration, ambient, particles }; native_add_effect gives one as
vanilla's LivingEntity.addEffect (Bukkit's addPotionEffect), so a weaker
or shorter one waits under it; native_remove_effect takes one off as
/effect clear does; native_set_effects replaces them all, what a mod
restoring a saved player needs instead of writing PlayerEffects into the
hub's guests. The client gets UpdateMobEffect with the ambient and
particle flags from the tick's sync. Horizon keeps no hidden-icon flag on
an effect, so none is offered. Nothing calls them without a mod.

Unproven: not yet compiled or probed live (build queue); the integration branch tests it.
2026-11-06 07:31:33 +00:00
Detimes 3e6a1990ce Native mods: read a player's food, set health, food and experience (player_status)
native_player_food(uuid) reads a player's food level, saturation and
exhaustion; native_set_player_status(uuid, health, food, xp) sets each
that is given, as Bukkit's setHealth, setFoodLevel, setSaturation,
setExhaustion, setLevel, setExp and setTotalExperience. The write goes
through the player's turn as /kill and /effect reach a player
(for_player), so their client gets SetHealth and SetExperience with the
tick's sync. Health is capped at the maximum; 0 or less kills as /kill
does, with "<player> died" (Bukkit's setHealth(0), LivingEntity.die with
the generic source). The mod's food is not heard as the player's own
change (native_food_level_changed) outside their turn. Nothing calls it
without a mod.

Unproven: not yet compiled or probed live (build queue); the integration branch tests it.
2026-11-06 07:31:32 +00:00
Detimes 56b7dcbe62 Native mods: a player's compound as the server saves it (player_compound)
The helper native_player_compound(uuid) gives a player's compound as the
server would save it now (players/data/<uuid>.dat, playerdata::player_tag),
vanilla's ServerPlayer.saveWithoutId: what a mod keeping players in step
across servers (a HuskSync port) stores, instead of calling player_tag on
the hub's guests itself. None inside a player's turn, where their live
state is out of their record, and for a player not on. The new module
native_player_state.rs holds this group's helpers. Nothing calls it
without a mod.

Unproven: not yet compiled or probed live (build queue); the integration branch tests it.
2026-11-06 07:31:32 +00:00
Detimes 7c411b3a58 Renumber registry ids for a 26.2 client, with stand-ins for what 26.2 lacks
What a Horizon server sends a 26.2 client (accept_older_clients) now carries 26.2's
registry ids: tags, block states, entity, block entity, block, attribute, sound and
particle ids, and items and components through the existing Way::Down writers, which
now leave out components the client lacks. A creative slot or container click from the
client is read with its ids up.

generate_registry_id_adapter.py --substitutes writes, from both versions' data
reports, a stand-in for each 26.3 entry 26.2 lacks (poplar as birch, wool and concrete
slabs and stairs as stone ones of the same shape, ...); what has none (the cushion
entity) isn't sent. v26_2_ids.rs is regenerated with it.

Part of #10404.
2026-11-06 07:29:28 +00:00
DUzzL b6e947c9a4 Merge remote-tracking branch 'upstream/main' into fix/10051-frame-item-count
# Conflicts:
#	crates/horizon-client/src/game/commands/items.rs
2026-11-06 08:29:22 +01:00
Ludovic Marie 0d21d5520e Merge pull request #10477 from Detimes/feat-10404-older-clients-1a
🤖 Agent: a 26.2 client is now refused in configuration before any untranslated registry data, tags or play packets (bebf670); 26.3 path byte-for-byte unchanged with the option off
2026-11-06 08:27:12 +01:00
Detimes 29a0f2dc72 Native mods: players the server knows, by name or id (profile_cache, #10461)
native_profile_by_name(name) gives the id and name, and
native_profile_by_uuid(uuid) the name, of a player who is on, else one
in a dedicated server's profile cache (usercache.json): vanilla's
GameProfileCache, Bukkit's getOfflinePlayer without its network
lookup. They ask no profile service and make up no offline player;
UserCache::find_by_name reads without changing the cache. native_player_head
names a player who is off from the cache too. Ports parse
usercache.json or ask Mojang themselves today.
2026-11-06 07:25:24 +00:00
Detimes bb4f25c694 Native mods: any player's held stack and equipment from any server hook (player_equipment)
Unproven: not compiled or run yet (build queue stopped for the integration build); see proof/hooks-pread/PROOF_PLAN.md.
2026-11-06 07:25:15 +00:00
Detimes 2a86196e83 Native mods: the server's player limit, folder and log file (server_files)
Unproven: not compiled or run yet (build queue stopped for the integration build); see proof/hooks-pread/PROOF_PLAN.md.
2026-11-06 07:25:15 +00:00
Detimes ea6382f2a1 Native mods: a player's statistics and advancements, read and replaced (player_records)
Unproven: not compiled or run yet (build queue stopped for the integration build); see proof/hooks-pread/PROOF_PLAN.md.
2026-11-06 07:25:15 +00:00
Detimes 2f4294aba1 Native mods: what a player's client told the server (player_client)
Unproven: not compiled or run yet (build queue stopped for the integration build); see proof/hooks-pread/PROOF_PLAN.md.
2026-11-06 07:25:15 +00:00
Detimes eacaa4db69 Native mods: more of a player on NativeServerPlayer (server_player_state)
Unproven: not compiled or run yet (build queue stopped for the integration build); see proof/hooks-pread/PROOF_PLAN.md.
2026-11-06 07:25:14 +00:00
AkirabaneandClaude Opus 5.5 db1cde61a8 dedicated: transfer-status, the counts a proxy reads before it sends players
A proxy that balances players between servers has to know, before it sends
one, how many players a server has and whether a login would be refused as
full. transfer-status answers with counts only: the players placed, held and
expected, the hand-overs under way, the places the player limit counts, the
limit, and whether the server is full. It names no player, changes nothing
and writes nothing to the log, since a proxy asks it often. Like the player-*
requests, it is refused while [transfer] is off.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 08:23:45 +01:00
Web-mc1 89bac30424 tests: pin how the dev tools take their arguments
Both tools above used to crash, or write into a flag-named path, instead of
asking for what they need. These tests run them in an empty folder and check
that they exit non-zero with a usage line and write nothing.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
2026-11-06 07:20:12 +00:00
Web-mc1 cd5b93a54a bake_bricolage: ask for its two fonts
Both arguments were read unguarded, so running it with none, or with one, was an
IndexError rather than the command its docstring shows. Print the usage line.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
2026-11-06 07:20:12 +00:00
Web-mc1 35bb6ede00 json_load_fixtures: ask for an output directory
The script read sys.argv[1] straight away, so a missing argument ended in an
IndexError traceback, and a mistyped flag created a directory named --help and
filled it with 66 MB of fixtures. Print the usage instead: a flag is not a path.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
2026-11-06 07:20:12 +00:00
Detimes ca41bb4780 Packets: one zlib compressor per thread, reset for each packet
frame::contents made a new ZlibEncoder for every packet above the
compression threshold, which allocates and clears zlib's window and hash
tables (some 300 KiB) each time. A thread-local Compress, reset before each
packet and driven with the encoder's own calls (the input unflushed, then
finish), writes the same bytes. A new test checks them against a fresh
encoder for bodies from 256 bytes to 300 KiB, in both orders.
2026-11-06 07:19:47 +00:00
Arslan ArmanulyandClaude Opus 5.5 da06786848 The dragon fight's world fog leaves out the sky, as vanilla does
GameRenderer.renderLevel gives LevelRenderer.render shouldRenderSky false
while a boss bar makes world fog (the Ender Dragon's), so the frame is only
cleared to the fog colour. Horizon drew the End's sky box behind the island
during the fight. fog::sky_pass_skipped now holds vanilla's conditions:
LevelRenderer.addSkyPass's (lava, powder snow, blindness or darkness) and
the boss fog.

Part of #10491

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 12:19:07 +05:00
Detimes f1586af649 Native hooks: set one checked inventory slot, take exact stacks (helpers)
native_set_inventory_slot(uuid, kind, slot, expected, new) puts a stack
in any slot of a player's inventory or ender chest, armour and off hand
included, if it still holds exactly `expected`, and returns what was
there (Bukkit's setItem). native_take_stacks(uuid, slots) takes exact
stacks out of the given slots, all or none. Both are checked like
native_move_stack and work live or on the player's file. Wanted by the
InvSee, AdminKit and Wanted ports (armour slots, splitting, stakes);
Wanted edits LanHost guests directly for this today.

Unproven: not yet built or run on a server (handed to the hooks-all integration test).

Part of #10461.
2026-11-06 07:12:26 +00:00
Detimes a7c6eff569 Native hooks: replace a player's whole inventory (set_inventory helper)
native_set_inventory(uuid, kind, slots, selected) replaces a player's 41
inventory slots or 27 ender chest slots (Bukkit's
PlayerInventory.setContents), live or in their file like
native_inventory. For the inventory it sets the held hotbar slot, empties
the cursor and the 2x2 crafting grid, and sends a player on the server the
whole inventory at once. The HuskSync port writes Guest fields directly
for this today.

Unproven: not yet built or run on a server (handed to the hooks-all integration test).

Part of #10461.
2026-11-06 07:12:26 +00:00
Detimes 8e61d452e0 Native hooks: a guest's block placement before it happens (block_place)
native_block_place(player, name, stack, pos, state, cancel) is heard
where BlockItem.place has found the position and the state and checked
survival, before anything is set (Bukkit's BlockPlaceEvent, NeoForge's
BlockEvent.EntityPlaceEvent). A canceled placement is a failed one: no
block, no use, no swing, and the client gets its blocks and inventory
back. The position and state come from the existing dry run
(Behaviours::placement_state). Ports cancel placements today by claiming
native_use_item_on, which also stops doors, chests and the item's other
uses.

Unproven: not yet built or run on a server (handed to the hooks-all integration test).

Part of #10461.
2026-11-06 07:12:26 +00:00
Detimes 531a026a14 Native hooks: a guest's drag over slots, cancellable (inventory_drag)
native_inventory_drag(player, name, window, kind, slots, cancel) hears a
guest's quick craft in their own inventory or a vanilla menu once, at its
end, with the kind and the window slots the client dragged over (Bukkit's
InventoryDragEvent). A canceled drag's end is handled like a canceled
click: the drag ends empty and the client's prediction is answered with
the server's slots and cursor.

Unproven: not yet built or run on a server (handed to the hooks-all integration test).

Part of #10461.
2026-11-06 07:12:26 +00:00
Detimes 96d477b41b Native hooks: a guest's inventory and menu clicks, cancellable (inventory_click)
native_inventory_click(player, name, window, slot, button, mode, cancel)
hears a guest's click in their own inventory or a vanilla menu they have
open before any handler applies it (Bukkit's InventoryClickEvent). A
canceled click becomes a drag start of no type, which every menu takes as
resetQuickCraft and nothing more, so nothing moves and the client's
predicted slots and cursor are answered with the server's, as for any
click the server refused. Drags and a mod's own menus are not heard.
Wanted by the HuskSync and Essentials ports (data lock, freeze).

Unproven: not yet built or run on a server (handed to the hooks-all integration test).

Part of #10461.
2026-11-06 07:12:26 +00:00
Detimes 2e5d156644 Native hooks: a guest's throw, cancellable (player_launch_projectile_v2)
native_player_launch_projectile_v2(player, name, stack, cancel) is heard
where ProjectileItem.use creates a snowball, egg, ender pearl or a mod's
thrown item, before the projectile exists (Bukkit's ProjectileLaunchEvent
for a player). A canceled throw is a failed use: nothing is thrown, used
or put on cooldown, v1 hears nothing, and the guest's client, which took
the item at once, gets its inventory back. Wanted by the HuskSync port's
data lock (no ender pearls while locked).

Unproven: not yet built or run on a server (handed to the hooks-all integration test).

Part of #10461.
2026-11-06 07:12:26 +00:00
Kacpherek 02bdb03444 Vehicles: seat riders and find boat contacts without a pass over every mob per vehicle
position_riders finds every vehicle's riders in one pass over the mobs instead
of one per boat, minecart and primed TNT. A boat tests a mob's box before
anything else, and works out the scoreboard teams only when something touches
it, instead of once per boat a tick.
2026-11-06 08:10:31 +01:00
Kacpherek c7183d6232 Trace: every thread's spans as a timeline Perfetto opens
HORIZON_TRACE=FILE, horizon-ctl trace FILE [SECONDS] and horizon-trace on a
dedicated server's console write each span of every thread as a Chrome trace
event, so what ran beside what, and what a thread waited for, can be read off
in ui.perfetto.dev. A thread that records spans but is never taken from (a far
terrain or lighting worker while spans are on) now drops them past 16k instead
of growing for as long as spans stay on.
2026-11-06 08:10:30 +01:00
Florian ERNSTandCodex 8b33877963 Bind spellcaster NBT to the live spell counter
Load and save Evoker and Illusioner SpellTicks using the counter already consumed by casting goals and server AI. Cover the real codec and actual Game tick path.

Co-Authored-By: Codex <[email protected]>
2026-11-06 08:05:53 +01:00
Alysia Zappavigna 3b56859e04 Tick TNT that joined during a tick in that tick's late pass (fixes #10063) 2026-11-06 18:05:23 +11:00
Detimes 157208790d Native mods: an online player's textures and a head stack of a player (player_textures, #10461)
native_player_textures(uuid) gives the textures property (value and
signature) of a player who is on, as their player list entry carries
it (Paper's Player.getPlayerProfile). native_player_head(uuid) gives a
minecraft:player_head whose profile holds the player's id, and their
name and skin when they are on (Bukkit's SkullMeta.setOwningPlayer).
Ports look the current skin up at the session server today, or build
heads with a name-only profile.
2026-11-06 07:03:55 +00:00
Detimes 16750b8c90 Native hooks: a player logging in, held for the mods' answers (player_pre_login, #10461)
native_player_pre_login(player, name, address, defer) runs on the
connection's own thread once the session service or the proxy has named
the player and before the server's own checks (Bukkit's
AsyncPlayerPreLoginEvent). A mod that needs time, a permissions or
inventory load, or may refuse, pushes a Receiver<Result<(), Text>> onto
defer and answers from any thread: Ok lets the player on, Err refuses
them with the reason. The login waits for every answer, 20 s at most in
all; a missing or dropped answer refuses with a plain Disconnected.
Today such loads start in native_player_login, where a database failure
cannot refuse the player.
2026-11-06 07:02:40 +00:00
Detimes 6564832337 Native mods: set a profile property and show the player again (profile_refresh, #10461)
native_set_profile_property(player, name, value) sets or removes a
property of an online player's profile (Paper's Player.setPlayerProfile);
native_refresh_player(player) shows them again at the end of their
level's pass: everyone who sees them gets their list entry anew and
their entity again, and the player is respawned in place into the same
level with all data kept (Paper's hidePlayer/showPlayer plus the
respawn), so their own client draws a new skin too. Both work from any
server hook, native_server_tick included. They replace the raw HEAD
injection on Game::flush_native_net that edits Guest.properties and
sends the player info itself.
2026-11-06 07:01:16 +00:00
Detimes 413c2b1593 Native mods: command arguments that ask the server for completions, and the request split into label and arguments (tab_complete_v2) (unproven) 2026-11-06 07:00:51 +00:00
Detimes 78f3b6fca9 Native mods: look up a server command by name, aliases resolved (command_info) (unproven) 2026-11-06 07:00:50 +00:00
Detimes 7495f4c56b Native mods: run a console command that reaches the mods' commands (run_mod_command) (unproven) 2026-11-06 07:00:50 +00:00
Detimes 90a091b024 Native mods: run a command as a player, at their level, with its answer (run_command_as) (unproven) 2026-11-06 07:00:50 +00:00
Detimes 41413878d0 Native mods: run a console command and keep its result and answer (run_command_captured) (unproven) 2026-11-06 07:00:50 +00:00
Detimes 9b670c7cfc Native hooks: a joining player's profile on the connection thread (player_profile, #10461)
native_player_profile(player, name, properties) runs on the
connection's own thread once the server has let a player in and before
it sends LoginFinished. A mod may change, add or remove the profile's
properties (a skin's textures): the player's own client and every
player list get the new ones (Velocity's GameProfileRequestEvent,
Paper's PlayerProfile at AsyncPlayerPreLoginEvent). It replaces the raw
HEAD injections on lan::conn::login_and_configure and
Game::lan_guest_joined that skin and Floodgate ports use today.
2026-11-06 07:00:34 +00:00
Ludovic Marie 98af9399cd Merge pull request #10509 from horizon-rust/perf/launcher-prepared-cache
🤖 Agent: is_prepared now answers from a 5 s per-version cache (21.4 µs to 0.21 µs per call); need_prepared and the CLI still run the full per-file check, so an empty JSON is caught before launch.
2026-11-06 07:53:06 +01:00
Detimes 0da0001f38 Native mods: a player's permission level, also at login (player_permission, #10461)
The helper native_player_permission(player) gives the permission level of a
player on the server or joining it (0 to 4, ops.json's for an operator,
PlayerList.getProfilePermissions). It works inside native_player_login for the
player that hook hears, whose level the hub set on admitting them, so a login
gate that lets operators through needs no ops.json of its own. The hook's
contract is unchanged.
2026-11-06 06:51:55 +00:00
Detimes a4a64c560d Native mods: send a plugin channel message to a player (send_custom_payload_to_player, #10461)
The helper native_send_custom_payload_to_player(player, channel, data) queues
a ClientboundCustomPayloadPacket on any namespace:path channel outside
horizon: (at most ClientboundCustomPayloadPacket's 1 MiB) for one player's
client, vanilla or Horizon, sent at the tick's end with the mod messages, so
it works from any server hook. Bukkit's Player.sendPluginMessage.
2026-11-06 06:51:55 +00:00
Detimes 27200f64cd Native hooks: a player's plugin channel message (custom_payload_from_client, #10461)
native_custom_payload_from_client(player, channel, data) hears a guest's
ServerboundCustomPayloadPacket in play on any channel that isn't Horizon's own
(horizon:mod_net, horizon:quiet_commands, horizon:lod), which the server drops
today. Like mod messages it waits for the tick's end, with the hub back, so
every helper works; it is heard from a dead player too, as vanilla's
handleCustomPayload doesn't look at health. Horizon's own channels are handled
as before. The client side is #6774.
2026-11-06 06:51:55 +00:00
Detimes 01fec61d2d Handshake: a host of up to 32767 characters behind a proxy (#10461)
With proxy forwarding on (forwarding.secret, offline mode, not the integrated
server's own player) the handshake's host is read with Short.MAX_VALUE as its
limit, as Paper does behind a proxy, so a proxy's data after the host (a
Floodgate player's, often longer than 255 characters) no longer drops the
login. Without forwarding the limit stays vanilla's 255; it is the only string
read with that limit.
2026-11-06 06:51:55 +00:00
Detimes ac6ff90c34 Native hooks: a client's handshake on the connection thread (handshake, #10461)
native_handshake(address, host, intent, cancel, reason) is a free function in
horizon_client_lib::lan::conn, called on the connection's own thread for each
network connection's handshake, before the server acts on the intent. A mod may
rewrite the host (a proxy's data after it, Floodgate's, included) or refuse the
connection: a login or transfer is disconnected with the reason, a status ping
is closed unanswered. Wired through NativeClientHooks like player_chat. Nothing
changes without a mod.
2026-11-06 06:51:55 +00:00
Horizon agentandClaude Opus 5.5 bebf670623 Turn a 26.2 client away before untranslated registry data
Only packet ids are translated for an older client so far (#10404): the
registry data, tags (registry ids) and play packets (chunks, light, login,
player_action numbering...) would reach a real 26.2 client in 26.3 ids and
layouts. With accept_older_clients on, such a client now logs in and is
told so in configuration, right after the known packs and before any of
them. NetConfig::older_clients_play (tests only, not in server.toml) keeps
the play path reachable for the id renumbering test until the later parts
translate the contents.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 07:51:40 +01:00
MegaBytesMe 376be8985d fix: handle spaces in Windows resource paths 2026-11-06 06:51:09 +00:00
Horizon agent 5daf0853e3 Merge remote-tracking branch 'origin/main' into review-10477 2026-11-06 07:44:24 +01:00
Horizon agentandClaude Sonnet 5.5 bb8a9d9524 Launcher: pages drawn ask "is it prepared?" from memory, not the disk
#10389 made is_prepared parse research-lock.json and the manifest, list the
prepared folder and stat every table on each call. Pages call it while drawing
(instance rows, the Play menu, can_start), so every frame did file I/O.

is_prepared now keeps its answer per version folder for 5 s and drops it when
the launcher installs, removes, reuses resources or a job exits.
check_prepared runs the full check and refreshes the answer; starting the game
or a server (need_prepared), the CLI and the post-prepare auto-play use it, so
an empty JSON is still caught before a launch.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-06 07:44:15 +01:00
voidsip a4586852b1 Merge remote-tracking branch 'refs/remotes/origin/leash-refresh-main' into fix/10445-leashed-happy-ghast
# Conflicts:
#	mods/horizon/src/test/java/dev/horizonlab/parity/VanillaCommandExecOracle.java
2026-11-06 00:31:03 -06:00
Detimes c87a60d013 Native hooks: creative slots with the whole stack (creative_slot_v2)
native_creative_slot_v2(player, name, slot, stack, cancel) is an
additive version of native_creative_slot with the whole ItemStack put in
the slot (none for an emptied one), components included. It is heard
just before v1, which keeps its signature and firing point and hears
nothing of a packet canceled here.

Part of #10461.
2026-11-06 06:30:14 +00:00
Detimes 960634a9ad Native hooks: throws with the whole stack (item_dropped_v2)
native_item_dropped_v2(player, stack) is an additive version of
native_item_dropped with the whole ItemStack thrown, components included
(enchantments, unbreakable, attribute modifiers...), heard just before
v1, which keeps its signature and firing point.

Part of #10461.
2026-11-06 06:30:13 +00:00
Detimes a425fe4152 Native hooks: pickups with the whole stack, thrower and position (item_picked_up_v2)
native_item_picked_up_v2(player, stack, thrower, position, canceled) is
an additive version of native_item_picked_up: the whole ItemStack with
its components, the item entity's thrower UUID (none for block and mob
drops) and its position. It is heard just before v1, which keeps its
signature and firing point and hears nothing of a pickup canceled here.

Part of #10461.
2026-11-06 06:30:11 +00:00
Detimes 192898e730 Native hooks: where a joining player is placed (player_spawn_location)
native_player_spawn_location(player, name, dimension, position,
rotation) is heard once per join after the saved data is loaded (or a
newcomer's spawn found) and before the level is chosen and the world is
sent (Spigot's PlayerSpawnLocationEvent). A mod that changes them places
the player there, in that level, with no teleport afterwards. An unknown
dimension or a non-finite value leaves that part unchanged.

Part of #10461.
2026-11-06 06:30:10 +00:00
Detimes 14af676a89 Native hooks: player-versus-player damage with both players (player_hurt_by_player)
native_player_hurt_by_player(attacker, attacker_entity, victim,
victim_entity, source, damage_type, amount, canceled) names both players
by UUID and network entity id, and how the hit came: melee, sweep, or the
projectile's entity type (arrow, spectral arrow, snowball, egg, a mod's
thrown type). It is heard where native_living_incoming_damage is, at the
victim's hurtServer past its guards (pvp rule and teams allow it, victim
not invulnerable or dead), and just before it: changing the amount
reaches v1, canceling skips the hurt and v1 alike. The PvP call sites
name the hit in a thread-local scope that hurt_server reads.

A snowball or egg does zero damage, where vanilla's Player.hurtServer
stops: it is heard there with amount 0 and nothing the mods set applies.
Thrown tridents hurt no players in Horizon yet and are not heard.

Part of #10461.
2026-11-06 06:30:09 +00:00
Detimes 3515e99feb Native hooks: a player logging out while still present (player_logging_out)
native_player_logging_out(player, name) is heard when a placed player's
connection is gone (lost, kicked, quit) but before their menu closes and
they are saved and removed (Bukkit's PlayerQuitEvent): they are still in
their level and listed in native_server_players. No state is swapped in;
the hub is back in self.lan for the call, so the UUID helpers and
native_run_command reach them. A death a mod causes there (`kill <name>`)
is died at once (death message, drops, native_player_died), since the
leaving player gets no further tick to die in; they are then saved dead.
Not heard when the server stops, where native_player_logged_out already
tells with them still listed. Unmodded servers are unchanged: the death
step only runs when the hook took the player from alive to zero health.

Part of #10461.
2026-11-06 06:30:08 +00:00
Ludovic Marie 3a81751180 Merge pull request #10458 from TheMrZZ/fix/h028-stand-fall-save
🤖 Agent: Entity.checkFallDamage subtracts (float)ya into the double, which the f64::from(.. as f32) step reproduces, and Entity.load reads fall_distance back. Main merged in again after #10422 landed (GitHub read the criss-cross bases as a conflict); the diff to main is this PR's own 6 files.
2026-11-06 07:28:07 +01:00
Horizon-agentandClaude Opus 5.5 ec72efe92d Merge main into #10458 after #10422 landed
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 07:25:35 +01:00
Ludovic Marie 87c51ab9c9 Merge pull request #10471 from arslanarm/shaderpacks/shadow-pass-block-entities
🤖 Agent: `camera_list` is now collected into a Vec on every frame, even with no pack, where the old iterator was lazy; it's one small allocation per frame. The shadow-only set and the spawner's `shadow_pass_drew` scan (linear in shadow_visible per spawner) run only under a pack whose shadow draws entities.
2026-11-06 07:23:45 +01:00
voidsip f2a63474b9 Match vanilla server-address port syntax 2026-11-06 00:22:03 -06:00
voidsip 24224f4728 Respect the controlling passenger for remote boats 2026-11-06 00:21:23 -06:00
voidsip 4e580b2732 fix(server): apply only final values in config batches 2026-11-06 00:21:04 -06:00
Arslan ArmanulyandClaude Opus 5.5 4eb7a27e58 Shader packs: terrain without a program in the pack is drawn by nothing
A pack (or the dimension folder it uses) with no gbuffers program for terrain
had the game draw terrain itself, into colortex0 and depth. Iris 1.11.7 draws
none: with probe packs of ours having no gbuffers programs, at the root or in
world-1 below a root that has them, colortex0 held only the sky (or fog
colour), entities and particles, depthtex0 had no terrain, and colortex1 to 7
none either; water and lava were missing too.

When no program of gbuffers_terrain's chain (nor _solid or _cutout) is in the
folder, the pack takes solid and cutout terrain from the game and draws
nothing; the same for translucent terrain and gbuffers_water's chain. A
program that is there but fails to load still leaves terrain to the game.

Fixes #10455

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:18:32 +05:00
Arslan ArmanulyandClaude Opus 5.5 0a14bac24c Shader packs: biome_category matches Iris's readings, fitted to vanilla's biome tags
`biome_category` read 0 (CAT_NONE) in lush caves and the other cave biomes,
where Iris 1.11.7 reads 5 (CAT_PLAINS), and differed in 12 more vanilla
biomes. A probe pack of ours read Iris's value in each of vanilla 26.3's 67
biomes (`fillbiome` around the camera): they fit vanilla's `is_*` biome tags
(BiomeTags; `is_savanna` unused), then desert, swamps, mushroom fields and
snowy plains by name, NONE for the void, and PLAINS for the rest. Mountain
biomes read 17, which Iris names CAT_MOUNTAIN; CAT_UNDERGROUND is 18.

Biomes vanilla doesn't have keep the old guess from words of their name.

Fixes #10453

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 11:18:32 +05:00
Kacper 108c130237 Merge pull request #10340 from CocofireHD/fix/10327-docs-readme-md-index-the-benchmark-and-perfo
docs: index benchmark and performance result pages in docs/README.md
2026-11-06 07:18:18 +01:00
Kacper 73fdce3c01 Merge pull request #10375 from voidsipp/fix/10326-docs-index
Index the missing design, server and release docs
2026-11-06 07:18:10 +01:00
Kacper bf22a35ab3 Merge pull request #10350 from xxaxio/docs/10325-index-player-launcher-docs
docs/README.md: index missing player, launcher and platform pages
2026-11-06 07:18:04 +01:00
Kacper e41b6599ec Merge pull request #10351 from xxaxio/docs/10324-remove-stale-not-ported-notes
docs: remove stale "not ported" notes
2026-11-06 07:17:58 +01:00
Kacper 0c2aec59f6 Merge pull request #10234 from KBroneZ/claude/fix-10164-forceload
Load chunks in /forceload add, as vanilla does
2026-11-06 07:17:48 +01:00
Kacper 50c2ade6c6 Merge pull request #10233 from KBroneZ/claude/fix-10163-template-loot
Keep a placed template's container loot table
2026-11-06 07:17:42 +01:00
Kacper 3281781334 Merge pull request #10370 from xxaxio/fix/10322-map-can-place-on-banner
game/maps: honour can_place_on when using a map on a banner in adventure mode
2026-11-06 07:17:35 +01:00
Kacper 5871fa922f Merge pull request #10278 from Joansitoh/fix/10044-underwater-sky
Stop the underwater sky scan on kelp and seagrass
2026-11-06 07:17:25 +01:00
Kacper 16bb8b3e65 Merge pull request #10277 from Joansitoh/fix/10044-bucket-liquid
Replace a bubble column when a bucket empties into it
2026-11-06 07:17:18 +01:00
Kacper 1e35996644 Merge pull request #10274 from DUzzL/fix/10250-zip-end-record-disk
Normalize zip END records with equal non-zero disk numbers
2026-11-06 07:17:12 +01:00
foudretbr 8be5c59fce Native mod API: item tooltip render surface and container item drawing
Fixes #10199
2026-11-06 07:17:10 +01:00
Florian ERNSTandCodex 3aaff8da93 Restore NoAI Dolphin air after its superclass tick
Use the existing species maximum after shared Mob tick work, preserving the normal underwater countdown. Cover the real full-tick path and actual Game commands against fresh Java.

Co-Authored-By: Codex <[email protected]>
2026-11-06 07:16:46 +01:00
Ludovic Marie 499b049803 Merge pull request #10422 from TheMrZZ/fix/h046-loot-pickup-save
🤖 Agent: Mob.addAdditionalSaveData always writes canPickUpLoot(), so moving it out of the put-if-absent defaults is the fix (Allay and sulfur cube overrides now win over a loaded byte). Main merged in after #10393 landed; clippy and the entity_nbt tests pass on the merge.
2026-11-06 07:16:40 +01:00
Ludovic Marie 29825eb774 Merge pull request #10411 from DUzzL/fix/10318-piglin-gear-compare
🤖 Agent: piglin_replace follows Piglin.canReplaceCurrentItem in order (PREVENT_ARMOR_CHANGE first, then wanted/unwanted, then Mob.canReplaceCurrentItem), with getPreferredWeaponType null for babies.
2026-11-06 07:12:21 +01:00
Horizon-agentandClaude Opus 5.5 ded0d1a283 Merge main into #10458
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 07:11:56 +01:00
Horizon-agentandClaude Opus 5.5 1c8729793f Merge main into #10422
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 07:11:51 +01:00
Ludovic Marie 69c1400e58 Merge pull request #10389 from CocofireHD/fix/10303-my-game-crash-after-launch
🤖 Agent: `is_prepared` now reads the manifest and stats every top-level JSON on each call, and the launcher calls it from render paths (glass_home, glass_palette, can_start): a few hundred µs per frame at most, but a cache keyed on the folder would remove it. I dropped the unused `is_json_file`; launcher clippy and backend tests pass.
2026-11-06 07:11:07 +01:00
Ludovic Marie 948c6d6a88 Merge pull request #10438 from megabytesme/fix/windows-cross-compile-icon
🤖 Agent: checked on Linux with mingw: the build script now compiles `main_res.o` for x86_64-pc-windows-gnu and passes it to the link, which the host-gated `#[cfg(windows)]` skipped. I kept the warn-only fallback outside CI for windres paths with a space (#10398).
2026-11-06 07:11:03 +01:00
Ludovic Marie b8c058a249 Merge pull request #10386 from voidsipp/fix/10355-boat-attack-knockback
🤖 Agent: causeExtraKnockback's non-living branch is Entity.push (no knockback resistance, no halving), as strike_boat does; the guest path still checks the attack range before skipping look_at_entity. We merged #10434 into this branch: both extend the boat fixture and harness.
2026-11-06 07:10:59 +01:00
Ludovic Marie 5a0c5d522f Merge pull request #10437 from voidsipp/fix/10401-snbt-float-text
🤖 Agent: for one-digit mantissas {:.1e} picks the closest two-digit decimal, which is what Java's Float/Double.toString does (Float.MIN_VALUE prints 1.4E-45). We switched the harness to the shared release world_probe: debug at CARGO_BUILD_JOBS=1 would have compiled horizon-world again in CI.
2026-11-06 07:10:55 +01:00
Ludovic Marie ff27525aaf Merge pull request #10448 from masa-masa-0314/fix/realms-add-realm-strings
🤖 Agent: RealmsPdpScreen shows the trial description (and a Try for free button) only when trialAvailable; Horizon does not ask for it, so we dropped the description and kept the title with Buy, vanilla's no-trial layout. Titles are drawn plain where vanilla underlines/bolds them: small follow-up.
2026-11-06 07:10:52 +01:00
Ludovic Marie 54112649da Merge pull request #10413 from DUzzL/fix/10319-golem-reset-anger
🤖 Agent: start() matches NeutralMob.stopBeingAngry plus IronGolem.startPersistentAngerTimer (nextInt(381) + 400 = rangeOfSeconds(20, 39)) at target priority 4. We moved the creeper test's doc comment back onto its own test: the new test had been inserted between them.
2026-11-06 07:10:48 +01:00
Ludovic Marie ef207a7c92 Merge pull request #10391 from masa-masa-0314/feat/realms-tier-distance
🤖 Agent: the tier fetch and both PUTs hit the same paths as RealmsClient.getRealmTierConfiguration/updateRenderDistance/updateSimulationDistance, and a reply missing either range is an error as in vanilla; nothing beyond what the vanilla settings tab offers.
2026-11-06 07:10:43 +01:00
Ludovic Marie c86e080406 Merge pull request #10388 from voidsipp/fix/conversion-command-tags
🤖 Agent: only Tags move to the new id, as ConversionType.convertCommon copies them; the old raw compound (UUID, species data) is left behind, which the save/reload test checks.
2026-11-06 07:10:39 +01:00
Ludovic Marie 26243ba3b7 Merge pull request #10392 from voidsipp/fix/7436-fishing-ground-motion
🤖 Agent: hook_block_hit now runs on the client before moving, as FishingHook.checkCollision does on both sides, and gravity is f64::from(0.03f32) (getDefaultGravity is a float).
2026-11-06 07:10:35 +01:00
Ludovic Marie a4f021ec67 Merge pull request #10414 from voidsipp/fix/4832-remote-pig-steering
🤖 Agent: matches Pig/Strider.getControllingPassenger (saddled, first passenger a player holding the right stick in either hand); the server side reads the same rule through moved_by_client, so it no longer simulates the mount twice.
2026-11-06 07:10:31 +01:00
Ludovic Marie a7bf97e67d Merge pull request #10417 from voidsipp/fix/next-1-glint-override
🤖 Agent: component_bool matches NbtOps.getBooleanValue (doubleValue != 0, so 0.5f and NaN are true, Short 256 too), not NumericTag.asBoolean's byteValue; that is the Hypixel glint_override report.
2026-11-06 07:10:28 +01:00
Ludovic Marie 11e2482ae5 Merge pull request #10426 from voidsipp/fix/levitation-fall-distance
🤖 Agent: LivingEntity.aiStep resets fall distance for slow_falling or levitation before travel, NoAI included; dropping the later travelInAir-style reset is right, it erased the current tick's descent.
2026-11-06 07:10:24 +01:00
Ludovic Marie 363906684c Merge pull request #10430 from voidsipp/fix/10397-packet-utf8-replacement
🤖 Agent: packet_utf8 differs from from_utf8_lossy only on ED A0..BF (encoded surrogates), which Java consumes as one replacement; the length limits still use the encoded bytes first and UTF-16 units after, as Utf8String.read does.
2026-11-06 07:10:20 +01:00
Ludovic Marie a567b64f20 Merge pull request #10433 from voidsipp/fix/10399-unreadable-moderation-lists
🤖 Agent: reading the bytes once and parsing them removes the exists()-then-load race too; only NotFound starts an empty list, so a directory or a locked ops.json now stops the stopped-server command instead of being overwritten.
2026-11-06 07:10:16 +01:00
Ludovic Marie faa7e2746f Merge pull request #10407 from DUzzL/fix/10323-evoker-fangs-teams
🤖 Agent: fangs_allied follows Entity.isAlliedTo both ways (AbstractIllager tag only when both are teamless, Evoker's vex root-owner rule); guests on the evoker's team are still bitten, as before, which is a follow-up rather than a regression.
2026-11-06 07:10:12 +01:00
Horizon-agent b4197ddd94 Merge remote-tracking branch 'origin/main' into w-10471
# Conflicts:
#	crates/horizon-client-render/src/gfx.rs
2026-11-06 07:08:12 +01:00
Ludovic Marie ad8663977c Merge pull request #10459 from TheMrZZ/fix/armor-stand-command-equip-events
🤖 Agent: follows LivingEntity.onEquipItem (same item+components or firstTick: nothing; EQUIPPABLE on the new stack picks equip over unequip) and data merge keeps first_tick. We removed the docs/parity run-log file.
2026-11-06 07:03:36 +01:00
Ludovic Marie 0b3a4bda9c Merge pull request #10424 from TheMrZZ/fix/hostile-sound-categories
🤖 Agent: all nine families are Monster subclasses (entity.hostile.* is Monster's shared hurt/death/fall set), so HOSTILE is right; the list scan is 34 short strings per sound, negligible. We removed the docs/parity run-log file.
2026-11-06 07:03:32 +01:00
Ludovic Marie 5fb8f59ea0 Merge pull request #10432 from TheMrZZ/fix/numeric-damage-component
🤖 Agent: DataComponents.DAMAGE goes through Codec.INT (Number.intValue: truncation, Long keeps its low 32 bits), and dynamic::as_int already does that. We removed the docs/parity run-log file.
2026-11-06 07:03:29 +01:00
Ludovic Marie 62d2b18c16 Merge pull request #10400 from TheMrZZ/fix/inline-fishing-tool-context
🤖 Agent: LootCommand.dropFishingLoot's no-tool endpoint passes ItemStack.EMPTY, and the code only reads tool_stack when a tool, mainhand or offhand was given, so an omitted tool stays empty. We removed the docs/parity run-log file.
2026-11-06 07:03:25 +01:00
Ludovic Marie 17541e8bff Merge pull request #10387 from TheMrZZ/fix/baby-pig-passenger-attachment
🤖 Agent: Pig BABY_DIMENSIONS has PASSENGER (0, 0.5, 0), so the one match arm is right. We removed docs/parity/baby-pig-passenger-attachment.md: run logs and hashes belong in the PR description, not in docs/.
2026-11-06 07:03:22 +01:00
Skr1zzzz a8107ee025 Player heads: parse a profile once, not on every draw
head_skin decoded the textures property (base64, JSON, URL checks) and hashed
the skin location (SHA-1) for every head on every frame, before asking the
atlas whether the skin was resident; an armor stand's worn head also turned
its profile NBT into JSON each frame. With 60 stands wearing textured heads
and 36 heads in the inventory that was ~9 us a head a frame (#4664: a server
full of menu heads, CPU-bound with the inventory open).

The pure part (profile -> textures -> skin location, failures included) is
now kept in a bounded cache beside the resolver; worn heads reuse the JSON of
an unchanged profile. The download state, atlas residency, drawn tracking and
eviction re-uploads still run per draw.
2026-11-06 09:02:52 +03:00
Kevin RamirezandClaude Fable 5.1 927b546c9a Crossbows: Flame ignites the arrows they shoot (#10482)
`Game::shoot_crossbow` set the arrow's weapon and Piercing count but never
ran the weapon's `projectile_spawned` ignite, which only the bow path
applied. In vanilla, `AbstractArrow`'s constructor runs
`EnchantmentHelper.onProjectileSpawned` for every arrow built with a
weapon, bow or crossbow, so a Flame crossbow shoots burning arrows that
prime TNT and set entities on fire.

The bow's `bow_projectile_ignite` becomes `ignite_spawned_arrow(weapon,
arrow)`, shared by both weapons; the crossbow ignites each arrow of its
volley after Piercing, as the constructor does. The port map rows follow
the rename, and a resource-backed regression checks no Flame, Flame and
Flame with Multishot.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-11-06 00:02:20 -06:00
Ludovic Marie fff2a81e41 Merge pull request #10395 from DUzzL/fix/10321-spawnpoint-bubble-column
🤖 Agent: confirmed in Blocks.<clinit>: water, lava and bubble_column are the only three blocks with .liquid(), so the name list is complete for Block.isPossibleToRespawnInThis.
2026-11-06 07:02:01 +01:00
Ludovic Marie 1f883e7a79 Merge pull request #10402 from Skr1zzzz/server-tick-spans
🤖 Agent: `tick_blocks` starts a fresh `laps` after the scheduled ticks, so the stretch between `tb_entity_inside` and `tb_broadcast` shows only through `random_ticks`, `tb_scheduled` and `tb_fluids`. The counters and laps cost one relaxed load each when spans are off.
2026-11-06 07:01:57 +01:00
Ludovic Marie c956e11ff8 Merge pull request #10470 from DUzzL/fix/10441-ghastling-sounds
🤖 Agent: HappyGhast.getHurtSound/getDeathSound pick GHASTLING_* by isBaby, and happy_ghast::sounds already does exactly that, so the new voice_sounds arm is the whole fix.
2026-11-06 07:01:53 +01:00
Ludovic Marie f6a9444deb Merge pull request #10416 from Skr1zzzz/spawn-parity
🤖 Agent: checked against 26.3: `ChunkAccess.getHeight` returns `getFirstAvailable - 1` and `getRandomPosWithin` adds the 1 back, so topEmptyY is Horizon's `world_surface` itself. The old +1 made the bound 130 for a column solid to y 63. run-parity's natural_spawn harness checks the draws end to end.
2026-11-06 07:01:50 +01:00
Ludovic Marie 0610b830ca Merge pull request #10434 from voidsipp/fix/4545-boat-paddle-transition
🤖 Agent: rowing_time now gates on paddle_controller like AbstractBoat.getPaddleState (flag AND a controlling passenger) and clamps a as Mth.clampedLerp does; #10386 carries this branch merged, queued after it.
2026-11-06 07:01:46 +01:00
Ludovic Marie 8a21328406 Merge pull request #10439 from Skr1zzzz/random-ticks
🤖 Agent: the `[false; 256]` table relies on a local palette never holding more than 256 entries (global palettes keep the per-block path). The test covers 1, 2, 256 and 300 states, a stale palette entry, and the LCG draws against an independent oracle.
2026-11-06 07:01:39 +01:00
Ludovic Marie d165254d41 Merge pull request #10393 from TheMrZZ/fix/h011-orb-saved-uuid
🤖 Agent: matches Entity.saveWithoutId: the orb's command NBT now reads its registered uuid_for(id) instead of a fresh random UUID per read; queued first of the 10393/10422/10458 stack (the other two carry this branch merged).
2026-11-06 07:01:34 +01:00
Ludovic Marie 0125ca5281 Merge pull request #10435 from Hiuoy/feat/model-layers
🤖 Agent: `native_bake_layer` re-reads entity-models.json on every call for a vanilla layer no mob has (the doc says so), so a mod that bakes per frame would hitch; caching that document is the natural next step. The humanoid-against-zombie#main bake test is the right oracle, and the fragment stamps cleanly (tests.test_stamp_native_hooks).
2026-11-06 07:01:29 +01:00
Ludovic Marie 743d6229fe Merge pull request #10419 from senjinthedragon/resize-free-first
🤖 Agent: the early return for an unchanged size stays ahead of the new `device.poll(wait)`, so the GPU sync happens once per real size change. A drag-resize pays it per new size, a fair price for not holding both sets of buffers.
2026-11-06 07:01:25 +01:00
Ludovic Marie 6df8d31833 Merge pull request #10456 from arslanarm/shaderpacks/smooth-across-teleports
🤖 Agent: `world_seen` never resets inside a `Custom`, so the restart on entering the Nether relies on the pack being rebuilt for each dimension. If `PackPost` is ever kept across dimensions, it will need resetting there.
2026-11-06 07:01:21 +01:00
Ludovic Marie cc4911fc2b Merge pull request #10457 from arslanarm/shaderpacks/colortex-mips-blend
🤖 Agent: only `Source::Color` gets the linear-mip sampler; shadowcolor, custom textures and the lightmap keep `Linear` (nearest level), which matches what was measured. Buffers without mipmaps read the same as before.
2026-11-06 07:01:17 +01:00
Ludovic Marie 8d1a46ef24 Merge pull request #10467 from arslanarm/shaderpacks/shadow-depth-filter-precision
🤖 Agent: the 256ths weights are a fit to one NVIDIA GPU but now apply on every GPU, so an AMD or Intel reading of Iris may move them later. The cost is two floors and a multiply per filtered shadow read.
2026-11-06 07:01:13 +01:00
Ludovic Marie a6a228a193 Merge pull request #10469 from arslanarm/shaderpacks/shadow-pass-spectator
🤖 Agent: the spectator rule keys on `pack_ids.is_some()`, so a native pack's shadow keeps the first-person model, as the test pins. The other game modes stay as before, since only spectator was measured.
2026-11-06 07:01:09 +01:00
Ludovic Marie 57140bb20f Merge pull request #10473 from arslanarm/shaderpacks/terrain-vertex-grid
🤖 Agent: the +1/32 bias moves only places within one float step under a 32768th line, so #9839's water tops (29094.34) and 13/15 offsets (28398.93) still floor as before, and the new test case pins the 1, 0.5 and 9.5 edges.
2026-11-06 07:01:05 +01:00
Ludovic Marie 0c7ddd45c5 Merge pull request #10436 from arslanarm/shaderpacks/shadow-pass-depth-culling
🤖 Agent: with the near and far planes dropped, the shadow pass now draws every section inside the box's sides out to its reach, so a pack with tight planes draws more shadow terrain than before. Worth watching in the cave perf numbers. #10471 conflicts with this in gfx.rs; I'll merge main into it once this lands.
2026-11-06 07:01:00 +01:00
Horizon-agentandClaude Opus 5.5 c9b0011952 Merge #10434 (boat paddles) into #10386
Both extend the boat authority fixture and harness: keep the paddle and
attack cases, both Case fields and both docstring paragraphs. The harness
keeps main's fast-profile build with #10434's --resources option.
Queued after #10434, so either landing first leaves the other mergeable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:57:38 +01:00
Horizon-agentandClaude Opus 5.5 a25a11fbf7 Merge #10422 and #10393 into #10458
All three add a command_exec fixture group at the same lines and a test
module at the end of entity_nbt.rs; keep every group, flag, dispatch block
and module. Queued in order so whichever lands first leaves the others
mergeable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:56:29 +01:00
Horizon-agentandClaude Opus 5.5 9f9daee867 Merge #10393 (orb saved UUID) into #10422
Both add a command_exec fixture group at the same lines; keep both groups,
their isolation sets, flags and dispatch blocks. Queued in order so either
landing first leaves the other mergeable.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:55:28 +01:00
DUzzL 468a73f128 Keep the lead in creative and break mob leads when the holder spectates 2026-11-06 06:54:41 +01:00
Horizon-agentandClaude Opus 5.5 c542322582 Float text harness: build the shared release world_probe
A debug world_probe at one job compiles horizon-world from scratch beside
the release probe every other horizon-world harness uses.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:54:15 +01:00
Horizon-agentandClaude Opus 5.5 264d5f38c7 Realms product page: the trial header without its description
Horizon does not ask trialAvailabilityTask. Vanilla shows the trial
description (and offers the trial in the footer) only when one is
available; otherwise the header is the title alone and the footer offers
Buy, which is what this screen shows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:53:56 +01:00
Horizon-agentandClaude Opus 5.5 dd23018283 Keep the creeper test's doc comment on its own test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:53:11 +01:00
Horizon-agentandClaude Opus 5.5 3b575a0016 Drop the run-log document from the PR
The PR description already holds the vanilla reference and the measured
results; docs/ keeps player and contributor documentation only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:52:48 +01:00
Horizon-agentandClaude Opus 5.5 94d295d20c Drop the run-log document from the PR
The PR description already holds the vanilla reference and the measured
results; docs/ keeps player and contributor documentation only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:52:45 +01:00
voidsip 625f6ca1a5 Match Happy Ghast leash movement and distances 2026-11-05 23:52:43 -06:00
Horizon-agentandClaude Opus 5.5 ec080faa9f Drop the run-log document from the PR
The PR description already holds the vanilla reference and the measured
results; docs/ keeps player and contributor documentation only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:52:42 +01:00
Horizon-agentandClaude Opus 5.5 1d1b060e1a Drop the run-log document from the PR
The PR description already holds the vanilla reference and the measured
results; docs/ keeps player and contributor documentation only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:52:37 +01:00
Horizon-agentandClaude Opus 5.5 5e8d9a5123 Drop the run-log document from the PR
The PR description already holds the vanilla reference and the measured
results; docs/ keeps player and contributor documentation only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:52:26 +01:00
voidsip d12a260a53 Reject overflowing native shader float options 2026-11-05 23:52:06 -06:00
voidsip 0d70c87082 Keep UTF-16 key order in compound SNBT 2026-11-05 23:47:01 -06:00
Horizon-agentandClaude Opus 5.5 3649dfb9eb json_file: drop is_json_file, which nothing calls
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:45:51 +01:00
Horizon-agentandClaude Opus 5.5 94d8386027 build.rs: keep the no-icon fallback for local builds
The GNU windres the launcher's native mod builds use can't take a path with a
space, so a local build there must go on without the icon (#10398 asked to keep
it); CI still fails without it. One Windows block for the stack and the icon.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 06:45:50 +01:00
Detimes da8c2cfc55 Native hooks: each line written to the log (log_line)
native_log_line(level, thread, message, line) hears each message
written to logs/latest.log, on the log's writer thread after the file
has it, so the thread that logged never waits on a mod and a Discord
console bridge needs no tailing of latest.log. line is the text as the
file got it. The logger calls it through logging::set_line_hook,
installed before the log opens; trait mods are reached through
NativeClientHooks. A callback that panics is not called again, so its
own panic line can't loop.

Refs #10461
2026-11-06 05:42:47 +00:00
Detimes 2f2eecf40d Native hooks: every console and RCON line before it runs (console_line)
native_console_line(source, line, cancel, replies) hears each line the
dedicated server's console is about to run, before vanilla's commands,
the dedicated-only ones and mods' registered ones: source is console
(typed on the console, or sent by horizon-server as the console) or
rcon. Set cancel and it does not run; replies are logged as the
console's feedback, so they also reach the RCON or horizon-server
caller. Until now native_perform_console_command only heard commands
a mod registered, so an audit mod could not see ordinary console
commands (Bukkit's ServerCommandEvent / RemoteServerCommandEvent).

Refs #10461
2026-11-06 05:42:46 +00:00
TheMrZZandCodex gpt-6.1-sol 81cf3ef358 fix: name actual macro argument NBT type
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 06:41:16 +01:00
Detimes 6e4fbfa79f Native per-player view distance (native_set_player_view_distance)
A mod limits one player's view distance below the server's: only that
player is told its radius (SetChunkCacheRadius) and is sent and tracks
chunks within it; native_player_view_distance reads it. The guest's
distance is ChunkMap.getPlayerViewDistance under the limit; with no limit
(every unmodded server) it is exactly as before. Part of #10461.
2026-11-06 05:40:48 +00:00
Detimes 9c23922432 Native world border read helper (native_world_border)
A level's world border bounds as the server enforces it, for a web map's
border line and a pre-generator's border selection. Vanilla 26.3 keeps a
border per level (ServerLevel.getWorldBorder, each level's
data/minecraft/world_border.dat); Horizon keeps one for every level, so
the helper answers the same bounds for each level until that is fixed.
No behaviour change. Part of #10461.
2026-11-06 05:40:07 +00:00
Detimes 2d2936b9df Native chunk tickets in a level nobody is in (native_force_chunk_in)
native_force_chunk only reaches the level in place, so a mod could not hold
a chunk of the Nether or the End from a server-wide hook, and a level not
open yet never ticked: a pre-generator for those levels never advanced.
native_force_chunk_in takes the dimension; a level held this way opens at
the next server tick and ticks while held. Unmodded servers hold no mod
tickets, so nothing changes for them. Part of #10461.
2026-11-06 05:39:58 +00:00
HiuoyandClaude Opus 5.5 5c1d4d53f6 Native hooks: PlayerTickEvent.Pre, the start of a player's server tick (#10464)
native_player_tick_pre() runs once a tick for every living player in the
world, at the top of their ServerPlayer.tick, before their digging, item
use and cooldowns. native_player_tick (API 31) already ends the same tick.
It fires in tick_guests after the checks that skip a dead player and one on
a server trip, so the pair brackets the same players. Single player's own
player is a guest of its internal server.

The fragment player_tick_pre.json labels it PlayerTickEvent.Pre (side
server, not cancellable) for horizon-port-pack events. 23 of ATM10's mods
listen to it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:36:24 +04:00
martipopsandClaude Opus 5.5 b32520088e Controllers: read Bluetooth Xbox pads from their layout, on any firmware
SDL rows match a firmware version exactly, and gilrs' copy has no row for
045e:0b13 at 0x0520. The pad then falls back to gilrs' default mapping: its
right stick is on ABS_Z/ABS_RZ, which nothing read, its triggers on
ABS_GAS/ABS_BRAKE, which that mapping doesn't name, and X and Y stay crossed
because it lists C and Z.

Recognise that layout from what the pad reports, so a new firmware works
without a row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:28:36 -05:00
Arslan ArmanulyandClaude Opus 5.5 460ecfce99 Shader packs: terrain's mc_Entity.y reads 0 off fluids, as in Iris
Terrain's `mc_Entity.y` was -1 on every block but water and lava, and -1 on
those too when the pack has no block.properties. Iris 1.11.7, with probe packs
of ours, reads 1 on water and lava and 0 on every other block, whether
block.properties maps it, leaves it out, or the pack has no such file.

The sprite layers' table now starts each layer at (-1, 0), with y 1 on the
fluid sprites, before block.properties is read.

Fixes #10452

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:27:02 +05:00
Arslan ArmanulyandClaude Opus 5.5 b1574b9721 Shader packs: a program writing buffers sized relative and in pixels draws while they match
A composite program writing two colortex buffers was skipped whenever their
`size.buffer` lines differed, even when one relative size and one pixel size
came to the same size (`0.25 0.25` and `320 180` at 1280x720). Iris 1.11.7
draws it (a probe pack of ours: texel (100, 50) of both reads 100.5, 50.5 and
texcoord 0.314, 0.281).

Sizes of one kind that differ still skip the program as the pack loads. A mix
of the two kinds is kept, and the pass is skipped only at render sizes where
the buffers it writes resolve to different sizes.

Fixes #10454

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:26:01 +05:00
Arslan ArmanulyandClaude Opus 5.5 c9bc6104b3 Shader packs: a terrain vertex a float step under a 32768th sits on it
A shader pack's terrain gl_Vertex floors each vertex's place in its
section to a 32768th. Plants whose random offset is 9/15 of a block came
out a 32768th under a half block, where our probe packs read Iris
1.11.7's exactly on it: we guess, untested, that our float sums put them
a hair under the line. A place up to 1/32 of a 32768th under a grid line
is now on it.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:26:00 +05:00
Arslan ArmanulyandClaude Opus 5.5 02c05de455 Shader packs: shadowtex filters in whole 256ths, as our Iris readings fit
A pack's texture() on shadowtex0/1 through a plain sampler blended the
four texels with the exact fraction. Iris 1.11.7's filtered reads on our
NVIDIA GPU fit weights in whole 256ths: the fraction rounded half up on
each axis, the far corner's weight their product over 256 rounded half
up, the others the rest of each axis. #8464's lit probe then differs
from Iris's at no pixel of ow_ground_noon.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:25:52 +05:00
Arslan ArmanulyandClaude Opus 5.5 6a737285e8 Shader packs: the shadow pass takes the block entities of the sections it draws
A shader pack's shadow pass drew the block entities the camera's view
had built: a chest beside the view, in a section whose terrain the
shadow pass draws, was left out, and a spawner's mob out of the view
and out of those sections went in. Our probe packs' Iris 1.11.7 shadow
maps hold a chest wherever the shadow pass draws its section's terrain
and nothing of the one spawner in our scenes, which lies outside them.
The block entities of the sections the shadow pass drew last frame are
now added hidden from the camera, and a spawner's mob that is out of
the view and out of those sections is left out.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:25:51 +05:00
Arslan ArmanulyandClaude Opus 5.5 1354db6f08 Shader packs: a spectator's own model stays out of the shadow pass, as in Iris
In first person, a shader pack's shadow pass drew the spectator's own
model (its head) at the camera. Our probe packs' readings of Iris
1.11.7's shadow map in spectator mode hold the terrain below the camera
there.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 10:25:42 +05:00
HiuoyandClaude Opus 5.5 3e4828d791 port-pack events: ModConfigEvent counts as hooked when its three events are
37 of ATM10's mods listen to the parent ModConfigEvent, which hears the
three events NeoForge's config page documents under it: Loading, Reloading
and Unloading. config_loading, config_reloading and config_unloading answer
them, so the parent now counts as hooked rather than as another phase's:
93 of 326 names, up from 92.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:24:40 +04:00
Detimes d503874bff Let 26.2 clients join a Horizon server, off by default: handshake and packet ids
A server.toml option, [server] accept_older_clients (horizon-accept-older-clients), off
by default. On, a login handshake stating 776 opens an adapter::ClientSession on the
connection instead of being refused, and a 26.2 status ping is told its own protocol.
The session follows the connection's states as Session does, mirrored, and renumbers
packet ids both ways with the inverse of the generated 26.2 tables, in the blocking
login path and in the io_pool play path. Off, nothing changes.

Part of #10404.
2026-11-06 05:22:16 +00:00
Florian ERNSTandCodex f97e4b9527 Emit game events for armor-stand command equipment changes
Co-Authored-By: Codex <[email protected]>
2026-11-06 06:12:01 +01:00
TheMrZZandCodex gpt-6.1-sol 504b987937 Save armor stands' actual fall distance
Adopt and project the existing fall counter, including Java's per-movement float narrowing before double accumulation. Preserve travel and landing-call order, and cover the real tick/save route plus decoded saves without raw data.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 05:59:09 +01:00
masa-masa-0314andZCode c81874d772 Realms: the settings sliders' tooltips
`createDistanceOption`'s cached constant tooltips, shown while the
pointer is over either distance slider.

Co-Authored-By: ZCode <[email protected]>
2026-11-06 12:55:30 +08:00
Ludovic Marie a589a3dd9b Merge pull request #10449 from horizon-rust/fix/native-stale-engine-target
🤖 Agent: staged files now get the current time, so a Nightly's source unpacked with its commit's older mtimes can no longer pass for already-compiled crates (Windows/macOS fs::copy kept them); one-time stage rewrite for old stages.
2026-11-06 05:53:41 +01:00
Arslan ArmanulyandClaude Opus 5.5 8d38356bf4 Shader packs: smooth() keeps going through teleports, as in Iris
A custom uniform's smooth() restarted on every frame whose camera biome was
unknown, so after each teleport it started again, and it started from the
unknown frame's values (biome -1, biome_category 0, temperature 0): the
frame that restarted it also stored them, and the first frame that knew the
biome went on from there.

Now smooth() restarts only until the first frame that knows the camera's
biome, that frame included, and then keeps going for as long as the pack
runs. With a probe pack of ours, Iris 1.11.7 kept smoothing across
teleports within a dimension and started again when the player entered
the Nether.

Fixes #10450
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:53:24 +05:00
Arslan ArmanulyandClaude Opus 5.5 4c0ef1d7df Shader packs: colortex mipmaps blend their two nearest levels, as in Iris
A shader pack's colortex read between two mipmap levels (textureLod at a
fractional level) took the nearest level: the buffers' sampler had the
default, nearest, mipmap filter. In Iris 1.11.7 our probe packs read every
colortex, in composite and in final, as the two nearest levels mixed. The
buffers now get a sampler of their own that blends them; shadowcolor and
the other textures keep theirs (not measured).

Fixes #10451
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:53:24 +05:00
Horizon agentandClaude Opus 5.5 4b24e21a8a launcher: format the stamping test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 05:49:06 +01:00
DUzzL ebafc91999 Use ghastling hurt and death sounds for baby HappyGhasts 2026-11-06 05:47:17 +01:00
HiuoyandClaude Opus 5.5 e925e25781 port-pack events: ModelEvent.RegisterAdditional is looked up as RegisterStandalone
NeoForge's documentation describes one event under two names. 1.21.1's
"Registering Additional Models" (ModelEvent.RegisterAdditional, one
ModelResourceLocation per model) is "Registering Standalone Models"
(ModelEvent.RegisterStandalone, a key and a baker per model) from 1.21.5 on.
Both versions use the same sentence, and the example method is still
registerAdditional. standalone_models (API 151) answers it, so the 47 ATM10
mods that listen to it now count as hooked: 92 of 326 names, up from 91.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 08:46:39 +04:00
Horizon agentandClaude Opus 5.5 1fe1d5e860 launcher: stamp staged native source files with the current time
A Nightly's source keeps its commit's mtimes when unpacked, and fs::copy
keeps them on Windows and macOS, so a changed engine file reached
native/stage/ older than the previous build and Cargo reused the old
horizon-core. sync now sets each written file's mtime to now; a stage
written before this is rebuilt once.

Fixes #10421

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 05:41:23 +01:00
masa-masa-0314andZCode 382f264b6a Realms: the Add Realm screen as vanilla's product page
The screen behind Add Realm is 26.3's `RealmsPdpScreen`: the header's
logo, the trial header, then the friends, minigames and private-server
entries with their 80x45 pictures and 182-wide text, scrolling between
the header and the footer, whose buttons are the PDP's own title (the
buy link) and Back.

Its old strings were dead: `mco.title` never existed, and 26.3's
deprecated.json removes `mco.selectServer.popup` and
`mco.selectServer.buy`, so the screen drew raw keys.

Co-Authored-By: ZCode <[email protected]>
2026-11-06 12:39:31 +08:00
repositorykreml1nandCodex f4b9bc1471 Merge upstream main to resolve PR #10015 conflicts
Keep the command-block /say LAN regression and upstream legacy PvP checks,
with both registrations intact. Preserve upstream function bodies and the
original say regression unchanged.

Validation: Python syntax/AST and registrations checked; public-tree check
passed; Python unittest suite passed (41 skipped). pr_ready could not start
Rust checks because Cargo is unavailable in this restored environment.

Co-Authored-By: Codex <[email protected]>
2026-11-06 07:37:01 +03:00
Ludovic Marie 679f8c6e3f Merge pull request #10440 from horizon-rust/fix/options-emote-key
🤖 Agent: options.txt is vanilla's key list again; the emote key is saved as emoteKey in horizon-extras.txt and an old key_key.emote line is migrated, options parity passed on the desktop.
2026-11-06 05:18:23 +01:00
Horizon agentandClaude Sonnet 5.5 c6d5769e4f Options: rustfmt keybinds.rs
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-06 05:13:24 +01:00
Ben 1ad899c7b5 fix: embed Windows icon resources for cross builds 2026-11-06 04:13:14 +00:00
voidsip b9439b3508 Match Java text for tiny floats and doubles 2026-11-05 22:12:54 -06:00
Arslan ArmanulyandClaude Opus 5.5 6a8ea6fc18 Shader packs: the shadow pass draws terrain outside the pack's near and far planes
Our probe pack's Iris 1.11.7 shadow maps hold the terrain at every depth we
put it along the light (from 535 blocks beyond the shadow camera to about 120
before it), whatever shadowNearPlane and shadowFarPlane the pack sets, or
none. Horizon dropped the sections outside the shadow projection's near and
far planes; since #9768 those are the pack's own planes with no offset, so
in our probe's cave scene with the planes at 0.05 and 256 the surface toward
the light was missing from the shadow map. A shaders/ pack's shadow pass now
culls by the box's sides only; Horizon's own packs keep their planes.

Fixes #10403

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 09:06:26 +05:00
Florian ERNSTandCodex 1e1c6a900f Decode numeric damage components with Codec.INT semantics
Co-Authored-By: Codex <[email protected]>
2026-11-06 05:03:08 +01:00
voidsip ff381b5dc4 fix(server): reject unreadable lists in stopped moderation commands 2026-11-05 22:02:46 -06:00
HiuoyandClaude Opus 5.5 7dc8ba568f Native mods: entity model layers built in code, baked and drawn (#10425)
A NeoForge mod builds its entity models in code (createBodyLayer: a
MeshDefinition of CubeListBuilder boxes at PartPoses, a LayerDefinition),
registers them in EntityRenderersEvent.RegisterLayerDefinitions, bakes them
with EntityModelSet.bakeLayer and draws the ModelPart tree it posed in
setupAnim with ModelPart.render. Native mods get the same:

- the builders and ModelPart, ported from vanilla (mobs/model_layers.rs):
  box UV from texOffs, mirror, deformation, texture scale and visible faces
  as ModelPart.Cube bakes them; poses, reset_pose, get_child;
- native_layer_definitions, a collect hook asked once at start, keyed by
  ModelLayerLocation ids in the mod's own namespace;
- native_bake_layer: a registered layer, or vanilla's by its
  entity-models.json name (minecraft:zombie#main);
- native_draw_model_part: ModelPart.render under a mob's or block entity's
  frame, textured from an atlas sprite.

The humanoid mesh built with these builders bakes to exactly vanilla's
minecraft:zombie#main (an ignored test against the prepared resources).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 08:02:00 +04:00
Ludovic Marie a1710b6962 Merge pull request #10429 from horizon-rust/perf/seen-snapshot
🤖 Agent: the kept buffer is cleared at the end of every tick, so no villager Arc<villager::Seen> outlives the tick, and #[inline] on Mob::seen only changes where each Seen is written (in its slot instead of a stack copy): same values, same order. creature_prepare 1.2 → 0.8 ms in both A/B rounds.
2026-11-06 04:56:53 +01:00
voidsip e7956591f3 Match vanilla replacement decoding for packet strings 2026-11-05 21:53:19 -06:00
Horizon agentandClaude Sonnet 5.5 4b352e89c1 Options: the emote key tests read a versioned options.txt, rustfmt
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-06 04:52:55 +01:00
Horizon agent 2726d8040b Merge remote-tracking branch 'origin/main' into perf/seen-snapshot
# Conflicts:
#	crates/horizon-client/src/game/creatures.rs
2026-11-06 04:52:00 +01:00
Horizon agentandClaude Sonnet 5.5 6da74ac228 Options: the emote key lives in horizon-extras.txt, options.txt is vanilla's again
Options parity regression from #10092: key.emote was in the vanilla key list, so options.txt
gained a key_key.emote line and every key after it shifted. It is now a Horizon-only mapping
(HORIZON_MAPPINGS): bound in Controls and translated like the others, saved as emoteKey in
horizon-extras.txt. A key_key.emote line written by the broken build is read once and dropped.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-06 04:44:03 +01:00
voidsip 6716cb7407 Merge remote-tracking branch 'refs/remotes/validation/worker7-main' into fix/4545-boat-paddle-transition
# Conflicts:
#	crates/horizon-client/src/game/remote_vehicles.rs
2026-11-05 21:43:23 -06:00
yahya-nusseibehandClaude Opus 5.5 58d5f3b519 Merge main into fix/nbt-heap-quota
Brings the branch up to date and re-runs CI: verify failed once in
horizon-relay's slow_first_lines_and_too_many_connections_are_closed (a
timing test, 5.6 s against its 3 s on a loaded runner), which this
change doesn't touch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-06 05:43:15 +02:00
voidsip 8181ce0222 Reset mob fall distance before travel 2026-11-05 21:42:45 -06:00
Ludovic Marie 76b8b4eb83 Merge pull request #10423 from horizon-rust/fix/sculk-ticking-mobs
🤖 Agent: a mob that did not tick still gets its last position refreshed (no step), so one moved by a command while out of range doesn't count a walk or a landing once it ticks again; damage and mount events stay for every mob since vanilla raises them from hurt/startRiding. Proven on vanilla 26.3 with ore_probe2.py (settled pig/item stop lighting redstone ore once the player is 6 chunks away).
2026-11-06 04:40:22 +01:00
Florian ERNSTandCodex 6303017b03 Classify the verified hostile sound families as hostile
Co-Authored-By: Codex <[email protected]>
2026-11-06 04:38:19 +01:00
Horizon agent e16a5ef0da Merge remote-tracking branch 'origin/main' into fix/sculk-ticking-mobs 2026-11-06 04:37:28 +01:00
voidsip 76addba6cc Tick remote boat paddles and stop empty boats rowing 2026-11-05 21:36:53 -06:00
Skr1zzzz cc643f4af4 Random ticks: a section's candidate mask classifies each palette entry once
Rebuilding a changed or new section's random-tick candidate mask asked
whether the block state ticks randomly for each of its 4,096 blocks. A local
palette holds a handful of states: each entry is now classified once and the
mask built from the decoded palette indices. Single-value and global
palettes keep the previous path. Same mask, same draws.
2026-11-06 06:35:23 +03:00
TheMrZZandCodex gpt-6.1-sol c843c1aa05 Save the modeled mob loot-pickup flag
Write CanPickUpLoot on every save through the existing virtual getter, preserving the Allay and sulfur-cube overrides instead of retaining stale raw bytes. Cover decoded and mutated models plus frozen actual command reads.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 04:30:58 +01:00
Skr1zzzz 283c6723c0 Natural spawning: a start's height ends at the column's first empty block
NaturalSpawner.getRandomPosWithin draws Y in [minY, getHeight(WORLD_SURFACE)
+ 1], and ChunkAccess.getHeight is getFirstAvailable - 1: the top is the
first empty block. Horizon's world_surface already returns that block and
the spawner still added one, so every start could land one block higher
than vanilla's, in the air above the surface, where vanilla's draw never
goes; nextInt's bound was one larger too, so the draws differed from
vanilla's from there on.
2026-11-06 06:22:19 +03:00
Ludovic Marie 1532c9c153 Merge pull request #10331 from horizon-rust/perf/lan-entity-state-cache
🤖 Agent: idle entities (outside the simulation-level entity-ticking range) no longer run sendChanges, as in vanilla's ChunkMap.tick, so their 60-tick MoveEntityPos stops; a bare state (no data) is never paired, and the idle check follows the tracker's viewers (placed or held). tick_lan −1.66 ms (−24 %) in 3 A/B pairs.
2026-11-06 04:22:17 +01:00
voidsip 23a880843d Preserve custom model data flags through the item render bridge 2026-11-05 21:20:17 -06:00
voidsip 7b4634a912 Fix client steering for saddled pigs and striders 2026-11-05 21:20:10 -06:00
Ludovic Marie 2daa740748 Merge pull request #10412 from horizon-rust/fix/remote-player-offhand-nametag
🤖 Agent: queued. Every tracked entity now records what it rides, so any passenger (item/block display included) hides the player's tag as vanilla's isVehicle does; one risk checked: a removed vehicle leaves a stale id in passengers' vehicle, harmless since tags look up live entities.
2026-11-06 04:19:50 +01:00
Ludovic Marie cd547654d9 Merge pull request #10406 from horizon-rust/fix/aistep-ticking-mobs
🤖 Agent: the gate is the set of mobs the loop actually ticked (Creatures::ticked), not a second ticking-area test, so a mob that walks out of range during its own tick still finishes its pickups as in vanilla; proven against a vanilla 26.3 server (pickup_probe.py: only the ticking villager and zombie take their item).
2026-11-06 04:08:51 +01:00
Horizon agentandClaude Opus 5.5 0ed3868329 Name tags: any passenger hides its vehicle's name, as isVehicle does
Vanilla's LivingEntityRenderer.shouldShowName hides a name tag for any
entity with a passenger (`!entity.isVehicle()`). The client only counted
mobs, text displays, armor stands and interactions as passengers, so a
server that hangs its own name over a player with an item display, a
block display or any other entity still got the player's tag drawn
under it: two names (#5666).

Every tracked entity now records the vehicle SetPassengers puts it on,
and the name tags read that, whatever the passenger's type. The typed
lists stay for placing the riders.

The rest of #5666 was fixed already on main: the sword drawn in the
off hand by #5111 (each remote hand renders its own stack), the double
tag of custom-named players by #8457, and text display and armor stand
riders by #9754.

Fixes #5666

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 04:07:46 +01:00
SenjinandClaude Opus 5.5 7b80ab60c7 Shader packs: free the old buffers before a resize makes the new ones
A pack with large buffers (SEUS PTGI at 3440x1440) held both sets at once while resizing and ran
out of video memory going back to fullscreen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 04:00:58 +01:00
voidsip 6beb50ebcd Preserve boolean glint overrides in item model views 2026-11-05 20:58:38 -06:00
Horizon agent c1d16aabde Merge remote-tracking branch 'origin/main' into perf/seen-snapshot 2026-11-06 03:55:25 +01:00
Horizon agent c0c3096e85 Merge branch 'fix/aistep-ticking-mobs' into fix/sculk-ticking-mobs 2026-11-06 03:55:04 +01:00
Horizon agent 74150d5ef8 Merge remote-tracking branch 'origin/main' into fix/aistep-ticking-mobs 2026-11-06 03:54:43 +01:00
Skr1zzzz d3678c9112 Atlas readback tests skip without an adapter, as the module's others do
CI runners have no GPU: the two material-atlas tests panicked in
request_adapter instead of returning like layers_land_in_their_cells.
2026-11-06 05:53:06 +03:00
DUzzL dfb9757d97 Evoker fangs: honour scoreboard teams when deciding allies 2026-11-06 03:50:47 +01:00
Skr1zzzz a58d5700f3 Merge remote-tracking branch 'origin/main' into server-tick-spans
# Conflicts:
#	crates/horizon-client/src/game/creatures.rs
2026-11-06 05:50:36 +03:00
Skr1zzzz 9cdcf66e69 Spans: inside the server tick's mob spawning, block and creature steps
A slow tick named spawn_natural, tick_blocks and tick_creature_entities but
nothing inside them. With 100 bots they take ~11, ~21 and ~10 ms a tick, and
an index for spawn_selection's nearest-player shortlist saved nothing: the
time was elsewhere. Their steps now have laps (sn_*, tb_*, tc_*), and the
natural-spawn pass counts its attempts and spawns (sa_attempts/sa_spawns,
one note a tick). They show the attempt loop itself: ~18,850 attempts a tick
for ~3 spawns (#3910). Only when HORIZON_SPIKES is set.
2026-11-06 05:48:23 +03:00
Horizon agentandClaude Opus 5.5 c442edabd9 Merge origin/main; idle states follow the tracker's viewers (held players too)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:43:53 +01:00
Florian ERNSTandCodex 29f04e40ed Pass the tool to inline fishing loot
Co-Authored-By: Codex <[email protected]>
2026-11-06 03:42:10 +01:00
Horizon agentandClaude Opus 5.5 04f6bd1214 Sculk events: only mobs and items that tick step, land and flap
Steps, landings, splashes, flaps and stepping on redstone ore come from
an entity's own tick in vanilla (Entity.move, applyEffectsFromBlocks
from LivingEntity.aiStep and ItemEntity.tick), and ServerLevel.tick
ticks only the entities in entity-ticking chunks. Here every loaded mob
and dropped item on the ground stepped on the block under it each tick:
a pig or an item that had settled on redstone ore kept lighting it after
the player went six chunks away.

Those events are now for the mobs tick_creature_entities ticked (and
those standing in the ticking area, as the sculk fixture posts before
the mobs tick) and the items in the ticking area. A mob that did not
tick keeps where it is as its last position, without a step. Damage and
mounting are raised by hurt and startRiding, so they stay for every mob.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:29:26 +01:00
TheMrZZandCodex gpt-6.1-sol 27d093c94f Use registered XP-orb UUID in saved command reads
Reuse the existing identity getter rather than generating a UUID per read. Cover repeated saved reads, absent raw input and stale raw UUIDs through the actual Game getter and command fixture.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 03:21:06 +01:00
voidsip 36c0d3f938 Predict fishing bobber block hits before client movement 2026-11-05 20:20:54 -06:00
DUzzL 6bd507f89d Treat bubble column as liquid when validating a respawn point 2026-11-06 03:20:54 +01:00
masa-masa-0314andZCode 7a846eebc6 Realms: the settings tab's render and simulation distances
RealmsSettingsTab's two distance sliders, fed by
getRealmTierConfiguration (asked until it answers, as
tierConfigurationFetched does) and saved with updateRenderDistance /
updateSimulationDistance only when a value changed, alongside
saveSettings' existing name, description and region update.

Co-Authored-By: ZCode <[email protected]>
2026-11-06 10:17:04 +08:00
DUzzL bd152b1ecf Piglins: compare armour and weapons when both or neither item is wanted 2026-11-06 03:13:01 +01:00
Florian ERNSTandCodex 6f9981bfbf Use the baby pig passenger attachment when seating riders
Co-Authored-By: Codex <[email protected]>
2026-11-06 03:10:51 +01:00
voidsip 6cf1b6b74c Keep command tags when converting mobs 2026-11-05 20:10:33 -06:00
DUzzL 12454e54bd Port ResetUniversalAngerTargetGoal to iron golems 2026-11-06 03:08:44 +01:00
Horizon agentandClaude Opus 5.5 6b2c83656c Mob snapshot: build each Seen in its slot and keep the buffer
tick_creature_entities snapshots every loaded mob (`Seen`, ~400 bytes)
before the loop and again after each mob's tick. Mob::seen lives in the
client lib and was not inlined into the game crate, so each one was
built on the stack and copied into its slot, and the snapshot's
megabytes were allocated and freed every tick.

Mob::seen is #[inline] now, and the emptied buffer stays in Creatures
for the next tick. The snapshot holds the same values.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:07:34 +01:00
voidsip d7e1eaf955 Apply melee extra knockback to boats 2026-11-05 20:06:28 -06:00
08393ef6f7 Parse entity-models.json once at startup
Part of #9998.

Startup parsed entity-models.json (2.4 MB) into a serde_json::Value six
times: for the handoff's GameCpuResources, MobTypes, the player type, both
player arm models and the spin-attack model. The rest worker did five of
those one after another. Nothing in the document changes during a loader
generation, and a pack change starts a new one.

load_resources now parses it once on its own step beside load_core and
shares it as an Arc<EntityModelDocument> with every model loader. A
missing or malformed document leaves each caller on its old path, so
errors and fallbacks are unchanged; a dedicated server draws no models and
skips the parse.

Callgrind, the whole resource load in the game (to "resources loaded"):
21.576 G -> 20.631 G instructions (-4.38%); the entity-model path alone
1.594 G -> 0.349 G (-78.1%). A new test builds MobTypes from a shared
document and from its own parse and compares every type, model and layer.

A first draft by DeepSeek V4.1 Flash (pi coding agent), reviewed by Claude.

Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fekd3xkgubE8VfXrALh2td
2026-11-06 03:04:20 +01:00
ebe3818eb7 Light a section from its palette's light info, not its state ids
light_chunk filled its 48x48xheight region from every non-air section by
unpacking 4,096 state ids into a scratch array, then reading each one
back, looking up LightTables::info and storing 2 of its bytes. That round
trip was 35% of a light job, and the flood fill only ever reads those 2
bytes.

PalettedContainer::unpack_ids_mapped decodes the packed storage through a
lookup table in one pass (out[i] = map[id of entry i]), with a fast path
for 4 bits per entry, a linear palette's usual width. light.rs's new
states_of maps a section's palette to the packed light info once (at most
256 entries), then copies the region's 16-wide rows from the staged
values. A section whose palette emits light stages full infos and seeds
block light and the queue per cell, in the same order as before. Air now
writes info[0], which is 0, the value the region already holds (asserted
in debug builds).

Callgrind, gen_light_bench --seed 1234 --radius 4 --threads 1 (81 lit
columns): the light job 1.991 G -> 1.260 G instructions (-36.7%), the
whole run -1.15%. The light job is 5-7% of the workers' CPU, so this is
about 2% of the chunk pipeline.

Exact: gen_light_bench's block and light checksum is identical for three
seeds and centres at 1 and 4 threads, light_chunk_matches_old_algorithm
(an independent light implementation over random 3x3 regions) passes
unchanged, and the new section_states_match_the_unpacked_ids compares
states_of with a walk over unpacked state ids for one value, 4 to 8 bits
and the global palette, with and without light sources.

A first draft by DeepSeek V4.1 Flash (pi coding agent), reviewed by Claude.

Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fekd3xkgubE8VfXrALh2td
2026-11-06 03:04:19 +01:00
CocofireHDandopencode 7c57f5f906 prepared files: treat a killed export as unused
A killed exporter left an empty gameplay-facts.json (or another table) behind.
The preparer only checked that the path exists, so "prepare again" kept the
empty file, and the launcher called the folder prepared and started the game
on it. Trust an earlier export only when it still parses, and treat a folder
with an empty top-level JSON table as not prepared so Play prepares it again.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 03:01:43 +01:00
voidsip 11dda135a8 Attribute bursts of short LAN events in slow tick notes 2026-11-05 19:52:54 -06:00
CocofireHDandopencode 7cc9057fa6 Extras: keep the Zoom hint out of the Mods page's way in
The not-installed Zoom row pushed Page::Mods from Extras, which
every_setting_and_page_is_reached_from_one_place refuses: Options > Mods
already has the one way in. It is now a note that says where to install
Zoom; once the mod is built in the row is still its switch and Settings
button.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 02:47:58 +01:00
voidsip 8a65d28c0d fix(server): recover interrupted region pruning before startup 2026-11-05 19:47:51 -06:00
Ludovic Marie 69fab597a6 Merge pull request #10203 from Detimes/feat-10174-mod-deps
🤖 Agent: Checked on the desktop against main 9f8f6842d (merges cleanly, lock passes --locked). All 19 new lock packages are permissive (MIT/Apache/BSD-3/ISC-style webpki/Zlib/BSL option on ryu); the only build scripts (mysql, mysql_common) just pass target env vars, no downloads; TLS is rustls+ring only, flate2 uses miniz_oxide, no openssl/native-tls/-sys/cmake. cargo tree for horizon-client (default, --no-default-features, --target all) is identical to main, so players' game and native-mod compile are unchanged; the launcher's cold cargo fetch gains 21 crates / +2.5 MB once (noise in wall time). Note for later: webpki 0.22 and rustls-pemfile are unmaintained upstream; worth moving to mysql 28 when it no longer rewrites the lock.
2026-11-06 02:45:58 +01:00
Florian ERNSTandCodex 8dd47b5ef2 Omit default hive block state from saved item patches
Co-Authored-By: Codex <[email protected]>
2026-11-06 02:37:28 +01:00
voidsip 790e5f14d4 docs: index design server and release guides 2026-11-05 19:35:16 -06:00
CocofireHDandopencode 3c01dce30d resources: name the JSON file a parse error came from
serde's "expected value at line 1 column 1" says nothing about which of the
prepared folder's tables is damaged, so a player with a killed export only saw
a crash report. Read the core tables through horizon_world::json_file, which
prefixes the path, and have the startup check name a top-level table left
empty before the window opens.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 02:28:51 +01:00
Skr1zzzz 643d699d51 Player list: keep the shown list's layout between frames
While Tab is held every frame sorted every entry twice (once for the faces,
once for the rows), allocated team keys inside the comparator, rebuilt and
measured every row's text, split the header and footer, and pruned the
health states with a scan per row (#10108). TabList now keeps the sorted
order and the laid-out rows, rebuilt only when their inputs differ (players,
scoreboard, header/footer, width, font and language); the faces reuse the
order; sort keys are borrowed; the health states go by a uuid set. Hearts
still animate every frame.
2026-11-06 04:24:37 +03:00
Horizon agentandClaude Opus 5.5 66dc3229a0 Mobs outside the simulation distance pick nothing up
Mob.aiStep's looting, the villagers' item sensor and the fox, dolphin,
panda, piglin, allay and sulfur cube item passes run after the mob loop
over every loaded mob. In vanilla they are part of the mob's own tick,
and ServerLevel.tick ticks only the entities in inEntityTickingRange: a
villager six chunks from the nearest player, loaded but outside the
simulation distance, took the bread at its feet here and not there.

The loop records the mobs whose tick ran (Creatures::ticked), and those
passes leave the others alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 02:20:47 +01:00
AxxioandAntigravity 2948fb86ed game/maps: honour can_place_on when using a map on a banner in adventure mode
In adventure mode, vanilla allows ItemStack.useOn if the item's can_place_on component matches the clicked block. Previously, use_map_on_banner checked only !self.may_build() and returned early without checking can_place_on.

This honours can_place_on_in_adventure on the banner's block position, allowing adventure mode players with matching maps to toggle banner markers.

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 02:18:05 +01:00
Ludovic Marie 9f8f6842db Merge pull request #10366 from horizon-rust/perf/sculk-entity-events
🤖 Agent: each mob footstep scanned every mob three times (two id lookups and the warden/allay listener loop); now an event with no sculk block in reach and no warden or allay within 24 blocks skips the context and the listeners. Listeners give up past 16 blocks before any side effect, so vibrations are unchanged.
2026-11-06 02:16:56 +01:00
TheMrZZandCodex gpt-6.1-sol 7d5d34ae32 Play ordinary arrow hits in the neutral sound category
Override only ArrowSound::HitGround through Audio::play_on. Add an actual Game/Java block-hit capture using the existing headless client, Studio recorder and official sound codec.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-06 02:16:39 +01:00
Ludovic Marie 1525ae0e3b Merge pull request #10364 from horizon-rust/fix/founder-claim-use-checkout
🤖 Agent: launcher-only fix. Maps the service's 403 use_checkout / not_offered_here to their own messages (86 tables), and drops one_click after use_checkout so the Shop opens the free checkout. Risk: none on the hot path; only the claim's error branch changes. Launcher tests 719 passed on the desktop.
2026-11-06 02:09:16 +01:00
Ludovic Marie 09450fb6f9 Merge pull request #10361 from horizon-rust/perf/mob-loop-overhead
🤖 Agent: villager_pickups tested every loaded villager against every dropped item, looking up tags before the reach: that was 12.8 of 23 ms of the mob phase in the load test (22.7 → 8.1 ms after this). The new ItemScan keeps the full scan's order and falls back to it if the item list changes under it.
2026-11-06 02:07:26 +01:00
Florian ERNSTandCodex 97d414cc4d Load animal age and love with NumericTag integer conversion
Co-Authored-By: Codex <[email protected]>
2026-11-06 02:04:52 +01:00
Horizon agentandClaude Opus 5.5 53bc816fa1 Launcher: rustfmt the claim refusal test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 02:03:19 +01:00
Ludovic Marie 2993ef0b20 Merge pull request #10116 from horizon-rust/pvp/legacy-knockback
🤖 Agent: merging. Every knock_back caller outside the legacy rules passes legacy = false, so the 26.3 path is unchanged. 1.8's air lift and the unhalved sprint push only run under the hosted-world switch.
2026-11-06 01:53:48 +01:00
Horizon agentandClaude Opus 5.5 1411281eea Launcher: a refused founder claim says why and leads to the checkout
The cosmetics service answers POST /v1/claim with 403 use_checkout where
Paddle sells (the set goes through its free checkout there) and 403
not_offered_here where the claim is left out. The launcher mapped neither,
so a player saw "Horizon's cosmetics service had a problem" (Discord bug
thread, 2026-11-05).

- Both codes are the Shop's own refusals now, each with its own words, in
  all 86 lang tables.
- After use_checkout the saved answer drops one_click, so the Wardrobe stops
  offering "Claim for free" and the Shop's button opens the free checkout on
  playhorizon.dev.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 01:52:42 +01:00
Skr1zzzz f0de57ae23 Merge remote-tracking branch 'origin/main' into mp-fps-4664
# Conflicts:
#	crates/horizon-client/src/game/scoreboard.rs
2026-11-06 03:52:21 +03:00
Ludovic Marie 12d7eaccdb Merge pull request #10115 from horizon-rust/pvp/legacy-server
🤖 Agent: merging. [combat] is only written when legacy is on, and unknown keys are rejected. The game reads it in dedicated.rs, and a bad section logs and keeps 26.3's rules. The test now looks for the section line, not the template's '# [combat]' comment, which made verify fail on the first head.
2026-11-06 01:51:17 +01:00
Horizon agent 92bc3bc40f Merge remote-tracking branch 'origin/main' into perf/sculk-entity-events 2026-11-06 01:50:16 +01:00
Horizon agent d6d7cc3e29 Merge remote-tracking branch 'origin/main' into perf/mob-loop-overhead 2026-11-06 01:49:56 +01:00
Ludovic Marie 342824b506 Merge pull request #10114 from horizon-rust/pvp/legacy-rules
🤖 Agent: merging. legacy_rules() is remote.is_none() && combatRules, so a window on any server keeps 26.3's cooldown and damage. Measured over LAN: 1.8 rules 7 and 7 (diamond axe, charged then right after a swap), 26.3 9 and 3.6.
2026-11-06 01:48:43 +01:00
Skr1zzzz 2f075689f5 Multiplayer: skip name tags whose entity is off screen before laying out their text
Every frame built, styled and measured the name tag of every player within
64 blocks, drawn or not: with 150 players and the camera facing the sky that
was ~0.74 ms a frame (#4664). A tag is now built only for an entity vanilla
would render, its culling box grown by 0.5 in the frame's frustum
(EntityRenderer.shouldRender), checked before any text work; armor stands
likewise. Status and native snapshot queries still see every tag.

other_players finds each player's entity through one id->index map instead
of scanning every entity per player (the first match, as iter().find).
2026-11-06 03:47:29 +03:00
Ludovic Marie 195515000a Merge pull request #10112 from horizon-rust/pvp/legacy-effects
🤖 Agent: merging. The 1.9 hit sounds and particles are filtered on receipt (remote.rs, lan_particles.rs), so nothing sent changes. Hurt sounds and crit particles stay, and 1.8 hit sounds are off by default.
2026-11-06 01:46:16 +01:00
rison1337andCodex GPT-6 3f11bff706 Keep blob-shadow culling enabled with native mods
Remove the blanket trait-mod presence bypass while preserving shader-pack guards. Verify the projected shadow in a game with an active native camera hook.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 05:44:30 +05:00
Ludovic Marie 1ff952a4f0 Merge pull request #10111 from horizon-rust/pvp/legacy-hand
🤖 Agent: merging. Legacy only moves the hand's matrices, and only for items with vanilla's flat display (pack-moved and post-1.8 items keep 26.3's). It's off by default, and the renderer is the only reader of the equip state it changes. On scripted fights against a vanilla server, #10117's Modern-vs-Legacy packet comparison matched on every scenario that is stable between two identical clients.
2026-11-06 01:43:50 +01:00
CocofireHDandopencode 27b110085e Docs: note the Zoom row on the Extras page
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:40:05 +01:00
CocofireHDandopencode 5bb149a282 Extras: a Zoom row that reaches the mod that replaced the extra
Zoom moved from the built-in extras to Horizon's official native mod
(#2030), so the Extras page lost its Zoom setting. Players looking for it
there, including the macOS report in #8713, found nothing.

The Extras page now keeps a Zoom row: with the mod built in it is the mod's
own on/off switch plus a Settings button that opens its page; without the
mod it opens the Mods page where it is installed. The cause is not
macOS-specific: every platform lost the row when Zoom moved.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:40:04 +01:00
rison1337andCodex GPT-6 4b3116c54f Keep flame culling enabled with native mods
Remove the blanket trait-mod presence bypass. Retain shader-pack and TAA guards, and document the native camera-hook image checks.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 05:37:40 +05:00
PzmuV1517 7b2ce7a1bc Shaders: the shader screen's words in every language
The shader screen's 17 new captions and descriptions, in the 86 languages besides English.
2026-11-06 02:30:40 +02:00
PzmuV1517 9e90050c60 Shaders: the old shader pack pages go
The shader screen (shader_ui) draws the pack list and every pack's settings,
so the options screen's own rows for them go: the Shader Packs and Shader
Options button lists, a Horizon pack's option pages, their actions and the two
pack slider kinds, with the label fitter only they used and the four lang keys
only they read.
2026-11-06 02:30:40 +02:00
PzmuV1517 131d5d73e2 Shaders: the shader screen reads the picked pack's settings however it opens
A button that pushes the Shader Packs page (as one on the Options screen would) listed the packs but left the picked pack's settings unread until a pack was picked again. Every way into the screen now reads them with the list.
2026-11-06 02:30:40 +02:00
AxxioandAntigravity b08f1074b1 docs: remove stale "not ported" notes
Co-Authored-By: Antigravity <[email protected]>
2026-11-06 01:28:10 +01:00
AxxioandAntigravity 9a076b6a24 docs/README.md: index missing player, launcher and platform pages
Co-Authored-By: Antigravity <[email protected]>
2026-11-06 01:26:57 +01:00
Horizon agentandClaude Opus 5.5 b40c996fb9 Mob looting: walk only the items in the cells a reach meets
A mob near some dropped item tested every item on the level (2,000 in
the load test: 1.5 ms a tick). ItemAreas now keeps the items of each
cell, and ItemScan walks the list in its order from one item in a met
cell to the next, testing what the scan tested; it keeps up with the
items the walk removes, and tests every item from there on if the list
changes any other way. The villagers' pickups walk the same way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 01:26:11 +01:00
PzmuV1517 f488baa0ab Shaders: the shader pack list and each pack's settings in the settings frame
Shader Packs... opens a screen in the frame of the graphics and controller
settings (settings_shell) instead of two vanilla button lists. Its first page
lists the installed packs (a tick box on the one in use; picking it again
turns shaders off), the pack folder, and Horizon's own Shadow Cache under
"Built-in settings". Under the picked pack's name, the page list holds the
pack's own screens (a shaders/ pack's screen.NAMEs, a Horizon pack's
[[pages]]), as "Pack settings": switches, sliders, choices, profiles and links
to deeper screens, with the pack's own descriptions in the strip. The search
finds an option on any of the pack's screens. A shaders/ pack's changes apply
with Apply or when the screen closes; picking another pack keeps the last
one's changes first.
2026-11-06 02:24:17 +02:00
Florian ERNSTandCodex 1d13a300fa Keep guest respawn world information separate from personal landing
Co-Authored-By: Codex <[email protected]>
2026-11-06 01:23:39 +01:00
Horizon agent 99e1caf4db Merge remote-tracking branch 'origin/main' into perf/sculk-entity-events 2026-11-06 01:22:32 +01:00
Horizon agent 400335608e Merge remote-tracking branch 'origin/main' into perf/mob-loop-overhead 2026-11-06 01:22:31 +01:00
CocofireHDandopencode 54642a5e80 Pin the hopper minecart's one-item-a-tick transfer (#8668)
MinecartHopper.tick has no transfer cooldown in 26.3: it calls tryConsumeItems every tick, and consumedItemThisFrame caps a tick at one item even when makeStepAlongTrack tries too. The reported pre-#2250 whole-stack pickup is gone; add the regression test that catches it coming back, and cite the per-frame cap next to hopper_cart_suck.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:20:48 +01:00
Ludovic Marie e5875b0b19 Merge pull request #10342 from horizon-rust/agent/native-call-attribution
🤖 Agent: launcher-only fix in native mod error tracing; fmt/clippy/613 lib tests green on the desktop, new test fails without the fix.
2026-11-06 01:18:17 +01:00
Horizon agentandClaude Opus 5.5 1ab39a15ec Native mods: an error in the call horizon-mixins generates for a helper names the helper's mod
The launcher traced a compile error to a mod only inside the helper function itself. horizon-mixins
wraps each helper in a block that calls it, `{ fn helper(…) { … } helper(…) }`. When Horizon
changes a type the helper's signature mentions (flair 1.0.0: `extra` became five-element tuples),
the error lands on that generated call, so no mod was named and the whole build failed instead of
leaving that mod out. A block holding only the helper and its call now counts as the helper's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 01:16:25 +01:00
rison1337andCodex GPT-6 5905a4977f Skip offscreen burning-entity flame geometry
Cull conservative bounds of flame billboards before light lookup and vertex expansion. Keep shader/mod/TAA paths and unsupported dimensions, with emitted-geometry and edge-image coverage plus paired FPS samples.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 05:16:18 +05:00
Skr1zzzz 98d37c0887 Spans: the frame's prepare steps
HORIZON_SPIKES named almost nothing inside bench's prepare phase: with 150
players on a server and the camera facing the sky, prepare took ~0.43 ms a
frame and its spans accounted for ~0.005 ms. Each step between the fixed
ticks and the renderer now has a lap (pr_hud, pr_entities, pr_lights,
pr_frame, ...), dynamic lights two finer ones. They showed the cost: name
tags (eg_name_tags) built every frame for every player, drawn or not (#4664).
2026-11-06 03:13:25 +03:00
CocofireHD be24d83d97 Merge remote-tracking branch 'origin/main' into fix/10273-reoccuring-freezes-on-diamondfire-server-mcd
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:12:19 +01:00
CocofireHDandopencode 89ca6821b8 test: keep docs/README.md links and benchmark index resolving
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:10:13 +01:00
CocofireHDandopencode 4eafef1180 docs: index the benchmark and performance result pages
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:10:11 +01:00
CocofireHDandopencode 561f797c04 Multiplayer: a guest server's resent columns keep the packet's light
A resident column the server sends again was always relit here, for a
Horizon server's bulk edits whose packet carries the light from before
its relight. A proxy's next server resends its whole world at the same
coordinates, so every column queued light jobs for itself and its eight
neighbours, which a small CPU could not work through.

Relight a resent column only when the server announced Horizon's quiet
commands, the same feature flag Horizon servers send in configuration;
any other server's packet light is final, as vanilla applies it. A
reconfiguration resets the announcement until the new server sends its
own.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-06 01:05:31 +01:00
Evrim Cicek dcfa4b07f7 Merge upstream/main 2026-11-06 03:05:16 +03:00
Evrim CicekandClaude Opus 5.5 00b9953741 Dragon: hover's fly speed, Mth.wrapDegrees, Mth trig on the server step, scanning's 10 degrees
From review, each measured against vanilla:
- DragonHoverPhase.getFlySpeed is 1, not the default 0.6: a hovering dragon thrown upwards
  climbs back at vanilla's rate (new harness case, 12 commands off before).
- wrap / wrap64 are Mth.wrapDegrees: Java's % then one step of 360. rem_euclid lost the low
  bits of a negative angle (-10.123456 became -10.123444) every tick (unit test).
- The parts' placement, the strafe aim and the flap timing use Mth.sin / Mth.cos and vanilla's
  float casts.
- DragonSittingScanningPhase turns only while the player is more than 10 degrees off the
  heading, with Mth.atan2 and vanilla's degree constant; dist is clamped in both divisions.
- A dragon's parts start at the origin, as vanilla's part entities do until the first aiStep,
  so its first scanning turn aims from there.

The command execution harness gets "dragon scanning turn" (a dragon facing a survival player and
one turned away): 6/12 before, 12/12 now. All dragon and End harnesses match; the whole command
execution harness: 15832/15832.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:05:04 +03:00
Horizon agentandClaude Opus 5.5 e82ac45192 Format the idle-entity change
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 01:04:26 +01:00
Ludovic Marie 764df32826 Merge pull request #9987 from horizon-rust/mods/hotload-design
🤖 Agent: merging the mod hot-load design note, docs only. The point to check is the native measurement it rests on: a one-line gameplay change rebuilds in 570 s against 812 s cold, and mold doesn't help, which is why the run-time switch came before background builds.
2026-11-06 00:53:50 +01:00
Ludovic Marie 75e54413d7 Merge pull request #10217 from horizon-rust/mods/hotload-content
🤖 Agent: merging live switching of content mods. The point to check is the server's world tables: with no data pack they used to stay on the startup resources, so a switched mod's data never applied. After a live switch they are now built from the new root (data_switched), checked in game by a mod function that loads, unloads and loads again.
2026-11-06 00:53:47 +01:00
PzmuV1517 623aa1d3ea Shader packs: a texel offset given as a parameter moves the coordinate
GLSL asks a texel offset to be a constant expression; GL drivers also take one a
function receives as a `const in ivec2` parameter (Arc's FXAA samples its neighbours
that way, so its final pass was skipped in every dimension). Such an offset now moves
the coordinate by that many texels of the base level; a constant one stays an offset.
2026-11-06 01:52:10 +02:00
PzmuV1517 82e2c96a20 Shader packs: CAT_* and PPT_* are macros of the stage files too
The biome categories and precipitations custom uniforms compare with are defined for the
GLSL as well, with the same values: Sildur's Enhanced Default reads
`biome_category == CAT_THE_END` in gbuffers_skytextured, which failed on the unknown
name. BIOME_* still needs the world's registry and stays in custom uniforms.
2026-11-06 01:52:10 +02:00
PzmuV1517 ee50f8ae1a Shader packs: empty declarations, precision on parameters, infinities in functions
Three things GL drivers take that naga's GLSL front end refused:
- an empty declaration (`;`) at global scope in any version, as a macro that writes a
  function leaves after it (DrDestens: 56 programs skipped);
- a precision qualifier on a function parameter (`float rand(highp vec2 p)`,
  TrailerShaders: final and composite);
- a constant expression in a function whose value is infinite or NaN
  (`1.0 / toeX` with toeX 0, Epoch's tonemap at its defaults): it is computed at run
  time, as GL does, instead of folded into a literal that can't hold it. WGSL keeps
  refusing it.
2026-11-06 01:52:10 +02:00
PzmuV1517 bfc40195eb Shader packs: texture() and textureLod() read integer samplers
GLSL lets a pack sample a usampler2D or isampler2D with texture(); GL drivers read an
integer texture with nearest filtering, so it is the texel the point falls in. naga
rejects sampling an integer image, and Mello, Voyager, Exposa and Reverie lost the
programs that read their integer colortex buffers that way (Mello 20, Voyager 42, Exposa
48). Those calls now go to helpers that texelFetch the texel under the point, clamped to
the edge, at level 0 (textureLod: the level it names).
2026-11-06 01:52:10 +02:00
PzmuV1517 cb65d4f80d Shader packs: a global constant reads an element of a constant array
`const vec4 s = vec4(scale[0].x, ...)` over a constant array read the index and the
array from the function's arena while folding in the module's: naga panicked (index out
of bounds) and Ebin Resurrected failed to load. Both now come from the global arena.
2026-11-06 01:52:10 +02:00
PzmuV1517 04116c33c6 Shader packs: real_packs reads past a pack that panics and sums up each pack
A panic in one pack no longer stops the run: it is caught, and each pack ends with a
SUMMARY line of the programs skipped in each dimension. A pack without shaders.properties
keys is read as one (every key at its default), not a failure.
2026-11-06 01:52:10 +02:00
PzmuV1517 1da773a501 Shader packs: a matrix built from one vector takes its components column by column
`mat2(v)` with a vec4 `v` is the matrix with columns v.xy and v.zw in GLSL, as when the
components come as several arguments. naga's GLSL front end made each column the whole
vector, which failed validation ("Composing 0's component type is not expected"):
Bloop's water normals (prepare1) were skipped.
2026-11-06 01:52:10 +02:00
PzmuV1517 a02c7fcd4b Shader packs: DH_BLOCK_* are defined, with or without Distant Horizons
Packs compare block ids with Distant Horizons' block materials outside any
DISTANT_HORIZONS guard (Exposa: `blockID == DH_BLOCK_LEAVES`), so Iris defines them in
its standard macros whether the mod is there or not. Exposa's composite and composite1
failed on the unknown names; DH_BLOCK_UNKNOWN to DH_BLOCK_ILLUMINATED are now 0 to 15,
the mod's EDhApiBlockMaterial numbers.
2026-11-06 01:52:10 +02:00
PzmuV1517 19f87e8059 Shader packs: an integer vertex output travels flat without saying so
GLSL asks a fragment input of integer type to be `flat`, but since 4.30 only the
fragment stage's interpolation qualifier counts: a vertex stage may write
`out uint frag_worldTN;` and GL drivers link it to `flat in uint`. naga left such an
output without interpolation and refused it (Alpha Piscium: 111 programs). An integer
vertex output or fragment input with no qualifier is now flat.
2026-11-06 01:52:10 +02:00
PzmuV1517 83e2920c5e Shader packs: a paste that makes no single token stays its tokens
`colors2_colorspaces_ ## a ## _to_ ## b ##(x)` (Alpha Piscium) pastes the name's last
part to its `(`. The preprocessor made one token of `..._6_to_8(`, which named no macro,
so the call to the function-like macro `colors2_colorspaces_6_to_8` was left for the
compiler as an unknown function. As GL drivers' preprocessors do, the paste now stays the
tokens it is made of, and the name is rescanned with its `(`.
2026-11-06 01:52:10 +02:00
rison1337andCodex GPT-6 9734a8a255 Cache unchanged entity name-tag glyph layouts
Retain bounded local text quads while updating camera transforms, lighting and cosmetic marks every frame. Invalidate on text/font/atlas/bidi changes and bypass animated or inline content.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 04:49:27 +05:00
Florian ERNSTandCodex cd7d6a3dcc Preserve inherited squid damage acceptance result
Co-Authored-By: Codex <[email protected]>
2026-11-06 00:48:21 +01:00
Ludovic Marie e0bd7cd69a Merge pull request #8742 from Akirabane/feat/7484-request-logs
🤖 Agent: the request log reads the player's name before the request runs, so a release or drop that removes the guest still logs who it was. The e2e this PR adds ran on this exact stack against current main: 31/31 over real control sockets with horizon-bots.
2026-11-06 00:47:45 +01:00
Ludovic Marie be04fbb531 Merge pull request #8735 from Akirabane/feat/7484-import-position
🤖 Agent: pushed 4020e2537 for martipops' review. It refuses a rotation that overflows f32 (1e39 became inf/NaN after the cast), refuses a named position when the record still has a RootVehicle to restore (the vehicle would undo it), keeps recentered on retries, and documents that velocity is the server's body only. Tests added for each; union-tested on the desktop.
2026-11-06 00:47:41 +01:00
Ludovic Marie 745956e29b Merge pull request #8730 from Akirabane/feat/7484-import
🤖 Agent: the commit point holds. place_arrival writes the .dat with write_dat_durable plus the advancements and stats with write_durable, each a Result, before ok, and the player stays held on any error. Retries after the commit answer through placed_arrival. martipops' two reviews are answered point by point in the comment above. server_transfer_e2e passed on the final stack (31/31).
2026-11-06 00:47:37 +01:00
Ludovic Marie 7b36dd82a3 Merge pull request #8716 from Akirabane/feat/7484-held-player
🤖 Agent: with [transfer] off, no login is expected, so held stays false and watches() equals here(): every here→watches swap in hub.rs and the loading_chunks refactor are no-ops for ordinary servers. With it on, a held player is a viewer but not tracked, and gets list changes through broadcast_watching. Union-tested with main and the stack on the desktop, plus server_transfer_e2e (31/31).
2026-11-06 00:47:32 +01:00
Horizon agentandClaude Opus 5.5 153044c095 Sculk: a mob's game event costs nothing when nothing can hear it
tick_mob_sculk_events posts every loaded mob's footsteps, landings and
splashes. Each event looked its mob up by a scan of every mob (for the
context and the supporting block) and asked every mob whether it was a
warden or an allay, so a server with thousands of mobs paid three scans
of them for each footstep near no listener.

Now, as GameEventDispatcher only visits the listeners around the event:
while the mobs' events are posted, mobs are found by id through a slot
map, the wardens and allays are listed once with their positions, and
an event with no sculk block in reach and no warden or allay within 24
blocks skips the context and the listeners. A mob that stands as it
stood with no step due (every mob outside the simulation distance)
leaves at once, and a mob riding nothing, as before, skips the mount
bookkeeping. Mods still get every event (native_game_event).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:43:01 +01:00
Horizon agentandClaude Opus 5.5 f43950f5fb Design note: the measured times, and what PRs 2 to 4 did
Native rebuild times measured on the desktop, live switch times in game
on the ThinkPad and the content root on the desktop. Option A as built:
trait mods switch at run time, RenderHooks and the launcher's build-all
come next. The browser's buttons and index fetch as implemented.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:38:39 +01:00
Horizon agentandClaude Opus 5.5 b0aacebaab Mods hot load: a test follows the root-per-mod-set rule
blocks_and_items_declared_in_code_get_ids expected a changed declaration
to rewrite the same root's stamp; each set now has its own root, so it
checks the new root and that the old one is left as it was. Found by
running the content-root tests on real prepared resources.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:37:43 +01:00
Horizon agentandClaude Opus 5.5 1d8a1699ad Mob loop: villagers test reach before item tags, no player view copies
villager_pickups tested every loaded villager against every dropped item,
looking the item up in the villager food table and tags before the reach:
on a server with hundreds of villagers and thousands of items that was
most of the mob phase. The reach comes first now, and a villager with no
item in its cells skips the scan (ItemAreas, as the looting passes do).

Mobs outside the simulation distance only check despawning, which reads
the nearest player and the difficulty: they no longer copy the player
view they see. A ticking mob borrows its guest's view for its tick (the
tick reads it through &MobWorld) instead of cloning it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:30:30 +01:00
Ludovic Marie 42e6d9cc49 Merge pull request #10263 from foudretbr/fix/unknown-block-state-panic
🤖 Agent: unknown states now read as air at the three places they enter (chunk palettes, block updates, BlockFacts::get), and the palette scan is skipped when nothing is unknown, so a vanilla-width chunk costs one maybe_has over its palette. Vanilla would disconnect on such an id (MissingPaletteEntryException) instead; air matches what Horizon already did for a wider host, so I took it. Union-tested on the desktop (clippy, wide_states, horizon-items).
2026-11-06 00:29:30 +01:00
Ludovic Marie 369e466faa Merge pull request #10080 from foudretbr/prepare-ignore-global-java-options
🤖 Agent: removing JAVA_TOOL_OPTIONS, _JAVA_OPTIONS and JDK_JAVA_OPTIONS in java::command reaches every exporter JVM and javac but not the game's own launch, so a player's global -XX:+UseZGC still applies in play and only stops clashing with BATCH_OPTIONS' SerialGC during preparation. Built and tested in a union on the desktop (clippy, horizon-prepare tests).
2026-11-06 00:29:25 +01:00
Horizon agent 4f3355c151 Merge remote-tracking branch 'origin/main' into perf/lan-entity-state-cache 2026-11-06 00:26:38 +01:00
Horizon agentandClaude Opus 5.5 34ddea78e1 Merge #8735's review fixes into #8742
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:22:26 +01:00
Horizon agentandClaude Opus 5.5 4020e25379 player-import: refuse an overflowing rotation and a named position over a vehicle
Answers martipops' review of #8735:
- A rotation finite as f64 but past f32::MAX became an infinity (and a NaN pitch) after the
  cast; it is refused now, like any rotation that is not a number.
- A record with a vehicle still to restore (RootVehicle) seats the player back on it at the
  vehicle's own place, which undid a position the request named; the two are refused together.
- A retried import of the same hand-over, or a retry after the commit, answers the same
  `recentered` as the first reply (kept on the guest until it is placed).
- docs/server.md says the velocity is the server's only: the arrival's teleport carries no
  motion, as a vanilla login's does not.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:22:22 +01:00
Horizon agentandClaude Opus 5.5 7dcc5b46f3 Server: send idle entities' changes as ChunkMap does, describe them bare (#10253)
ChunkMap.tick runs ServerEntity.sendChanges only for an entity that changed
section, needs sync, or is in DistanceManager.inEntityTickingRange. Horizon
ran it for every tracked entity, so an idle one sent its position every 60
ticks and advanced its update clock. EntityState::idle now carries the
range (the simulation level, Levels::in_entity_ticking_range), and the
tracker skips sendChanges for an idle entity that stayed in its section and
needs no sync; a lead change still goes out at once (Leashable).

The game then stops describing such entities in full every tick: an idle
mob or item that was in the tracker's last pass at the same place, carries
no passengers, and is already tracked by every client within its tracking
range is described bare (where it is, none of its data). The tracker never
reads its data and never pairs a bare state, so every pairing still sends
the entity as it is (game/lan_idle.rs).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:21:37 +01:00
Skr1zzzz d5479156d7 Multiplayer: the hidden player list and its skins stop costing per player
draw_tab_list ran every frame and, through remote_tab_players, cloned every
listed player and formatted two object-skin keys for each before it knew
whether the list was shown; update_remote_skins then searched every entity
once per level player and cloned the whole skin map. With 150 players and
none drawn a frame still cost ~0.4 ms more than with none (#4664).

- The list's visibility comes from the listed count; the players are built
  only while it is shown or when it opens/closes (narration).
- The id:/name: object skins publish only when a player, its skin or the
  atlas's object skins changed (a fingerprint and an atlas revision), so
  inline faces keep the server's skin as before.
- update_remote_skins finds players and mannequins through one id->index map
  and moves the resolved skins in instead of cloning them.
2026-11-06 02:18:39 +03:00
masa-masa-0314andZCode f221ae33ef Realms: the invite-code list and its edit screen
The Players tab's second button now opens RealmsInviteCodesScreen: the count in the
title, the subtitle, a row per code with its expiration (or Never), Copy / Edit / Delete
per row, and Create. RealmsEditInviteCodeScreen edits one code: its active flag, the
seven InviteCodeExpiration choices (a click on the slider track picks the nearest — this
screen has no drag event yet), Copy to the clipboard (a new Outcome::Copy the title
turns into Action::CopyText), Delete, Done and Back.

RealmsClient gains inviteCodes (GET api/v1.0/invite/codes?realmId={id}),
createInviteCode (POST .../invite/codes), updateInviteCode (POST .../update) and
deleteInviteCode (DELETE .../delete/{code}), all on the invite-code service with the
token in x-mojangauth, with InviteCode's DTO. The existing redeemInviteCode now goes
through the same helper. An existing expiration reads back as the closest of the seven
choices (InviteCodeExpiration.closestTo).

Co-Authored-By: ZCode <[email protected]>
2026-11-06 07:16:04 +08:00
Ludovic Marie 2b74332fbe Merge pull request #10117 from horizon-rust/pvp/wire-stream-legacy
🤖 Agent: merging. The default --clients vanilla,horizon run is unchanged, and so is main's one-client capture (now 'one or two of'). horizon-legacy only adds a horizon-extras.txt in its own scratch home. On the desktop, Modern vs Legacy matched on 14 of 16 scenarios; eat and dig_teleport also differed between two identical Modern clients there.
2026-11-06 00:13:00 +01:00
Ludovic Marie f563b7aafe Merge pull request #10308 from horizon-rust/fix/launcher-pictures-gpu-limit
🤖 Agent: merging. Pictures are now sized to the window × scale and capped by the atlas limit (the wgpu adapters' min on Linux, 16384 on Metal/DX), instead of a fixed 3840. RSS stays flat at 173 MiB for 5 min on a real GPU. One thing to watch: the Linux probe opens its own Vulkan/GL instance at startup on a background thread, and until it answers pictures cap at 8192.
2026-11-06 00:12:56 +01:00
Skr1zzzz b73ef3faa3 Shader packs: material maps share the stitched colour atlas's layout
With a stitched colour atlas (sprites at their own sizes), the normal and
specular atlases were still packed as a grid by PackAtlas::material, so a
block's material texels sat under another sprite's colour (#9570). The
companion atlases now reuse the colour atlas's size, rectangles and padding;
each map is nearest-sampled from its single level into its sprite's
rectangle (an fs_material entry point), and animated sprites copy into the
same rectangles. The grid path is unchanged.
2026-11-06 02:12:54 +03:00
Evrim CicekandClaude Opus 5.5 b7841886cf A summoned Ender Dragon hovers where it was put, as vanilla's does
EnderDragon's constructor sets EnderDragonPhase.HOVERING; only EnderDragonFight.createNewDragon
sets the holding pattern. Dragon::new started every dragon holding its pattern, so a dragon from
/summon, a spawn egg or a save without DragonPhase flew off. It now starts hovering, and
create_new_dragon sets the holding pattern.

Hovering had never run on the server, and the flight step diverged there: the target is a hair
away, where Vec3.normalize returns zero under 1.0E-5 and vanilla's Mth.sin/cos table and
Mth.atan2 turn the dragon differently from the exact functions. The step now uses them, the
doubles of moveRelative's input vector and vanilla's 0.91F and 0.8F as doubles.

The command execution harness gets "summoned dragon hovers" (phase and position every 20 ticks
over 120): 0/13 before, 25/25 now. The dragon harnesses (server dragon, contacts, NoAI, not the
fight's, death outside the End, death roar, forced End, spawn eggs in fluids, dispensers) match.

Fixes #10285

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 02:11:40 +03:00
rison1337andCodex GPT-6 868e494b0c Cull off-screen blob shadows before scanning blocks
Use conservative projected-shadow bounds, retaining shadows whose casters are outside the view. Keep shader packs and native mods on the original path. Add resource-backed bounds coverage and measured FPS results.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-06 04:11:09 +05:00
AxxioandAntigravity 576c5942f1 launcher: fix account skin arm-type segmented control overflow and caption layout
In the glass theme account settings (skin_panel), long translated option
labels such as French 'Bras classiques' protruded outside the segmented
control pill due to an inherited ml(40px) margin and cross-axis stretching.
Additionally, the multi-sentence caption wrapped into a 10-line block inside
the narrow column, clipping off the bottom on 1080p displays.

- Remove the ml(40px) margin in glass theme mode.
- Set items_start() on the control column so segments size to natural width.
- Adjust segments option padding to 12px with flex_none and justify_center.
- Reorganize skin_panel so the caption spans full width across the bottom.
- Adjust Microsoft card width to 360px and reduce vertical padding in glass_who_plays.

Co-Authored-By: Antigravity <[email protected]>
2026-11-06 00:10:36 +01:00
Skr1zzzz 575fadbeeb Friends: Cancel and Escape go back to the screen that opened it
Join a Friend opens from the title screen's Friends button and from the
server list. Both exits always went to the server list, so the title's
entry landed the player on a list they never opened (#9190, item 10).
The page now remembers whether the title opened it.
2026-11-06 02:10:26 +03:00
Horizon agentandClaude Opus 5.5 f8c3edd81e launcher: make pictures at the drawn size, capped by the GPU's limit
#10236 capped screenshots and the background at a fixed 3840 px, so an
8K screen lost detail. Pictures are now made at what the screen shows:

- viewer and background: the window's longer side x its scale factor
  (7680 px on an 8K screen), tiles 360 points x the scale factor, in
  256 px steps so a resize doesn't remake them every frame;
- never above the biggest texture GPUI's atlas holds: on Linux the
  smallest max_texture_dimension_2d over wgpu's Vulkan/GL adapters
  (asked once in the background, 8192 until known), elsewhere the fixed
  16384 of the Metal and DirectX atlases. A larger picture would make
  the atlas add a max-size texture on every frame (zed#66595).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:10:21 +01:00
Florian ERNSTandCodex 6df80522aa Omit the saved lodestone tracker true default
Co-Authored-By: Codex <[email protected]>
2026-11-06 00:08:11 +01:00
MSedgeMC f16012e26c Entity saves: a UUID bridge for references to a mob
A save can now name an entity it only knows as a `Who`: `mob_to_nbt` takes a
`uuid_of` resolver, `who_uuid` writes a `Who` as a UUID and `who_by_uuid` turns a
stored UUID back into the `Who` of a loaded player or mob. `EntityMemory::uuid_of`
supplies the id -> UUID map the game layer keeps, and the game layer's `mob_by_uuid`
now resolves through the same seam.

The neutral mobs' `angry_at` is the first writer: anger at another mob now saves
that mob's UUID instead of nothing. The warden's suspect list and the shulker
bullet's target are the two follow-ups this bridge unblocks; neither is written
here.
2026-11-05 23:06:45 +00:00
foudretbr 5a92cfb6b1 launcher: retry native cache fixture exec on ETXTBSY and copy via temp rename
Under Linux, executing a freshly copied test fixture can fail with ETXTBSY (os error 26) if another test thread forked while the file was being written. Copy the fixture via a temporary file before renaming it into place, and retry exec/spawn if ETXTBSY occurs.

Fixes #10271
2026-11-06 00:04:15 +01:00
Horizon agentandClaude Opus 5.5 5ba18cae86 PvP & HUD: rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 f06b417ac4 Server: the [combat] test looks for the section, not the template's comment
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 346a0a8d81 lan_e2e: legacy-pvp hits right after a hotbar swap for an uncharged hit
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 fc8781c2ce PvP & HUD: document Legacy's sounds and particles
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 6d36365aeb PvP & HUD: rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 3400a0316b PvP & HUD: 1.8 knockback under legacy combat rules
With the legacy rules on, a player's knockback follows 1.8.9's
EntityLivingBase.knockBack: the motion halves and the 0.4 push lifts in the
air too (26.3 lifts only from the ground), and knockback resistance is a
chance to take none instead of a cut. The sprint and Knockback push is added
on top as attackTargetEntityWithCurrentItem's addVelocity (0.1 up) instead
of going through knockback, for players and mobs alike.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 4d4c3ff32f Server: [combat] legacy = true in server.toml for 1.8 combat rules
horizon-server accepts and keeps a [combat] section (off by default, left out
of the file while off); the dedicated game reads it itself, like
[anticheat], and turns on the legacy rules a hosted world's PvP page switch
gives. A wrong section is logged and fights keep 26.3's rules.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 932050ffab lan_e2e: legacy-pvp scrolls the PvP page to its switch
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 04c3b710cf PvP & HUD: Legacy leaves out the sweep, damage hearts and 1.9 hit sounds
Under Combat feel Legacy the client draws no sweep_attack or
damage_indicator particles a server sends (1.8.9 had neither), and with
'1.8 hit sounds' plays none of the entity.player.attack.* sounds 1.9
added. Hurt sounds, crits and enchanted hits stay. Only what this client
shows changes; nothing is sent.

1.8.9 drew no red tint over armour (LayerArmorBase.shouldCombineTextures
is false, so setBrightness skips it) and the body's flash is red at 0.3,
as 26.3's: both already match.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 a6f2414081 PvP & HUD: document Combat feel
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 62db23c23b PvP & HUD: rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 eabc4160a8 PvP & HUD: Combat feel Legacy draws the hand as 1.8.9 did
Combat feel: Modern (default) or Legacy (1.8.9). Legacy changes only the
picture: a flat item in first person takes 1.8.9's ItemRenderer pose
(swing, eating and drinking, bow pull) through 26.3's own display
transform, a sword is held up while use is held (1.8.9's block pose,
swinging as 1.7.10 did), the main hand no longer dips after each hit,
and the attack indicator hides unless the player keeps it. The clicks
and every packet stay 26.3's. Poses 1.8.9 lacked (crossbow, trident,
spear, brush) and resource packs' own displays stay modern.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 cccc296d9a PvP & HUD: the fall distance is already an f64
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agentandClaude Opus 5.5 270e435b3f PvP & HUD: 1.8 combat rules in worlds you host
A PvP page switch, off by default: single player and LAN worlds resolve
melee hits by 1.8.9's rules (EntityPlayer.attackTargetEntityWithCurrentItem):
no attack cooldown and no sweep, 1.8.9 weapon damage (ItemSword 4 / ItemAxe 3 /
ItemPickaxe 2 / ItemSpade 1 plus ToolMaterial), Strength x(1 + 1.3 per level)
and Weakness -0.5 per level, Sharpness 1.25 per level, and critical hits that
allow sprinting and need no charge. Only a game that is the server uses them;
a window playing on a server never does, so what it sends stays the same.

The window passes the switch to its server thread each frame, like the
distances. lan_e2e's legacy-pvp check measures two diamond axe hits 12 ticks
apart: 7 and 7 under the rules, 9 then a cooldown-cut hit without.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:01:54 +01:00
Horizon agent 5f625c748e Merge remote-tracking branch 'origin/main' into pvp/wire-stream-legacy
# Conflicts:
#	tools/test_wire_stream_parity.py
2026-11-06 00:00:26 +01:00
Ludovic Marie 2266c4de14 Merge pull request #10211 from horizon-rust/pvp/hud-editor-main
🤖 Agent: merging. These are #10079's commits merged with main and nothing else; #10079 had landed in the already-merged pvp/hud-layout. The editor opens only from a world and is the only screen that writes the layout.
2026-11-06 00:00:04 +01:00
Ludovic Marie 76c0362eb8 Merge pull request #10225 from horizon-rust/agent/native-broken-on
🤖 Agent: verified on the desktop: horizon-mods 36 tests, launcher 589 tests, clippy -D warnings clean. broken_on is read only from the registry index (the publish job adds it from horizon-mods' daily native-compat run). The warning shows only when the entry's Horizon is the commit the mod would build on, so a mod pinned elsewhere doesn't warn.
2026-11-05 23:57:29 +01:00
Horizon agent 12c5d55a95 Merge commit '765da7031e28448d840a218a2b87686d8fb6d59d' into agent/native-broken-on 2026-11-05 23:51:42 +01:00
Ludovic Marie 765da7031e Merge pull request #10082 from horizon-rust/pvp-safe/wire-stream-harness
🤖 Agent: the harness itself: 16 scenarios, real vanilla 26.3 client vs Horizon, per tick and field; on main 11/16 match, with the series + #9918 16/16. Named-only and solo in parity_hosts (needs a virtual screen, ~15 min). Merge of main resolved a game.rs mod-list conflict (tick_profile + tick_script).
2026-11-05 23:50:44 +01:00
Ludovic Marie 89e625981b Merge pull request #10084 from horizon-rust/pvp-safe/held-slot-tick
🤖 Agent: wire stream harness (#10082) on main: number keys' SetCarriedItem left a tick early (hotbar_swap, 15 ticks; dig_teleport) and a key tapped with a swing at the sky sent the slot before the Punch; with this change every tick matches vanilla. Head = cc7200cd7a's +35 −43 plus merges of #10083, #10085 and main (conflicts in remote.rs resolved to the tree the harness after-run built and passed).
2026-11-05 23:50:40 +01:00
Ludovic Marie 1f09d99dad Merge pull request #10215 from horizon-rust/agent/native-build-health
🤖 Agent: verified end to end on the desktop: with 30 registry mods on Nightly 2026-11-05, main's launcher fails the whole build (eatinganimation E_TARGET_NOT_FOUND), and this one builds 22 and leaves out 8 with their reasons. MIXINS_REV moves to 5a64af9 (horizon-mixins#27, #28). The risk to watch: a compile failure costs one full recompile per round, capped at 3.
2026-11-05 23:50:36 +01:00
Horizon agentandClaude Opus 5.5 fe49561c78 Merge main into held-slot-tick after #10083 and #10085 landed
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:48:21 +01:00
CocofireHDandopencode 22807a3f3a Audio: play multi-channel buffers direct like OpenAL
Stereo music and records were downmixed to mono and panned by the camera, so turning away hard-panned them and distance attenuation could silence them. OpenAL Soft plays a multi-channel buffer without spatialization or distance attenuation (AL_SOFT_source_spatialize auto), as vanilla's HRTF-off context does.

Keep the decoded channel count through the cache and mixer; mono buffers keep the existing panning and linear attenuation. Add a mixer test that stereo frames stay direct and unattenuated at any camera angle or distance.

Fixes #10037

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 23:47:14 +01:00
Ian WorleyandClaude Opus 5.5 b27965b4ca test: pass the drop-motion and install.sh signature tests on Apple silicon
Vanilla's Math.sin is HotSpot's intrinsic, which rounds one of the drop
test's throws a bit lower on AArch64 than on x86-64; a vanilla 26.3 run
on an M-series Mac gives the AArch64 value. The install.sh fixture only
holds the Linux x86-64 archive and ran the cases with /usr/bin's
OpenSSL, LibreSSL on macOS: a fake uname picks that archive, and the
cases get the OpenSSL 3 the script checked for.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:41:36 -06:00
Horizon agent 152eaed3bd Merge remote-tracking branch 'origin/agent/native-build-health' into agent/native-broken-on 2026-11-05 23:36:37 +01:00
Horizon agent efbd46aaad Merge commit '6d99e14ff0c70ae60bc56b67829b526409fa068e' into agent/native-build-health
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 23:36:06 +01:00
Ludovic Marie 6d99e14ff0 Merge pull request #10189 from horizon-rust/pvp-safe/click-slot-order
🤖 Agent: inventory in the wire stream harness: a shift-click's changed slots went out 10, 37 where vanilla's Int2ObjectOpenHashMap iterates 37, 10; now byte-identical, unit test pinned to the captured order.
2026-11-05 23:30:06 +01:00
Ludovic Marie 6421a384f5 Merge pull request #10188 from horizon-rust/pvp-safe/pick-view-vector
🤖 Agent: bridge in the wire stream harness: 19 of 19 UseItemOn cursors differed from vanilla by ~6e-5 on main (double trig in the pick ray), all bit-identical with Mth's float table. Hot path cost: one table lookup per frame instead of libm sin/cos.
2026-11-05 23:30:02 +01:00
Ludovic Marie 39e99fdbd6 Merge pull request #10085 from horizon-rust/pvp-safe/vehicle-order
🤖 Agent: read against LocalPlayer.sendChanges and AbstractBoat.tick (rider look, vehicle, then sprint; paddles first). No riding scenario in the harness yet, so the proof is the read and the unchanged 16 scenarios. Land before #10084, which merges this branch.
2026-11-05 23:29:58 +01:00
Ludovic Marie ad1a6a3101 Merge pull request #10083 from horizon-rust/pvp-safe/dig-sequence
🤖 Agent: verified with the wire stream harness (#10082): on main, dig_teleport answered a teleport mid-dig with a new START (sequence 34) instead of vanilla's ABORT; with the series every tick matches. pr_ready clean except the desktop nix-shell's uname-less install.sh test.
2026-11-05 23:29:54 +01:00
Horizon agentandClaude Opus 5.5 e692430ffe Merge #10085 (pvp-safe/vehicle-order) into held-slot-tick: both rewrite remote_send_state
The held slot leaves remote_send_state (this PR); the boat's paddles go first and a
passenger sends its look, vehicle, then sprint (#10085).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:26:29 +01:00
Horizon agentandClaude Opus 5.5 861d3168cb Merge #10083 (pvp-safe/dig-sequence) into held-slot-tick: both edit the login's reset
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:26:03 +01:00
Horizon agentandClaude Opus 5.5 3431d0e18f wire stream: a one-client run (--clients horizon) stops after its capture
A loaded desktop's vanilla client failed to log in; rerunning Horizon alone and
comparing with the earlier vanilla capture crashed on the missing client.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:23:56 +01:00
Evrim CicekandClaude Opus 5.5 d95d0be4e4 /damage on the dragon: a perched dragon refuses a direct arrow, the hit pushes it, the right guest gets the kill
From review:
- A perched dragon shrugs off a direct arrow, trident or wind charge given as `by`
  (AbstractDragonSittingPhase.onHurt is 0, under 0.01): the command is refused.
- dealDefaultKnockback: a counted hit of a type not in #no_knockback pushes the dragon away from
  the command's source position (`at`, else the direct entity), or in no direction without one.
- credit_dragon_kill switches to the credited guest whenever they are not the one acting, as the
  mob kill credit does, so a guest's command that credits another guest awards that guest.

The harness scenario summons its dragons holding their pattern (DragonPhase 0): vanilla summons a
hovering dragon, which is sitting, and Horizon one holding its pattern; that gap is separate.
The intermediate build: 36/39; now 39/39.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 01:22:38 +03:00
Ludovic Marie c4e4800d44 Merge pull request #10241 from martipops/controller/buttons-prompts-v2
🤖 Agent: the keyboard path is a mechanical move from keys.contains to held(Action), with the same default keys underneath; old horizon-controller.txt files keep their buttons (no bindLayout line), and the pad's stack drop also reaches the server path through drop_stack. Union pr_ready green.
2026-11-05 23:19:40 +01:00
Ludovic Marie f33f49aa06 Merge pull request #10181 from martipops/fix/split-pack-overlay
🤖 Agent: a guest now gets host.vanilla_root(), not P1's overlay, so build_overlay_uncached can no longer delete the files it is building from.
2026-11-05 23:19:36 +01:00
Ludovic Marie c4c815689b Merge pull request #10059 from martipops/fix/pad-e2e-linux
🤖 Agent: harness only; hard links keep 60000 regions cheap on disk, and focus_on no longer raises StopIteration on a scrolled-out row.
2026-11-05 23:19:32 +01:00
Ludovic Marie 2aaaa76e9b Merge pull request #10056 from martipops/feat/pad-status-mapping
🤖 Agent: the router's per-frame status list is built only while a control socket is open, so splitscreen play without horizon-ctl pays nothing for it.
2026-11-05 23:19:28 +01:00
Ludovic Marie 460dbb0e5e Merge pull request #10054 from martipops/fix/ctl-socket-path
🤖 Agent: the check is SocketAddr::from_pathname, and the test compares it with a real bind at every length 1-150, so it follows the OS limit (104 on macOS, 108 on Linux) without a hardcoded number.
2026-11-05 23:19:24 +01:00
Ludovic Marie b756cf01e6 Merge pull request #10029 from foudretbr/feat/launcher-socks-proxy
🤖 Agent: SOCKS goes through ureq's own socks-proxy feature (socks 0.3.4, MIT/Apache), and the loopback SOCKS5 test proves the request reaches an .invalid host only through the proxy. Nothing changes for players without a proxy set.
2026-11-05 23:19:20 +01:00
Ludovic Marie d6bac327fa Merge pull request #9966 from Mayonesu0/fix/9633-wheel-scrolls-settings-list
🤖 Agent: matches 26.3: OptionsList inherits AbstractScrollArea.mouseScrolled, so the wheel scrolls the list and never reaches a row's CycleButton; the old key is dropped because every install saved it as true. Main merged in, union pr_ready green.
2026-11-05 23:19:15 +01:00
Ian WorleyandClaude Opus 5.5 8f4888a2ef docs(native-mods): document client world snapshots
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:14:46 -06:00
Ian WorleyandClaude Opus 5.5 d1eef82997 feat(native-api): client world snapshots for mods' worker threads
Client mods that bake from the world in bulk (a minimap's tiles, a world
map export) had to clone `game.world.chunks` and read `world_biomes!`,
which pins them to one Horizon commit. Four stable helpers replace that:

- `native_client_chunks(from, to)` shares the client world's loaded
  chunks of an area as `NativeClientChunks` (Send + Sync): block state,
  biome id, loaded, each column's top non-empty section, build height.
- `native_client_chunks_changed(&snapshot)` names the chunks that differ
  from the snapshot now. A held chunk is copied before any edit, so the
  same allocation means the same chunk.
- `native_client_biomes()` gives the biome table the terrain is drawn
  with (`NativeClientBiomes`): names and tints by `NativeBiomeId`.
- `native_client_biomes_changed(&biomes)` says when that table was
  replaced (a mod's seasons, a resource reload, another world).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:14:46 -06:00
Ian WorleyandClaude Opus 5.5 dfa637e389 fix(native-mods): a world is known once logged in, and a pack reload covers it
A connection reported its world while still connecting, before a failed
login could drop it, and a resource-pack reload's loading screen still
counted as playing.

Co-Authored-By: Claude Opus 5.5 <[邮箱]>
2026-11-05 16:14:45 -06:00
Ian WorleyandClaude Opus 5.5 897f17114a fix(native-mods): keep a friend's relay in its world identity
Round-two review finding: a long invite's identity kept only the host's
addresses when it had any. Two friends on other networks behind the
same private address and the fixed direct port (25580), each reached
through a relay, shared one NativeWorldId. The relay's address and
nameplate now stay beside the addresses; neither secret takes part.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:14:45 -06:00
Ian WorleyandClaude Opus 5.5 8f75db7d5c fix(native-mods): portal loading covers the world; friends' worlds stay apart
Review findings on the session state helpers:

- NativeClientState.screen read remote_loading(), which leaves out the
  portal loading screens because they draw no panorama blur, so a
  minimap drew over them. A connection's screen is now one predicate,
  remote_screen_up(), which remote_overlay draws by too.
- Every long friend invite showed as the same "Friend's world", so two
  friends' worlds shared one NativeWorldId and a mod's per-world data.
  A friend's world is now friend: and the invite's public part (the
  host's addresses, else its relay nameplate), never its secret.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:14:45 -06:00
Ian WorleyandClaude Opus 5.5 6975556e3d feat(native-mods): session state a client HUD reads
A minimap or waypoint mod can now read what it needs through stable
helpers instead of Game's fields, on a server's client and in single
player alike:

- native_client_state() gains screen (NativeScreenKind: playing, chat,
  death or another screen), debug_screen (F3), world (NativeWorldId:
  single player's save folder or the server's address, for per-world
  data) and sleeping_pos (the bed, LivingEntity.getSleepingPos).
- native_biome(pos): the biome at a block as Level.getBiome picks it,
  through BiomeManager's zoom.
- native_data_dir(mod_id) joins the helper catalog.

The window's connection remembers the save it joined through memory,
its own server thread's or P1's for a splitscreen seat, so the world
identity needs no mixin on open_integrated. A friend's world is named
as players see it, never by its invite secret.

Helper-only API fragment session_state.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:14:45 -06:00
Horizon agent a35ea8b72a Merge remote-tracking branch 'origin/pvp-safe/wire-stream-harness' into pvp/wire-stream-legacy 2026-11-05 23:11:05 +01:00
Horizon agent ec21e06600 Merge remote-tracking branch 'origin/main' into pvp-safe/wire-stream-harness
# Conflicts:
#	crates/horizon-client/src/game.rs
2026-11-05 23:08:51 +01:00
Horizon agentandClaude Opus 5.5 9027fcd390 Time tick_creature_entities' steps as spans for horizon-report
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:07:47 +01:00
Florian ERNSTandCodex 9c5982d8e0 Default loaded item frames down when Facing is absent
Co-Authored-By: Codex <[email protected]>
2026-11-05 23:07:30 +01:00
Horizon agentandClaude Opus 5.5 8d09d8b3f2 wire stream: a teleport's two answer ticks compare with each other, the rest on the script's clock
Aligning every tick after the teleport on its acceptance moved the script's own
packets (the button release, the 20-tick position) by the server's lag: on the
desktop vanilla got the answer at tick 26, Horizon at 29, and both clients'
packets were otherwise identical tick for tick.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:06:50 +01:00
Horizon agentandClaude Opus 5.5 25d55ff457 Merge main into mods/hotload-content (after #10104 and #10218)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:06:43 +01:00
Ludovic Marie 4f4a931b21 Merge pull request #10269 from horizon-rust/agent/redstone-catalog-harness
🤖 Agent: harness and examples-only probe, nothing players run. New head: CI's parallel shards raced on one schematic .part file; each process now writes its own (three concurrent downloads checked against the pinned digest), and parity / parity passes. Point to check: item-moving builds are reported as not comparable, never as a pass. Result: 111/111 verified contraptions match vanilla.
2026-11-05 23:05:43 +01:00
Horizon agent cabfc96e14 Merge remote-tracking branch 'origin/agent/native-build-health' into agent/native-broken-on 2026-11-05 23:00:38 +01:00
Horizon agentandClaude Opus 5.5 2044fc756c launcher: trace a compile error inside a mod's support module to that mod
The second end-to-end build of 30 registry mods on 6475f62 stopped on
errors inside carryblock's, distant_horizons', smartf3's and treefall's
support modules (made before the crate split), which named no mod: only
injected helpers were traced. An error inside the inline module a mod
declared (one the pristine source doesn't have) now names that mod, so
the build leaves all of them out in one more compile.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:00:08 +01:00
MSedgeMC 1665d177c6 test: a playing jukebox posts jukebox_play every twenty ticks
A windowless game with a resident chunk, a jukebox at (0, 64, 0) and a disc put
in through use_jukebox: 41 ticks of the jukebox tick post the event at ticks 0,
20 and 40 and nothing else, and a jukebox that is never played posts none.
2026-11-05 21:59:53 +00:00
MSedgeMC 36dac313c8 fix: a playing jukebox tells the listeners around it
JukeboxSongPlayer.tick posts GameEvent.JUKEBOX_PLAY at the block every
PLAY_EVENT_INTERVAL_TICKS (20) ticks of a song, right before the music
particles:

  37: invokevirtual #97   // Method shouldEmitJukeboxPlayingEvent:()Z
  44: getstatic     #101  // Field GameEvent.JUKEBOX_PLAY
  56: invokeinterface     // LevelAccessor.gameEvent:(Holder,BlockPos,Context)
  66: invokestatic  #104  // Method spawnMusicParticles:(LevelAccessor,BlockPos;)V

Horizon drew the note particle in the same twenty-tick branch and posted
nothing, so a sculk sensor beside a playing jukebox never clicked and an allay
never heard the song, although the stop half (JUKEBOX_STOP_PLAY with level
event 1011) was already posted when the jukebox went away.
2026-11-05 21:59:53 +00:00
Evrim CicekandClaude Opus 5.5 25bec21906 /damage hurts the Ender Dragon and End crystals as vanilla does
cmd_damage had no arm for them, so both said "Target is invulnerable".

EnderDragon.hurtServer is hurt(body, ...): a quarter plus up to 1; reallyHurt runs only with a
player behind the source or an #is_explosion type and refuses fire, and hurt succeeds either
way unless the dragon is dying or the hit is under 0.01. EndCrystal.hurtServer refuses an
Invulnerable crystal (unless generic_kill, out_of_world or a creative player), fire and the
dragon's own damage; else it is removed, and blows up unless the damage is an explosion.

The command execution harness gets a "dragon command damage" group: 15/31 before, 31/31 after.

Fixes #10265

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:59:38 +03:00
Horizon agentandClaude Opus 5.5 043705f347 Redstone catalogue: each shard writes its own schematic part file
CI runs the three shards at once on an empty .local/lra-catalog: two of them
downloaded the same schematic into one `.part` name and one rename found it
gone. The part file now carries the process id.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:58:28 +01:00
Ludovic Marie 0f5dc8d6d5 Merge pull request #10268 from horizon-rust/agent/redstone-catalog-oracle
🤖 Agent: test-only oracle (mods/horizon/src/test). Point to check: run() clears block ticks after it loads containers and comparators, and sets the lectern's page field directly. Otherwise setChanged and setPage schedule ticks or power the lectern before step 0, which is what made 29 builds differ by an extra tick (see #10269's numbers).
2026-11-05 22:55:22 +01:00
009378de29 Stop the underwater sky scan on kelp and seagrass.
canSeeSkyFromBelowWater stops when a block dampens light and is not liquid(). Kelp and seagrass hold water, so guardians under them were still rolling the 1-in-20 and spawned less often.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-05 22:53:40 +01:00
b9607ce998 Replace a bubble column when a bucket empties into it.
emptyContents destroys the block only when it is not liquid(). A bubble column is liquid and not a LiquidBlock, so breaking it first played destroy particles and a sculk vibration.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-05 22:53:40 +01:00
MSedgeMC 9fb4fde63b test: a use start is an item_interact_start vibration
A windowless game from the prepared resources, a bow in hand: the two events the
use posts are read back from the game's own event recorder, at the player's feet
(10, 64, -4). A spear, whose use effects set interact_vibrations false, posts
neither.
2026-11-05 21:49:11 +00:00
MSedgeMC d2cd07f1ac fix: starting an item use posts item_interact_start
LivingEntity.startUsingItem ends with
this.useItem.causeUseVibration(this, GameEvent.ITEM_INTERACT_START), and
ItemStack.causeUseVibration posts only when the stack's use effects ask for
interaction vibrations - the same gate and the same position stopUsingItem
already used for ITEM_INTERACT_FINISH. Horizon posted only the finish event, so
a sculk sensor beside a player drawing a bow never clicked and an allay never
heard the use start.

use_vibration takes the event now, and start_using_item calls it where vanilla
posts it: after the use is set up, before the kinetic weapon's map (which this
change leaves alone).
2026-11-05 21:49:11 +00:00
Ludovic Marie 83da211d38 Merge pull request #10267 from horizon-rust/agent/redstone-catalog-data
🤖 Agent: data and reader only, nothing players run. The catalogue is WTFPL from maybeachromium (LICENSE beside it). Point to check: tools/lra_schem.py is the one parser of untrusted downloaded schematics. It reads gzip NBT with struct only, never executes or imports from them, and rejects a palette/varint count mismatch.
2026-11-05 22:48:26 +01:00
Florian ERNSTandCodex dec4b523f3 Use neutral source for ominous item warning
Co-Authored-By: Codex <[email protected]>
2026-11-05 22:42:57 +01:00
Horizon agentandClaude Opus 5.5 b7df353fdf Merge main into fix/9633-wheel-scrolls-settings-list
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:42:57 +01:00
MSedgeMC 3236b57773 Declare no extra comment paths for the trample hook fragment
stamp_native_hooks.py refuses a fragment that lists a guarded file
(native_hooks.rs is the generated source it stamps itself), so the fragment
declared an invalid comment path and the repo-wide stamp check failed.
2026-11-05 21:35:17 +00:00
foudretbr d55c2840a7 Block states: map states past registry to air instead of panicking
A server with mods or states the client does not have can send block states outside our registry. Instead of panicking with an out of bounds slice access when indexing BlockFacts or unpacking palettes, map unknown states to air.
2026-11-05 22:34:51 +01:00
DUzzL cb7babf1cf Normalize END records whose disk numbers are equal but not zero 2026-11-05 22:32:18 +01:00
Ludovic Marie 3c7bb44aa1 Merge pull request #10220 from xxaxio/fix/10072-launcher-auto-update-race
🤖 Agent: per-home mod lock fixes the stale-manifest race; UI-thread switch/remove use try_lock_home so an update's downloads never freeze the window.
2026-11-05 22:31:06 +01:00
Ludovic Marie 51e5d01c83 Merge pull request #10145 from horizon-rust/cosmetics-fx/wardrobe
🤖 Agent: Wardrobe layer, retargeted to main after #10144. preview::Draw gains alpha and Draw::model; main's live view now builds the body with it. A companion is drawn at its anchor, an aura see-through at the feet with its turned-away faces skipped, and a trail as a short run of particles. The Wardrobe gets Companion, Trail and Aura rows, in all 86 launcher languages. Verified on this head: rustfmt on game and launcher, 71 horizon-cosmetics tests, launcher wardrobe/i18n/lang tests (23 + 7).
2026-11-05 22:29:16 +01:00
Ludovic Marie b0a738abc3 Merge pull request #10144 from horizon-rust/cosmetics-fx/render
🤖 Agent: render layer, retargeted to main after #10143. It draws companions on the GPU skin path (identical ones share one buffer) and trails and auras in one see-through span after the players. Nothing is drawn beyond 48 blocks or outside the view. Measured in-game with HORIZON_COSMETICS_LOCAL: +0.6 ms prepare at 16 wearers and +1.1 to 2.4 ms at 64, mostly the auras built on the CPU; the follow-up is #10251. Verified on this head: horizon-client clippy -D warnings.
2026-11-05 22:26:58 +01:00
Evrim CicekandClaude Opus 5.5 c572bb7f88 Port pack: folder entries don't vote in old_layout; the report's reason in one line
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:25:58 +03:00
Ludovic Marie c8eee9842f Merge pull request #10143 from horizon-rust/cosmetics-fx/settings
🤖 Agent: settings layer, retargeted to main after #10142. The unused "Show other players' auras and trails" (off) becomes "Hide other players' trails and auras", off by default; it is the cosmeticsHideFx line, shared by the game's Extras and the launcher's Wardrobe, and the old cosmeticsAuras line is dropped on save. Translated into the game's 75 languages and the launcher's 86. Verified on this head: clippy -D warnings on game and launcher.
2026-11-05 22:24:25 +01:00
Ludovic Marie 731544d1c5 Merge pull request #10218 from horizon-rust/mods/native-switch
🤖 Agent: merging the native on/off switch. The point to check is the hot path: each Hooks dispatch adds one relaxed atomic load per mod, and lists that aren't a slice of the global MODS (tests, single-mod dispatch) are never gated. RenderHooks and mixin code are left unswitched on purpose, as the PR says.
2026-11-05 22:24:20 +01:00
Ludovic Marie a27b7f20fe Merge pull request #10213 from horizon-rust/agent/smp-profiler
🤖 Agent: horizon-report format 2 (mob costs, level spans, tick_lan/sculk sub-spans, workers, network, overhead, stop/status). verify green on eb55d13; live on the SMP since 21:08Z as prof2 at 0.03 % of MSPT; the one desktop test failure is a load-sensitive 1 s login cap that passed 3/3 alone. Only spans and counters added, no gameplay change.
2026-11-05 22:24:16 +01:00
MSedgeMC 9329e154cb Merge remote-tracking branch 'upstream/main' into HEAD 2026-11-05 21:21:52 +00:00
MSedgeMC 72a1b13eae Merge remote-tracking branch 'upstream/main' into fix/trample-hook 2026-11-05 21:21:41 +00:00
Ludovic Marie 2ed3ede17f Merge pull request #10142 from horizon-rust/cosmetics-fx/runtime
🤖 Agent: runtime layer, retargeted to main after #10140. follow.rs (frame-rate independent lag, snap after a teleport, kept out of the crosshair cone and clear of the head) and trail.rs (spawns by distance walked, 24 particles per player and 512 in all) are pure horizon-cosmetics code, plus the fx_bench example. Nothing in the game calls them yet. Verified on this head: clippy -D warnings and 68 horizon-cosmetics tests.
2026-11-05 22:21:39 +01:00
Ludovic Marie 11e7d633d6 Merge pull request #10214 from xxaxio/fix/9948-music-toast-animated-sprite
🤖 Agent: any GUI sprite with an animation section now packs its first frame and animates, so the 16x64 music_notes strip no longer squashes into one slot; load-time only.
2026-11-05 22:21:35 +01:00
Evrim CicekandClaude Opus 5.5 2ea59c309b Port pack: a mod made for 1.20 is not carried or counted as come over
check already refused a jar whose data folders are named as before 1.21; carry and report did
not, so a 1.20 pack's mods were carried and counted as partial while 26.3 read none of their
recipes, loot tables, tags or structures (Prominence II: 149 of 308 "come over", 28,948 recipes
and 8,784 loot tables in recipes/ and loot_tables/).

old_layout moves to carry and reads every folder 1.21 renamed (structures/, advancements/,
tags/blocks/, ...), by the larger share: a 1.20 jar may hold a misnamed singular tag folder and
a 1.21 jar a stale plural copy. carry writes old_layout to carry-report.json and carries
nothing; the report's row says why, is not one Port can carry over, and the launcher does not
count it as portable.

Fixes #10249

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:21:33 +03:00
Ludovic Marie 8dcf45e95e Merge pull request #10171 from xxaxio/fix/9957-sulfur-cubes-parity
🤖 Agent: sulfur cube blast is TNT-interaction only with mobGriefing (NONE without), ENTITY_INTERACT sourced by the cube, tnt.disabled overlay and used stat.
2026-11-05 22:21:31 +01:00
Ludovic Marie 489a1334a6 Merge pull request #10122 from xxaxio/fix/9961-camel-second-seat-passenger
🤖 Agent: camel husk jockey seats its parched; camel capacity is vanilla's canAddPassenger size() <= 2, babies included.
2026-11-05 22:18:26 +01:00
Ludovic Marie 166addb003 Merge pull request #9994 from xxaxio/fix/9958-ender-eye-frame-drops-and-used-stat
🤖 Agent: completing the ring destroys the 3x3 opening with drops (EnderEyeItem.useOn destroyBlock(pos, true)) and awards ender_eye used.
2026-11-05 22:18:22 +01:00
Ludovic Marie f66e017f5c Merge pull request #9986 from xxaxio/fix/9793-riding-body-yaw-portal-inventory
🤖 Agent: rider body follows the vehicle kind as in vanilla (boat yaw, solveBodyRot's 85° clamp for living vehicles, on-foot turn in minecarts); portal E guard only while inside the portal this tick.
2026-11-05 22:18:17 +01:00
Ludovic Marie 2d483f7ffc Merge pull request #10140 from horizon-rust/cosmetics-fx/format
🤖 Agent: format layer of the companions/trails/auras stack, rebased onto the emotes: Slot gets Companion, Trail and Aura next to Emote, and Item::parse checks both kinds of block. Item::animate also builds its animated-part table once at parse instead of searching by name every frame, which saves about 0.3 ms a frame at 64 wearers (profiled). Verified on this head: clippy -D warnings on game and launcher, 60 horizon-cosmetics tests; nothing draws the new slots yet.
2026-11-05 22:18:13 +01:00
Horizon agent 52b0be4adb Merge remote-tracking branch 'origin/agent/native-build-health' into agent/native-broken-on 2026-11-05 22:16:44 +01:00
Horizon agentandClaude Opus 5.5 38db573502 launcher: trace a compile error to a helper by its name when formatting changed it
The end-to-end build of 30 registry mods on 6475f62 stopped on
eatinganimation's `held-frame` helper (E0063, ItemRenderState's new fields)
without naming it: the staged item_render.rs carries the helper with a
trailing comma rustfmt added, so its tokens no longer equal the plan's
helper_source. A helper is now also the function of its name in the staged
file, when the pristine source has no function of that name; two mods'
helpers of one name still name neither.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:16:23 +01:00
Horizon agentandClaude Opus 5.5 d3a00abb07 launcher: mods page never waits on an update's folder lock; fetch the index before locking
The switch and remove buttons run on the window's thread: while an
automatic update holds the game folder they now say "Wait for the current
operation to finish." instead of freezing the launcher until its downloads
end. auto::run takes the lock after the registry index arrives.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:15:52 +01:00
Horizon agentandClaude Opus 5.5 4d4872cedd Redstone catalogue: Horizon probe and the parity harness
redstone_catalog_fixture builds the same contraptions in a map-backed
horizon-world level, clicks the same levers and buttons
(Behaviours::use_without_item) and runs tick_scheduled, run_block_events
and tick_block_entities, printing what VanillaRedstoneCatalogOracle prints.

tools/test_redstone_catalog_parity.py reads the catalogue, downloads the
archive's schematics into .local/lra-catalog/ (pinned to da4dd51, one
sha256 over all 188), turns each entry into a case (switches off except
those the catalogue held on, a neighbour update on every block, then input
steps: every vector up to 8 bits, random ones beyond, random changes for
stateful builds) and compares both sides step by step. verified entries
must match; partial and unverified ones are reported. Entries whose inputs
are analog levels, lecterns or bare wire are listed as unsupported for
now. Three shards; --full runs larger sweeps and every vector up to 16
bits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:15:27 +01:00
Horizon agentandClaude Opus 5.5 603584ef2e Redstone catalogue: vanilla oracle that runs whole level ticks
VanillaRedstoneCatalogOracle builds each contraption (block states and
block entities the harness reads from its schematic) in the overworld of a
real flat-world server, one contraption per server tick. Comparators keep
their saved output, containers and lecterns get real items (set silently:
no comparator ticks, no lectern pulse), and the comparator level of every
block entity is printed for the Horizon probe. The game clock keeps
running between contraptions (LevelTicks orders chunks by trigger time);
the probe starts from the time printed.

Levers and buttons are driven with useWithoutItem, and each tick runs what
ServerLevel.tick runs for blocks: scheduled block and fluid ticks, block
events and block entities, so pistons move. After each tick it prints the
outputs that changed, and at the end of each step the tick it ended on,
whether the level went quiet and a hash of the contraption's box. A tick
budget per contraption (the probe counts the same) bounds machines that
never settle; containers at or next to hoppers, droppers, dispensers and
crafters are watched every tick, and a changed level marks the run as not
comparable (the probe does not move items). HORIZON_CATALOG_TRACE=1 adds
every block tick and every block at a step's end.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:15:27 +01:00
Horizon agentandClaude Opus 5.5 e9eed40a30 Redstone catalogue: commit the LRA catalogue (WTFPL) and a schematic reader
tools/lra-catalog/entries.jsonl is maybeachromium's catalogue of the Logical
Redstone Archive (#10103): input and output positions, encodings and checks
for every entry, one entry per line. They shared it under the WTFPL
(LICENSE). The archive's schematics have no licence and stay out of the
repository.

tools/lra_schem.py reads Sponge schematics (versions 1 to 3), written from
the format's specification.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:15:27 +01:00
TaquitoSuabe cf92894885 gfx: enable early depth occlusion culling on OpenGL (Backend::Gl)
Cull::supported previously gated early-depth occlusion culling to Vulkan and Metal. Enable wgpu::Backend::Gl when the device supports SHADER_EARLY_DEPTH_TEST alongside indirect first instance and storage buffer writes.
2026-11-05 14:14:46 -07:00
Florian ERNSTandCodex 0e05b77d95 Keep glow squid dark countdown running with NoAI
Co-Authored-By: Codex <[email protected]>
2026-11-05 22:00:30 +01:00
Horizon agent 20dc224154 Merge remote-tracking branch 'origin/main' into cg53-pr-10220 2026-11-05 21:58:37 +01:00
Horizon agentandClaude Opus 5.5 7a08446cfe sulfur cubes: the blast keeps blocks without mobGriefing; the cube sources ENTITY_INTERACT
SulfurCube.tickFuse explodes with ExplosionInteraction.TNT only when
mobGriefing is on (NONE otherwise), and equipItem's gameEvent has the
cube, not the player, as its source. Drop tests that checked constants.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:58:29 +01:00
MSedgeMC 4fea4cfd63 test: the shipped presets' disable_mob_generation, through the client seam
The three vanilla dimensions, built from the shipped data, must answer what
their noise settings say: the overworld and the nether add creature packs at
generation, the End does not. This is the value `spawn_generation_creatures`
now returns on, read through `VanillaTerrain`, so it covers the whole path from
`end.json` to the gate instead of just the JSON read.

With the setting read as a hardcoded false (the pre-change behaviour, where only
`Dim::End` decided) it fails as
`assertion left == right failed: disable_mob_generation of minecraft:the_end —
left: false, right: true`.
2026-11-05 20:58:11 +00:00
MSedgeMC 61e96ef269 fix: the noise settings' disable_mob_generation skips the creature packs
`NoiseBasedChunkGenerator.spawnOriginalMobs` reads
`NoiseGeneratorSettings.disableMobGeneration` and places nothing when it is
set. The shipped presets are explicit about it: `end` sets it true, the other
six set it false. Horizon never read the field and instead skipped the pass for
`Dim::End` alone, so a datapack dimension that used the End's noise settings -
or any preset asking for no creature packs - still got them, and the reason the
End was empty lived in a dimension check rather than in the data.

The setting now reaches the client seam: `Generator`'s settings carry it, the
pipeline trait exposes it next to `sea_level`, `VanillaTerrain` answers it, and
`spawn_generation_creatures` returns before doing any work when it is set. The
existing `Dim::End` check stays as well, so a preset that fails to load cannot
start filling the End; for the shipped data the two agree, since `end.json` is
the only preset that sets the flag.
2026-11-05 20:58:06 +00:00
Horizon agentandClaude Opus 5.5 0a2d899426 mobs: camel passengers follow Camel.canAddPassenger (size <= 2, babies too)
Vanilla's Camel.canAddPassenger accepts a rider while two or fewer ride,
with no baby check. Drop two tests that only checked their own inputs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:58:03 +01:00
Horizon agent 9e317340d9 Merge remote-tracking branch 'origin/main' into cg53-pr-10171 2026-11-05 21:58:03 +01:00
Horizon agentandClaude Opus 5.5 2228fd7be1 end: propagate the level events' error; drop a test that checked its own loop
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:57:23 +01:00
Horizon agent 188ad5b2d1 Merge remote-tracking branch 'origin/main' into cg53-pr-10122 2026-11-05 21:57:23 +01:00
Horizon agentandClaude Opus 5.5 9e239c1c0c perspective: rider body follows vanilla per vehicle kind; portal key guard only while inside
A boat sets its passenger's body to its yaw (AbstractBoat.clampRotation);
a living vehicle's rider is drawn from the vehicle's body with the head at
most 85 degrees off and a fifth of the offset past 50
(LivingEntityRenderer.solveBodyRot). Minecarts and cushions keep the
on-foot turn, as vanilla's LivingEntity.tick does. The inventory key is
held back only while the last tick found the player in the portal, not
during the effect's fade-out.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:57:16 +01:00
Horizon agent 78c6d024dd Merge remote-tracking branch 'origin/main' into cg53-pr-9994 2026-11-05 21:57:16 +01:00
martipopsandClaude Opus 5.5 2cce72df97 Merge upstream/main into controller/buttons-prompts-v2
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:56:51 -05:00
Horizon agent 54fd1a76d8 Merge remote-tracking branch 'origin/main' into cg53-pr-9986 2026-11-05 21:56:02 +01:00
HiuoyandClaude Opus 5.5 b5f50bc41d Native hooks: AnvilUpdateEvent, a mod's say in an anvil's result (#10230)
anvil_update fires where AnvilMenu.createResult ends, once vanilla has
worked out the result, while the left slot holds an item: on the host for
each player's menu (single player's server thread, a LAN or dedicated
host), before prepare_anvil, and in the window's own menu. A mod is told
the inputs, the rename box's text and vanilla's result. It may change the
output, the level cost and the material cost, or cancel, which leaves no
result (NeoForge's CommonHooks.onAnvilUpdate).

The host asks again after a rename that leaves the result as it was, as
setItemName runs createResult. The window's click prediction starts from
the station's cost and material cost, so a result a mod made can be taken
there as on the server. The window names the player as the server knows
them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:52:39 +04:00
Riccardo GuerrieroandClaude Opus 5.5 9ee63c0510 Aquifers keep their 12 nearby cells unpacked and reuse 8 of them a cell down
Part of #9996.

NoiseBasedAquifer::compute_substance is 4.7% of the overworld's proto
phase by itself. A sixth of that went to rebuilding the 12 grid cells
around a block whenever its aquifer cell changed: every location was
unpacked again from its packed long, and the neighbourhood was copied
as a 288-byte Option. The fill walks each column from the top down, so
a rebuild usually follows the cell just above.

Each drawn location is now also kept as the coordinates BlockPos.getX,
getY and getZ read back from it, and the 12 cells are arrays of grid
index, coordinates and horizontal distance to the column. One cell
down, the 8 cells at the old anchor's y and the y below move up two
slots and only the 4 new ones are loaded, in vanilla's loop order. A
cached fluid status is read inline; computing a missing one is out of
line. Default air, water and lava are classified by their IDs before
any block name is read.

Every block, pending fluid update, error and cached cell is the same as
before: the same operations in the same order, the same positional
random draws for the same cells. A test runs the previous search
against the new one over whole columns, scrambled blocks and positions
outside the grid, and the snapshots match byte for byte.

A first draft by DeepSeek V4.1 Flash, via pi, was reviewed and
reworked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 21:52:13 +01:00
MSedgeMC cf6a402c41 port_map: EntityRenderer.getBoundingBoxForCulling, the interpolated box
The ledger named the four renderer overrides but not the method they override;
it now says the box is the frame's, grown by 0.5, and points at the dropped
item's own copy of it.
2026-11-05 20:49:37 +00:00
MSedgeMC 4ebdcbf7b2 test: the dropped item's cull box over the frame
An item sliding from x = 0 to 1 in camera space: the box is -0.625..0.625 at the
start of the frame, -0.125..1.125 halfway through and 0.375..1.625 at its end,
where the old code had the last on every frame. A falling item's box floor is
65.0 halfway down a 66 -> 65 drop instead of the tick's 64.5.
2026-11-05 20:49:37 +00:00
MSedgeMC 0fdc892ef9 fix: cull a dropped item at the box the frame draws
EntityRenderer.shouldRender tests the frustum against
getBoundingBoxForCulling(entity, partialTick), which EntityRenderer leaves as
Entity.getInterpolatedBoundingBox(partialTick) - ItemEntity overrides neither -
grown by 0.5 all round.

The dropped-item pass built that box from the tick's own position, so for the
frames of a tick in which an item crossed the frustum edge it was culled at the
cell it was about to reach: an item sliding from x = 0 to 1 had its box at
0.375..1.625 (camera space) on every frame, where at the start of the frame it
stands at -0.625..0.625.

dropped_item_cull_box(previous, position, alpha, camera) is that box now, the
interpolated position grown by the item's 0.125 half-width and 0.5, with the
0.25 height below and 0.75 above.
2026-11-05 20:49:37 +00:00
Ludovic Marie 5849fa33b3 Merge pull request #10104 from horizon-rust/mods/in-game-browser
🤖 Agent: merging the in-game mod browser. The point to check is that install never trusts the download: each file goes through horizon_mods::index::check_file (size, SHA-256, signature, manifest against the index) before it reaches horizon-mods/, and revoked files are refused. Tests pin both cases with a real .hmod.
2026-11-05 21:46:40 +01:00
Horizon agentandClaude Opus 5.5 f23fd18f41 Cosmetics: companions, trails and auras in the Wardrobe
The Wardrobe's picture (horizon_cosmetics::preview, its live view
included) draws a companion at its anchor, an aura see-through at the
feet and a trail as a short run of particles behind; the Wardrobe gets a
row for each slot.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:42:38 +01:00
Horizon agentandClaude Opus 5.5 725efc0eca Cosmetics: draw companions, trails and auras in the game
A companion is posed on no bone: it follows its anchor with its lag,
keeps clear of the head and, for the player's own in first person, out of
the middle of the view. Trails spawn by distance walked and are drawn as
camera-facing particles; auras blend at the feet with their back faces
dropped. Trails and auras of every player go in one see-through span after
the players. Others' follow "Other players' cosmetics" and "Hide other
players' trails and auras". Nothing is an entity or reaches a server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:42:37 +01:00
Horizon agentandClaude Opus 5.5 f17a44249f Cosmetics: "Hide other players' trails and auras"
The setting that was "Show other players' auras and trails" (off, for
items that didn't exist) becomes a hide switch, off by default: others'
trails and auras show like their other cosmetics unless the player hides
them. In the game's Horizon settings and the launcher's Wardrobe, the same
cosmeticsHideFx line of horizon-extras.txt; the old cosmeticsAuras line is
dropped on save.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:42:37 +01:00
Horizon agentandClaude Opus 5.5 7df444ab89 Cosmetics: how companions follow and trails spawn
follow.rs: a frame-rate independent lag toward the anchor, a snap after a
teleport, and the two rules that keep a companion out of the way (out of
a cone round a first-person crosshair, clear of the head). trail.rs:
particles spawned by distance walked only, placed from their age alone,
24 per player and 512 in all. fx_bench measures their CPU cost for 1, 16
and 64 wearers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:42:35 +01:00
Horizon agentandClaude Opus 5.5 2a76fd1a00 Cosmetics: companion, trail and aura item format
Three new slots that ride no bone: a companion floats beside the player,
a trail leaves particles where they walk, an aura glows faintly at their
feet or round the body. Their blocks, limits and checks live in fx.rs;
item.rs reads them, refuses a bone on them and measures their extent.
Nothing draws them yet.

Item::animate no longer looks its parts up by name every frame (a tree
search and string compares per animation, for every wearer): the animated
parts are found once, when the item is read. At 64 wearers in view that
was 0.3 ms a frame on an i7-7500U.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:42:35 +01:00
MSedgeMC 9794dd7ca9 port_map: Entity.getLightProbePosition, the eye over the frame
The ledger had no row for the probe the renderers read; it now names the mob and
projectile port and where the armour stand's override lives.
2026-11-05 20:40:55 +00:00
MSedgeMC 5d76ff0225 test: the mob and projectile probes over the frame
A mob walking 0.9 -> 1.1 (eye 1.62) reads the cell at 0 at the start of the frame
and 1 from the boundary on, where the old probe read the tick's cell 1 on every
frame; a falling one reads 71.62 then 70.62. An arrow falling 64 -> 65 reads
64.13, 64.63 and 65.0, so the cell at 65 only at the end of the frame, where the
old probe read 65.0 throughout.
2026-11-05 20:40:52 +00:00
MSedgeMC caa5c31dd0 fix: light a mob and a projectile from the block its eye is in that frame
EntityRenderer.getPackedLightCoords takes BlockPos.containing of
Entity.getLightProbePosition(partialTick), and Entity leaves that method as
getEyePosition: getEyeHeight() over getPosition(partialTick), which is
Mth.lerp(partialTick, xo, x) on each axis.

The mob pass probed floor(position) with position.y + eye_height(), and the
projectile pass the same on its own position: the tick's own position, so a mob
walking from x = 0.9 to 1.1 was drawn lit by the cell at 1 for every frame of the
tick, and a falling one by the cell it was about to reach, instead of the cell it
was drawn in.

Mob::light_probe(partial) and Projectile::light_probe_y(partial) now give the
probe of the frame, through mobs::living::eye_probe_cell (Mth.lerp plus the eye
height, floored per axis).

Old/new for a mob walking 0.9 -> 1.1 (eye 1.62): cell x = 1 on every frame before,
x = 0 at alpha 0 and 1 from the boundary on. For an arrow falling 64 -> 65: 65.0
before, 64.13 at alpha 0 and 64.63 at alpha 0.5, the cell at 65 only at alpha 1.
2026-11-05 20:40:49 +00:00
martipopsandClaude Opus 5.5 9dae50aefd Controllers: bindable actions, prompts over gameplay, and button art
Held mappings are named by an Action rather than read by key, per device, and more actions can be bound: hotbar
slots, fullscreen, toolbars, social and quick actions. Held drop repeats and throws the stack. Readers of the
movement keys see the left stick, and a bumper steps the hotbar through the wheel's gates. Prompts over gameplay
are pad-driven and keep what's behind them, the keyboard opens on every field, and focus follows its widget.
Buttons and the crosshair draw from Horizon's own art, one file per pad family, instead of text labels.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:37:30 -05:00
Ludovic Marie 86a9d9e669 Merge pull request #10236 from horizon-rust/fix/launcher-screenshot-memory
🤖 Agent: merging. The screenshot tiles, viewer and background no longer go through GPUI's never-evicted img(path) cache. Two 32k×16k screenshots on Home went from an 8.7 GiB peak to 277 MiB on the desktop. One risk to watch: the 2 s grace lets the budget overflow while more than 256 MiB of tiles is on screen at once (about 280 tiles with Show all), which is bounded by the list itself.
2026-11-05 21:37:05 +01:00
Horizon agentandClaude Opus 5.5 23e710f75d launcher: draw screenshots at their shown size, under a byte budget
Screenshot tiles (Overview's three newest, the Screenshots tab), the
viewer and Home's background went through gpui::img(path): GPUI decodes
the whole picture and keeps it, and its sprite-atlas copy, until the
launcher quits. One 32768x16384 screenshot is 2 GiB decoded; with two
of them Home alone reached 8.7 GiB (24 GiB reported on Linux).

- pictures (lib): PNGs are read a row at a time and box-filtered down to
  the drawn size, never held whole; other formats decode whole only
  under 256 MiB. Lru keeps made pictures under a byte budget, sparing
  those drawn in the last 2 s.
- picture_cache: makes them off the UI thread (3 at once), 640 px for
  tiles, 3840 px for the viewer and background, 256 MiB budget; evicted
  images are dropped from the window's sprite atlas.
- glass_shot_blur's blurred copies go through it too, from a shrunk read
  instead of image::open.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:35:43 +01:00
MSedgeMC 4465421885 port_map: ArmorStand.getLightProbePosition is ported, not skipped
The row said the probe was drawing-only and skipped; it is what selects the block
an armour stand is drawn with, so it now points at the code that walks it.
2026-11-05 20:35:23 +00:00
MSedgeMC 1d38cdeba7 test: the armour stand's probe cells and packed light
Four cases over the jar's values: a full stand's eye at 65.7775 (cell 65, where
the old base + 0.5 read 64), a small stand at 64.2 (65.1875, cell 65, where the
old read 64), a marker's box scanned for a 15 at (0, 65, 0) and falling back to
the first cell walked (0, 64, 0), y walked before z, the cell above the box left
out, and the marker branch ignoring the partial tick; and a burning stand's block
light at 15 with the sky light left as the probe cell's.
2026-11-05 20:35:23 +00:00
MSedgeMC 249341e7cc fix: light an armour stand from its own light probe
EntityRenderer.getPackedLightCoords reads the block Entity.getLightProbePosition
is in, and ArmorStand overrides that method:

- a marker walks BlockPos.betweenClosed of getDimensionsMarker(false) - the box it
  would have without the marker flag, 0.5 by 1.975, or 0.25 by 0.9875 when small -
  from position() with no partial tick, and returns the first cell whose
  max(block, sky) brightness is 15, else the brightest, ties going to the first
  cell BlockPos walks (x fastest, then y, then z);
- every other stand is Entity.getLightProbePosition, the eye: eyeHeight 1.7775 for
  a full stand and 0.9875 for a small one (BABY_DIMENSIONS), at the frame's
  interpolated position.

Horizon read floor(base + 0.5) for every stand, so a stand standing on a lit floor
in a dark room was drawn with the floor's light: a full stand on y = 64 read the
cell at 64 where its eye at 65.7775 is in the cell at 65, and a hologram marker on
a dark ceiling was lit from its feet's block instead of the brightest cell of the
box it would have without its flag.

EntityRenderer.getBlockLightLevel also puts a stand that isOnFire at 15 block
light, which was not read at all.
2026-11-05 20:35:23 +00:00
Florian ERNSTandCodex 85c277b5e2 Correct empty tag selector predicates
Co-Authored-By: Codex <[email protected]>
2026-11-05 21:31:34 +01:00
Horizon agentandClaude Opus 5.5 27c2e6457f Mods hot load: rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:26:56 +01:00
Horizon agentandClaude Opus 5.5 eb55d13be3 horizon-report: overhead counts the tick's own spans; other worker jobs counted, not timed
On a smoke server (2 ms ticks) the overhead line charged the chunk pump's spans of the
wait between ticks (1,788 a tick) to MSPT. Only the spans the tick records count now,
and a dedicated server records spans anyway for its slow-tick line (HORIZON_SPIKES,
on by default), so they are listed apart and left out of the report's own cost.
The 'other' worker jobs ran at 278k/s for 0.0 ms each: two thread-CPU clock reads
each cost more than the job; they are counted only. A sum of no players' rates no
longer reads -0.0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:26:41 +01:00
MSedgeMC 309ddb48ed port_map: CreativeModeInventoryScreen.selectTab is the creative tab switch 2026-11-05 20:20:54 +00:00
MSedgeMC 87a0ccc90c test: the creative search box loses the focus with its tab
`Creative::select`: the box starts unfocused, the search tab focuses it, and
another tab takes the focus away.
2026-11-05 20:20:54 +00:00
MSedgeMC 1e36a5da1f fix: the creative search box loses the focus with its tab
`CreativeModeInventoryScreen.selectTab` shows and focuses the search box only
on the search tab; any other tab hides it, lets it lose the focus and clears
its query. Horizon focused the box when the search tab was selected but never
took the focus away, so the box stayed focused behind every other tab.

`Creative::select` now sets the focus from the tab it shows, keeping the blink
reset the search tab always had.
2026-11-05 20:20:54 +00:00
Ludovic Marie 3d33b0299d Merge pull request #10223 from horizon-rust/feat/shop-tab-button-label
🤖 Agent: the Wardrobe button opens the launcher's Shop tab now, so its label drops "on playhorizon.dev"; the key is renamed in place in all 86 tables (no stale key left), and the i18n test passes.
2026-11-05 21:20:36 +01:00
Horizon agentandClaude Opus 5.5 50d9f3d7f7 launcher: warn before installing a native mod that doesn't build on your Horizon
The registry's daily check (horizon-mods native-compat.yml) marks the
versions horizon-mixins refuses on the newest Nightly with `broken_on`
({horizon, code}) in index.json. The browser's fit line now says
"Doesn't build on the Horizon you play (CODE): a build leaves it out until
its author updates it" when that Horizon is the one the mod would build on.

- horizon-mods index: `Version.broken_on` (empty and absent by default).
- native_fit: `Fit::Broken { code }` for a hook mod, or a mod pinned to the
  played commit, when an entry names that commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:19:51 +01:00
foudretbr f15ca8d82a Preparing ignores the player's global Java options
A collector chosen in JAVA_TOOL_OPTIONS or _JAVA_OPTIONS stopped every JVM
horizon-prepare starts, as they pass -XX:+UseSerialGC: "Multiple garbage
collectors selected", so the version was never prepared. java::command, which
they all go through, now removes those variables and JDK_JAVA_OPTIONS.

Fixes #10067
2026-11-05 21:17:58 +01:00
MSedgeMC 6ad03dd306 port_map: the dropped item's light probe and block light
The ItemEntityRenderer row names where its light comes from now, and
EntityRenderer.getBlockLightLevel's note covers dropped items beside mobs and
projectiles.
2026-11-05 20:14:11 +00:00
MSedgeMC 837ec11a1a test: the dropped item's probe, frame position and packed light
The three cases quote the old values: the failing probe cell of a falling item
(64 against the eye's 65), the tick's own position against the frame's halfway
one (64.7125 rather than the destination 65.2125), and a burning item's block
light (15 over a dark cell's 0, sky untouched).
2026-11-05 20:14:11 +00:00
MSedgeMC a26ffbb007 fix: light a dropped item from the block its eye is in
EntityType.ITEM declares eyeHeight(0.2125), and EntityRenderer.getPackedLightCoords
reads the block of Entity.getLightProbePosition, which Entity leaves as
getEyePosition: the item's position plus that eye height, at the position the frame
draws (getPosition(partialTick)), not this tick's own.

The dropped-item pass probed the cell of base + 0.1 at the tick's position, so a
falling item at y = 64.85 was lit by the cell at 64 (the floor's light) while its
eye at 65.0625 sits in the cell at 65, and the light jumped a frame early when an
item crossed a cell boundary.

EntityRenderer.getBlockLightLevel is isOnFire ? 15 : the level's block light, so a
burning item is now drawn at full block light with the level's sky light; the
item's fire ticks already clear for a fire-immune stack, which ItemEntity.fireImmune
makes never on fire.

A picked-up item's particle is not an entity and keeps the cell it is drawn in.
2026-11-05 20:14:11 +00:00
AxxioandAntigravity 46d83389ac launcher: serialize mod mutations per game home (fixes #10072)
Background automatic registry updates and manual mod operations (install,
remove, enable, disable) could mutate the same game home folder concurrently.
Because auto-update loads `mod-browser.json` before downloading and saves upon
completion, a manual install or removal occurring during the download would
be overwritten by the stale snapshot upon completion, losing manually added
mods or reviving removed mods. In addition, temporary save files could collide.

Introduce a per-game-home re-entrant mutex (`browser::lock::lock_home`)
covering mod mutation critical sections across background auto-updates and
manual operations. Use unique atomic temporary file paths during save to
prevent collision.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 21:11:12 +01:00
Horizon agentandClaude Opus 5.5 422c36b236 wire stream: eat's bites last 10 ticks, far from the server's 32
A loaded server catching up ran a 20-tick bite's remaining ticks in a burst and finished the apple.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:08:47 +01:00
Horizon agentandClaude Opus 5.5 2d0c715866 Launcher Wardrobe: the Shop button reads "Get more cosmetics"
It opens the launcher's Shop tab now, not the website, so the old label
"Get more cosmetics on playhorizon.dev" was wrong. The key is renamed in all 86
tables, in place.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:08:34 +01:00
Ludovic Marie f00c4c6392 Merge pull request #10139 from horizon-rust/feat/shop-tab-3
🤖 Agent: Pay checks that the checkout's uuid matches the recipient and only opens playhorizon.dev/shop/pay with a validated txn_ id; no card entry in the launcher.
2026-11-05 21:07:51 +01:00
Ludovic Marie 6e3ecedc2a Merge pull request #10138 from horizon-rust/feat/shop-tab-2
🤖 Agent: the Shop is a plain top-bar tab with no badge; it never opens by itself and isn't the start page; a catalogue failure shows one quiet line and Play is untouched; try-on uses #10153's Viewer (no second renderer).
2026-11-05 21:05:28 +01:00
Florian ERNSTandCodex 8089757807 Reject missing advancement selector criteria
Co-Authored-By: Codex <[email protected]>
2026-11-05 21:05:15 +01:00
Horizon agentandClaude Opus 5.5 532421c5f0 launcher: leave out the native mods that break a build, not the build
One mod that horizon-mixins refuses or that doesn't compile used to fail the
whole native build (#horizon-support 2026-11-05: eatinganimation's moved
target, hitbox_plus's support module, worldedit's moved chat.rs). The build
now leaves that mod out, with the mods that need it, and builds again:

- isolate.rs names the mod a horizon-mixins diagnostic is about (mod_id, its
  manifest, its support module's name or destination), or the mods the
  compiler's errors were traced to, and adds their dependents.
- A refusal costs one horizon-mixins run; a compile failure one compile, at
  most 3 times. A server engine export still fails as before.
- native-mods.json records `left_out`, so the same mods turned on read as up
  to date, and the Mods page says "X was left out: made for another Horizon
  version / clashes with Y / it needs Z, which was left out" (86 languages).
- MIXINS_REV → 5a64af9: horizon-mixins#27 resolves `horizon_client::…`
  targets that moved into horizon-client-lib, #28 follows workspace globs for
  a module's name and puts the mod id on module errors.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:58:01 +01:00
AxxioandAntigravity cfccbb8f40 hud: slice animated GUI sprites to first frame in atlas packing (fixes #9948)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 20:54:48 +01:00
Horizon agent e78ed4a7a1 Merge remote-tracking branch 'origin/main' into pvp/hud-editor-main 2026-11-05 20:43:04 +01:00
martipopsandClaude Opus 5.5 db65974cd8 Controllers: new default buttons
New players get the new layout. A file that bound a button by hand keeps it through the change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:40:02 -05:00
MSedgeMC eb6d139e34 test: a pack update keeps the player's mod choices
The fixture registry takes a pack version so the test can install
cozy 1.0.0, turn coords_hud off as the Mods page does, and install
cozy 1.1.0: the file is updated and the mod is still off. Turned back
on, 1.2.0 keeps it on. With the old unconditional set_enabled(m.on)
the update re-enables it and the test fails at 'the update kept it
off'.
2026-11-05 19:39:47 +00:00
MSedgeMC fc6f53a337 fix: a pack update keeps the player's mod choices
install_version_in() applied java::set_enabled(home, id, m.on) for
every mod of the pack on every install, and installing a registry pack
again is how it updates. horizon-mods.txt is the same list the Mods
page toggles, so a mod the player turned on or off in a pack profile
was reset to the pack author's 'on' by the next pack update.

The install now reads the pack's previous record (pack.json's 'id
version' entries) and carries each listed mod's current on/off choice
through the install, which turns a mod on as an explicit install. A
mod the new pack version adds takes the pack's own 'on', and a fresh
install has no previous record so every mod does, as before. The Java
mod path already keeps '.disabled' through updates the same way.
2026-11-05 19:39:47 +00:00
Ludovic Marie c58f498288 Merge pull request #10137 from horizon-rust/feat/shop-tab-1
🤖 Agent: shop price rules (sets partly owned, items inside a set in the basket, Supporter once) match the site's pricing.ts and are tested against hand-worked values; nothing player-visible yet. Requeued after merging main (#10153's lang tails).
2026-11-05 20:39:00 +01:00
DUzzL a13106bc0b Format container tooltip lines with numbered translation arguments 2026-11-05 20:35:02 +01:00
Ludovic Marie 1fdf875085 Merge pull request #9918 from oxi-does-dev/fix/interact-hit-location
🤖 Agent: Requeued on the same head: the parity run died in 1 s on the runner's missing .horizon-prepare.lock (environment, no harness ran), so it's rerunning (42040505129). The interact packet now carries the ray's hit point from the mob's feet. Union on main dd675b574 green.
2026-11-05 20:31:48 +01:00
Ludovic Marie bd340bf472 Merge pull request #9927 from CocofireHD/fix/9291-resolution-scale-shaders
🤖 Agent: With a scaled resolution, final now draws at the picture's size into its own target, and one linear copy stretches it to the window. Same-size output and packs without a final keep their old paths, so only resolution-scaled pack users take the extra pass. Union on main dd675b574 green (fmt, clippy, 4426 client tests including the render crate).
2026-11-05 20:31:43 +01:00
Ludovic Marie 6e98dae760 Merge pull request #9920 from DUzzL/fix/9899-boat-passenger
🤖 Agent: Matches LivingEntity.calculateEntityAnimation: a passenger calls walkAnimation.stop(), which zeroes speed, old speed and position, so the seat moving with the boat no longer swings the legs. Union on main dd675b574 green (fmt, clippy, 4426 client tests).
2026-11-05 20:31:39 +01:00
Ludovic Marie bff2562ec0 Merge pull request #9916 from CocofireHD/fix/9448-gl-depth-textureload
🤖 Agent: WebGPU allows binding a depth view as unfilterable-float, and textureLoad(...).r is the same texel, so Vulkan, DX12 and Metal read the same depth. The translation test now fails on any GL-run entry point that doesn't translate, except the one known glint case. Union on main dd675b574 green (fmt, clippy, 4426 client tests).
2026-11-05 20:31:35 +01:00
Florian ERNSTandCodex 16d11c6bb2 Require matching furnace output components before cooking
Co-Authored-By: Codex <[email protected]>
2026-11-05 20:28:16 +01:00
HiuoyandClaude Opus 5.5 b248489d15 Native hooks: PlayerEvent.BreakSpeed, how fast a player digs (#10198)
break_speed fires at the end of Player.getDestroySpeed, on the Game that
works out a dig: the window's own prediction, and the host's (single
player's server thread, a LAN or dedicated host) for each player. A mod is
told the block and vanilla's speed, and may change it;
BlockBehaviour.getDestroyProgress then divides it by the hardness.

tick_mining now takes its progress from destroy_progress, like the other
digs, so the speed is worked out in one place. The remote client asks it
for blocks of hardness 0 too, so a mod's speed of 0 stops those as well.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 23:28:08 +04:00
Ludovic Marie e3ad1ce3ee Merge pull request #9922 from MSedgeMC/test/animate-tick-neighbours
🤖 Agent: Merged main (9906 landed with the INF-seeded outline_center fold, kept from main) and ran marker_particles on this head: PASS, 2,187 cases and 462,718 lines matching the pinned client. The hive's loop now draws nextInt(1) again on the check that ends it, as BeehiveBlock.animateTick's for-condition does.
2026-11-05 20:27:42 +01:00
MSedgeMC a266ac1c43 test: an empty-criteria advancement is done and is the one auto-granted
`Progress::done` on an advancement parsed from `{"criteria": {}}` (true, no
requirement groups to wait for) against one with a criterion (false), and
`Tree::automatic` over a tree holding both: only the empty one is listed, which
is what the load pass grants the rewards of.

Without the `done` change the first assertion fails at once
(`assertion failed: Progress::default().done(&empty)`), and `Tree::automatic`
does not exist to compile against.
2026-11-05 19:25:02 +00:00
MSedgeMC 349ec13a9b fix: an advancement with no criteria is done and pays out
`AdvancementProgress.isDone` loops over the requirement groups, so with no
groups at all it answers true: an advancement whose `criteria` map is empty is
finished from the moment the player's progress loads. `PlayerAdvancements.
checkForAutomaticTriggers` walks every advancement, and for those with no
criteria calls `award(holder, "")` - which finds no `""` criterion to grant, so
it neither toasts nor announces - and then `AdvancementRewards.grant`s it. The
rewards are granted again on the next load, because there is no criterion to
record them under.

Horizon required `!requirements.is_empty()`, so such an advancement was never
done and never paid out. `Progress::done` now keeps vanilla's shape (every group
satisfied, vacuously true with no groups), `Tree::automatic` lists the
advancements nothing can grant, and the progress load grants their rewards
through the new `grant_rewards`, which is the `AdvancementRewards.grant` half of
`award` split out so the toast and announcement stay with a real completion.

Scope, plainly: **no vanilla advancement has an empty criteria map** - all 1224
in the shipped data have at least one criterion - so nothing in the game's own
data changes. What changes is a datapack's: before this, an advancement it
declared with `"criteria": {}` could never be obtained and its rewards never
arrived, where vanilla hands it over on load.
2026-11-05 19:24:58 +00:00
DUzzL 878342f98e Keep a synched horse rear until its flag clears 2026-11-05 20:22:16 +01:00
Riccardo GuerrieroandClaude Opus 5.5 6165c33ebe Climate searches pass distances along and stop a box's sum once it is too far
Part of #9997.

ClimateIndex::search, vanilla's Climate.RTree search, is about 5% of
overworld proto generation (callgrind, generate_proto, seed 12345) and 2%
of the nether's. About 21 boxes are measured per search: the root's two
children and the 19 below.

Three changes, none of which changes which leaf a search returns:

- The best leaf's distance goes down into each subtree and back up with
  its result. Vanilla measures the best leaf again at the start of every
  subtree and after it returns; the values are the same.
- The offset axis is always 0 in the target, so each box's squared
  offset distance is stored once instead of measured every time.
- When every bound in the tree and every target value is within 2^29,
  no term or sum of a distance can overflow, so a partial sum only grows.
  A box's sum then stops once it reaches the best distance so far: the
  search would only compare it with that distance and skip the box. The
  axes are added depth first, as surface biomes are points in depth.
  Otherwise the full wrapping sum is used as before.

The search still walks the same children in the same order from the
same previous leaf, and replaces the best only on a strictly smaller
distance, so ties go the same way. New tests compare it with a copy of
the old search over long target sequences that keep the previous
result, on the vanilla overworld and nether lists and on generated
lists full of ties, inverted spans, extremes and values at the bound.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 20:15:24 +01:00
Ludovic MarieandClaude Opus 5.5 e6d6ce8df2 Revert the command registries change: not shown to work in game
Commands typed in the window parse tags against the window's own tags,
which no server packet updates; that gap predates live switching and
needs its own change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:14:15 +01:00
foudretbr 1f15474046 Launcher: the proxy hint names SOCKS in every language
The Settings hint now reads (http://host:port, socks5://host:port). Each language keeps its translation, without the word HTTP and with the socks5 form added; i18n's tests want every string in every table.
2026-11-05 20:14:11 +01:00
foudretbr 114dbd0e21 Launcher: downloads can go through a SOCKS proxy
Turn on ureq's socks-proxy feature and stop refusing SOCKS addresses, in port-pack's parse and the launcher's settings. socks5h, curl's name for SOCKS5 with remote DNS, is read as socks5, which already works that way in ureq. A test runs a request through a stand-in SOCKS5 proxy.

Fixes #8143
2026-11-05 20:14:10 +01:00
DetimesandClaude Opus 5.5 6e083af988 Native mods: crates/horizon-mod-deps puts mysql, redis, tungstenite and rusty-opus in Cargo.lock (#10174)
A workspace member with no code whose dependencies are all optional: Cargo
locks them, so a native mod's crate may name them, and no build compiles them
(horizon-client's dependency tree, default and --no-default-features, is
unchanged). All four are pure Rust and use the rustls + ring already locked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:11:58 +00:00
martipopsandClaude Opus 5.5 d8781cb9df Splitscreen: a player's pack reload no longer deletes P1's overlay
Fixes #10179.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:10:42 -05:00
andrescamilomartinezandAntigravity 2.0 313f65e049 fix(client): prevent taskbar overlap and window shift in borderless fullscreen
When entering borderless fullscreen on Windows while the window is
maximized, winit's WM_NCCALCSIZE handler forces the client area to
match the desktop work area (rcWork), which excludes the taskbar.
Because the window is sized to monitor dimensions, this clamps the
bottom edge above the taskbar and shifts the top edge offscreen by the
taskbar height (~48px), leaving the taskbar visible on ultrawide and
standard displays (upstream MC-311683).

Ensure that any transition to fullscreen unmaximizes the window first,
and explicitly pass the active monitor handle to Borderless mode so
the window covers the entire display area without vertical offset.

Fixes #9984

Co-Authored-By: Antigravity 2.0 <[email protected]>
2026-11-05 14:08:33 -05:00
Horizon agentandClaude Opus 5.5 abf29eb414 Multiplayer: a click lists its changed slots in the order vanilla's hash map iterates
MultiPlayerGameMode.handleContainerInput puts the changed slots into a new fastutil
Int2ObjectOpenHashMap and ServerboundContainerClickPacket writes them as it iterates: the key 0
first, then the table from its last position down. Horizon listed them in slot order. The wire
stream harness's inventory scenario caught it: a shift-click from slot 37 into slot 10 went out
37, 10 from vanilla and 10, 37 from Horizon, a byte-level difference any server can see.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:08:04 +01:00
Ludovic Marie 5b4823c198 Merge pull request #10093 from horizon-rust/emotes/3-network
🤖 Agent: emotes, part 3 (#10069): others see your emote through the cosmetics service, now based on main. CI verify is green. Polling is bounded: at most once a second, only while Horizon players are in view, and only with Other players' cosmetics on Shown. Sending is signed and only while Show me as Horizon is on. Without horizon-cosmetics#14 deployed, the service answers 404 and the game backs off.
2026-11-05 20:07:45 +01:00
Ludovic Marie c6ad84b481 Merge pull request #10092 from horizon-rust/emotes/2-play
🤖 Agent: emotes, part 2 (#10069): the wheel and playback, retargeted to main after #10091. CI verify is green. Two clients played every emote in game; captures are in the PR. Visual only, nothing sent to game servers. #10093 follows.
2026-11-05 20:05:22 +01:00
MSedgeMC 4b8abba21c test: an automatic update keeps a disabled mod off
Install ruby 1.0.0, turn it off in the Mods page, run the automatic
update to 1.1.0: the file is replaced and the mod is still off. Turned
back on, the next update keeps it on, and an explicit install of a
disabled mod turns it on again. Without the fix the first update
re-enables it and the test fails at 'the update kept it off'.
2026-11-05 19:04:13 +00:00
MSedgeMC e932cade0c fix: an automatic update keeps a disabled mod off
install_version_guarded() turned every mod it installed on with
java::set_enabled(home, id, true), and the automatic registry update
(browser/horizon/auto.rs update) installs through the same function.
A content mod the player had turned off in the Mods page therefore came
back on after every automatic update: 'off' is the absence of the
'mod.<id>:true' line in horizon-mods.txt, which the update re-added.

The guarded install no longer touches the flag. The player's own
install (install_version, which the browser and modpack paths use)
turns the mod on after the guarded install, as before, while the
automatic update leaves the mod's current on/off choice alone. A new
install still starts on because it goes through install_version.
2026-11-05 19:04:13 +00:00
Horizon agentandClaude Opus 5.5 4a5450c6f8 horizon-report: is_multiple_of (clippy)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:02:47 +01:00
MSedgeMC 404cf600c1 port_map: CreativeModeInventoryScreen.keyPressed is the chat key's search tab 2026-11-05 19:02:11 +00:00
MSedgeMC b66525f609 test: the creative chat key opens a focused, empty search tab
`Creative::search_tab` and `select` as `Game::key_pressed` uses them: the
search tab is found by its type, selecting it focuses the search box, and the
query the previous tab left is emptied.
2026-11-05 19:02:11 +00:00
MSedgeMC 1b8b51978d feat: the chat key opens the creative inventory's search tab
`CreativeModeInventoryScreen.keyPressed` takes the chat key on a tab that is
not the search as "show the search tab": it selects
`CreativeModeTabs.searchTab()` and sets `ignoreTextInput` so the letter the key
carries never reaches the search box. Horizon had neither: T on a creative tab
did nothing, and the letter went nowhere.

`Creative::search_tab` finds the search tab by its type, so the key selects it
through the same path a tab click uses (`select_creative_tab`, which clears the
quick-craft clicks as `selectTab` does). `Game::key_pressed` takes the chat key
there, and the `ignoreTextInput` flag it sets makes `type_chars_into_screen`
(`charTyped`) drop that one letter and no other.
2026-11-05 19:02:11 +00:00
Ludovic Marie 0508903777 Merge pull request #10091 from horizon-rust/emotes/1-format
🤖 Agent: emote format, the bottom of the emotes stack (#10069). pr_ready on the whole stack is green except the NixOS-only uname test. pose_body copies the target at full weight: 0.26–1.1 µs per emoting player and frame, measured. #10092 follows once this lands.
2026-11-05 20:01:16 +01:00
Horizon agentandClaude Opus 5.5 d216a5477d horizon-report: loose entities of every level; rustfmt
The items, orbs, projectiles, minecarts and boats were counted for the level in place
only (the End, on the SMP, when the report ends), so tick_boats' 3 ms had no count
beside it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:00:16 +01:00
Horizon agentandClaude Opus 5.5 216a6218af wire_stream: compare Horizon's Legacy combat feel with Modern, packet for packet
--clients horizon,horizon-legacy plays every scenario with two Horizon clients, the
second with the Legacy feel and every combat extra on, the 1.8 rules included, and
compares their serverbound streams as the vanilla comparison does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:59:02 +01:00
Horizon agentandClaude Opus 5.5 5f69e3a789 Mods: commands see the tags a live switch or a data pack added
The command registries took item and block tags from the resources the
game started with and were built once per world, so /execute and
/give refused a tag a switched mod added (seen in game: Unknown item tag
'c:gems/glint'). They now read the world's tags and are built again
after a live switch's data reload.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:57:44 +01:00
AxxioandAntigravity be42c5d25e game, mobs: align sulfur cube prime, vibrations and TNT drop decay with vanilla (fixes #9957)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 19:56:42 +01:00
Horizon agentandClaude Opus 5.5 ef4463a476 horizon-report: spans inside tick_lan and tick_sculk_entities
On the official SMP, 12.6 of tick_lan's 14.7 ms a tick and all of tick_sculk_entities'
6.4 ms had no span below them. Name the steps: guest ticks, entity states, the hub's
level pass (world sync, chunks, block changes, containers, entity tracking, player
state), the end of the tick and the host events; sculk's prepare, wardens, step-on and
entity game events. Spans cost nothing while off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:56:42 +01:00
Horizon agentandClaude Opus 5.5 ee7a6dd9c8 wire stream: dig_teleport compares the ticks after the teleport aligned on its acceptance
The client sends /tp at a fixed tick, but a loaded server answers 2-6 ticks later from one run to the
next (seen with two identical Horizon clients). The ticks before the teleport compare one to one; from
the client's AcceptTeleportation on they align on it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:54:30 +01:00
Horizon agentandClaude Opus 5.5 e7c5cb7331 Merge main (via feat/shop-tab-2) into feat/shop-tab-3
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:54:27 +01:00
Horizon agentandClaude Opus 5.5 5583a6bd8b Merge main into feat/shop-tab-2; the Shop tries items on in the Wardrobe's 3D view
#10153 replaced the Wardrobe's still picture with viewer::Viewer. The Shop now
uses its own Viewer (drag to turn, scroll to zoom, Reset view) instead of the
still picture and its Turn button, so there is one renderer.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:54:15 +01:00
Florian ERNSTandCodex 7c171a543c Preserve loaded furnace cooking totals until completion
Co-Authored-By: Codex <[email protected]>
2026-11-05 19:53:51 +01:00
Horizon agentandClaude Opus 5.5 e15d46d371 Merge main into feat/shop-tab-1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:52:52 +01:00
degepieandClaude Sonnet 5.5 dcffd6cae9 Fire the enter_block trigger from the server's block pass
23f8bd4fe removed the only consumer of MotionEvent::EnterBlock, and the event was pushed only in the client pass. The server pass pushes it now and the guest server tick triggers it for the acting player.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 23:52:32 +05:00
Nikita Maximilian GrimmandGPT 6.1 Sol e1e307cbb5 fix(launcher): preserve native shader selection and options
Fixes #10123

Co-Authored-By: GPT 6.1 Sol <[email protected]>
2026-11-05 19:48:31 +01:00
Horizon agentandClaude Opus 5.5 ab9929b5ab horizon-report: per-mob-type cost, level spans, chunk workers, network, overhead, stop/status
The official SMP is mob-bound (5,400 mobs, 73 ms a tick at 60 players) but the report
only said tick_creature_entities took the time, not which mobs. Format 2 adds, without
a restart and at a measured cost:
- mob_costs: one tick in four times every mob's tick and sums it by dimension and type
  (ms a tick, mobs ticked, us a mob);
- level_* spans: each dimension's share of the server tick;
- chunk_workers: streaming::job_stats while recording (load/generate/decorate/light...);
- network: wire bytes in/out per player and in all (connection counters, no new
  per-packet cost);
- overhead: spans and mob samples a tick at their price measured on the machine;
- horizon-report stop / status.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:47:54 +01:00
Nikita Maximilian GrimmandGPT 6.1 Sol 64297b8e8b fix(shaders): preserve native pack identity across updates
Part of #10123

Co-Authored-By: GPT 6.1 Sol <[email protected]>
2026-11-05 19:45:17 +01:00
KBroneZandClaude Opus 5.5 7fcf18b6df test: /forceload add loads the chunk for the next command
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:38 +02:00
KBroneZandClaude Opus 5.5 ed01ae3029 fix: handle the container loot table event in the block update probe
Goes with "keep a placed template's container loot table".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:37 +02:00
KBroneZandClaude Opus 5.5 a9c7b82772 fix: load chunks in /forceload add, as vanilla does
`ServerLevel.setChunkForced` calls `getChunk` when a chunk becomes forced, so a
command right after `/forceload add` finds it loaded. Horizon only queued it, so
`execute in minecraft:the_nether run function` with `forceload add`, `place
jigsaw` failed with "That position is not loaded" and a skyblock pack's nether
island never appeared.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:37 +02:00
KBroneZandClaude Opus 5.5 694d953496 feat: read structure templates from data packs
A data pack's `data/<ns>/structure/*.nbt` were ignored. A jigsaw pool naming one
placed nothing, so `/place jigsaw` failed with "Failed to generate jigsaw" and
`/place template` said the template does not exist; that is how a skyblock pack
places its island. Packs also could not replace vanilla's templates.

The enabled packs' templates now join the worldgen overrides, highest pack first,
over the prepared ones, as `StructureTemplateManager` reads resources. `/place
template` and structure blocks look in the world's `generated` folder, the packs,
then the game's own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:36 +02:00
KBroneZandClaude Opus 5.5 2f9ec5be67 test: a placed template's chest keeps its loot table
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:36 +02:00
KBroneZandClaude Opus 5.5 cf31690cae fix: keep a placed template's container loot table
`LevelAccess::load_block_entity` decoded a template's block entity, and that
drops `LootTable`, so a chest placed by `/place template`, `/place jigsaw` or a
structure block came out empty. A skyblock pack's starter chest has its items in
a loot table. Report the table as a level event and hand it to `take_loot_table`,
as `/setblock` and `/data` do (`RandomizableContainer.tryLoadLootTable`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:36 +02:00
KBroneZandClaude Opus 5.5 3f782b89ee fix: upgrade a simple state provider to a state object, not a bare id
26.3 reads a direct block state provider as a state object. A bare string names
a provider in the `worldgen/block_state_provider` registry, so an upgraded
feature with a default-state `simple_state_provider` failed with
`unknown state provider minecraft:air` and was skipped at world start. Vanilla's
own features write `{"id": "minecraft:clay"}`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:35 +02:00
KBroneZandClaude Opus 5.5 5062da5567 fix: upgrade a 26.2 noise with no octaves to a silent 26.3 noise
Void packs turn off vanilla's iceberg noises with
`{"firstOctave": -1000, "amplitudes": []}`. The upgrader left this as it was and
called it "more than 32 octaves or negative amplitudes"; 26.3 cannot read it, so
the world failed to start with `invalid world generation field: base_octave`.
With no octaves the noise is 0 everywhere, which 26.3 writes as one muted octave:
`{"base_octave": 0, "amplitude_modifiers": [0.0]}`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:34 +02:00
KBroneZandClaude Opus 5.5 69ecedfc01 fix: read default_block and default_fluid in 26.3's block state form
`state()` in the generator read only `{"Name", "Properties"}`, or the id alone,
so noise settings with `{"id", "properties"}` failed the world start with
`invalid generator resources: block state`. That is the form 26.3 uses and the
one `horizon-port-pack upgrade-datapack` writes for any non-default state. Decode
these two fields with `BlockStateJsonCodec`, as the rest of worldgen does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:44:34 +02:00
DUzzL f2dfddea11 Handle F3 debug keys on the title screen 2026-11-05 19:44:34 +01:00
Horizon agentandClaude Opus 5.5 b5ed12bc33 wire stream: compare a click's state id as its lag behind the server; hotbar_swap swings at the sky
A run's state ids count every change its server made since the login, so a click numbered 53 in one
run and 55 in the other were both up to date. hotbar_swap now also taps a number key with a swing at
nothing, where vanilla sends the slot only on the next tick.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:43:04 +01:00
Horizon agentandClaude Opus 5.5 e5105f07eb Pick: aim with vanilla's table trigonometry, so a placement's cursor matches to the bit
GameRenderer.pick aims along Entity.calculateViewVector: float radians through Mth.sin and Mth.cos,
the 65536-entry table. Horizon aimed with exact double trigonometry, which moved the hit point a
placement sends (ServerboundUseItemOnPacket's cursor) and an interaction's point by up to ~1e-4 of a
block. The wire stream harness's bridge scenario showed it: same position and look, cursor z
0.7173712 from vanilla, 0.7173119 from Horizon. Anticheats that recompute the cursor from the ray
compare it to vanilla's arithmetic. The other view_vector callers (projectiles, dragon, boats...)
are left for their own checks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:42:49 +01:00
Ludovic Marie f2f95dad8a Merge pull request #10153 from horizon-rust/agent/wardrobe-3d
🤖 Agent: verified on the desktop at this tree: horizon-cosmetics fmt/clippy/tests (40) and the launcher's fmt/clippy/tests (695) green; pr_ready skipped because the desktop disk was under 45 GB. Point to watch: preview::render now takes a Camera (launcher is its only caller), and lib.rs will conflict by one line with the emote format PR #10091.
2026-11-05 19:38:53 +01:00
VaspyyyandClaude Opus 5.5 0e33837c78 Play a station's take sound once per shift-click
Shift-clicking a stonecutter result queued ui.stonecutter.take_result for
every item taken, and the server side played every copy at once: cutting
60 granite played the sound 60 times stacked. Vanilla's lastSoundTime
lets it through once per tick. The menu now queues each take sound once
per click (stonecutter, loom, cartography table), and lan_station_sounds
plays one sound per tick.

Fixes #10148

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:37:45 +01:00
makscoder 53aad00102 Snow, dripstone clay and spreading sculk lift what stands on them
`Block.pushEntitiesUp` runs when these blocks gain the collision they add on
top. The farmland/path to-dirt path already emits the `push_entities_up` block
event; the three growth paths did not, so a body standing on them sank into the
new block.
2026-11-05 21:35:45 +03:00
martipopsandClaude Opus 5.5 f513a784f0 Merge upstream/main into fix/pad-e2e-linux: #9946 edited the same file
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:34:34 -05:00
Ludovic Marie 56349b9713 Merge pull request #9926 from masa-masa-0314/feat/realms-settings-tab
🤖 Agent: Fixed to the 26.3 jar's DTOs: the region preference travels as its id (0/1/2) and the region as its nameId, so a Realm that has a preference parses again (it used to fail the whole RealmServer parse). preferredRegions reads regionDataList/regionName, and the configuration POST puts the slot under options with update_slot's RealmsSlotUpdateDto. New tests pin the POST body and the parsing; CI verify is green on this head.
2026-11-05 19:34:24 +01:00
Vinícius MoreiraandCodex 3c794c2825 Fix shader-pack color-buffer reads in translucent draws
Preserve the preceding scene color when a translucent shader draw reads
and writes the same buffer, rather than sampling a white fallback.
Reuse the existing snapshot mechanism to avoid attachment feedback.

Validated with Sildur’s Vibrant before-and-after captures and an
original asset-free GPU regression.

Co-Authored-By: Codex <[email protected]>
2026-11-05 15:33:25 -03:00
HiuoyandClaude Opus 5.5 29662d963c Native hooks: LivingDamageEvent.Pre and .Post (#10132)
living_damage_pre and living_damage_post fire at the end of
LivingEntity.actuallyHurt, for players, mobs and the ender dragon. Pre is
told what is about to come off the health, once armour, enchantments,
effects and absorption have taken their part, and a mod may change it.
Post is told what came off, 0 when nothing did. The translated actuallyHurt
in horizon-world gets two hook points whose defaults do nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:32:13 +04:00
Horizon agentandClaude Opus 5.5 4f0af1f5f2 Merge main (via feat/shop-tab-2) into feat/shop-tab-3
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:29:24 +01:00
Horizon agentandClaude Opus 5.5 75adf421e0 Merge main (via feat/shop-tab-1) into feat/shop-tab-2
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:29:22 +01:00
Horizon agentandClaude Opus 5.5 ec78951af1 Merge main into feat/shop-tab-1 (lang tables re-appended)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:29:20 +01:00
degepieandClaude Sonnet 5.5 c69d03f96e Resource packs: read a lang file leniently when merging the layers
#9560 made Language::load lenient, but the pack merge parsed each layer with strict JSON and dropped the ones that failed, so a lang file with # comments never got that far.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 23:25:08 +05:00
Horizon agentandClaude Opus 5.5 85056bcaaf Realms settings: rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:20:46 +01:00
Florian ERNSTandCodex de4029c1df Merge main and retain raid harness observations
Co-Authored-By: Codex <[email protected]>
2026-11-05 19:16:54 +01:00
Evrim CicekandClaude Opus 5.5 1bf04aa84f The vehicle's box is read now, and only for the vehicle ridden
From review, against vanilla (getAttackBoundingBox and getHitbox read the
current getVehicle()):
- A mob vehicle's box comes from the mob snapshot, which holds it as it is
  after its own tick this tick, not from the last positionRider.
- The seat positionRider records names its vehicle, and is used only while
  the mob rides that one: a dismount or a new vehicle no longer reads an old
  vehicle's box or attachment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 21:13:30 +03:00
Federico TaglianiandCopilot App 8a2619be29 Make launcher titlebar match glass UI
Co-authored-by: Copilot App <[email protected]>
2026-11-05 19:11:13 +01:00
HiuoyandClaude Opus 5.5 8ec7d24add port-pack events: RecipesUpdatedEvent is looked up as RecipesReceivedEvent
1.21.1's client had every recipe and 27 of ATM10's mods listen to
RecipesUpdatedEvent to rebuild what they made of them. 26.1's client gets
only the recipe types a mod asks for, in RecipesReceivedEvent (NeoForge's
documentation, "Client-Side Recipes"), which recipes_received answers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:10:13 +04:00
Ludovic Marie ff4b4b17bd Merge pull request #9944 from MSedgeMC/fix/steering-stick-wear
🤖 Agent: wear() still fires the durability trigger after Unbreaking, and the broken stat now comes before the rod. The parity failure at ~17:00 was the playhorizon runner's dead .horizon-prepare.lock link, not this change: rerun on this head.
2026-11-05 19:09:06 +01:00
Ludovic Marie 120fe4d7e3 Merge pull request #9941 from MSedgeMC/fix/bundle-tooltip-display
🤖 Agent: The height probe and the draw share bundle_tooltip, so a hidden grid can't leave an empty box taller than its lines. The parity failure at ~17:00 was the playhorizon runner's dead .horizon-prepare.lock link, not this change: rerun on this head.
2026-11-05 19:09:02 +01:00
Ludovic Marie bebaecd65a Merge pull request #9939 from degepie/fix/name-tag-interact-trigger
🤖 Agent: The tag is copied before name_mob uses it up, like handleInteract's itemStackBefore. Follow-up gap, not this PR: vanilla also fires the trigger with an empty stack on non-item successes. The parity failure at ~17:00 was the playhorizon runner's dead .horizon-prepare.lock link, not this change: rerun on this head.
2026-11-05 19:08:58 +01:00
Ludovic Marie 674de01447 Merge pull request #9938 from degepie/fix/drink-consume-trigger
🤖 Agent: Same two calls finish_consuming makes, before the stack shrinks, as Consumable.onConsume orders them. drink_ominous_bottle awarded neither, so nothing counts twice. The parity failure at ~17:00 was the playhorizon runner's dead .horizon-prepare.lock link, not this change: rerun on this head.
2026-11-05 19:08:52 +01:00
Ludovic Marie d1dc1cb937 Merge pull request #9937 from degepie/fix/structure-tag-predicates
🤖 Agent: The pack-less structure tags are a OnceLock keyed on the first resource root, as StructureSpawns::shared is. location_structure_tags_hold_their_structures passes with prepared resources. The parity failure at ~17:00 was the playhorizon runner's dead .horizon-prepare.lock link, not this change: rerun on this head.
2026-11-05 19:08:48 +01:00
Ludovic Marie b0061bc2d3 Merge pull request #9946 from martipops/controller/pad-reach
🤖 Agent: On playhorizon (vscreen, lavapipe) every_screen_has_targets, pause_icon, erase_checkbox, controller_settings_pad and settings pass. The empty-label target can add a second target on a rect that already called target_as: drawn_targets dedups only against labelled widgets, not between targets. Nothing on main seems to do that today. Union run on playhorizon (main a1e0c0db0 + this gate's 11 PRs): fmt, clippy, all touched crates' tests pass.
2026-11-05 19:08:44 +01:00
Horizon agentandClaude Opus 5.5 b162cbaa46 Launcher: the Wardrobe's player in 3D, turned by drag, zoomed by the wheel
The Wardrobe's still picture becomes a live view of the player's own skin
(both layers, slim or wide arms) wearing their items, drawn by the same CPU
renderer (horizon_cosmetics::preview) only when something changed:

- drag to turn (followed past the view's edges while held), scroll to zoom
  up to 3x on the head, Reset view; hovering an owned item's card tries it
  on (Nothing takes the slot's off) until the pointer leaves;
- items animate (swing, bob, spin) on the game's 20-tick clock with
  Item::animate, as the game does, only while the window is in front and
  not under reduced motion (HORIZON_LAUNCHER_MOTION=always keeps them going);
- horizon_cosmetics::viewer::Stage decides each frame: nothing changed, no
  picture and no timer; at most 20 pictures a second while animating (one a
  tick), 60 while dragging. Pictures are drawn at the view's size in points
  (240x330, about 2 ms) and baked up to device pixels;
- preview::Camera holds the turn, tilt and zoom (with headroom above the
  head for tall hats and back items), Draw::item_posed keeps items on posed
  bones, and viewer::BodyMotion is the hook an emote plays through
  (Viewer::play), for the emote format in flight (#10091).

The view is a GPUI view of its own, so a new picture rebuilds it alone;
the pointer is handled by the page's element around it, since GPUI Fast
hit-tests the page's own elements. "Turn" gives way to "Reset view" and a
hint, in every language.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:08:41 +01:00
Ludovic Marie 0ad6666970 Merge pull request #9942 from TheMrZZ/fix/enderman-neighbour-placement
🤖 Agent: Deferring the attempt to the event phase lets update_from_neighbour_shapes see the real level and ticks, as EndermanLeaveBlockGoal.tick does. leave_block_goal_uses_real_neighbor_shapes_and_keeps_air_placement passes with prepared resources. Still missing: canSurvive and the BLOCK_PLACE game event. Union run on playhorizon (main a1e0c0db0 + this gate's 11 PRs): fmt, clippy, all touched crates' tests pass.
2026-11-05 19:08:39 +01:00
HiuoyandClaude Opus 5.5 62a47df275 Native hook catalog: NeoForge labels for five more hooks
frame, gui_render_post, tooltip_component, experience_orb_pickup and
item_dropped get the NeoForge event ATM10's mods listen to for the same
point: RenderFrameEvent.Pre, RenderGuiEvent.Post,
RenderTooltipEvent.GatherComponents, PlayerXpEvent.PickupXp and
ItemTossEvent. The bracketed notes say where a hook differs (#9921).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 22:07:18 +04:00
CocofireHDandopencode 232b0e85aa UI: a button's highlight goes when its screen leaves
Vanilla's Gui.setScreen calls Screen.clearFocus on the screen it leaves, so
a screen that comes back starts with no widget focused. Horizon kept the
focus a mouse click gave a button (the title's focused_button, pause_focus,
death_focus, the options' focus), so going back to the main menu redrew that
button highlighted (AbstractWidget.isHoveredOrFocused) with the pointer
somewhere else.

The front screen is now tracked with key_screen() (which the keyboard's focus
already uses for its own screen changes); when it changes, every menu drops
its click focus. The startup menu does the same for its title behind the
options, before the loading hands that title to the game.

Fixes #9273

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 19:06:06 +01:00
Horizon agent 1011abe707 Merge branch 'emotes/2-play' into emotes/3-network 2026-11-05 19:04:32 +01:00
Horizon agentandClaude Opus 5.5 6f88fac9a9 Emotes: the wheel's key release and click in emote_wheel.rs (keeps game.rs's release block whole)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:04:32 +01:00
Nikita Maximilian Grimm 02ba821bcf Merge remote-tracking branch 'origin/main' into hdr-note-serve-0-1-until-step-5
Co-Authored-By: GPT 6.1 Sol <[email protected]>

# Conflicts:
#	docs/api/shaders.md
2026-11-05 19:04:22 +01:00
MSedgeMC 3ef1d5a7aa port_map: StatsScreen.keyPressed is the statistics tab bar's keys 2026-11-05 18:04:22 +00:00
MSedgeMC fadcfcd6d0 test: the statistics tab bar takes Ctrl+Tab, Ctrl+Shift+Tab and Ctrl+number
`StatsUi::key` as `Game::stats_key` calls it: the cycle wraps both ways, a
number picks its tab and is clamped to the last one, a key without Control is
left alone, an empty Items or Mobs tab is skipped (and not switched to by its
number), and a switch scrolls the new list back to the top.
2026-11-05 18:04:22 +00:00
MSedgeMC c679dff99e feat: Ctrl+Tab switches the statistics screen's tabs
`StatsScreen.keyPressed` runs `MenuTabBar.keyPressed` first: with Control
held, `TabNavigationBar` cycles the tabs on Tab and Shift+Tab (skipping a tab
that cannot be chosen) and picks one on the number keys, where
`InputConstants.Key.getNumericKeyValue` reads the main row's digits and the
keypad's. Horizon's statistics screen had no keyboard handling at all, so only
the mouse could change tabs.

`StatsUi::key` is that tab-bar key press: it reports whether the bar took the
key, moves to the chosen tab when that tab has rows, and starts that list at
the top as a tab press does. `tab_key` ports `getNextTabIndex` (a digit is
`floorMod(digit - 1, 10)` clamped to the last tab; Tab walks to the next tab
that can be chosen, one full turn at most) and `numeric_key_value` ports
`getNumericKeyValue`. `Game::stats_key` gives it the tabs that have rows
(`stats_tab_active`) and plays the tab's click when the tab changes, and
`Game::key_pressed` hands the statistics screen its keys.
2026-11-05 18:04:22 +00:00
MSedgeMC d0a71973e8 The health HUD state's doc drops the removed heights
native_health_hud_state described left_height and right_height as the ones
that place the selected item's name and the action bar. They are gone, and
the text no longer reads them.
2026-11-05 18:04:12 +00:00
Ludovic Marie baa9153c82 Merge pull request #9943 from CocofireHD/fix/9439-tank-refuse-fluid
🤖 Agent: native_fluid_at now allocates the block id String on every call, drains and tank queries included. That's fine for mod helpers, but worth an &'static str if a tick path ever calls it. The fragment stamps (tests.test_stamp_native_hooks passed in the union run). I ran the native_fluids tests on this head on playhorizon, 12 passed, the new dispatch test included.
2026-11-05 19:03:54 +01:00
MSedgeMC 15a6831a33 The HUD probe fixture drops the bars' height with the argument
The two calls in hud_fixture/kinds.rs still passed the 39 the old signatures
took. The probe compares against the vanilla client, which reads neither, so
nothing else changes.

cargo clippy --examples -p horizon-client-lib -p horizon-client -- -D
warnings is clean.
2026-11-05 18:03:21 +00:00
Horizon agentandClaude Opus 5.5 a14ca6acf4 wire stream harness: headless slot clicks, the reset's self-teleport, signed block positions, two short bites
- click-slot without a renderer clicks the menu itself (the vanilla driver's handleContainerInput); horizon-ctl's
  clicks where the slot is drawn, which --headless never does.
- The reset's last teleport is the player onto itself: from the console `~` is the world spawn, which put the
  player inside bridge's stone.
- BlockPos.asLong decodes with each part signed.
- eat cuts its two bites short: when a full one ends is the server's entity event, late on a busy server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:00:39 +01:00
Nikita Maximilian GrimmandGPT 6.1 Sol ffd9b8f475 chore: remove agent markers from shader code comments
Co-Authored-By: GPT 6.1 Sol <[email protected]>
2026-11-05 19:00:20 +01:00
Nikita Maximilian Grimm ceeb7841e2 Merge remote-tracking branch 'origin/main' into shader-api-02-serve 2026-11-05 18:59:42 +01:00
Horizon agentandClaude Opus 5.5 cc7200cd7a Multiplayer: the held slot goes out with the packets vanilla's game mode guards, not with any click
MultiPlayerGameMode.ensureHasSentCarriedItem runs in attack, interact, useItem, useItemOn,
releaseUsingItem, dropItem, piercingAttack and continueDestroyBlock only. A swing at nothing,
the start of a dig or an offhand swap sends no slot: a number key pressed in that tick goes out
at the start of the next one (MultiPlayerGameMode.tick). Horizon sent the slot whenever the tick
had a click. The wire stream harness's hotbar_swap scenario caught it: a tap of 2 with a swing at
the air sent SetCarriedItem before the Punch, a tick before vanilla.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:59:36 +01:00
makscoder 05bc471106 A refused spectator opening gets the red action bar
ServerPlayer.openMenu, when createMenu returns null for a spectator, sends
container.spectatorCantOpen to the action bar (red). Horizon's refusal in
spectator_open_menu and the chest-boat guest handler was silent.
2026-11-05 20:59:03 +03:00
Ludovic Marie 936594e71b Merge pull request #10078 from horizon-rust/pvp/hud-layout
🤖 Agent: merging. The layout lines only add keys to horizon-extras.txt; files without them load as before (every widget at its default place and style). Verify is green; stacked on #10077.
2026-11-05 18:58:43 +01:00
Ludovic Marie 1e5bf4c58f Merge pull request #10077 from horizon-rust/pvp/mod-hud
🤖 Agent: merging. Every widget reads local state only (no packets, no server queries); with every widget off, draw_pvp_hud returns after Hud::any(), so a player who never turns one on pays nothing. Verify is green; pr_ready --quick on playhorizon fails only the render crash:: tests that SIGSEGV under llvmpipe there, and crash.rs is untouched.
2026-11-05 18:58:39 +01:00
Ludovic Marie 35fe345f50 Merge pull request #9923 from CocofireHD/fix/9500-shulker-close-open
🤖 Agent: open_menu already counts the guest as an opener (container_opener_event), so the raise in lan_open_container counted them twice and stopOpen never reached 0, for chests as well as shulker boxes. The author's lan_e2e shulker_menu check pins the lid open, then shut, on both host and guest. Union on main dd675b574 green.
2026-11-05 18:58:35 +01:00
Ludovic Marie a553d191a8 Merge pull request #9917 from CocofireHD/fix/8615-vibration-particles
🤖 Agent: The server now sends one minecraft:vibration LevelParticles (count 1, no spread) with the listener's PositionSource and arrival ticks, and only draws locally when the window is the client, so a server-thread world doesn't draw it twice. Tested at a729b3cfe in a union on main dd675b574 (fmt, clippy, 4426 client tests green); this head only adds the author's main merge (test helper conflict) on top.
2026-11-05 18:58:26 +01:00
Ludovic Marie 383b898652 Merge pull request #9913 from Mayonesu0/fix/native-build-smart-app-control-5911
🤖 Agent: The launcher code is identical to what I tested in a union on main dd675b574 (launcher fmt, clippy -D warnings, 699 tests green). With no check before the build, a machine whose build programs ran before Smart App Control was turned on still builds (Mayonesu0's case 1), and only an os error 4551 leads the error with the explanation.
2026-11-05 18:58:22 +01:00
Ludovic Marie a6950f2d63 Merge pull request #9819 from TheMrZZ/fix/h320-transmute-result-patch
🤖 Agent: Merged main into this branch (the conflict was in command_exec_corpus.py: the transmute group now sits beside main's mob-effect and frame-map groups, and its recipe pack stays in ISOLATED_RECIPES). The result patch is now applied after the input patch, so a '!minecraft:damage' removal wins over inherited damage, as ItemStackTemplate.apply does. Union on main dd675b574: fmt, clippy, 4426 client tests green; CI parity runs command_exec.
2026-11-05 18:58:17 +01:00
AxxioandAntigravity 8c3f6e7d7b Seat camel husk jockey's parched in camel second seat (fixes #9961)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 18:57:44 +01:00
DUzzL 9cf77dcdd7 Run location effects of newly worn armor at once 2026-11-05 18:54:19 +01:00
DUzzL 7858858b94 Make thrown potions bounce off a breeze 2026-11-05 18:54:16 +01:00
Evrim CicekandClaude Opus 5.5 934c4aaabf A mounted mob reaches from its vehicle's width, and a rider is hit from its seat up
Mob.getAttackBoundingBox widens the attacker's box on x and z to its
vehicle's before the melee reach is added, so a zombie on a horse, a
chicken jockey or a mob in a boat reaches as far as the side of what it
rides. LivingEntity.getHitbox starts a rider's box at its seat
(vehicle.getPassengerRidingPosition(this).y), so its legs can't be hit; for
a riding player that is 0.6 above its feet (Avatar.DEFAULT_VEHICLE_ATTACHMENT).

positionRider now records each mob rider's own vehicle attachment and its
vehicle's box (mobs, boats, minecarts, primed TNT, displays), which the
melee check and the slime's touch read.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:50:13 +03:00
Claude cc474268e7 Launcher: a server on another computer, run over SSH
An Entry on the Servers list can name a host: its folder is then on that
computer, and Entry::command runs the same horizon-server arguments there
as `ssh HOST horizon-server --dir DIR ...`, with the system's ssh and the
player's keys (BatchMode, so no password is asked for or kept). Each
argument goes to the remote shell in single quotes, and a host that ssh
could read as an option is refused. remote::check asks the server there
for its status and says in plain words what is missing: a key, the host,
horizon-server, or a server in that folder.

Lists written before keep reading as they were; nothing on the page uses
remote entries yet.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jh5A2PzdusBChqE38VZ3zD
2026-11-05 17:48:14 +00:00
Ludovic Marie d545e9df60 Merge pull request #10068 from horizon-rust/fix/launcher-main-red
🤖 Agent: tests now check all four hosts' cargo commands on every runner (main's launcher red since #9479); launcher dry run 42072143059 green on windows-arm64 and macos-x64.
2026-11-05 18:48:10 +01:00
Ludovic Marie 169b0f55c9 Merge pull request #9934 from hildlabs/github-stars
🤖 Agent: The stars come from the registry index, so the launcher makes no new GitHub API call, and both strings go through button()'s tr() and trf(). Until horizon-mods#285 lands, every page shows "0 stars on GitHub". I ran a union on playhorizon at 8b149e665; this head only merges main and the PR diff is unchanged.
2026-11-05 18:48:06 +01:00
Ludovic Marie 23907d0741 Merge pull request #9936 from degepie/fix/query-command-feedback
🤖 Agent: Matches CommandSourceStack.sendSuccess(…, false) for every query. I ran the ignored a_silent_source_drops_query_results_but_still_stores_them with prepared resources and it passes. Union run on playhorizon (main a1e0c0db0 + this gate's 11 PRs): fmt, clippy, all touched crates' tests pass.
2026-11-05 18:48:02 +01:00
Ludovic Marie b2a67d7572 Merge pull request #9930 from NikitaMGrimm/shader-api-02
🤖 Agent: I pushed d4e13b4c0, which drops the "🤖 Agent:" markers from code comments and from the 0.2 prelude's COLOR_HELPERS text, since that text is composed into every 0.2 stage. Only 0.1's prelude digests are pinned, so they don't move. Added frame-identity now that #9900 is in. Union run on playhorizon (main a1e0c0db0 + this gate's 11 PRs): fmt, clippy, all touched crates' tests pass.
2026-11-05 18:47:58 +01:00
DUzzL 4272d9ae41 Mobs on a ladder or vine are not pushable; no crit while climbing 2026-11-05 18:47:31 +01:00
Nikita Maximilian Grimm 276919beae Merge remote-tracking branch 'fork/shader-api-02' into shader-api-02-serve 2026-11-05 18:47:17 +01:00
Horizon agentandClaude Opus 5.5 e26dae0527 wire stream harness: 15 minutes for a loaded machine's server to start
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:45:08 +01:00
yahya-nusseibehandClaude Opus 5.5 45af2b23b2 Network NBT: no more tags than vanilla's quota admits
Vanilla reads network NBT with NbtAccounter's 2 MiB quota, which
charges each tag an estimate of its heap size: at least 8 bytes (EndTag,
the smallest; a byte tag in a list costs 13). Horizon counted only the
bytes read, plus up to 1,000,000 nodes, so a crafted server could send a
1 MB list of byte tags that vanilla refuses and have Horizon build ~1 M
32-byte Tags from it, about 16 times the memory.

- buf::Reader::nbt caps a network NBT value at NBT_QUOTA / 8 = 262,144
  tags (NBT_MAX_TAGS). Every value vanilla accepts has at most that
  many, so nothing vanilla accepts is refused, and one value's tags now
  take at most ~8 MiB here.
- custom::validate checks component values with the same reader, so it
  follows.
- Test: a list of 262,143 byte tags (with its root, 262,144) reads; one
  more is refused although it is a few hundred KB.

A full port of NbtAccounter's per-type charges would be closer still;
this bound needs only its smallest charge, so it can't refuse what
vanilla takes.

Found in yahya-nusseibeh's review of Horizon (L2).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:44:32 +02:00
Horizon agentandClaude Opus 5.5 186a478e21 test_wire_stream: load the decoder by path, so tools/ never shadows a tests/ module
tools/ first on sys.path made unittest discovery import tools/test_world_spawn_parity.py for
tests/test_world_spawn_parity.py.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:44:02 +01:00
DUzzL de7d92e134 Keep one item in item frames filled by /item or NBT 2026-11-05 18:43:51 +01:00
yahya-nusseibehandClaude Opus 5.5 e0b19571dd RSA: constant-time private-key operations
The dedicated server and a LAN host decrypt each client's
ServerboundKey with the server's private exponent. That used
square-and-multiply, which multiplies only on the exponent's set bits,
with a data-dependent final subtraction in each Montgomery product, and
no blinding: a client controls the ciphertext and can time the reply,
the classic extra-reduction channel. The key is regenerated every start
(as in vanilla) and a recovered one still needs an on-path position,
but vanilla's JCE blinds this operation.

- Mont::mul subtracts n through a mask chosen from the overflow and
  the borrow of t − n, never a branch (Big::sub_with_borrow,
  Big::select). Every product takes the same steps.
- Mont::pow_secret: a square and a multiply for every bit of the
  exponent's limbs, the product kept by a mask. RsaPublicKey::
  apply_secret uses it; RsaPrivateKey::decrypt and ::sign call that.
  Public-key verification and encryption stay on the faster pow.
- Big::from_be_bytes no longer skips zero bytes: it parses the private
  exponent on every call, and the result is the same.
- Test: pow and pow_secret agree with a reference modular power, for
  moduli near a limb boundary (the overflow case) and exponents with
  leading zero limbs. The 11 existing RSA tests (OpenSSL signature,
  known-key decryption, generated keys) pass unchanged.

No wire or parity change. Multiplicative blinding would be the more
complete fix once a modular inverse exists.

Found in yahya-nusseibeh's review of Horizon (L1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:40:43 +02:00
MSedgeMC 8c6933bda4 port_map: Hud.extractTabList is tab_list::visible
The row was absent; map-check: 150 rows, no problems.
2026-11-05 17:40:40 +00:00
MSedgeMC 4ea8baade5 test: F1 hides the player list even while Tab is held
tab_list::visible with the HUD hidden answers false for a held key with two
listed players and for a local server with a list objective, and the
scoreboard's LAN test asserts the same through the game's call.
2026-11-05 17:40:32 +00:00
MSedgeMC 15047ffac4 fix: F1 takes the player list away
Hud.extractRenderState calls extractTabList inside `if (!isHidden)`, so with
the HUD hidden (F1) the player list does not draw at all, whatever the
player-list key or the list objective says.

Horizon's hud_layers only skipped the tab list under F1 when the chat screen
was open: with no screen, holding Tab drew the list over the hidden HUD. The
gate is now in tab_list::visible, next to the key and the local-server rules
it already applied, so every caller gets it.
2026-11-05 17:40:23 +00:00
yahya-nusseibehandClaude Opus 5.5 3b71256fcd RCON: one password guess per connection
After a refused login the connection stayed open, so within the 30 s
login window a client could send as many AUTH packets as it liked: with
the 16 connections allowed at once, thousands of guesses a second
against an RCON port on 0.0.0.0, each adding a supervisor log line.
Vanilla does the same, but this module already departs from vanilla for
safety (constant-time comparison, login deadline, client cap), and this
belongs with those.

- A refused login (wrong or empty password) answers as before, waits
  REFUSED_PAUSE (1 s) and closes the connection. Its place stays taken
  meanwhile, so the client cap also bounds how fast passwords can be
  tried: at most 16 guesses a second instead of thousands.
- A command before the login is still refused without closing, and the
  wire protocol is unchanged; standard RCON clients connect again after
  a refusal.
- Tests: a wrong and an empty password each end their connection after
  the pause; a failed login after a successful one does too. The pause
  is a Limits field so tests use 200 ms.

Found in yahya-nusseibeh's review of Horizon (L3).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:37:23 +02:00
DUzzL b20ab60d01 Make a splash of harming teleport an enderman instead of hurting it 2026-11-05 18:35:16 +01:00
CocofireHDandopencode 1c712e434a Mod fluid tanks: prove a hook refusal through the dispatch
A focused test drives the public block_entity_accepts_fluid dispatch, as the game does: a water tank that refuses a mod's fluid leaves both insert passes empty and offers native_move_fluids no room, while water and a tank with no hook still fill.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 18:34:29 +01:00
yahya-nusseibehandClaude Opus 5.5 c0e2f62b35 Telemetry server: hub, intake and export off the async workers too
The server runs two async workers and keeps SQLite on tokio's blocking
pool (lib.rs, `blocking`), but /hub/vote, /hub/votes and /hub/report and
the token-protected /v1/export/* still called app.store() on the
workers. While a blocking-pool task holds the store mutex (a large batch,
the daily upkeep), two votes or reports from different addresses park
both workers on it and every handler stops answering, /healthz
included; per-address limits don't help, it takes two requests.
/hub/votes also held votes_cache's lock while waiting for the store.

- hub::vote and hub::votes run their store calls through blocking();
  votes reads the cache, releases its lock, counts on the blocking pool
  and stores the result after.
- intake::receive checks the day's count and queues the report in one
  blocking() call, under one store lock as before; report marks the
  issue filed through blocking().
- export::benchmarks and export::hardware read through blocking().
- A failed blocking task answers 503 "try again later" through each
  module's own refusal (the hub's keeps its CORS headers).

Behaviour is unchanged otherwise: the crate's 58 tests pass as they are.

Found in yahya-nusseibeh's review of Horizon (M1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:32:12 +02:00
Riccardo GuerrieroandClaude Opus 5.5 817c37749f Perlin volumes skip the Ys a lerp does not read, and batch Ys across cells
Part of #9996.

The terrain noise (old_blended_noise) is a lerp between two 16-octave
smeared Perlin stacks. Lerp reads its first branch only where alpha is
not 1 and its second only where alpha is not 0, and in the overworld
about 37% of the coarse points have alpha 1 and 50% alpha 0. The lerp
now passes each branch a bit per Y of each column it reads, and a noise
volume skips the others (DensitySampler::sample_needed,
Noise::add_to_needed; samplers without it still sample everything).

When a column's Y cells change at every Y, as in the stacks'
high-frequency octaves, four Ys in four cells now go through the same
vectorized lane code as four Ys in one cell, each lane with its own
cell's corner terms. Gradient lookups are a loop rather than
array::map, which LLVM had left out of line.

Every value is computed by the same operations in the same order:
snapshots match byte for byte and the tests compare against the
unbatched, unmasked loops.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fekd3xkgubE8VfXrALh2td
2026-11-05 18:29:50 +01:00
yahya-nusseibehandClaude Opus 5.5 4f49676a37 port-pack: cap every jar and pack entry it inflates
horizon-port-pack read jar entries with read_to_string/read_to_end and
no limit. zip stops an entry only at the size it declares, which can be
4 GiB (more with zip64), and the launcher runs check::check in its own
process on every Java mod a mod-browser search lists, without a click.
So one published mod with a highly compressible mods.toml could abort
the launcher of everyone whose search showed it. A 522 KB jar whose
mods.toml inflates to 512 MiB made `horizon-port-pack check` reach
594 MiB and abort; with this change it peaks at 9 MiB and refuses the
jar.

- crate::entry: read()/read_text() with a per-entry cap, and a Budget
  for a running total over one pass of an archive, so many entries
  under their cap can't add up either. Limits: 16 MiB per text entry
  (JSON, TOML, the manifest), 64 MiB per file kept as it is, 1 GiB per
  archive pass. Only bytes actually read count; an entry declaring more
  than what is left is refused unread.
- Every zip read in the crate goes through it: jar::read's metadata
  (a too-large file refuses the jar), carry's namespace scan and file
  copy, worldgen's piece counts, structure-set and biome tags,
  processors, upgrade::read_pack and import's JSON index. check.rs kept
  its 16 MiB per-class cap and now also refuses a jar whose classes
  together unpack to more than 1 GiB (a thousand entries just under the
  per-class cap was the same attack).
- Tests: the cap and the running total (declared or not, refused
  entries counting), UTF-8, and check() refusing a small jar whose
  mods.toml unpacks past 16 MiB.

No parity change: real mod files are far under these limits.

Found in yahya-nusseibeh's review of Horizon (H1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:28:47 +02:00
Horizon agentandClaude Opus 5.5 0068b79fc4 parity: wire_stream runs when named (and nightly), alone
It drives a vanilla client on a virtual screen for ~15 minutes: no automatic selection on every client change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:26:04 +01:00
Horizon agentandClaude Opus 5.5 0744579340 wire stream harness: give a loaded machine's vanilla client 15 minutes to reach the world
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:25:03 +01:00
Horizon agentandClaude Opus 5.5 ce9d8ae6cf Emotes: the measured cost per frame in docs/cosmetics.md
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:23:53 +01:00
Horizon agentandClaude Opus 5.5 4a59bae751 Emotes: the wheel reads the cursor as it is (clippy)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:23:53 +01:00
Horizon agentandClaude Opus 5.5 95244a792a Emotes: others see yours (the cosmetics service), the wheel lists the account's
- Mine: while an emote plays, the game tells the cosmetics service, signed in as for "Show me
  as Horizon" and only while that is on: `POST /v1/emote`, with a session kept an hour
  (`account::Kept`). A loop is said again every 30 s.
- Theirs: `horizon_cosmetics::Feed`, a thread, asks `POST /v1/emotes` which Horizon players in
  view play one, at most once a second and only while some are in view, then fetches the item
  files once. On failure it waits 15 s, doubling up to 10 min. Their models play the emote from
  where it is; a player who moves ends theirs at once.
- Follows "Other players' cosmetics": with anything but Shown, nothing is asked and nothing is
  drawn.
- The wheel: the account's emotes (`GET /v1/me`'s `emotes`), read when it opens, at most every
  10 minutes.

Nothing reaches game servers; vanilla clients see nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:23:53 +01:00
yahya-nusseibehandClaude Opus 5.5 24987d6660 Launcher: rustls 0.23.46 for four RustSec advisories
launcher/Cargo.lock pinned rustls 0.23.45 (through ureq 2.12), which
cargo audit flags with RUSTSEC-2026-0346 (client session Debug impls
expose TLS secrets), -0347 (buffered data sent to the outer server after
an ECH rejection), -0348 (Brotli certificate decompression) and -0349
(post-quantum downgrade from an unauthenticated ServerHello), all fixed
in 0.23.46. The launcher carries Microsoft sign-in tokens over this
stack.

- `cargo update -p rustls --precise 0.23.46`: rustls alone moves.
- cargo audit on the new lockfile: no vulnerabilities (8 informational
  warnings: unmaintained or yanked crates, unchanged).

Found in yahya-nusseibeh's review of Horizon (L4).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:21:11 +02:00
Evrim CicekandClaude Opus 5.5 391ce2975d A nautilus's target is within 16 blocks, and pandas and polar bears panic on the same causes
From review, against vanilla:
- Sensor.isEntityTargetable is range(16) from feet to feet, a sphere; the
  nearby scan alone was a cube.
- Panda's PandaPanicGoal and an adult PolarBear's panic use
  #panic_environmental_causes as well; their own copies of it lacked freeze
  and sulfur_cube_hot. They now share armadillo::environmental.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:19:00 +03:00
yahya-nusseibehandClaude Opus 5.5 ce92f7e45a Launcher: open HORIZON_LAUNCHER_PROJECT once without remove_var
The mod page opened the project named by HORIZON_LAUNCHER_PROJECT (a
screenshot aid) and then called std::env::remove_var so it wouldn't open
again. By then the download, check and keyring threads are running, and
remove_var races getenv on any of them (glibc's DNS resolution
included): unsound, which is why edition 2024 makes it `unsafe`. The
launcher is on 2021, so it compiled silently.

- A static flag remembers that the page was opened this run; the
  environment is no longer written. Same behaviour: the page opens once,
  after the first search that finds it.

Found in yahya-nusseibeh's review of Horizon (L5).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 19:11:41 +02:00
DUzzL a7f6ecbdfb Send the carried slot before a drop (ensureHasSentCarriedItem) 2026-11-05 18:11:01 +01:00
Horizon agentandClaude Opus 5.5 f2f3363a0a wire stream harness: start a scenario once the client has taken the reset's teleport
A busy server ran hit_target's reset teleport on the scenario's first tick: the vanilla client's first move
said off the ground and every later move differed. The reset now ends with a teleport, and the scenario waits
until the client has accepted it and ticked 40 more times.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:09:23 +01:00
Horizon agentandClaude Opus 5.5 ff8997168c Emotes: horizon-ctl emote in the generated command reference
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:08:59 +01:00
Horizon agentandClaude Opus 5.5 2fed27fce9 Emotes: play one from the emote wheel, on your model and in the camera
- The wheel: Emote Wheel (`key.emote`, B by default, unbound on a controller; rebindable in
  Controls) shows up to 8 emotes around the middle of the screen. To play one: hold the key and
  release on an emote, click one, or press its number.
- The emote cancels (and fades out) on movement, an attack, mining, using an item, being hurt,
  riding, sleeping, gliding or sneaking. Only a standing player starts one.
- From first person, the camera turns to the front view and eases out from close for the emote,
  then comes back.
- Bones: `anim::setup` lays the emote over vanilla's animation, keyed bones only.
- Props: drawn with the cosmetics, growing in and out with the blend.
- `horizon-ctl emote [ID|stop]`.
- HORIZON_COSMETICS_LOCAL: the wheel offers the folder's emotes, and mannequins play its first
  one (the art and the bench).

Visual only: nothing reaches the game server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:08:59 +01:00
Horizon agentandClaude Opus 5.5 44c14af45a Emotes: fully in, the bones take the target as it is (no quaternion round trip)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:08:58 +01:00
DUzzL 31c1bd1f8b Honor can_place_on for buckets and flint/fire charge in adventure mode 2026-11-05 18:08:40 +01:00
Horizon agentandClaude Opus 5.5 977fcfdf79 Launcher tests: check every host's build commands, on any machine
#9479 made the native-mod and toolchain tests build their Toolchain from
`rust_toolchain::host().unwrap()`. On the macOS x64 runner there is no
managed host (None: 7 panics); on the Windows ARM64 runner it is the ARM64
host, whose cargo gets its target's rustflags as `--config` before the
subcommand, which the tests' argument layout didn't expect.

The code is right on both: the rustflags are scoped to
target.aarch64-pc-windows-gnullvm and only the ARM64 host adds them. The
tests now run each check for every host the launcher ships a toolchain
for, expect the host's own arguments first, and pin that only the ARM64
host adds any.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:08:11 +01:00
Horizon agentandClaude Opus 5.5 a6730f376e Native mods switch: rustfmt and a doc lint
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:07:43 +01:00
Horizon agentandClaude Opus 5.5 aa6fc60c4a Merge mods/in-game-browser into mods/hotload-content
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:06:51 +01:00
Horizon agentandClaude Opus 5.5 315d1e424d Mods: a live switch's data reaches the server with no data pack
The server built its world tables from the content root only when the
world had data packs; without one it kept the tables loaded at startup,
so a switched mod's recipes and tags never applied (seen in game:
/recipe give still said Unknown recipe). After a live switch the tables
are built from the new root. An install that waits for a restart says so
on the project page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:06:51 +01:00
Horizon agentandClaude Opus 5.5 b1e41d46ec Mods browser: its search box says it searches mods
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:05:56 +01:00
hildlabsandClaude Opus 5.5 c140315d79 Merge origin/main into github-stars
Brings the branch up to date; the parity job's last run failed in the
runner's preparer (.local/.horizon-prepare.lock missing), as it did for
every PR between 14:17 and 14:41 UTC.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:05:21 +01:00
Horizon agentandClaude Opus 5.5 071feb3308 Cosmetics: the emote format (keyframed bones, root, props)
An item of the new `emote` slot carries an `emote` block: per-part tracks of keys (rotation,
offset, a prop's scale) eased smooth (Catmull-Rom), linear, in_out or step, one-shot or looping,
faded in and out over `blend` ticks. `Item::parse` checks its limits (200 ticks, 16 tracks,
32 keys a track and 192 in all, 360° turns and 8 px moves, the root 720° and 16 px, props'
reach through every key). `Emote::pose_body` poses the six bones (keys replace a bone's
rotation, keyed bones only), `pose_props` the props, and `Playing` gives an emote's time and
weight, its cancel included. Visual only, nothing random.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:04:09 +01:00
MSedgeMC 7e5063a4d5 port_map: ScreenRectangle.intersection is the scissor stack's cut 2026-11-05 17:03:55 +00:00
MSedgeMC 185908fd38 test: nested GUI scissors clip to the intersection and restore the outer clip
`screen_draw::paint`: an inner scissor draws to the overlap of both, a scissor
with nothing around it is cut to the screen, and one that misses the outer
clip draws nothing while the outer clip takes over again when the inner closes.
2026-11-05 17:03:55 +00:00
MSedgeMC dd0bffd667 feat: a nested GUI scissor is intersected with the one around it
`GuiGraphicsExtractor.ScissorStack` pushes each clip intersected with the one
around it (the screen while there is none, `ScreenRectangle.empty()` when they
miss) and pops it back when it closes. `screen_draw::paint` kept a single clip
that each `Draw::Scissor` replaced: a nested scissor stopped the outer clip
applying at all, and the outer one never came back after the inner closed.

`paint` now keeps the stack: `Draw::Scissor` pushes the intersection with the
enclosing clip (or the screen) and `Draw::NoScissor` clips what was drawn since
and pops back to the enclosing clip. `intersect` is `ScreenRectangle.intersection`.
2026-11-05 17:03:54 +00:00
MSedgeMC c8f1559eb0 port_map: the region file version's configure row is ported
`RegionFileVersion.configure` was the row that named this gap: read from
server.properties and never applied. `Compression::configure` now applies it
from the dedicated server's startup and `compress_records` asks
`Compression::selected()`, so the row says `port` with what each half does.
2026-11-05 17:02:04 +00:00
Evrim CicekandClaude Opus 5.5 1f031f6bd3 A nautilus only goes for a pufferfish it sees, and frost and a hot sulfur cube panic armadillos
NautilusAi.findNearestValidAttackTarget picks the closest #nautilus_hostiles
in water from NEAREST_VISIBLE_LIVING_ENTITIES, the sensor's list of what the
nautilus can see; Horizon took any pufferfish within 16 blocks, through
walls. Its cooldown and 1 in 2 are drawn from the level's random
(level.getRandom()), not the nautilus's own.

Armadillo.actuallyHurt panics (rolls out) on #panic_environmental_causes,
which also holds freeze and sulfur_cube_hot; Horizon listed the other six.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:02:04 +03:00
MSedgeMC 2e7afbfe64 test: the region compression follows region-file-compression
Two cases, one per half of the port:

- `compression::tests::the_selected_region_version_follows_the_property` walks
  the four names `RegionFileVersion` registers (`gzip`, `deflate`, `none`,
  `lz4`) against `Compression::selected()`, checks the default before any is
  set is `deflate`, and checks that `zstd` and the wrong case `LZ4` are refused
  and leave the selection alone, as vanilla logs the value and keeps the one it
  had.
- `save::tests::a_chunk_is_written_with_the_selected_region_version` runs the
  function the save path calls, `compress_records`, once per name, and asserts
  the record's codec is the selected one and that its bytes decode back to the
  same chunk.

With `compress_records` back on `Compression::Zlib` the second test fails with
`left: Zlib, right: None` ("written with none") and would then fail on `lz4`
with `left: Zlib, right: Lz4`.

Also here: the helper-parity test next door now dates its expected records with
`Compression::selected()` rather than `Zlib`, so it keeps checking that the
helpers agree with one thread whatever version the process selected instead of
pinning the default.
2026-11-05 17:02:04 +00:00
MSedgeMC 5a3dc52e1f fix: region files are written with region-file-compression
Vanilla's `Main.main` hands `server.properties`' `region-file-compression` to
`RegionFileVersion.configure` right after the settings are saved, and every
`RegionFile` written afterwards uses the version it selected (`getSelected`):
`gzip`, `deflate` (the default), `none` or `lz4`. Horizon parsed the key into
`ServerProperties.region_file_compression` and nothing read it, so every chunk
region was written as `Compression::Zlib` whatever a server asked for.

`Compression::configure(name)` and `Compression::selected()` are the two halves
of `RegionFileVersion` over the same kind of process-wide selection, starting at
`VERSION_DEFLATE`, with `REGION_FILE_NAMES` in the registration order vanilla's
error message lists. A name is matched exactly, as `VERSIONS_BY_NAME` does; an
unknown one changes nothing and says so. The dedicated server's startup calls it
where vanilla does, before the EULA check, and logs vanilla's line for a value
the table does not know. `save.rs::compress_records` encodes each record with
the selection, so the chunks the save path writes carry it; the read side needs
no change, as `Compression` already decoded all four and the header's version
byte names each chunk's codec.
2026-11-05 17:02:04 +00:00
martipopsandClaude Opus 5.5 7a882e8442 pad_menu_e2e: pass on a fast machine and at 16:9, and say when the window isn't
focus_on looked a widget up before stepping, so a row scrolled out of view
ended the case: it now steps until the row is drawn. optimize_world's job
finished before the case could look on a fast machine: 60000 linked regions
take long enough and cost almost nothing. A tiling compositor sizes the hidden
window too, which fails every case that scrolls, so the harness warns.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:59:35 -05:00
Horizon agentandClaude Opus 5.5 1fcc43f60f Realms settings: the 26.3 JSON shapes for the region preference and the configuration
RegionSelectionPreference travels as its id (0, 1, 2) and the region as its nameId, so a
Realm with a preference no longer fails to parse as a String. PreferredRegionsDto reads
regionDataList / regionName / serviceQuality, RealmsConfigurationDto posts the slot under
options with update_slot's RealmsSlotUpdateDto (worldTemplateId, worldTemplateImage), a Realm
without a preference is automatic by owner (RegionSelectionPreferenceDto.DEFAULT), and the
labels use the realms.configuration.region* keys vanilla's settings tab shows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:58:19 +01:00
Horizon agentandClaude Opus 5.5 b4644568dd wire stream harness: an absolute --bin, since horizon-ctl runs from the scratch home
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:57:02 +01:00
Horizon agentandClaude Opus 5.5 44c13ed7ac vanilla_client: this machine's natives when prepared.json lists another's
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:57:02 +01:00
Horizon agentandClaude Opus 5.5 6039812a76 VanillaTickScript: 26.3's SDL key and mouse codes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:57:02 +01:00
Horizon agentandClaude Opus 5.5 93fc52b7ef wire stream harness: the vanilla client on the virtual screen, with Mesa's EGL
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:57:02 +01:00
Horizon agentandClaude Opus 5.5 875f62b6b5 Wire: compare the client's packets with vanilla's, tick by tick
tools/test_wire_stream_parity.py: a Horizon client and a real vanilla 26.3
client join a vanilla server through tools/net_capture.py and play the same
tick-indexed scenarios (walk, sprint-jump, strafe, w-tap, sneak, hits,
sprint combos, block-hit, rod, bow, eat, bridging, inventory, hotbar swaps,
a teleport during a dig). tools/wire_stream.py cuts each serverbound stream
into client ticks (ClientTickEnd ends one) and decodes the packets; every
tick must hold the same packets with the same fields.

Horizon plays a scenario from HORIZON_TICK_SCRIPT (game/tick_script.rs),
before each client tick; vanilla from VanillaTickScript.java, which hooks a
fixed point of Minecraft.tick (Tutorial.onLookAt) and presses the keys
through KeyMapping.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:57:02 +01:00
Horizon agentandClaude Opus 5.5 96d60b9daf Multiplayer: sneaking for a use is the input of the last tick, as vanilla's isShiftKeyDown
LocalPlayer.isShiftKeyDown reads input.keyPresses, which LocalPlayer.aiStep
sets from the keys: what the last ServerboundPlayerInputPacket said.
Minecraft.handleKeybinds runs before it, so a sneak pressed in the frame of
a right-click counts from the next tick. Horizon read the live Shift key for
ServerboundInteractPacket's secondary flag and for every secondary-use
check (isSecondaryUseActive): a click in that frame sent a sneaking
interaction a server saw before the sneak itself.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:56:54 +01:00
Horizon agentandClaude Opus 5.5 eb7cc8305c Multiplayer: a rider's sprint and a boat's paddles go out in vanilla's order
LocalPlayer.sendChanges sends a passenger's look, then the vehicle it
controls, and only then its sprint (sendIsSprintingIfNeeded); riding a
vehicle it doesn't control, it sends no sprint at all. AbstractBoat.tick
sends the paddles from the boat's own tick, before its passenger's
sendChanges. Horizon sent the sprint first and for any vehicle, and the
paddles last.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:56:53 +01:00
Horizon agentandClaude Opus 5.5 9e7b50b560 Multiplayer: a new held slot goes out at the start of the next tick, as vanilla
MultiPlayerGameMode.tick, first in Minecraft.tick and run with a screen
open too, sends the slot chosen since the last tick
(ensureHasSentCarriedItem); LocalPlayer.sendChanges sends no slot. Horizon
also sent it at the end of the tick that changed it, before the input and
the move: a number key pressed in a tick went out one tick early, on the
other side of the movement packet a translating proxy (ViaVersion to an
older server) turns into that tick's flying packet. An action in the same
tick still sends the slot first, as ensureHasSentCarriedItem does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:56:53 +01:00
Horizon agentandClaude Opus 5.5 635fa28928 Multiplayer: a teleport aborts the dig on the wire, and a new level counts predictions from 0, as vanilla
ClientPacketListener.handleMovePlayer accepts the teleport, then runs
MultiPlayerGameMode.stopDestroyBlock: a dig the teleport interrupts (an
anticheat's setback while mining or bridging, a /tp) sends
ABORT_DESTROY_BLOCK and starts the attack strength again. Horizon dropped
the dig without a word, so the server kept counting it.

BlockStatePredictionHandler lives in the ClientLevel: handleLogin (a proxy's
server switch) and handleRespawn into another dimension make a new one, so
the use and dig sequence numbers count from 0 again. Horizon counted on for
the whole connection.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:56:52 +01:00
MSedgeMC 51a4b98992 port_map: the item name and the action bar rows name their layout fns
Hud.extractSelectedItemName is hud_overlays::ToolHighlight::draw and
Hud.extractOverlayMessage is hud_overlays::Titles::overlay_message; neither
row existed. map-check: 151 rows, no problems.
2026-11-05 16:54:42 +00:00
MSedgeMC 0e97601d25 test: the item name and the action bar sit at vanilla's heights
text_position_tests asks a ToolHighlight holding a stack and a Titles
holding an action bar for their draw calls at 320x240: the name's text top
is 240 - 59 (and 240 - 45 in a mode that cannot hurt the player), the action
bar translates to 240 - 68 with its text four pixels under that, and a
zeroed timer draws nothing. The font is built from no resources, as
chat_tests does, so the test needs no game assets.
2026-11-05 16:54:42 +00:00
MSedgeMC d48f37c2a4 fix: the selected item name and the action bar keep vanilla's height
Hud.extractSelectedItemName draws the name at guiHeight - 59, plus 14 in a
mode that cannot hurt the player, and Hud.extractOverlayMessage puts the
action bar at guiHeight - 68: both constants, which the health, food, air and
mount rows above the hotbar never move.

Horizon moved both up by max(leftHeight, rightHeight). That is 59 for a
survival player with armour, so the common case agreed by accident, but it is
69 with a second row of hearts and 81 for a player underwater on a mount, and
the two texts floated ten or twenty pixels above where vanilla draws them.

Both use the constants now, and HealthHud no longer keeps the heights: 26.3's
Hud has no such fields, only the locals of extractItemHotbar that the
contextual-info bar reads.
2026-11-05 16:54:42 +00:00
Nikita Maximilian GrimmandClaude Opus 5.5 785a399740 docs: HDR design note: only the 0.1 shim goes with 0.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:53:11 +01:00
martipopsandClaude Opus 5.5 7ac3021dfb Show each controller's mapping source and uuid in pad status
A mapping bug report needs to know whether gilrs found an SDL row for a pad
and which id such a row would need. pad status now lists mapping_source
(sdl, driver or none) and uuid (32 hex digits, the id an SDL row starts with).

With splitscreen on, PadRouter holds the gilrs reader, so pad status said
backend:false and listed no controllers, unless the reader was lent to P1.
The router now leaves what it sees for the status of each game that has a
control socket; that list is only built when one is open.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:53:03 -05:00
Horizon agentandClaude Opus 5.5 ad73e4a692 Launcher Shop: a basket, gifts, the Supporter badge, and Pay in the browser
Add to basket on the chosen card, the basket's lines and total with the
service's rules (owned items and items inside a set in the basket cost
nothing), a gift to a friend looked up by name, the Supporter badge's
amount within the catalogue's range, and "Ask whoever pays before you
buy. Refunds within 14 days for any reason." by Pay, which starts
Paddle's checkout on the service and opens playhorizon.dev/shop/pay.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:52:57 +01:00
Horizon agentandClaude Opus 5.5 fac063268c Launcher: a plain Shop tab beside the Wardrobe
The catalogue's items, sets, collections and the Supporter badge as cards
with their prices, the chosen card tried on the account's own skin, and
the founder set's claim (one click where Paddle doesn't sell, else
Paddle's free checkout in the browser). No badge on the tab, it never
opens by itself, and a catalogue that doesn't load says so in one line.
The Wardrobe's one shop button now opens this tab.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:51:19 +01:00
Horizon agentandClaude Opus 5.5 56c896f51c docs/mods.md: which switches wait in a world
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:51:12 +01:00
HiuoyandClaude Opus 5.5 bb4d24e909 Native hook catalog: RenderLevelStageEvent labels for the world drawing hooks
81 of ATM10's mods listen to RenderLevelStageEvent to draw in the
world at a stage of the frame. Main has three hooks for that, none
labelled:

- world_submit (fragment world_canvas): quads and text right after
  the particles, before the hand;
- world_primitives (fragment): lines, boxes, beams and text at the end
  of the world pass, before the hand;
- world_drawn (API 58): the drawn world's colour, depth and matrices,
  once the whole level is drawn.

Each gets "neoforge": "RenderLevelStageEvent (...)", with where it
draws in words, so horizon-port-pack events maps the 81 mods to them.
world_drawn also gets side client and cancellable false; the fragments
had theirs. NeoForge's user documentation doesn't cover this event,
so the labels use only its name.

Fixes #10036

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:50:51 +04:00
Horizon agentandClaude Opus 5.5 aa95310c72 Mods: in a world, content mods of assets and data stay switchable
The Mods page froze every content mod's toggle while a world was open.
Those with only assets and data now reload live, so their toggles stay
active in a world this game serves; the others keep the lock. Each mod
is classified once when the mods are listed, and a native mod's toggle
no longer triggers a resource reload.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:50:21 +01:00
Horizon agent 621d47ab6d Merge commit 'dd675b574115536446647051e29dac76f1055bc5' into cg51/pr-9922
# Conflicts:
#	crates/horizon-client/examples/probe/marker_particles_probe.rs
#	crates/horizon-client/src/animate_tick.rs
#	tools/test_marker_particles_parity.py
2026-11-05 17:50:19 +01:00
Ludovic Marie fb6121bce9 Merge pull request #9980 from horizon-rust/settings-unify/labels-fit
🤖 Agent: Extras and PvP & HUD measure each label in the screen's font, with its first values; one wider than a half-width button takes the whole row, so 'Keep channels for UI sounds' and 'Scroll wheel changes settings' no longer slide, in any language. verify green at this head; no caption, key or default changes.
2026-11-05 17:49:54 +01:00
Riccardo GuerrieroandClaude Opus 5.5 a88fab01a5 A mob's Thorns hurts the player who hits it
EnchantmentHelper.doPostAttackEffectsWithItemSource runs the victim's
equipment before the attacker's weapon: the player's melee hit and sweep,
arrow, trident and wind charge now meet the mob's own post_attack
effects. Thorns rolls from the level's random, hurts the player from
the wearer by 1-5 and wears the piece by 2 (Unbreaking, breaking with
its entity event); the player's own Thorns doesn't answer a thorns hit,
and a thorns hit that lands whole plays enchant.thorns.hit.

The mace harness gets armored targets with Thorns and Unbreaking; new
lan_e2e check mob-thorns.

Part of #9954

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:49:52 +01:00
Claude a94f171297 Merge main into fix/native-build-smart-app-control-5911
Resolves the launcher/lang/*.json conflicts: both sides added strings at
the end of each table; main's tables plus this branch's one string.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Y255SLkXwxkdNVZnS8dNbf
2026-11-05 16:49:33 +00:00
Horizon agent f4382aac3f Merge commit 'dd675b574115536446647051e29dac76f1055bc5' into cg51/pr-9819
# Conflicts:
#	tools/command_exec_corpus.py
2026-11-05 17:49:03 +01:00
Horizon agentandClaude Opus 5.5 d4e13b4c08 Drop agent markers from code comments and the 0.2 prelude
PR-comment markers do not belong in source, and the 0.2 prelude text is
part of every composed 0.2 stage.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:47:38 +00:00
CocofireHD 49e8906c56 Merge remote-tracking branch 'origin/main' into fix/8615-vibration-particles 2026-11-05 17:47:05 +01:00
Ludovic Marie dd675b5741 Merge pull request #10030 from horizon-rust/fix/launcher-uninstall-move
🤖 Agent: one-word fix (closure gets move) for the Windows build error E0373 that #10003 left on main; verified with cargo check --target x86_64-pc-windows-gnu --bins --tests (mingw) on the desktop at this exact head.
2026-11-05 17:46:55 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 02b2f7b9c0 docs: HDR design note: serve shader API 0.1 until 0.2 is on by default
The note kept 0.1 at least until 0.2 leaves experimental, which would keep
today's HDR path (marks, the 16-bit unorm frame, Smooth Gradients) alive for
0.1 packs alone. The shader API is experimental, which stability.md lets
change in any release without notice. So step 5, which turns 0.2 on by
default, also stops serving 0.1: 0.1 packs are refused with stability.md's
message, the registry lists 0.2 packs, and Golden Hour's 0.2 port becomes the
example. Later experimental lines follow the same rule. The shader API page
tells pack authors.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:46:24 +01:00
IsT3RiK 44b980def2 Merge remote-tracking branch 'upstream/main' into traduction-messages-mods
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 17:46:05 +01:00
martipopsandClaude Opus 5.5 1a7a3678bb Refuse a control socket path too long for a Unix socket up front
A --control-socket path over the system's limit was only found when the
game tried to listen, after loading resources: it logged the raw SUN_LEN
error, wrote a crash report and exited 1, leaving startedCleanly:false so
the next start reset fullscreen. It is a bad command line: exit 2 with a
plain message, before the game folder, options.txt or resources are
touched. horizon-ctl launch refuses it as usage too, without starting a
game or creating a log beside the socket. A .json endpoint is TCP and has
no such limit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:45:34 -05:00
Nikita Maximilian GrimmandClaude Opus 5.5 12daf9d13f Golden Hour 0.2: 0.1's look in the grade, Horizon's tone curve
The port ran 0.1's grade on linear values, which darkened midtones by about
nine 8-bit steps. The soft shoulder and the saturation are a look written for
display values, so the grade now runs them on the encoded picture, while the
golden tint, the glow and the vignette stay on light. The pack drops its own
[tonemap]: Horizon's curve clips at white in SDR as 0.1's clamp did, and light
above white survives the grade for HDR. The captures now match 0.1 within
about 0.3 steps on average; what's left is the glow around light sources.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:44:38 +01:00
HiuoyandClaude Opus 5.5 513312eb8b Native hook catalog: world_lifecycle also answers ServerStartingEvent
NeoForge documents ServerStartingEvent as fired after
ServerAboutToStartEvent and before ServerStartedEvent. Horizon has
server_about_to_start at the start of loadLevel, and world_lifecycle's
WorldOpenReady once the open has finished, before the tick loop.
Nothing of Horizon's runs between the two NeoForge events, so
WorldOpenReady answers both (30 ATM10 mods listen to Starting).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:43:53 +04:00
BnCattandClaude Opus 5.5 6c44743c21 Older servers: read 1.21.11's EitherHolder components
1.21.11 sends instrument, provides_trim_material, damage_type,
chicken/variant and zombie_nautilus/variant as EitherHolders (a flag,
then the entry's holder or its ResourceKey), as it does jukebox_playable;
26.1 made them plain holders. Horizon read the flag as the holder, so a
goat horn's slot came one byte long and the entity data around it one
byte short ("could not translate 1.21.11 packet 20/101").

The flag is read for all six now: a holder reads on as the pinned one,
a key (no pinned numbering for it) drops the component.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:43:38 +03:00
Evrim CicekandClaude Opus 5.5 7cead764e8 A held item's attack_range patch, and slimes' reach, follow it too
From review, against vanilla:
- getActiveItem().get(ATTACK_RANGE) includes the stack's patch: an added
  minecraft:attack_range (the codec's defaults: max_reach 3, min_reach 0,
  mob_factor 1) sets the reach, and !minecraft:attack_range takes a spear's
  away.
- AbstractCubeMob.dealDamage calls isWithinMeleeAttackRange, for the player
  it touches and for iron golems; it repeated the default formula.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:43:32 +03:00
IsT3RiK f719bbef1c Merge remote-tracking branch 'upstream/main' into traduction-fr
# Conflicts:
#	launcher/lang/fr.json
2026-11-05 17:43:05 +01:00
degepieandClaude Opus 5.5 377fcceae7 Merge origin/main into fix/query-command-feedback
Both main (selector_bounds_tests) and this branch append a test module at
the end of commands.rs; both are kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 21:40:22 +05:00
Horizon agentandClaude Opus 5.5 07c08509df Launcher Shop: prices, sets and the Supporter badge from the catalogue; the web checkout
The catalogue's shop fields (currencies, prices, sets, collections, the
Supporter badge), the player lookup and the web checkout in the wardrobe
client, and the basket's price rules in shop.rs, as the website applies
them. Nothing shows them yet: the Shop tab comes next.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:40:05 +01:00
Horizon agentandClaude Opus 5.5 bdfe6e4dd1 Mods hot load: rustfmt and clippy fixes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:39:33 +01:00
Ludovic Marie a1e0c0db0b Merge pull request #10027 from horizon-rust/browser-registry-filters
🤖 Agent: registry tags come from known_index(), so tags fetched before the first search finishes stay empty for that kind until the launcher restarts; searches always load the index first, so in practice the row is filled.
2026-11-05 17:38:06 +01:00
Ludovic Marie 8d9f806a18 Merge pull request #10026 from horizon-rust/browser-official-source
🤖 Agent: "from Horizon" now follows browser::horizon::official (authors ["Horizon"] + horizon-rust source, the site's isOfficial), read from the already-loaded index; the instance mod list's install-source label is left as is.
2026-11-05 17:38:00 +01:00
Horizon agentandClaude Opus 5.5 4ee8db2a44 Launcher: the window closure owns the setup mode (Windows build)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:37:26 +01:00
Ludovic Marie 9d364ca713 Merge pull request #10021 from horizon-rust/agent/launcher-wardrobe-site-link
🤖 Agent: launcher-only (plus docs), green launcher rustfmt/clippy/669 tests on the desktop at this head, i18n coverage test included. The one thing it keeps on purpose: Claim for free where Paddle doesn't sell, since the site sends those players to the Wardrobe.
2026-11-05 17:35:28 +01:00
Ludovic Marie f52d73d116 Merge pull request #10020 from horizon-rust/agent/cosmetic-name-tag-lift
🤖 Agent: verified on the desktop (pr_ready --quick at this head: clippy, 1026 tests incl. the new lift test) and on the ThinkPad's GPU (before/after shots). Point to watch: the lift is only ever added when an item is drawn on the body, so tags without tall cosmetics keep vanilla's exact 2.3 (tested); the extra per-tag cost is one max over ≤5 floats inside the existing cosmetics lookup.
2026-11-05 17:35:24 +01:00
DUzzL ef05a384d5 Burning players and mobs destroy powder snow they stand in 2026-11-05 17:29:34 +01:00
Evrim CicekandClaude Opus 5.5 d4a2f07032 A mob's melee reach follows the spear it holds
Mob.isWithinMeleeAttackRange takes the active item's attack_range when it
has one: the box is inflated by effectiveMaxRange (max_reach * mob_factor,
2.25 for every spear) instead of the default sqrt(2.04) - 0.6, and a target
within effectiveMinRange (min_reach * mob_factor, 1.0) is out of range.
Horizon always used the default, so a mob given a spear without a
SpearUseGoal (a vindicator, say) hit what stood right beside it and missed
what the spear reached.

New mob AI scenarios: a vindicator with a spear against a player out at the
spear's reach and right beside it, and zombies with a spear (their
SpearUseGoal; the probe now names it as vanilla does).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:29:23 +03:00
Horizon agentandClaude Opus 5.5 b90cebc01b Merge main into mods/native-switch
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:29:20 +01:00
Horizon agentandClaude Opus 5.5 d9e35999b6 Merge main into mods/hotload-content
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:29:04 +01:00
CocofireHDandopencode 40eeef6f80 lan_e2e: state the shulker lid check in the harness docs
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 17:26:35 +01:00
Ludovic Marie 2a013743f2 Merge pull request #10017 from horizon-rust/fix/launcher-uninstall-i18n
🤖 Agent: fixes the red launcher i18n tests that #10003 left on main (reuses already translated strings); fmt, clippy and the 579 launcher lib tests were green on the desktop for this change.
2026-11-05 17:25:52 +01:00
Horizon agentandClaude Opus 5.5 249aa0d4c1 Merge mods/in-game-browser into mods/hotload-content
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:25:38 +01:00
Horizon agentandClaude Opus 5.5 8aba8be7a9 Mods browser: rustfmt and a clippy fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:24:58 +01:00
Horizon agentandClaude Opus 5.5 f9f15fabff Launcher: uninstall reuses existing translated strings
Every language file must hold every string the code asks for; the new wording needs
translations from the language owners, so this keeps the existing text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:23:13 +01:00
Horizon agentandClaude Opus 5.5 3fc5f274be PvP & HUD: one stand-in line per widget in the editor
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:22:42 +01:00
Horizon agentandClaude Opus 5.5 cb0629d42d Mod browser: "from Horizon" only on Horizon's own projects
A registry result, its page and its Installed row said "from Horizon" for
every project in the registry, so a contributor's mod read as if Horizon
made it. Now only Horizon's own projects say it (author Horizon, source
under github.com/horizon-rust: the site's "Official", the rule
browser::horizon::official already used for the trust line); a
contributor's reads "by <author>" alone. Modrinth and CurseForge keep
"from <source>".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:22:29 +01:00
HiuoyandClaude Opus 5.5 0801940eeb Native hook catalog: label the screen input and render hooks, and item pickup
Seven more hooks whose doc comments already name their NeoForge event:
screen_render_post (ScreenEvent.Render.Post), the five screen input
hooks (ScreenEvent.MouseButtonPressed.Pre, .MouseButtonReleased.Pre,
.MouseScrolled.Pre, .KeyPressed.Pre, .CharacterTyped.Pre), all client,
and item_picked_up (ItemEntityPickupEvent.Pre, server), whose cancel
stands for setCanPickup(TriState.FALSE), with no forced pickup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:21:57 +04:00
Horizon agentandClaude Opus 5.5 3f164ac06a PvP & HUD: the HUD editor in main's new screen and menu matches
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:18:51 +01:00
CocofireHD a729b3cfe6 Merge remote-tracking branch 'origin/main' into fix/8615-vibration-particles 2026-11-05 17:18:40 +01:00
Florian ERNSTandCodex 5cede4ff4e Record refreshed raid loss and harness compatibility proof
Co-Authored-By: Codex <[email protected]>
2026-11-05 17:17:06 +01:00
MSedgeMC dd6a0b938d port_map: ChatScreen.formatChat is the input's draft formatter 2026-11-05 16:16:26 +00:00
MSedgeMC 4077c5e01c test: a restored chat draft draws grey italic and one Backspace clears it
Through `ChatScreen::new` with `is_draft`, `key` and `draw` - the calls
`Game::open_chat` and `Game::chat_key_code` make: the draft's drawn text is
grey and italic, an edited draft and a box opened on ordinary text are not,
and the first Backspace on an untouched draft empties the box where an
ordinary box loses one character.
2026-11-05 16:16:26 +00:00
MSedgeMC 6a51ac58a7 feat: a restored chat draft draws grey italic and clears on the first Backspace
`ChatScreen` keeps the `isDraft` its constructor argument sets: `formatChat`
draws the text grey italic until `onEdited` runs, and `keyPressed` clears the
whole box on the first Backspace while it is still set. Horizon tracked the
draft (`Game::chat_draft`, `chat_draft_shown`) but opened the box as plain
text: `open_chat_method` knew whether the text was the draft and did not pass
it.

`ChatScreen::new` takes `is_draft` (`ChatScreen.init`'s `isDraft`), `edited`
(`onEdited`) clears it, `key` (`keyPressed`) clears the input on a Backspace
while it is set, `format` applies the draft formatter (`formatChat`, which
`ChatScreen.init` registers before `CommandSuggestions.formatText`) and
`resize` keeps it (`ChatScreen.resize` keeps the field).

`Game::open_chat` threads the flag from `open_chat_method`
(`ChatComponent.createScreen`); the in-bed wake-up
(`InBedChatScreen.onPlayerWokeUp`) and `Gui.openChatAndAddText` open plain
text, as vanilla does.
2026-11-05 16:16:26 +00:00
repositorykreml1nandCodex 6d9e1191a2 fix: broadcast command-block say announcements to connected players
Broadcast unsigned say chat independently of feedback capture and keep the
command source display name. Add a two-client LAN regression for both
TrackOutput states and conditional-chain success.

Co-Authored-By: Codex <[email protected]>
2026-11-05 19:16:22 +03:00
Ludovic Marie 2c6ccefcb7 Merge pull request #9859 from TiggerTakkyTTT/fix/ender-dragon-spawn-egg
🤖 Agent: Checked against SpawnEggItem.useOn/spawnMob, EntityType.spawn/create and SpawnEggItemBehavior. I pushed d7e7b9273: the spawn yaw is wrapped, Mob.finalizeSpawn's triangle and nextFloat come from the level random, and the dragon seeds its own random. The author's dispenser height (16x8 box) and growl are kept. Known gap: Horizon keeps one dragon per level, so a second egg does nothing.
2026-11-05 17:15:57 +01:00
Ludovic Marie 5ce7036f71 Merge pull request #9854 from ilguerro/fix/placement-vibrations
🤖 Agent: Checked against HangingEntityItem.useOn, ArmorStandItem.useOn, EndCrystalItem.useOn, MinecartItem.useOn and EnderChestBlockEntity.startOpen. The armor stand now falls up to two blocks (getYOffset with movedUp). I pushed ada2c6b81: drop_offset keeps its three arguments and drop_offset_from takes movedUp, so #9859's new caller compiles whichever lands first. Main is merged in, and use_item_on keeps the wake, used-stat and placement cases.
2026-11-05 17:15:51 +01:00
makscoder 574b20a662 Loot: guests roll a container's table on open; double chests roll both halves
The host's open unpacks the loot table it still holds; the guest's
(lan_open_container) read the slots without it, so a LAN or splitscreen
player met an empty container that kept its table, and
player_generates_container_loot never fired for them. unpack_opened_loot
now runs for the acting guest too.

ChestBlock.getMenuProvider unpacks both halves of a double chest; only
the clicked half was unpacked. After its own roll is consumed, the
partner's is unpacked too (the consumed own roll ends the recursion).
2026-11-05 19:15:23 +03:00
some-du6eandClaude Opus 5.5 2a767017b9 OptiFine custom colours: read optifine/color.properties
Every documented key: particle, fog and sky colours, potions, spawn eggs,
map, sheep, collar and dye colours (with their old names), text and its
colour codes, the loading screen, clouds, xporb.time, palette.format and
palette.block.* assignments.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:15:15 -06:00
HiuoyandClaude Opus 5.5 a8ea55f39a Native hooks: ClientPlayerNetworkEvent.LoggingIn, and LoggingOut's label
A client mod hears the window's player log in to a server, single
player's own included: native_client_player_network_logging_in(), at
the end of the Login arm (ClientPacketListener.handleLogin), after the
new client level. Respawns and dimension changes don't log in again.
Horizon makes the player with the server's first position, so
native_player() is None there; native_send_packet reaches the server.

client_disconnecting, which main fires before a connection is
discarded (Minecraft.disconnect), gets the catalog label
ClientPlayerNetworkEvent.LoggingOut, so horizon-port-pack events finds
it. The docs describe the pair.

Checked in game with horizon-ctl and a local probe mod: one login on
opening a world and one on reopening it, none on a trip to the Nether
or a respawn, and a logout on Save and Quit and on quitting the game.

Fixes #10008

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 20:15:14 +04:00
Horizon agentandClaude Opus 5.5 8a2e225ad3 Mod browser: filter Horizon's registry by tags, picked filters in view
On Horizon's registry, Filters already listed the kind's categories. Now:

- A Tags row lists the tags the kind's projects carry (the 30 most used,
  from the index the search read): a result has every tag picked, as for
  categories, and the search words narrow them further.
- Categories read as playhorizon.dev names them ("Game mechanics", "World
  generation", "GUI"), in the registry's order, translated where the
  launcher's tables have the words.
- While the Filters panel is closed, every picked category, tag, loader,
  version or side stays in view as a chip with a cross that removes it,
  then Clear filters (which keeps the sort).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:12:30 +01:00
Horizon agentandClaude Opus 5.5 ece613e260 PvP & HUD: document the HUD editor; it frames only drawn widgets
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:10:41 +01:00
Horizon agentandClaude Opus 5.5 5b52010b12 PvP & HUD: a HUD editor to drag, snap and resize the widgets
Edit HUD on the PvP & HUD page (in a world) or the HUD editor key
(unbound at first) opens a screen over the game: each widget in a frame,
dragged anywhere with snapping to the screen's edges and middle and to
the other widgets, the wheel resizing it, a right click sending it back
to its corner; buttons for text colour, shadow, boxes and opacity, Reset
and Done. Widgets with nothing to show yet stand in by their name. The
layout is saved to horizon-extras.txt as it changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:10:40 +01:00
Horizon agentandClaude Opus 5.5 7b9daf7bbf PvP & HUD: document the widgets' style and layout
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:10:22 +01:00
Horizon agentandClaude Opus 5.5 afc6c3c43a PvP & HUD: free widget places, sizes, text colour, opacity and shadow
Each HUD widget can leave its corner for a place kept as fractions of the
screen's free room (so it stays on screen at any size) and a size from 50
to 200%, saved as hudPlace lines in horizon-extras.txt only once moved.
The page gains the boxes' opacity, the text colour and the text shadow.
Snapping (screen edges, middle, other boxes) is here for the editor.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:10:22 +01:00
Horizon agentandClaude Opus 5.5 69874aef48 PvP & HUD: no widget on, no HUD work; document the new widgets
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:10:17 +01:00
Horizon agentandClaude Opus 5.5 9f46032653 PvP & HUD: FPS, CPS, keystrokes, clock, reach and speed widgets
Six more widgets on the PvP & HUD page, off by default, each from the
client's own state: the frame counter, presses of the last second, the
movement and mouse keys while held, the local time, the distance of the
last hit the crosshair's ray measured, and horizontal speed sampled from
the player's position. Each caches its lines and rebuilds them only when
the shown value changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:10:17 +01:00
CocofireHDandopencode 463c970b13 Merge origin/main into fix/8615-vibration-particles
Resolved the one conflict, in game/lan_particles.rs: test_hub keeps the
shared helper the vibration test needs, and its guests now carry main's
Joined.expected field and bind through NetConfig::loopback. The
sculk_vibration LAN check survives main's rewrite of tools/lan_e2e.py.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 17:08:57 +01:00
DUzzL fb828fda35 Teleport spectators that use a nether portal, end portal or end gateway 2026-11-05 17:08:32 +01:00
umcekoandClaude Opus 5.5 0e80c10042 Projectiles owned by any non-player entity need mob_griefing
Projectile.mayInteract asks mob_griefing for every owner that isn't a
player, and canBreakBlockInAdventureMode lets no non-player owner break
a block without can_break. An owner UUID that names a loaded armor
stand, boat or other non-mob entity was treated as no owner, so a
burning arrow lit TNT and campfires with mob_griefing off. The arrow now
asks the level for the UUID, as getOwner does.

Fixes #9999

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:06:37 +03:00
Ludovic Marie d95fbb3240 Merge pull request #10003 from horizon-rust/fix/launcher-uninstall-window
🤖 Agent: launcher-only fix (Control Panel Uninstall opened nothing: the CLI parser refused 'uninstall'); fmt, clippy and tests green on the desktop at this exact head; the Windows build runs in CI.
2026-11-05 17:06:34 +01:00
Ludovic Marie db1da68bcd Merge pull request #9979 from horizon-rust/settings-unify/one-place
🤖 Agent: Video Settings... opens the graphics screen (as Sodium's), which gains vanilla's Video Settings rows it lacked and the picture extras; FOV and View Bobbing stay where vanilla has them; Horizon Settings and Extras drop their duplicate ways in. A test walks every menu and pins one place per setting and one way in per page. verify green at this head; saved keys and defaults unchanged.
2026-11-05 17:06:30 +01:00
Florian ERNSTandCodex 9fcd6133ed Merge main and preserve map, frame and raid harness records
Keep all observation kinds when resolving the three shared driver lists.

Co-Authored-By: Codex <[email protected]>
2026-11-05 17:06:23 +01:00
DUzzL 812702d809 Allay: aim deliveries at the player's eyes like vanilla 2026-11-05 17:04:45 +01:00
Ludovic Marie 34881c8cca Merge pull request #9978 from horizon-rust/settings-unify/shaders-in-graphics
🤖 Agent: Shader packs (both kinds) move to a new Shaders page of the graphics screen; Horizon Settings loses its second way into the pack list. Shadow Cache moves from Performance to Shaders. verify green at this head; no saved key or default changes. Bottom of the settings-unify stack (#9979, #9980 follow).
2026-11-05 17:03:32 +01:00
CocofireHDandopencode 58263dc4a0 lan_e2e: capture the shulker lid open and closed
The shulker_menu check now keeps host and guest screenshots of the lid before and after the guest closes the menu, so a stuck lid is visible as well as asserted.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 17:03:25 +01:00
some-du6eandClaude Opus 5.5 a1a052c35c OptiFine custom skies: read the sky layers of optifine/sky
Parse optifine/sky/world<N>/sky<M>.properties into layers (source, fade
times, blend, rotation, days, weather, biomes, heights, transition), the
sun and moon_phases replacements, and the 3x2 skybox grid's faces.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 09:59:41 -06:00
Florian ERNSTandCodex bfc26f944a Validate loaded bed claims for raid villages
Keep stale home memories while checking the current HOME POI before
counting a loaded bed claim as a village center. Preserve unavailable
claims and existing job/meeting paths.

Co-Authored-By: Codex <[email protected]>
2026-11-05 16:57:12 +01:00
Evrim Cicek 7658a24435 Merge remote-tracking branch 'upstream/main' into fix/enderman-teleport-danger
# Conflicts:
#	crates/horizon-client/src/game/creatures.rs
2026-11-05 18:54:56 +03:00
Evrim CicekandClaude Opus 5.5 394db9011a An enderman's teleport: waterlogged ground, the sculk event, and hurts without AI
From review of the previous commit, against vanilla:
- Enderman.canRandomlyTeleportTo refuses ground that holds water (a
  waterlogged slab, leaves or roots).
- containsAnyLiquid and findBlocksIn scan the same closed range of cells.
- Both teleport sounds play at the feet (Entity.playSound).
- The game's sculk layer now hears GameEvent.TELEPORT from a mob.
- Enderman.hurtServer teleports inside every hurt, not only the rain's: the
  fire, lava and block hurts of baseTick and a fall's, and a NoAI
  enderman's, which never teleported. A lingering cloud's indirect_magic
  draws the chance too; only a thrown potion skips it.

New mob AI scenarios: enderman_rain_waterlogged (red on the previous commit,
it landed on the slabs at tick 2) and enderman_rain_noai.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:49:28 +03:00
Riccardo GuerrieroandClaude Opus 5.5 7a7cf79d4b A player's death makes a vibration and counts toward Killed By
ServerPlayer.die posts entity_die at the player, and with a kill credit
awards killed_by for the killer's type. A creeper, sulfur cube or wither
whose own blast kills the player is still credited, as vanilla's player
dies inside the explosion: ExplosionSource carries the mob's type.
New lan_e2e check death-credit.

Fixes #9955

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 16:48:54 +01:00
AxxioandAntigravity 150423300c end: drop destroyed blocks and award used stat on portal completion (fixes #9958)
When the end portal frame ring is completed by placing the last eye of ender, vanilla destroys blocks in the 3x3 portal opening so that their drops and break effects occur instead of silently deleting them. Also awards minecraft:used for minecraft:ender_eye upon insertion into a frame.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 16:47:53 +01:00
DUzzL 37fed7a88e Credit damage_dealt_absorbed for a player's projectiles 2026-11-05 16:47:46 +01:00
Horizon agentandClaude Opus 5.5 d523cb803b Launcher: rustfmt the uninstall changes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:43:55 +01:00
MSedgeMC 955558305a Merge remote-tracking branch 'upstream/main' into fix/prediction-snap
# Conflicts:
#	crates/horizon-client/src/game/remote.rs
2026-11-05 15:42:26 +00:00
Claude 469f8d9b31 Native builds: explain Smart App Control only when it blocks (#5911)
Tested on Windows 11 with Smart App Control on (VerifiedAndReputablePolicyState
1): a build whose programs already ran before it was turned on still
succeeds, so refusing every build when the registry value is 1 stopped
builds that work. The check before the download is gone; a build that hits
os error 4551 still leads its error with what to do.

The message is translatable now, and says Windows also blocks some of the
toolchain's own programs (dlltool.exe was refused in a player's build), not
only the ones compiled on the computer.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Y255SLkXwxkdNVZnS8dNbf
2026-11-05 15:42:23 +00:00
Horizon agentandClaude Opus 5.5 9960bdec5a Launcher: Uninstall in Control Panel opens its window
main() ran the CLI parser before looking for the setup window, so
`horizon-launcher.exe uninstall` was refused as an unknown subcommand, with the
error going to a console Control Panel's start doesn't have: no window, nothing removed.
The setup modes are now decided first (setup_args::classify, pure and tested on every OS).
The uninstaller also asks a launcher that is still open to quit before removing its files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:41:12 +01:00
Horizon agentandClaude Opus 5.5 c8a86851b9 Native mods: a trait mod turned off stops at once, and resumes when turned on
Options > Mods' switch now reaches the trait mods' dispatch: the game skips
a turned-off mod's Hooks from the next call, on the server thread and in
the window, and turning it on again resumes it with no rebuild while it is
in the build. Registration at start is untouched, so its content stays
registered; RenderHooks keep drawing until a restart; mixin code still
checks native_on.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:38:22 +01:00
DUzzL b1896906d1 Clip projectiles with their own collision context (scaffolding open from below) 2026-11-05 16:36:59 +01:00
Horizon agentandClaude Opus 5.5 408044e9a9 Mods: content mods of assets and data switch on and off without a restart
A content root is built per set of mods (.horizon-cache/mod-content/<stamp>),
so a running game keeps reading the root it was given while a switch
builds another; roots unused for a day are removed. Turning a mod on or
off in Options > Mods, or installing one from Browse Mods, applies at once
when the mod has only assets and data: the client reloads its resources,
then the server reloads its data packs as /reload does and resyncs its
players. A mod that adds blocks, items, mob effects, entity or menu types
or changes world generation keeps loading at start, and the Mods page
says to restart.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:36:58 +01:00
Horizon agentandClaude Opus 5.5 c0323c7707 Mods: browse Horizon's registry and install content mods from inside the game
Options > Mods > Browse Mods lists the registry's content mods for this
game (the index the launcher reads), searchable by name, tag and category,
with a page per project: authors, licence, categories, description and
changelog, Install or Update, its switch once installed, and its page on
the website. An install is checked as the launcher's (size, SHA-256,
signature, manifest against the index) and recorded in mod-browser.json,
so the launcher lists, updates and removes it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:32:54 +01:00
Ludovic Marie 624c4f6265 Merge pull request #9900 from NikitaMGrimm/frame-identity-harness
Frame identity harness: the picture a change leaves alone, pixel for pixel (HDR step 2a)
2026-11-05 16:32:00 +01:00
Horizon agentandClaude Opus 5.5 e95658709a Design note: mods from inside the game, loaded and unloaded in seconds
What content mods can switch live (assets and data), what needs a world
reload (registries, worldgen), the in-game mod browser, and the native
fast path: every installed native mod built in and switched at run time,
with the dylib and wasm paths left to arismoko's fork.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:31:45 +01:00
AxxioandAntigravity 3c154d3bb6 perspective, game: align riding body yaw and block inventory in nether portals (fixes #9793)
In vanilla (\LivingEntity.tick\), a passenger's body yaw matches its vehicle's rotation
(or living body yaw), clamped to keep head yaw within [-85°, 85°], and does not update
limb swing / walk distance from vehicle motion.

Furthermore, \Minecraft.handleKeybinds\ blocks opening the inventory (\keyInventory\)
while in a portal transition (\handlePortalTransitionEffect\), preventing repeatedly
reopening the inventory screen while standing in nether portals.

- Compute ridden vehicle yaw for boats, minecarts, mobs, and cushions in \perspective.rs\.
- Align rider body yaw to vehicle rotation, clamped to [-85°, 85°] of head yaw.
- Disable walk distance accumulation when riding so the player model does not swing limbs.
- Guard \KeyCode::KeyE\ in \game.rs\ and \
emote.rs\ when in portal transition.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 16:31:29 +01:00
Ludovic Marie b4c92aba07 Merge pull request #9878 from ilguerro/fix/mob-vibrations
🤖 Agent: The host now forwards the mob game events vanilla's mobs post (prime_fuse, eat, container_open and close, entity_place, and entity_action beyond the armadillo's), and the sculk harness gets creeper and sheep scenarios that main fails. They run last so the catalyst scenarios' entity ids don't shift.
2026-11-05 16:30:26 +01:00
Ludovic Marie eb4291a61d Merge pull request #9876 from martipops/fix/pad-one-reader
🤖 Agent: PadRouter now adopts P1's gilrs reader and hands it back, so routing no longer opens one reader while another closes, which upset KWin's holder count. The router's reader also gets EXTRA_SDL_MAPPINGS (8BitDo Ultimate 2). It merges cleanly with #8233's triggers.rs.
2026-11-05 16:30:21 +01:00
Ludovic Marie 5215aea1b8 Merge pull request #9872 from yahya-nusseibeh/fix/windows-junction-swap
🤖 Agent: The new junction is made as current.switching before the old one goes, and the old one is restored if the rename fails, so a failed mklink no longer leaves the server with no current release. Paths are quoted through raw_arg, and % or a quote is refused. The author's Windows CI run built and tested it.
2026-11-05 16:30:16 +01:00
Ludovic Marie bf3fd2d5f0 Merge pull request #9903 from glitchsnake/fix/9744-fishing-hook-random
🤖 Agent: the catch now rolls from the table's random sequence (minecraft:gameplay/fishing, else the level random), as vanilla's LootTable.getRandomItems(LootParams) does, and test_fishing_rng_parity checks the hook's draws against a 26.3 server. I removed four tests that compared LegacyRandom with itself.
2026-11-05 16:30:12 +01:00
Evrim CicekandClaude Opus 5.5 debd86ea59 Endermen teleport where vanilla lets them, and water hurts them when it should
Enderman.teleport is LivingEntity.randomTeleport(x, y, z, true,
#enderman_does_not_teleport_to). Horizon took the first full-collision
block as the ground and landed on top of it, checked water only at the
eyes and lava only at the feet, and never looked at the tag, so endermen
teleported onto magma, into fire, sweet berry bushes and wither roses.

Now, as vanilla: the ground is the first #entities_can_teleport_to block
below the spot, and the enderman lands at the spot's height less the blocks
it went down. The ground and every block in the new box must not be in
#enderman_does_not_teleport_to, and the box must hold no liquid. The
motion is kept (Entity.teleportTo -> snapTo), both teleport sounds play at
the new spot, and GameEvent.TELEPORT goes out from the old one.

The new rain scenarios also showed when the water's hurt comes. It is the
end of LivingEntity.aiStep, after pushEntities, with or without AI, not
baseTick. Enderman.hurtServer draws its 1 in 10 teleport chance after
super.hurtServer (its hurt sound's pitch first), also when the hurt
cooldown refused the damage, and teleports at once. Horizon instead forced
a teleport in rain without the draw, one tick late.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:25:33 +03:00
Ludovic Marie b9913a1902 Merge pull request #9866 from Vaspyyy/native-fit-before-install
🤖 Agent: The fit line is worked out on the search thread, not the UI's. hooks_at reads raw.githubusercontent (no API quota) and is cached per commit. The four new strings go through tr/trf. One limit: it compares only the NATIVE_HOOKS_API number, so for a revision older than the hook catalog, a mod naming hook points can read 'Builds on the Horizon you play' and still be refused at build time. horizon-mods and launcher tests pass on the desktop with this head merged into main.
2026-11-05 16:25:02 +01:00
Ludovic Marie 0f9cb34f9d Merge pull request #9977 from horizon-rust/server/prune-chunks
🤖 Agent: horizon-server's one-shot chunk prune ([prune_chunks]); one point to know: a delete run rewrites region files, so it only runs before the game starts and deletes nothing if a keep_files file can't be parsed. Checked with vanilla 26.3's RegionFile on 3,743 real chunks from the official server: kept chunks equal as NBT, nothing with InhabitedTime >= 1200 pruned.
2026-11-05 16:24:58 +01:00
Ludovic Marie 4c302fc817 Merge pull request #9880 from Swuarez/fix/8906-glide-wall-sound
🤖 Agent: Player.playSound passes the player as except, so the server's broadcast skips the glider and its own client must play big_fall or small_fall. The client's travel now plays it and still never hurts. The unit test checks both.
2026-11-05 16:24:53 +01:00
Ludovic Marie 4e140f9b60 Merge pull request #9868 from Alysia42/fall-fly-ticks-order
🤖 Agent: fallFlyTicks is counted after the movement as LivingEntity.tick does, so updateFallFlying's +1 wears the elytra on the 20th glide tick, not the 19th, for the local player and LAN guests. Main merged in (both new motion tests kept).
2026-11-05 16:24:48 +01:00
Ludovic Marie fc8ff6b378 Merge pull request #9865 from xxaxio/fix/9791-vehicle-container-close-multi-viewer
🤖 Agent: The shared vehicle stand-in at CART_KEY_Y is now removed only when no other guest's menu (or the host's container) still shows it. The items are still stored back on every close. The proof comment's container.viewers mechanism isn't in this diff; I reviewed the diff itself.
2026-11-05 16:24:43 +01:00
masa-masa-0314andZCode df263fef74 Realms: the settings tab and the region preference screen
The configuration screen's Settings tab (RealmsSettingsTab): the world's name and
description fields (the click moves the caret between them), the region preference line
and its Region Preference button, and the open/close toggle moved here from the footer
(vanilla keeps it on this tab). Leaving the tab — switching tabs or Back/Escape — saves
the changed name, description and preference through RealmsClient.updateConfiguration
(POST worlds/{id}/configuration with RealmsConfigurationDto's JSON: the active slot, its
settings, the preference and the description).

RealmsPreferredRegionSelectionScreen: the automatic choices and every manual region, a
click selects, Done writes the choice back to the tab and Cancel drops it.

RealmsClient gains getPreferredRegionSelections (GET regions/preferredRegions) and
updateConfiguration; RealmServer reads regionSelectionPreference; RegionSelectionPreferenceDto,
RegionDataDto and RealmSetting serialization join the DTOs.

The tier's render/simulation distance options and the region quality icons are the next
PRs (the icons need sprites the GUI atlas does not carry yet); the region rows say their
name for now.

Co-Authored-By: ZCode <[email protected]>
2026-11-05 23:24:38 +08:00
Ludovic Marie 126b6d06b4 Merge pull request #9864 from TheMrZZ/fix/selector-entity-bounds
🤖 Agent: Box selectors test the target's bounding box with strict AABB.intersects (EntitySelector.getPredicate), while distance and sorting stay on the origin. Dragon, fangs, spawner, cloud and other kinds keep the point test until their boxes are modelled.
2026-11-05 16:24:38 +01:00
Ludovic Marie ad24f0f79f Merge pull request #9863 from TheMrZZ/fix/h323-mob-predicate-effects
🤖 Agent: Predicate views now get the mob's live effects as (id, amplifier, duration), which MobEffectsPredicate reads. Main merged in (corpus groups kept side by side); game tests pass on the desktop in a union with main.
2026-11-05 16:24:33 +01:00
Mayonesu0andClaude Opus 5.5 dec18717de Merge upstream main: keep pregen and the renamed wheel key
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:24:31 +01:00
Ludovic Marie 8af85a1e54 Merge pull request #9856 from Mayonesu0/fix/8036-wool-jump-vibration
🤖 Agent: Checked against Entity.applyMovementEmissionAndPlaySound and checkFallDamage in 26.3: STEP carries the getOnPos state and HIT_GROUND the mainSupportingBlockPos state, so a carpet dampens. A mob's support is looked up only when a step is due, not every tick. Climbing still reads the block at the feet.
2026-11-05 16:24:28 +01:00
Horizon agentandClaude Opus 5.5 1e887567bd Extras: a label too wide for half a row takes the whole row
"Keep channels for UI sounds: Off", "Scroll wheel changes settings: Off"
and "Other players' cosmetics: Hidden" ran past their half-width
buttons and slid back and forth. Extras and PvP & HUD now measure each
item's label, with its first values, in the screen's font when the page
is drawn; one wider than a half-width button holds gets the whole row.
It works in every language, with no caption shortened.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:20:09 +01:00
Horizon agentandClaude Opus 5.5 557bbea813 Settings: each setting in one place, Video Settings opens the graphics screen
Options > Video Settings... now opens Horizon's graphics screen, as
Sodium's replaces vanilla's video screen. It gains the vanilla rows only
vanilla's screen had (Graphics Preset, Exclusive Fullscreen and its mode,
Menu Background Blur, Texture Filtering, Max Anisotropy) and the extras
that change the picture (Fullbright, Dynamic lights, Hide block-break
particles, still saved in horizon-extras.txt). It drops FOV and View
Bobbing, which stay where vanilla has them (Options, Accessibility).

Horizon Settings drops its second ways into the graphics screen, the
controller screen (Controls has it) and Mods (the Options screen has
it); Extras drops its PvP & HUD link and the PvP preset, which PvP & HUD
opens with. Vanilla's own Video Settings layout stays for
horizon-ctl options video-vanilla and its tests; no menu opens it.

A test walks every menu from Options and checks that each setting and
each page has one way in, and that every row of vanilla's Video
Settings is on the graphics screen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:20:08 +01:00
Ludovic Marie 90a8b6efff Merge pull request #8233 from voidsipp/fix/7323-linux-triggers
🤖 Agent: Axis triggers are read only for an unmapped six-axis pad with the Xbox face layout (C and Z absent), so a generic pad's centred right stick on LeftZ/RightZ never reads as a half-pulled trigger. Main merged in beside #9830's face_buttons; game tests pass on the desktop in a union with main.
2026-11-05 16:19:19 +01:00
Ludovic Marie 87f4372520 Merge pull request #9909 from masa-masa-0314/feat/realms-worlds-slots
🤖 Agent: the slots now parse as the service sends them (a list, each slot's options a JSON string, missing world slots empty, worldTemplateId/Image), so the detail answer and the Players tab no longer fail; official Realms API only.
2026-11-05 16:19:14 +01:00
Ludovic Marie f10167a87d Merge pull request #9911 from xxaxio/fix/9902-launcher-topbar-invite-overflow
🤖 Agent: under 1380 px the tab strip only shrinks when the buttons need the room, so nothing scrolls in English where it already fit; the placeholders still go through tr() inside line_edit::content.
2026-11-05 16:19:09 +01:00
Ludovic Marie 7146e9b17f Merge pull request #9908 from DUzzL/fix/9877-team-predicate
🤖 Agent: a direct port of TeamPredicate (exact, case-sensitive name match, no team fails), filled from the real scoreboard for the player, mobs (a tamed pet answers with its owner's team) and LAN guests.
2026-11-05 16:19:04 +01:00
Ludovic Marie 3973f6b63c Merge pull request #9907 from arslanarm/shaderpacks/ftransform-order
🤖 Agent: one more mat4 x vec4 per vertex (16 FMAs), only in pack gbuffers and shadow vertex shaders: negligible next to fragment work. The GPU test tells the two rounding orders apart with exact expected bits.
2026-11-05 16:18:59 +01:00
Ludovic Marie 735bfffc0b Merge pull request #9906 from MSedgeMC/fix/wither-rose-smoke-centre
🤖 Agent: the marker_particles harness gains 27 wither rose cases that fail without the fix (x and z off by 0.5); I made outline_center bound only the shape's own boxes instead of starting from 0.5.
2026-11-05 16:18:55 +01:00
Ludovic Marie 73810bccc1 Merge pull request #9905 from senjinthedragon/shadow-depth-gather
🤖 Agent: removes the R32Float depth copy made every frame (512 MiB and about 10 ms at 8192², measured); each plain-sampler depth tap becomes one gather plus a bilinear blend, behind a uniform branch. The gather order (w,z / x,y) matches the blend.
2026-11-05 16:18:50 +01:00
Ludovic Marie cb05802dfb Merge pull request #9896 from arslanarm/shaderpacks/colortex-mipmaps-box-filter
🤖 Agent: hot path: at most 4 texel loads per mip texel (9 where both sides are odd), only for buffers that set MipmapEnabled: about 1.2M loads for a 1280x720 chain, well under 0.1 ms. Odd levels no longer skip texels.
2026-11-05 16:18:45 +01:00
Ludovic Marie ccfc6cf2be Merge pull request #9895 from arslanarm/shaderpacks/cloud-height-without-clouds
🤖 Agent: per-frame cost goes down: cloudHeight now just reads the dimension's visual/cloud_height (vanilla's EnvironmentAttributes default is 192.33), with no alpha branch; Nether and End packs stop seeing NaN.
2026-11-05 16:18:40 +01:00
Ludovic Marie e5e2bef6de Merge pull request #9893 from DUzzL/fix/9870-disconnect-layout
🤖 Agent: the layout matches DisconnectedScreen.init in the bytecode: cell padding 10 for the title and reason, 2 for the button, the reason wrapped to width - 50, the block centred. I moved wrap's doc comment back onto wrap.
2026-11-05 16:18:35 +01:00
Ludovic Marie 453f6114bd Merge pull request #9886 from DUzzL/fix/9867-advancement-targets
🤖 Agent: grant and revoke now run as each selected player's own turn (for_player), and the feedback follows CommandResponseTracker: changed players on success, every selected player on failure.
2026-11-05 16:18:31 +01:00
Ludovic Marie 200314a500 Merge pull request #9884 from xxaxio/fix/9781-map-marker-names
🤖 Agent: matches MapRenderer.render's name label in the bytecode: scale clamp(25/width, 0, 6/9), and z -0.025 then scale -1 and +0.1, so -0.125 in map space. I dropped a test that only checked its own closure.
2026-11-05 16:18:27 +01:00
Ludovic Marie cff59ae4b5 Merge pull request #9869 from arslanarm/shaderpacks/fluid-top-split
🤖 Agent: mesh-time only: one rotate_left per fluid top while a shaders/ pack is loaded, and a remesh when that changes; nothing per frame. The split rule is fitted to probe readings of Iris that was only run, not read.
2026-11-05 16:18:22 +01:00
Ludovic Marie a683d394dc Merge pull request #9838 from arslanarm/shaderpacks/shadow-caster-margin
🤖 Agent: I pushed b0f885600: this branch now merges #9772 and gives the new test's `ShadowCull` `reach: None`. Without that, whichever of the two landed second would break main's build. The union with #9772 passes every `shadow_caster_tests`. Parity's first run failed only on tab_list (its harness's missing vanilla.log) and the known lunge_motion flake, neither reached by shadow culling: rerun.
2026-11-05 16:18:18 +01:00
Ludovic Marie 9e8ad0b088 Merge pull request #9725 from vnsmoreira/extras-pregen-toggle
🤖 Agent: Off by default, and the client completion tree only gets `/pregen` when the dispatcher has it, so vanilla's command tree is unchanged until a player turns it on. Note for later: `pregen::command_tree()` duplicates the shape of `horizon_commands` in horizon-command; a change to one must follow in the other. I merged main (7899c12c4): the pregen line sits beside main's new `horizonMarks`/`cosmeticsSolo` extras, and 773e3526d queues every toggle on the server thread (no blocking `call`), with a test that a quick on-off reaches the server in order.
2026-11-05 16:18:13 +01:00
Horizon agentandClaude Opus 5.5 941d0553fd horizon-server: rustfmt and clippy for the chunk prune
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:17:22 +01:00
Horizon agentandClaude Opus 5.5 c047a7ef5a Cosmetics: rustfmt the name tag lift tests
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:16:46 +01:00
Horizon agentandClaude Opus 5.5 6c526709ff Fishing: drop the scenario tests that compared LegacyRandom with itself
test_fishing_rng_parity now checks the hook's draws against vanilla; the
four scenario tests seeded two LegacyRandoms alike and compared them, so
they could not fail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:15:48 +01:00
Horizon agentandClaude Opus 5.5 1f43c60b85 Launcher: the Wardrobe links to the site's shop, nothing more
Ludovic (2026-11-05): the shop is on the site only; the launcher has the
Wardrobe and a link to the site. The Wardrobe already had no prices,
product lists or checkout; its "Open the shop" button becomes one plain
secondary button, "Get more cosmetics on playhorizon.dev", opening
https://playhorizon.dev/shop, by the title or, for an account that owns
nothing yet, in the empty state. Translated in all 86 languages.

Kept: what you own, equip and unequip, the preview, the purchase history
and the founder set's "Claim for free" where Paddle doesn't sell (the
site sends those players to the Wardrobe: only the launcher can prove
the account with Mojang).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:15:45 +01:00
Horizon agentandClaude Opus 5.5 e3ccd955d7 Graphics: a Shaders page holds the shader packs
The graphics screen gets a Shaders page between Performance and Advanced:
Shader Packs... opens the pack list (Horizon's .hshader packs and shaders/
folder ones, with the picked pack's options), and Shadow Cache moves there
from Performance. Horizon Settings loses its Shader Packs button, so the
list has one way in, beside the other graphics settings, as Iris hangs
off Sodium's video screen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:13:39 +01:00
Horizon agentandClaude Opus 5.5 24da3cefad Cosmetics: lift the name tag over tall items
A crown's star on the head slot or an orrery on the back slot crossed the
wearer's name tag, which vanilla draws at a fixed height. Each item now
knows its top (Item:🔝 pixels above the model's neck with the bones at
rest, animations included), found in Item::parse with the reach check.
When the tallest item drawn on a player passes the tag's lowest pixel,
the whole tag (Horizon's mark and the badge row included) goes up to one
text pixel over it.

Players with no tall item, and anyone whose items aren't drawn (others'
cosmetics on Name tags only or Hidden, My cosmetics hidden, a helmet over
a hat), keep vanilla's height exactly: the lift is never added then.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:12:54 +01:00
HiuoyandClaude Opus 5.5 f9a93d080a Native hooks: ClientTickEvent.Pre / .Post (client_tick_pre, client_tick_post)
A client mod hears each client tick of the window, at the top and the
end of vanilla's Minecraft.tick: native_client_tick_pre() before any of
the tick's work, native_client_tick_post() once the world's tick, the
chat queue, music and sounds are done. They fire 20 times a second on
the title screen, while paused and in a world alike, as vanilla's tick
runs, and never on a server (single player's ticks on its own thread).
The fragment labels them ClientTickEvent.Pre / .Post, client, not
cancellable.

Checked in game with horizon-ctl and a local probe mod: about 20 calls
a second of each, always paired, on the title screen, in a creative
world and under the pause menu.

Fixes #9971

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 19:10:36 +04:00
LokiandAntigravity 78c23a4f37 tools: use lab.game_env in test_fishing_rng_parity (fixes #9744)
Co-Authored-By: Antigravity <[email protected]>
2026-11-06 00:10:36 +09:00
IsT3RiK f3e8afcb03 Launcher: add Glass, Blur and the server modes' notes to every table
These labels reach tr through a variable (the Launcher settings' slider rows, MODE_NOTES in
the server dialog), so the scan for tr("...") never asked for them and no table had them: they
showed in English in every language. Added to every table, in English, and translated in
fr.json.
2026-11-05 16:05:40 +01:00
Horizon agentandClaude Opus 5.5 89ab731e0c horizon-server: the prune report says how long the run took
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:03:51 +01:00
MSedgeMC caeaa41bdc Test: a landing change a mod cancels keeps the block as it was
The dispatch test asserts the hook's fields for a guest's trample, that a
cancel stops later mods, and that a change with no mods goes ahead.
2026-11-05 15:03:30 +00:00
MSedgeMC 15f071ff3b Landing: a cancelled trample keeps the block, its crop and its events
The landing's raw write is applied before the game finishes it: the game
now asks the hook there, and a cancel puts the previous block back and
skips the neighbour updates and the level events. The crop above trampled
farmland stays, because breaking it is the neighbour update and not a
write of its own.

`FallEvent::Write` carries the block it replaced, so the game has
something to put back. A guest's landing is asked with that guest's UUID.
2026-11-05 15:03:30 +00:00
MSedgeMC 4985864055 Native hooks: a landing's block change a mod can cancel
A player's landing tramples farmland into dirt and breaks turtle eggs with
no hook a server mod can cancel, so a land-claims mod cannot stop a guest
from trampling inside a claim (#9036, #9873 item 3).

`native_entity_change_block_v2` is asked with the player whose landing it
was, the position and both block names, and can cancel the change. It is
additive: `native_entity_change_block` keeps its signature and firing point.
2026-11-05 15:03:30 +00:00
Loki c22c38d8b8 Merge remote-tracking branch 'origin/main' into pr-9903 2026-11-05 23:58:45 +09:00
Horizon agentandClaude Opus 5.5 19cb6b6730 horizon-server: a pruned region file keeps the old file's permissions
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:53:36 +01:00
IsT3RiK 66beeb539d Launcher: the Mods page's install messages go through tr
The messages the Mods page shows after an install (a mod, a native mod, a data pack, a
modpack, a resource pack) and its "isn't set up" errors were built with format!, so they
stayed in English in every language: "Installed JEI for Horizon 0.1.0 into Horizon. Build to
play with it." on a French launcher. They now use trf with named holes. The 11 strings are
added to every table, in English, and translated in fr.json.
2026-11-05 15:47:58 +01:00
umcekoandClaude Opus 5.5 9d9617fe29 Save and load primed TNT's owner
PrimedTnt.addAdditionalSaveData stores the igniter as an `owner` UUID
(EntityReference.store) and getOwner resolves it against the level when
asked. tnt_to_nbt now writes the live owner's UUID (a guest, the host
player or a mob), and a loaded TNT looks its saved owner up when it
explodes, so the blast is credited to the player or mob that lit it after
a reload, a portal trip or a chunk unload. An owner that is not online
keeps its UUID through the raw compound.

Fixes #9951

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:45:51 +03:00
Ludovic Marie 66f3981f52 Merge pull request #9809 from voidsipp/fix/9742-account-refresh
🤖 Agent: Only the glass look changes: the actions move under the profile and wrap inside the 400 px card, and Night keeps its row through the same `slot_row`. Launcher fmt, clippy and 674 tests pass on the desktop with this head merged into main.
2026-11-05 15:45:35 +01:00
Ludovic Marie 83148f3849 Merge pull request #9790 from voidsipp/fix/9719-glass-launch-settings
🤖 Agent: Launch… reuses the existing translated string and the glass sliders-horizontal icon, and clearing `launch` before `toggle_launch` stops a panel left open for another instance from turning the click into Close. Launcher fmt, clippy and 674 tests pass on the desktop with this head merged into main.
2026-11-05 15:45:31 +01:00
IsT3RiK 3a298db1c0 Launcher French: translate the 38 strings left in English, address the player as vous throughout
38 entries still held their English text (the modpack coverage reasons, Performance mode, the
Vulkan fallback notice and a few command-line hints). 15 entries said tu while the other ~110
said vous, sometimes side by side on one page; they now say vous, as the game's own French
table does. Names that read the same in French (Mods, Console, Nightly, Snapshots...) stay.
2026-11-05 15:45:27 +01:00
HiuoyandClaude Opus 5.5 6d59c2da8d Port pack events: tests and the listener class fixture in tests/events.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:44:02 +04:00
Horizon agentandClaude Opus 5.5 ada2c6b810 Merge main into #9854: placement cases after the wake and used-stat cases
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:42:35 +01:00
Horizon agentandClaude Opus 5.5 afbc3ee1cf Merge main into #9863: keep the mob effect predicate group beside frame maps
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:42:20 +01:00
HiuoyandClaude Opus 5.5 8a3eafbd61 Port pack: events, a mod's NeoForge listeners against the hooks (#9921)
`horizon-port-pack events <jar or folder>` reads class files for the
NeoForge events a mod listens to (an @SubscribeEvent method's parameter,
a method reference or lambda that takes one event) and the Capabilities
fields it reads, and looks each name up in native-hooks.json's `neoforge`
labels: a hook, another phase's hook, a hook whose label says why it
isn't quite one, or none. Over a folder of jars it ranks the names by
how many mods use them. Names NeoForge changed after 1.21.1 are looked
up under their new ones. It reads names, never code.

On ATM10's 498 jars it takes 8 s: 416 of 497 mods use 326 names, 6,616
uses in all. Main's catalog has a hook for 23% of them, and 43% with
#9924's labels. The top gameplay gaps are ClientTickEvent (95 and 46
mods), RenderLevelStageEvent (81) and ClientPlayerNetworkEvent (53, 35).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:42:14 +04:00
Horizon agentandClaude Opus 5.5 ff980bfed4 Merge main into #8233: face_buttons beside triggers
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:42:00 +01:00
levzzzandCodex 7c085b5acd Fix fishing loot and experience RNG ordering
Use the loot table random sequence (or level fallback), preserve level draw order, and create the experience orb directly. Add vanilla server comparisons for casting, bobbing, catch timers, and retrieval.

Co-Authored-By: Codex (GPT-6) <[email protected]>
2026-11-05 19:39:19 +05:00
Horizon agentandClaude Opus 5.5 ccaa93ba2c horizon-server: [prune_chunks] server.toml round trip test, Cargo.lock
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:39:00 +01:00
Evrim Cicek f732441c84 rustfmt 2026-11-05 17:38:11 +03:00
Ludovic Marie 1eaf4d2e5b Merge pull request #9821 from voidsipp/fix/8583-lod-test-fixtures
🤖 Agent: Test-only, plus a `background` flag that only the bounded test turns off; the game still builds its decoration workers at idle priority.
2026-11-05 15:37:19 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 2bc92b6348 Serve shader API 0.2 behind HORIZON_HDR_FRAME=float
With the switch, the game draws 0.2 packs ([frame] blend = "linear" is
still refused). A renderer made with a 0.2 pack picked draws a float frame
where the device can, else the 8-bit one; a pack picked or dropped later
runs on the frame there is, logged once. The renderer's material, sky,
weather, vertex and shadow entries decode a 0.2 stage's colours and encode
its results; scene_color, opaque_color and the water mirror are decoded
copies, shadow_color is float, and Frame's sky and fog colours are
decoded. The post chain ends in a window-size picture, and the pack's
[tonemap] (or Horizon's clip) draws it onto the frame, encoded at alpha 1.
With HDR on, HDR output lifts nothing of a 0.2 pack's world.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:37:14 +01:00
Ludovic Marie 65af1daf70 Merge pull request #9772 from arslanarm/shaderpacks/shadow-distance-render-mul
🤖 Agent: The reach test prunes with the same grown box as the caster test. Packs without `shadowDistanceRenderMul` take `reach: None` and walk exactly as before. #9838 is stacked on this branch: it now contains these commits.
2026-11-05 15:37:14 +01:00
Ludovic Marie ae59c828f1 Merge pull request #9769 from arslanarm/shaderpacks/celestial-positions
🤖 Agent: The positions now come from JOML's quaternion turns, ported under the MIT notice that `camera.rs` and THIRD_PARTY_NOTICES already carry. It's a few float ops per frame, and the test pins five Iris readings bit for bit.
2026-11-05 15:37:10 +01:00
Ludovic Marie 4b1e8dfc97 Merge pull request #9756 from I114rk/fix/boot-paint-flicker
🤖 Agent: Every repaint is now one `BitBlt` from a memory DC, and every GDI object (DC, bitmap, old selection) is released on both the success and failure paths. PR CI doesn't build Windows; your Windows clippy run covers that.
2026-11-05 15:37:05 +01:00
Horizon agentandClaude Opus 5.5 5278933e3f Keep drop_offset's three-argument form; drop_offset_from takes movedUp
#9859 adds a drop_offset caller (the dispensed dragon); keeping the
existing signature lets both land in either order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:36:28 +01:00
Horizon agentandClaude Opus 5.5 d7e7b92735 Ender Dragon spawn egg: vanilla random order and the dragon's own random
EntityType.create draws the wrapped spawn yaw and Mob.finalizeSpawn the
follow-range triangle and the left-handed roll from the level random;
the dragon seeds its own random (Entity.random), not from the level one.
test_spawn_egg_fluid_parity gains the dragon egg used on a block.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:34:34 +01:00
Mayonesu0andClaude Opus 5.5 429defdece Options: the wheel scrolls a settings list past its buttons
A vanilla list keeps the wheel: AbstractContainerWidget inherits
AbstractScrollArea.mouseScrolled, which scrolls and never reaches the rows, so
CycleButton.mouseScrolled only runs for a button outside a list. Horizon stepped
the row under the cursor, so scrolling a long page changed options.

"Scroll wheel changes settings" keeps that behaviour as an opt-in extra, off by
default. Its saved key is new because the old one was written as true by every
install, and its tooltip has a new key because other languages said "as in
vanilla"; they fall back to English until translated.

Fixes #9633

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:33:32 +01:00
Ludovic Marie c4224839db Merge pull request #9845 from Joansitoh/fix/9778-missing-sounds
🤖 Agent: f4c4ab173 keeps `lastLevelUpTime` and `tickCount` per player entity (`LevelUpSound`, reset on respawn), as `Player.giveExperienceLevels` reads them, and `/xp points` now sounds per level crossed like `giveExperiencePoints`; the lan_e2e `levelup-sounds` check pins both players apart. Wither head particles are drawn on a following client only: the server's `particles.add` draws nothing. The guest tick loop is one add per player per tick.
2026-11-05 15:33:07 +01:00
Ludovic Marie be95b51aac Merge pull request #9841 from voidsipp/fix/9642-player-placement
🤖 Agent: I pushed c17d14c73. `GameLevel` now borrows `guest_boxes` outside a guest turn instead of cloning them on every level build. `for_turn` searches only the few borrowed ancestors (hub conns are unique), so each guest's turn is O(players), not O(players²), every tick. Your tests are unchanged and pass. Then I merged main (c4b85eaed): only the `lan_actions.rs` imports conflicted.
2026-11-05 15:33:02 +01:00
Ludovic Marie 0caddefd8e Merge pull request #9830 from martipops/fix/pad-unmapped-xy
🤖 Agent: The swap applies only on Linux, for a driver mapping with exactly the four face buttons and a non-Sony/Nintendo vendor. Pads with an SDL row aren't touched. It costs six `button_code` lookups per pad per poll.
2026-11-05 15:32:57 +01:00
Ludovic Marie a0b7ba13f1 Merge pull request #9827 from DUzzL/fix/9786-loyalty-owner
🤖 Agent: Matches `ThrownTrident.tick`'s `isAcceptibleReturnOwner` (checked in the decompiled source): a dead or spectating owner drops the trident (unless creative-only) and discards it, and an owner not in this level still keeps the old any-player pickup.
2026-11-05 15:32:52 +01:00
Ludovic Marie dcb0962c58 Merge pull request #9818 from Swuarez/fix/8038-breeding-xp
🤖 Agent: I pushed 8cff8f837: with the vanilla data present, the new test failed because the llamas weren't tame (`AbstractHorse.canParent`), so they never bred. Without the data it returned early, which is why it passed for you. Both llamas are now tamed, and it passes on real data.
2026-11-05 15:32:47 +01:00
HiuoyandClaude Opus 5.5 d9d4d19a1d Native hook catalog: label the hooks ATM10's mods listen to most
A scan of ATM10's 491 mods for their NeoForge event listeners (#9921)
showed nine unlabelled hooks behind events many of them use:
RegisterCommandsEvent (95 mods), ItemTooltipEvent (74), the item and
fluid block capabilities (129, 88), RenderHighlightEvent.Block (41),
ServerStartedEvent / ServerStoppedEvent (48, 41), LivingDropsEvent (36),
LivingKnockBackEvent and ExplosionEvent.Detonate. Each label comes from
the hook's own doc comment and firing point; `cancellable` matches its
mod_api contract, and block_highlight keeps the side and cancellable it
already had.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:32:46 +04:00
Ludovic Marie d52ff7a6f7 Merge pull request #9812 from rison1337/native-hook-stamp-selected
🤖 Agent: `--only` keeps first-parent order and leaves the other 18 fragments and their markers byte-identical. The pending signatures in #9431 and #9649 belong to other fragments, so stamping 410/411 doesn't settle them early. If another stamp lands first, regenerate.
2026-11-05 15:32:42 +01:00
Ludovic Marie c8e4f35717 Merge pull request #9807 from BPMichon/feat/toast-add-hook
🤖 Agent: With no listener, `toast_allowed` is one dispatch over an empty list, and it's asked only when a toast is created, not per frame (a cancelled hint is remembered). Updates to a shown toast are not re-asked, as with vanilla's addOrUpdate. I pushed 6fa2c9933: main added `Hint::SocialInteractions` (#9796), so `Hint::name` needed the arm to build, and that toast now also goes through `toast_allowed` (listed in the docs as `socialInteractions`).
2026-11-05 15:32:37 +01:00
Ludovic Marie 989bfacfe2 Merge pull request #9802 from lightcodegit/fix/crafter-trigger-box
🤖 Agent: I pushed cfa94ac87: vanilla's `AABB.intersects` is strict (`minX < maxX && maxX > minX`), so a player box that only touches a face of the 17-block box is out. The test is now `<`/`>`.
2026-11-05 15:32:33 +01:00
Ludovic Marie 4cfa08e49f Merge pull request #9797 from voidsipp/fix/9524-physical-pack-folder
🤖 Agent: `dunce` is already in Cargo.lock (MIT/Apache/CC0) and is Windows-only. The Help link now goes through `open_uri`, so a URL is no longer canonicalized as a folder path.
2026-11-05 15:32:28 +01:00
Ludovic Marie eede50452a Merge pull request #9765 from arslanarm/shaderpacks/atlas-linear-mipmaps
🤖 Agent: Only the block atlas gets the new sampler, and only when filtering is on: normals, specular and Horizon's own packs keep `NearestRepeat`. The GPU test pins the 0.5/0.25 level blends. I merged main (latest b859bcf31): the conflicts were only the test-module list in `pack_post.rs`.
2026-11-05 15:32:23 +01:00
Horizon agentandClaude Opus 5.5 5d9bd56ec8 horizon-server: prune barely visited chunks ([prune_chunks], #9949)
A one-shot step before the game starts, switched on in server.toml: chunks
with InhabitedTime under min_inhabited_ticks (1200) are deleted from region/,
entities/ and poi/, files rewritten compacted and emptied ones deleted.
Chunks near the spawn, named by keep_files (SMP claims and homes), that can't
be read, and a ring around every kept chunk stay. mode = "dry-run" only
writes the report while the game runs; either mode turns itself off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:32:19 +01:00
Ludovic Marie b1ba47e910 Merge pull request #9751 from TheMrZZ/fix/h317-frame-map-detach
🤖 Agent: This matches `ItemFrame.readAdditionalSaveData`: the old map is detached only when the old stack is non-empty and `ItemStack.matches` fails (count, item, components), and a glow frame keeps its type. I merged main (latest 5d54563cc), following your keep-both note: both isolation sets, the `expect_map` guard, and `frame_map_state`, `mob_anger` and #9848's `menu_state` all in the record accounting.
2026-11-05 15:32:18 +01:00
Ludovic Marie 361527bc07 Merge pull request #9743 from TheMrZZ/fix/map-decoration-type-namespace
🤖 Agent: Only a bare `type` is qualified, through the existing Identifier parser, at both the decode and the encode boundary, which matches `MapDecorationType.CODEC`'s holder-by-name read. #9814 (already merged) handles the empty default separately. I merged main (latest b9df50777), keeping your `canonical_other_component` beside #9814's empty-default filter in `encode_components`, and your fixture flags beside #9848's piglin ones.
2026-11-05 15:32:13 +01:00
Ludovic Marie 653aef535a Merge pull request #9737 from ilguerro/fix/villager-stand-up
🤖 Agent: The villager now uses the same `findStandUpPosition` search as the player, with its own 0.6 x 1.95 box and vanilla's fallback (0.1 above the block over the bed). The player path is a move with no change, and the `1.0E-5F` border inset is now the float vanilla uses. I merged main (8bbedb4d4): the use_item_on harness keeps your wake cases and #9775's used-stat cases as two separate seeded streams, so neither changes the other's cases.
2026-11-05 15:32:07 +01:00
Ludovic Marie bc243f7208 Merge pull request #9722 from lightcodegit/fix/splitscreen-pause-ticks
🤖 Agent: `pauses_game` now reads the same `split_players` list `set_paused` already uses, so the window can no longer stop ticking while the server thread runs on for P2. Solo, LAN and guest windows take the old path.
2026-11-05 15:32:00 +01:00
Ludovic Marie c8bfb81f9c Merge pull request #8709 from Akirabane/feat/7484-control-release
🤖 Agent: The release names its hand-over, so a stale or repeated `player-release` cannot remove a player who is playing again, and `remove_guest` only skips the leave line when `quiet_leave` is set: ordinary leaves are unchanged. Your rebase (ebfab7fc8) is the same patch; checked in a 21-PR union on main: fmt, clippy -D warnings, workspace tests.
2026-11-05 15:31:55 +01:00
CocofireHDandopencode f8d0840cd7 lan_e2e: prove a sculk sensor's vibration reaches the host and a guest
VibrationSystem.Ticker.trySelectAndScheduleVibration sends one vibration particle to every player within 32 blocks; the check asserts both the host's window and a LAN guest make it (#8615).

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 15:30:45 +01:00
Evrim CicekandClaude Opus 5.5 068b87cc25 An iron golem's anger target is kept, saved and forgiven like a wolf's
Now that the golem keeps its anger target through updatePersistentAnger:
NeutralMob.playerDied ends anger at the dying player even once the golem
lost sight of them; the save writes the anger target, not the current
target; and anger at a mob is written with that mob's UUID and read back
as anger at a mob, so a reloaded golem that was fighting a monster is not
isAngryAtAllPlayers under universal_anger. A calm mob no longer keeps a
loaded angry_at in its save.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:24:18 +03:00
MSedgeMC e51fd1dff4 port_map: ItemStack.hurtAndConvertOnBreak is the steering stick's rod
The row was `missing`; the conversion was in `use_steering_stick` but
built a fresh plain rod and skipped `hurtAndBreak`.
2026-11-05 14:24:14 +00:00
MSedgeMC c135993a63 test: a steering stick that breaks leaves the rod the stick was
`transmuteCopyIgnoreEmpty`: at 18 + 7 of 25 the carrot on a stick becomes
a fishing rod that kept its custom name and starts whole, where the old
path wrote a fresh plain rod. Wear that Unbreaking took away (0) breaks
nothing, and a stack with no durability never breaks.
2026-11-05 14:24:12 +00:00
MSedgeMC c942e133da fix: a steering stick's wear goes through the item's durability rules
`FoodOnAStickItem.use` wears the stick with
`ItemStack.hurtAndConvertOnBreak`, which runs `hurtAndBreak` - and
`hurtAndBreak` asks `processDurabilityChange` first, so Unbreaking has its
say before any damage lands.

`use_steering_stick` added the whole 7 (carrot on a stick) or 1 (warped
fungus on a stick) straight onto `stack.damage`, so an Unbreaking stick
lasted exactly as long as a plain one, and it never reached the mods'
item-damaged hook.
2026-11-05 14:23:55 +00:00
CocofireHDandopencode 8df11ffe4c Mod fluid tanks: let a tank refuse a fluid
A mod tank can now refuse a fluid through native_block_entity_accepts_fluid (NeoForge's IFluidHandler.isFluidValid). The server asks it before filling a tank in both insert passes and before native_move_fluids counts a tank's room; every tank accepts every fluid unless a mod cancels. A water tank can take no create:chocolate, and a removed mod leaves no fluid id it cannot use behind.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 15:21:31 +01:00
Horizon agentandClaude Opus 5.5 daf49b24bb rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:18:21 +01:00
Horizon agentandClaude Opus 5.5 f6f95b6900 rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:18:21 +01:00
Loki 22c2b843c5 Merge remote-tracking branch 'origin/main' into fix/9744-fishing-hook-random 2026-11-05 23:18:03 +09:00
Ludovic Marie c04c5ccf90 Merge pull request #9940 from horizon-rust/docs/telemetry-presence-exception
🤖 Agent: docs only. telemetry.md now lists Show me as Horizon (on by default) as the second exception to 'nothing without consent', which settles socksprox's point on #9440.
2026-11-05 15:15:50 +01:00
Horizon agentandClaude Opus 5.5 6968e9dd97 docs: telemetry.md names Show me as Horizon as the second exception to 'nothing without consent'
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:14:40 +01:00
MSedgeMC 3ef40bc92f port_map: BundleItem.getTooltipImage names the tooltip display rule
The row pointed at the geometry and carried the missing rule as a gap;
the grid now goes through `item_tooltip::bundle_tooltip`, which asks
`TooltipDisplay::shows("minecraft:bundle_contents")` before the layout.
2026-11-05 14:12:07 +00:00
hildlabsandClaude Opus 5.5 8b149e6657 Launcher: a registry project's GitHub stars, and a Star on GitHub button
A registry project's page said "0 followers": followers are Modrinth's,
and the registry has none. Its creators collect stars on their GitHub
repository instead, so the index gets `projects[].stars` (the source
repository's stars, which horizon-mods' publish job reads every hour, as
it adds `downloads`; absent when 0, the schema unchanged) and the page
shows "{count} stars on GitHub" there, with a "Star on GitHub" button
beside "Sign in with GitHub to rate" that opens the repository, where
the player stars it. No sign-in or permission is added.

`browser::horizon::github_repo` reads the repository from `source` (a
folder of it, `.git`, a query or an anchor are the same repository;
another host or a name GitHub doesn't allow gets no button). A project's
details now go to the page as the `Arc` it keeps them in, so the larger
`Details` doesn't make `Loaded` a large enum. Both strings are in every
language's table.

Fixes #9929

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:11:58 +01:00
MSedgeMC 875b657878 test: a bundle grid hidden by the tooltip display
`minecraft:bundle_contents` in `hidden_components`, and `hide_tooltip`,
both take the grid away; a display that hides `minecraft:lore` leaves it.
2026-11-05 14:11:58 +00:00
MSedgeMC 2ec1ce0480 fix: a bundle's contents grid follows the tooltip display
`BundleItem.getTooltipImage` returns nothing when the stack's
`minecraft:tooltip_display` hides `minecraft:bundle_contents`, so a
bundle given with `hide_tooltip` or with that component hidden drew no
grid. Horizon drew the grid for every bundle, and the tooltip box grew to
hold it.

`item_tooltip::bundle_tooltip` asks the display first and then defers to
`bundle::tooltip_layout`; the tooltip draw uses it for the height probe
and for the draw alike.
2026-11-05 14:11:58 +00:00
CocofireHDandopencode 4993bc83ac Shaders: upscale the scaled pack target to the full surface
A Resolution Scale leaves the pack's picture smaller than the window. Its final pass drew at the window's size, so a shader using gl_FragCoord as texel coordinates (Photon's final) only filled a quarter of the screen. Draw final at the picture's size into an offscreen texture and copy it once to the window with a screen-row fullscreen pass; the no-pack path and same-size output are unchanged.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 15:10:10 +01:00
Evrim CicekandClaude Opus 5.5 a5416ca075 Iron golems toss by knockback resistance and keep their anger like vanilla
IronGolem.doHurtTarget throws the target up only when the hit lands, by
0.4f * max(0, 1 - knockback resistance); Horizon threw everything it swung
at by 0.4. The hit and the toss are one event now, like the warden's
sonic boom.

IronGolem.aiStep ends in NeutralMob.updatePersistentAnger(level, true), the
same as a wolf's: every tick it holds a target, a monster too, the anger
timer restarts with a draw of the golem's random. Horizon only angered it at
a player who hit it, so a golem going after a monster drifted from vanilla's
random within a few ticks. The golem now uses the wolf's port, and its
player target goal asks isAngryAt.

LivingEntity.aiStep still drops motion below 0.003 and runs pushEntities
without AI; Horizon's NoAI branch skipped both, which the new mob AI
scenarios (a golem fighting a NoAI zombie, ravager and warden) showed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:09:34 +03:00
Florian ERNSTandCodex eb0193cb34 Normalize Enderman carried states before placement
Run neighbor-shape updates on the authoritative level before the existing eligibility checks, and clear the verified carrier after the handled placement.

Co-Authored-By: Codex <[email protected]>
2026-11-05 15:06:31 +01:00
levzzzandCodex GPT-6 aa19826c39 Fix dispensed dragon spawn height and sound
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 19:06:15 +05:00
levzzzandCodex GPT-6 773e3526dd Test queued pregeneration toggle changes
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 19:05:08 +05:00
Ludovic Marie a61e02d362 Merge pull request #9915 from horizon-rust/fix/gl-terrain-uint-select
🤖 Agent: fixes the OpenGL startup crash on old AMD drivers (#9892), a regression from #9584 in Nightly f319701. The new test fails on main and passes here.
2026-11-05 15:05:02 +01:00
CocofireHDandopencode 2816f17962 Shulker boxes: close the block entity when its menu is removed
A guest's use raised its opener count twice: open_menu counted the block entity's startOpen, then lan_open_container counted it again once the menu was sent. Closing lowered it once, to one, so stopOpen never ran and the lid stayed open with its raised collision (#9500).

The guest open path now relies on the count open_menu already made; the harness keeps the lid open and closed checks for both host and guest.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 15:01:27 +01:00
MSedgeMC afad8241b6 port_map: the two BeehiveBlock tick methods
animateTick and trySpawnDripParticles are block_animate_tick and beehive_drip;
the animateTick row records the attempt bound the loop re-reads.
2026-11-05 14:00:10 +00:00
MSedgeMC 677e00c72f test: a floor, a fluid and a whole sample box for the animateTick zoo
The tick cases' stand-in world stopped at y 32..96 and x/z -32..31, one block
short of every sample the loop takes and of the neighbours those samples read,
so a block at the edge compared air against the oracle's unending world: that
is where the beehive, the redstone ore and the pointed dripstone differences
came from. Fill the whole box plus a block either way, let a case name the
floor below y = 64, hand the loop the state's own fluid (a waterlogged block
has a water source, which it ticks and drips) and bind the client jar's block
tags in the oracle the way a datapack load does, which a bare bootstrap leaves
empty. Then add the families that read a neighbour, a shape or their own fluid:
dried ghast over soul sand, soul soil, stone and air; hives and a nest embedded
and hanging; a lit redstone ore and a deepslate one embedded and over air; and
stalactite tips over stone and water. 2,097 cases and 456,747 output lines
become 2,187 and 462,718.
2026-11-05 14:00:10 +00:00
MSedgeMC 9d11ae8abb fix: a hive asks the level random for its attempt bound until the loop ends
BeehiveBlock.animateTick is a `for (i = 0; i < random.nextInt(1) + 1; i++)`
loop, so the bound is drawn again on the check that ends it: a hive always
spends two nextInt(1) draws around its single trySpawnDripParticles attempt.
Horizon hoisted the bound out of the loop and spent one, which left the level
random a draw ahead of vanilla from the first hive the ambient loop touched:
every particle after it moved, and the honey drip of a hive hanging over air
came out at the wrong column.
2026-11-05 14:00:10 +00:00
MSedgeMC 25cb339d57 port_map: WitherRoseBlock.animateTick is block_animate_tick
The row records where the smell test sits now: three smoke puffs about the
centre of the pick shape, moved by the flower's per-column model offset.
2026-11-05 14:00:10 +00:00
MSedgeMC 3555aa81d3 test: a wither rose world in the marker-particles parity suite
The corpus had no wither rose case at all. Add a world that is all wither rose,
run the whole ClientLevel.animateTick loop over it and compare every smoke puff
by exact position bits in call order: twenty-seven cases, three seeds and
twenty-four at random ones. The probe now supplies the flower's per-column model
offset the way the client's mesh tables do (a case without it would compare a
shape the game never shows). 2,070 cases and 402,440 output lines become 2,097
and 456,747.
2026-11-05 14:00:10 +00:00
MSedgeMC af128082f4 fix: centre the wither rose's smoke on its pick shape
WitherRoseBlock.animateTick reads its centre off getShape(state, level, pos,
CollisionContext.empty()), a flower's own box whose bounds centre is 0.5/0.5
before the block's per-column model offset. AnimateContext::shape_y answers with
the collision shape, which is empty for every flower, so the centre came out as
0 and the whole smoke column sat half a block towards the block's north-west
corner. Take the centre from the pick shape's boxes (BlockFacts::outline) and
keep the model offset on top of it.
2026-11-05 14:00:10 +00:00
HiuoyandClaude Opus 5.5 2f9c963068 Native hook catalog: NeoForge labels for the core hooks (#9921)
The hooks that came before docs/native-hooks-neoforge.md had no
`neoforge`, `side` or `cancellable` keys, though NeoForge mods listen to
their events most: server and level ticks, block and item clicks,
attacks, joins, chunk loads, logins. Each value comes from the hook's
own doc comment or the vanilla method the proposal's table names, and
`cancellable` matches the hook's mod_api contract. Metadata only: no
name, signature or call site changes.

The proposal's status, its next-hooks table (most of it has landed)
and its capabilities and rollout notes now match main.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:59:50 +04:00
Nikita Maximilian GrimmandClaude Opus 5.5 36021e2559 Golden Hour's shader API 0.2 port, as a test pack of the SDK
The example pack written for 0.2: its sky and post passes in linear light,
with the 0.1 pack's gamma constants decoded, and a [tonemap] stage of its
own that eases colours above three quarters of the headroom toward it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:59:12 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 67382d8ed2 Shader API 0.2 reference: the lit colour may pass white, tonemap's picture size
Surface.color's "0..1" stays in the 0.1 reference only; the tonemap stage's
`previous` is the post chain's window-size picture, or the world's at the
world's size without a chain.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:58:52 +01:00
Horizon agentandClaude Opus 5.5 f03c416ebc Shader test: name the handle type the select check reads
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:57:58 +01:00
levzzzandCodex 3bba1744e6 Format shader validation test
Co-Authored-By: Codex <[email protected]>
2026-11-05 18:55:33 +05:00
Horizon agentandClaude Opus 5.5 b4d0f716e3 Merge main into #9868: keep both new motion tests
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:51:55 +01:00
Horizon agentandClaude Opus 5.5 479cfd7f17 Realms worlds tab: read the slot list as the service sends it
The detail answer's slots are a list (RealmsServer.slotList) whose options
are a JSON string each (RealmsWorldOptionsJsonAdapter); a map of objects
failed the whole answer, players tab included. Missing world slots 1 to 3
are empty (finalizeSlots), unset options keep new RealmsWorldOptions()'s
values, and the template fields are worldTemplateId and
worldTemplateImage, in the update too. Cycle labels join through
options.generic_value.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:51:51 +01:00
Horizon agentandClaude Opus 5.5 c8bf7d1993 Wither rose smoke: bound the pick shape from its own boxes
The fold started from 0.5 on every side, so a shape that does not cover
the block's centre would have been stretched to reach it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:49:08 +01:00
Horizon agentandClaude Opus 5.5 7a378e5d69 Map marker names: drop a test that only checked its own closure
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:48:55 +01:00
Horizon agentandClaude Opus 5.5 8d44fe96f8 Disconnect screen: keep wrap's doc comment on wrap
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:48:44 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 bd186dc732 Shader API 0.2: linear light, the display fields, [tonemap] and [frame] blend
Step 2 of docs/design/hdr-linear-light.md in horizon-shader-api. The line is a
value (`Api`), and `check` and `horizon-shader` serve 0.1 and 0.2. Under 0.2
the preludes and entries decode colours at the stage boundary (a 2.2 power,
negatives and NaN made 0 first) and encode what the frame stores; `Frame`
gains hdr, headroom, paper_white, peak and black_level, padded to WGSL's size
in Rust only, and 0.1's prelude still declares only its 49 fields. A
`[tonemap]` stage and `[frame] blend` are 0.2-only manifest keys.

0.1 does not move: its preludes and entries are pinned by digest, and
shaders-reference.md is unchanged; 0.2 has shaders-reference-0.2.md. The game
refuses 0.2 packs and `blend = "linear"`, and the registry refuses 0.2, each
with a message saying why.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:48:24 +01:00
DUzzL 61a74fdd0a Stop the walk animation of a player riding a boat 2026-11-05 14:48:21 +01:00
martipopsandClaude Opus 5.5 e58ee9c714 Controllers: icon buttons and two checkboxes take the focus, and a screen with no targets fails a test
An icon-only button recorded nothing, so the pad could not reach the pause
menu's Report Bugs, Feedback and Player Reporting icons, or Realms' icons. An
active button with an empty label is now a focus target. So are the
Multiplayer warning's "Do not show again" box and Optimize World's Erase
Cached Data box, which A now ticks.

tools/pad_menu_e2e.py gains every_screen_has_targets, which walks the title's
submenus and every options page and fails on a focus screen with no targets.
Replay Studio is allowlisted because it registers nothing yet. New pause_icon
and erase_checkbox cases press the new targets. The pause menu now has
unlabelled widgets, so splitscreen_e2e's setup scenario reads labels with get.
controller_settings_pad's check for a Navigation page now matches exactly: it
tripped on the General page's Navigation Repeat rows.

docs/controllers.md no longer says every rectangle registers itself or that
both modes reach every screen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 08:46:36 -05:00
CocofireHDandopencode 6492b16a7b Send minecraft:vibration particles from the server to nearby clients
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 14:46:21 +01:00
CocofireHDandopencode 2cb008e118 Shaders: read depth as an unfilterable float so GLSL translates
The world's depth was bound as a depth texture and read with textureLoad, which naga's GLSL backend cannot emit, so TAA, the depth blit and the shader depth test failed to build on OpenGL. Bind those views as unfilterable float textures instead: wgpu accepts a depth view there, and textureLoad returns the same depth texel on every backend (Vulkan, DX12, Metal, GL).

The shader translation test now fails on any shader the GL backend runs that cannot translate, and pins fs_taa and fs_blit; the HUD's glint, which fails for an unrelated two-samplers-per-image naga limit, is the one allowed exception.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-05 14:45:43 +01:00
Horizon agentandClaude Opus 5.5 b859bcf312 Merge origin/main into shaderpacks/atlas-linear-mipmaps
Keeps atlas_mip_tests beside #9764's wrong_type_uniform_tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:45:36 +01:00
Horizon agentandClaude Opus 5.5 7812fb0460 Terrain shader: scalar selects for the corner offset, so old AMD OpenGL drivers compile it (#9892)
#9584 replaced FXC-rejected dynamic component stores with a select between uvec3s on a
bvec3. naga writes that as GLSL's mix(uvec3, uvec3, bvec3), which needs GLSL 4.5 or
EXT_shader_integer_mix; AMD's 2016 driver for the Radeon HD 7400M resolves it to the float
overload and rejects the 'Terrain solid (flat)' vertex shader, so the game no longer starts
there. One scalar select per component compiles to ?: on every backend.

The shader translation test now refuses any select between integer vectors on a bool vector.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:45:29 +01:00
Horizon agentandClaude Opus 5.5 5d54563cc6 Merge origin/main into fix/h317-frame-map-detach
The command_exec harness records frame_map_state and #9848's
menu_state ops alike.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:45:02 +01:00
Horizon agentandClaude Opus 5.5 b9df50777c Merge origin/main into fix/map-decoration-type-namespace
Keeps the map decoration namespace flags beside #9848's piglin anger
memory flags in the command_exec harness.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:44:09 +01:00
levzzzandCodex GPT-6 b33e54cb46 Compare PNG pixel formats in frame identity
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 18:41:36 +05:00
Ludovic Marie 6ec78cc425 Merge pull request #9910 from horizon-rust/fix/native-build-needs-hook-catalog
🤖 Agent: launcher-only fix, green on the desktop at d984823 (cargo fmt --check, clippy --all-targets -D warnings, cargo test: 559 + 102 passed). One point to watch: the catalog is fetched from raw.githubusercontent only when a hooked mod uses named hook points, so builds of raw-target mods make no new request.
2026-11-05 14:41:21 +01:00
oxi-does-dev 0c897e3e33 Interact: send the point the ray hit, as vanilla does
MultiPlayerGameMode.interact sends EntityHitResult.getLocation() minus
entity.position() in every ServerboundInteractPacket. Horizon sent a
fixed [0.0, 1.0, 0.0] for mobs, a point in their middle: armour stands,
saddles, leashes and anything else that reads the click carried
interactAt to the wrong place, unlike vanilla, and a server's reach or
ghost-hand checks saw clicks that vanilla never makes.

mob_hit and the spear's attack_range pick now return the point along
the view ray where the mob's box was met, measured from its feet, and
every caller destructures the third element. A regression test checks
the packet's point against the ray.
2026-11-05 16:40:43 +03:00
degepieandClaude Opus 5.5 770e4461f8 Mobs: naming one or dyeing a sheep fires player_interacted_with_entity
ServerGamePacketListenerImpl.handleInteract fires the trigger with the
held stack as it was before the interaction whenever an item interaction
succeeds. NameTagItem.interactLivingEntity and DyeItem.interactLivingEntity
both succeed, but interact_mob returned after name_mob and after the sheep
dye before reaching the shared trigger call.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 18:40:41 +05:00
Ludovic Marie 868039b457 Merge pull request #9897 from horizon-rust/registry-v2/required
🤖 Agent: with levzzz5154's commits an entry's icon must be exactly icons/<id>.png, so one project can't show another's icon; all 94 entries on horizon-mods main already comply and none is incomplete.
2026-11-05 14:36:43 +01:00
AxxioandAntigravity 6cd586555f launcher: fix top bar action button clipping and invite field overflow (fixes #9902)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 14:36:15 +01:00
Ludovic Marie 42aad46379 Merge pull request #9733 from martipops/controller/settings-screen
🤖 Agent: The new pages go through the same English-as-key lang::get scheme as graphics_ui, and the detected glyph style now follows the named device's vendor before that pad has been used. fmt, clippy and the controller and settings tests pass merged with main.
2026-11-05 14:34:12 +01:00
levzzz 80c4551ac7 Merge pull request #9850 from UMCEKO/fix/lan-e2e-server-thread-name
lan_e2e: drop datapack-recipe's guard on the removed SERVER_THREAD flag
2026-11-05 18:33:39 +05:00
levzzzandCodex GPT-6 f4c4ab173c Keep level-up sounds on each player clock
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 18:33:16 +05:00
masa-masa-0314andZCode 85b037f347 Realms: the worlds tab with slot switching and world options
The configuration screen's Worlds tab: the four slots as vanilla lays them out (the
active one 80 square, the others 50, 16 apart), each with RealmsWorldOptions.getSlotName's
name; a click on another non-empty slot asks first, then RealmsClient.switchSlot
(PUT worlds/{id}/slot/{slotId}).

World Options opens RealmsSlotOptionsScreen: the world name (the default one stored as
empty), difficulty and game mode cycles, force game mode and spawn protection 0-16, with
vanilla's disabled states (a hardcore slot locks difficulty, game mode and force mode; a
world that is not NORMAL keeps its stored spawn protection and force mode). Done saves
through RealmsClient.updateSlot (POST worlds/{id}/slot/{slotId}, RealmsSlotUpdateDto's
JSON) and the tab reads the detail again.

RealmsServer reads the detail's slots (RealmsSlot, RealmsWorldOptions, RealmsSetting and
isHardcore). Slot images come with the pack-icon-slot PR; until then a slot is its frame
and name. The spawn-protection slider takes a click on its track: this screen has no drag
event yet.

Co-Authored-By: ZCode <[email protected]>
2026-11-05 21:32:31 +08:00
Ludovic Marie 867e9f0495 Merge pull request #9836 from masa-masa-0314/feat/realms-worlds-tab
🤖 Agent: The new calls go only to the official Realms service through the existing RealmsClient, and the UUID path segment must be 32 hex digits. Main is merged in to clear GitHub's criss-cross conflict after #9714 landed.
2026-11-05 14:29:56 +01:00
levzzzandCodex GPT-6 62c9562dc1 Require each expected frame identity screenshot
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 18:29:44 +05:00
Horizon agentandClaude Opus 5.5 d984823e56 Launcher: native mods with named hook points need the revision's catalog
A mod naming hook points (blockscope 0.3.1: hooks 2, all five by name) beside
one pinning 5e391eb (shulker_peek 0.1.0) built on the pin: 5e391eb already had
hook points v2, but native-hooks.json only came in d3e0774, still v2. So
horizon-mixins stopped with E_HOOK_CATALOG ("a named hook needs
native-hooks.json at the workspace root"), on every platform.

A revision now fits such a mod only when its native-hooks.json lists each named
hook point and version the mod injects at; otherwise the build moves on as it
does for missing hook points (the mods' tested commit, else the game's), and
the pinned mod waits for Build anyway.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:29:02 +01:00
Arslan ArmanulyandClaude Opus 5.5 c749ff3764 Shader packs: ftransform() applies the model-view matrix, then the projection, as Iris does
Read back with probe packs of ours, Iris 1.11.7's ftransform() has the bits of
gl_ProjectionMatrix * (gl_ModelViewMatrix * gl_Vertex) at every vertex our
probes read. Horizon multiplied the vertex by the product matrix formed once
per draw, which rounds differently in the last bits. With it, on a stack with
the other open #8464 PRs, a strip of water tops at ow_birdseye_noon read the
shadow map up to 73 units of 2^-24 from Iris's coordinates, and 35,632 and
50,761 of the texels our probe reads back differed from Iris's. How last bits
move a whole strip is not established.

The GPU test reads ftransform() back from a gbuffers and a shadow program with
matrices that tell the two orders apart (one rounding against two).

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:26:43 +05:00
Horizon agentandClaude Opus 5.5 19f6c2c382 Controllers: the detected-style test module goes last in options.rs
clippy's items_after_test_module (denied in CI) rejects the module
before windowed_again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:26:29 +01:00
degepieandClaude Opus 5.5 a85fb7c819 Advancement predicates read structure tags
A location predicate whose `structures` named a tag
(`#minecraft:shipwreck`, or a data pack's own `#pack:village`) never
matched: the predicates' tag lookup had no `worldgen/structure` case and
the world's pack tags didn't hold that registry. The world's tables now
load the structure tags with the packs' over the game's own, like the
entity type, biome and damage type tags, and a world without packs
reads the game's own structure tags (loaded once).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 18:26:17 +05:00
degepieandClaude Opus 5.5 ab4f48abfe Potions: drinking one awards the used statistic and fires consume_item
Consumable.onConsume awards the item's used statistic and fires the
consume_item trigger for a server player before the consumable's listeners
run and the stack shrinks. A potion and an ominous bottle finish through
finish_drinking, which did neither; food, honey bottles and milk go
through finish_consuming, which already does both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 18:26:14 +05:00
Ludovic Marie 38609b7b1f Merge pull request #9848 from TheMrZZ/fix/piglin-live-anger-memory
🤖 Agent: The angry_at memory is written only while this live guarded-block event owns it, and removed once the anger ends, so a stale raw Brain value cannot come back. Main is merged in, with both sides' recorded ops and fixtures (mob_anger, menu_state, map decoration) kept.
2026-11-05 14:24:36 +01:00
Ludovic Marie ad3442b001 Merge pull request #9764 from arslanarm/shaderpacks/builtin-uniform-wrong-type
🤖 Agent: read_as is only checked for built-ins while a shaders/ pack binds its programs, so nothing changes without a pack. Main is merged in, and wrong_type_uniform_tests now sits beside main's atlas_name and unknown_sampler test modules.
2026-11-05 14:24:31 +01:00
Ludovic Marie b38f983f88 Merge pull request #9901 from horizon-rust/registry-v2/launcher-4
🤖 Agent: native mods update only in the start's run; Play then runs the existing build_native(false) (stays on the instance's Horizon, mismatch notice as usual) and plays only if it succeeds; Nightly-pinned instances are left alone.
2026-11-05 14:24:27 +01:00
levzzzandCodex GPT-6 60b7ca324f Keep pregeneration toggle updates ordered
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 18:24:02 +05:00
levzzzandCodex GPT-6 69c5efdd99 Refresh native mod fit after a rebuild
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 14:23:36 +01:00
VaspyyyandClaude Opus 5.5 4caabfd21a Mod browser: say before install whether a native mod fits your Horizon
The registry's index now lists a native mod's hook points version
(`hooks`), and the browser and `mods search` compare it, or the mod's
pinned commit, with the Horizon the instance plays: builds there, needs
newer hook points, moves the game to its commit, or clashes with the
pins of mods turned on (Build anyway). Closes #9860.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:23:36 +01:00
MSedgeMC 7cb6fc552f port_map: WitherRoseBlock.animateTick is block_animate_tick
The row records where the smell test sits now: three smoke puffs about the
centre of the pick shape, moved by the flower's per-column model offset.
2026-11-05 13:23:18 +00:00
MSedgeMC 3b923fde88 test: a wither rose world in the marker-particles parity suite
The corpus had no wither rose case at all. Add a world that is all wither rose,
run the whole ClientLevel.animateTick loop over it and compare every smoke puff
by exact position bits in call order: twenty-seven cases, three seeds and
twenty-four at random ones. The probe now supplies the flower's per-column model
offset the way the client's mesh tables do (a case without it would compare a
shape the game never shows). 2,070 cases and 402,440 output lines become 2,097
and 456,747.
2026-11-05 13:23:17 +00:00
MSedgeMC 392b786d38 fix: centre the wither rose's smoke on its pick shape
WitherRoseBlock.animateTick reads its centre off getShape(state, level, pos,
CollisionContext.empty()), a flower's own box whose bounds centre is 0.5/0.5
before the block's per-column model offset. AnimateContext::shape_y answers with
the collision shape, which is empty for every flower, so the centre came out as
0 and the whole smoke column sat half a block towards the block's north-west
corner. Take the centre from the pick shape's boxes (BlockFacts::outline) and
keep the model offset on top of it.
2026-11-05 13:23:17 +00:00
Claude 918db2564f Explain Smart App Control blocking native builds (#5911)
Windows 11's Smart App Control lets only signed programs run, and a native
build runs programs it has just compiled (build scripts, horizon-mixins, the
game), so every build failed with os error 4551 and an opaque cargo error.

The launcher now checks Smart App Control before downloading anything
(VerifiedAndReputablePolicyState 1) and says how to turn it off; a build
that hits os error 4551 anyway leads its error with the same explanation.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Y255SPgcPcs89pQpqu1eES
2026-11-05 13:21:51 +00:00
levzzzandCodex 2a2cd81378 Format registry test assertions
Co-Authored-By: Codex <[email protected]>
2026-11-05 18:20:15 +05:00
levzzzandCodex 1f5ec291b3 Enforce each listed project icon path
Co-Authored-By: Codex <[email protected]>
2026-11-05 18:19:08 +05:00
Ludovic Marie d929725893 Merge pull request #9898 from horizon-rust/registry-v2/launcher-3
🤖 Agent: the content-id guard runs after index::check_file and before the rename, so a held update leaves the installed .hmod and its list entry untouched; the replaced file is kept until the next start for rollback.
2026-11-05 14:18:15 +01:00
Loki 4741885f8e Merge remote-tracking branch 'origin/main' into fix/9744-fishing-hook-random
# Conflicts:
#	crates/horizon-client/src/game/fishing.rs
2026-11-05 22:18:09 +09:00
DUzzL 0cc73f7550 Advancement command: count targets without a turn, restore the quiet flag 2026-11-05 14:16:22 +01:00
degepieandClaude Opus 5.5 18abadd471 Commands: query results go through the source's success feedback
A command run by a data pack function printed its query result in the
chat: `time query`, `seed`, `gamerule <rule>`, `difficulty`,
`experience query`, `list` and `help` wrote a system message directly,
past the silent source a function runs with. They now take
`feedback_unbroadcast` (`CommandSourceStack.sendSuccess` without the
broadcast to operators, as TimeCommand, SeedCommand, GameRuleCommand,
DifficultyCommand, ExperienceCommand, ListPlayersCommand and
HelpCommand send them): a silent source says nothing, a command block
keeps the line as its last output, and `send_command_feedback` off hides
it from a player. The returned value still reaches `execute store`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 18:16:15 +05:00
levzzzandCodex GPT-6 2896e005b0 Fix detected glyphs for the displayed controller
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 18:15:31 +05:00
SenjinandClaude Opus 5.5 4c44b57727 Shader packs: filter shadowtex through a plain sampler without copying it
A pack reading shadowtex0/1 through a plain filtering sampler got an
R32Float copy of the shadow depth every frame, made through a buffer
(#8999: wgpu filters a depth format only in a comparison). At a large
shadowMapResolution that is most of a frame: 8192² costs 512 MiB (the
buffer and the copy) and about 10 ms a frame on an RTX 2080.

The translator now sends texture calls on shadowtex0, shadowtex1,
shadow and watershadow declared as sampler2D to helpers that gather the
four texels and blend them bilinearly, as the filtering sampler did,
reading the depth itself. A uniform of ours, hz_depth_linear, says per
name whether it filters (shadowtexNNearest unset) or takes the nearest
texel. The pack's own functions given one of them as a sampler argument
carry its flag in an extra parameter (-1 for any other texture, which
then reads with the built-in the pack called). The copies, their buffer
and textures are gone, and devices without FLOAT32_FILTERABLE now filter
too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:15:19 +01:00
Ludovic Marie 48cf423414 Merge pull request #9820 from Ente3/feat/colored-singes
🤖 Agent: Each colour run is its own text call, and plain lines are still one call each. Plain lines now skip the component round trip every frame, and edits keep the old line's root style as SignBlockEntity.setMessages does.
2026-11-05 14:14:14 +01:00
Horizon agentandClaude Opus 5.5 8aeda11f56 Mod registry: a listed project needs a description, a category and its own icon
Registry v2, step 4 (docs/design/registry-v2.md): registry build refuses a
listed project with a description under 50 characters, no category, no
icon, or images that aren't kept in the registry (icons/, screenshots/).
The site and the launcher then load images only from the registry's host.
Lands once every entry on horizon-mods main is backfilled.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:12:39 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 74d3e1482c Frame identity harness: build the base for hook fragments, check the HDR pack
horizon-mod-api-gen generates Rust from tools/native-hook-fragments/, so
a change there builds the base instead of reusing the change's binaries.
Golden Hour with HDR on is checked against hdr_toggle's first shots (the
same settings without the pack), sidecars included, so a pack that only
fails under HDR doesn't pass as identical.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:12:08 +01:00
levzzzandCodex GPT-6 9ee022d926 Fix map marker name depth
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 18:11:05 +05:00
Horizon agentandClaude Opus 5.5 abb78e4e3a Launcher: native registry mods update at start, then build before Play
With automatic updates on, the start's run also updates native mods
(.hnmod), except for an instance that keeps a Nightly or a native build
made on one. The next Play of an instance whose native mods moved on
builds its game again first (the Mods page's Build), then plays it.
Play's own run never updates native mods: no build would follow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:10:40 +01:00
Horizon agentandClaude Opus 5.5 fb642d816c Launcher: registry mods update automatically
A setting, on by default (Settings > Game > Update registry mods
automatically): at start (every Horizon instance) and before Play (the
instance it starts), what the browser installed from Horizon's registry
moves to Index::installable, checked as an install is.

Never updated automatically: a modpack's profile (its .hpack pins each
mod by SHA-256), native mods (they need a build), data packs, a version
for another Minecraft, anything older than what is installed. A content
mod whose new version drops a block, item, entity, effect, menu or
creative tab waits for the player, with a notice naming them. The file
an update replaces is kept in mod-browser/replaced/ until the next
start, which puts it back if the update was cut short.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:10:07 +01:00
Ludovic Marie 3533cfb186 Merge pull request #9894 from horizon-rust/registry-v2/launcher-2
🤖 Agent: screenshots go through gallery_of, which drops any URL images::refuse rejects, so the image host allowlist stays as it was; the Gallery tab hides when a project has none.
2026-11-05 14:08:46 +01:00
DUzzL 599452907b Add TeamPredicate to entity_properties predicates 2026-11-05 14:08:36 +01:00
Horizon agent 09d8772fd8 rustfmt 2026-11-05 14:06:43 +01:00
Horizon agentandClaude Opus 5.5 b03d15d147 Mod browser: registry screenshots in a project's gallery
A registry project's `screenshots` (mirrored into the registry, served
from its Pages beside the icons; other hosts are left out) fill its
page's Gallery. The Gallery tab shows one image large, a click on it
shows the next, arrows and a strip of thumbnails pick another; a project
without images has no Gallery tab.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:06:43 +01:00
Horizon agentandClaude Opus 5.5 7899c12c4c Merge origin/main into extras-pregen-toggle
Keeps the pregen extra next to main's solo cosmetics entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:06:25 +01:00
Arslan ArmanulyandClaude Opus 5.5 4b6cd5e23c Shader packs: colortex mipmaps are a box filter of the level above
A buffer's mip level was one bilinear sample of the level above at the
centre of each destination texel. Along an odd side (45 rows of a
1280x720 buffer's fourth level, 11 of its sixth, 5 of its seventh) that
sample lands near one texel's centre instead of between two, so a small
bright spot was skipped: a probe pack of ours lost a 26x22-pixel square
of 10.0 entirely from level 7 down, where Iris 1.11.7 kept it down to
its 1x1 level, the buffer's mean.

Each level is now a box filter of the level above (OpenGL 4.6, 8.14.4,
recommends one): two texels along an even side, and three along an odd
side of 2d + 1 texels, weighted d - i, d and i + 1 over 2d + 1, so every
texel above counts as much. Our probes' Iris values fit this within
0.1 % at levels 6 and 7 and 1.5 % at level 10, in all 16 colortex
buffers, in 10 formats. Iris's level 5 of that buffer fits it in rows
but not in columns (undiagnosed).

Fixes #9891
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:05:56 +05:00
Horizon agentandClaude Opus 5.5 1b1e29d48a Merge origin/main into extras-pregen-toggle
Keeps the pregen extra next to main's Horizon marks entries in the
extras file and the menu-only item list.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:04:54 +01:00
Ludovic Marie d0cd32780b Merge pull request #9889 from horizon-rust/registry-v2/launcher-1
🤖 Agent: registry data packs reuse install_datapack but go through index::check_file first (signature and pack.mcmeta/data/), so they get the registry's full check, not just the SHA-256 Modrinth files get.
2026-11-05 14:04:44 +01:00
Ludovic Marie 77b5818638 Merge pull request #9885 from horizon-rust/registry-v2/service
🤖 Agent: the ownership point to watch is Edit project: page edits are allowed only to an owner matched by numeric GitHub ID (Ownership::ById), never by login alone, and an image upload always makes an update a reviewed one rather than an owner update.
2026-11-05 14:04:39 +01:00
Ludovic Marie c0de7a9fc3 Merge pull request #9888 from horizon-rust/feat/cosmetics-single-player
🤖 Agent: With the switch off, single player now asks the service nothing for the player's own model. Before, it asked /v1/worn about the offline UUID, which the service never knows. The cost when off is one bool test, on a path that runs only for the player's own model.
2026-11-05 14:04:35 +01:00
DUzzL 590148a5d3 Lay out the disconnect screen like vanilla so the button stays below the reason 2026-11-05 14:04:33 +01:00
Arslan ArmanulyandClaude Opus 5.5 ae9f02e676 Shader packs: cloudHeight reads the dimension's cloud height without clouds
A shader pack's `cloudHeight` read NaN in a dimension whose cloud colour
has no alpha (the Nether, the End), as ShaderDoc describes it. Probe
packs of ours read 192.33 there in Iris 1.11.7, in every program kind
they read it from (gbuffers_terrain, gbuffers_textured_lit,
gbuffers_skybasic, shadow, prepare, deferred, composite and final).
`cloudHeight` is now the dimension's `visual/cloud_height` with or
without clouds: `EnvironmentAttributes.CLOUD_HEIGHT`'s default, 192.33,
where the dimension doesn't set it.

Fixes #9890
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 18:04:29 +05:00
Horizon agentandClaude Opus 5.5 8bbedb4d4d Merge origin/main into fix/villager-stand-up
Keeps the villager wake cases and #9775's used-stat cases as two
separate streams in the use_item_on harness.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:03:09 +01:00
Ludovic Marie 5b58aaa152 Merge pull request #9440 from horizon-rust/feat/horizon-player-mark
🤖 Agent: The Horizon mark and the badge share one lock look per name tag (Frame::horizon beside Frame::worn), so the name tag cost did not move on playhorizon (eg_name_tags median 0.027-0.029 ms with the mark on and off). The Founder gem is the service's texture (horizon-cosmetics#8), so deploy that right after this merges.
2026-11-05 14:02:09 +01:00
LokiandAntigravity c2b3c2d103 fishing: draw hook tick, bite timers and catch from hook's own random (fixes #9744)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 22:01:42 +09:00
Horizon agentandClaude Opus 5.5 6fa2c99334 toast_add: ask for main's new socialInteractions tutorial hint too
Merging main brought Hint::SocialInteractions (#9796); Hint::name was no
longer exhaustive. The first-server hint is now asked like the others, and
is still spent when cancelled, as vanilla sets joinedFirstServer either way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:00:54 +01:00
Horizon agent 65326a0d84 Merge remote-tracking branch 'origin/main' into cg45/9807 2026-11-05 14:00:32 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 5934f591ee Frame identity harness: named only, run by the frame-identity label
It fails any change that moves the picture on purpose, so no automatic
selection may pick it (parity-main would bisect every visual fix to it).
A pull request that must leave the picture alone carries the
frame-identity label, which parity.yml reads as an input and then runs
the harness by name after the selected ones.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:56:00 +01:00
Florian ERNSTandCodex 41acd427ee Refresh main after selector validation
Co-Authored-By: Codex <[email protected]>
2026-11-05 13:53:36 +01:00
Horizon agent 425321db3d rustfmt 2026-11-05 13:51:31 +01:00
Florian ERNSTandCodex 8e7bb2dc6d Merge main and preserve both command fixture dispatches
Co-Authored-By: Codex <[email protected]>
2026-11-05 13:48:06 +01:00
AkirabaneandClaude Opus 5.5 db3dcf2dcc dedicated: log each player-* request, and document a hand-over in order
Each player-* request writes one log line with what became of it: INFO
and what it did, or WARN and the reason it was refused. The line names
the player and, for the export, the import, the confirm, the release and
a named drop, the hand-over id, so the lines of one hand-over can be
found on both servers. The record and the request's body are never
written.

docs/server.md gives a hand-over in order, with the ids, the commit
point and what undoes itself, and the log lines.

tools/server_transfer_e2e.py runs a whole hand-over between two
dedicated servers over their real control sockets: two engines with
[transfer] on and Velocity forwarding, a small relay per engine that
answers the forwarding request as a proxy does, and horizon-bots
clients. It follows the documented order step by step, checks the
target's player file is on disk when the confirm answers and that the
source writes nothing for a player moved away, then aborts a hand-over
back and checks the player plays on.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:46:57 +01:00
AkirabaneandClaude Opus 5.5 bc2363c93b dedicated: player-import takes the place the request names, and the view follows
The request may name where the player arrives, over the record's
compound: `position`, `rotation`, `on_ground` and, now that #8693 keeps
a guest's motion, `velocity`. They belong to the `position` part. The
pitch is taken as Entity.setXRot takes it (modulo a turn, then cut to
+-90) and the yaw as it is; a velocity component over 10 blocks a tick
is taken as 0, as Entity.load takes a saved Motion. A position must be
within the same bounds as the record's own (Entity.load's clamp:
30,000,512 on x and z, 20,000,000 on y), and a rotation or velocity must
be finite numbers.

When the import moves the player to another chunk than the one its view
was built around, the view moves at once: the chunk centre is sent
before the position to confirm, and the chunks out of reach are
forgotten. The reply says so with "recentered".

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:46:57 +01:00
AkirabaneandClaude Opus 5.5 f5aef42f71 lan: an arrival's join line uses the team and nickname display name
#9829 made the join and leave lines name a player as Player.getDisplayName
does (its team's format and its nickname). place_arrival, which announces
a player placed by player-import or player-confirm, still used the plain
name; it now uses player_display_name too, so an arrival is announced as
a login is.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:46:56 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 e60be31a02 Frame identity harness: the base's and the change's pictures, pixel for pixel
tools/test_frame_identity_parity.py builds the base (BASE_SHA, else the merge-base
with origin/main) in a worktree and target folder of its own under
.local/frame-identity/, draws fixed scenes offscreen with it and with the change
under HORIZON_STILL=1, and requires identical decoded pixels, writing a diff image
where they differ: HDR off (day over water, night with light sources, stained
glass, the Nether), Golden Hour (API 0.1) in SDR and with offscreen HDR output,
and HDR turned off in a running game. Each session checks it drew what it is for,
so a pack that fails to load, or HDR that stays off, in both builds can't pass.
Lavapipe where it is installed, else the same adapter for both builds. A change
that touches no file a game binary is built from reuses the change's binaries.

The differential screenshot test of docs/design/hdr-linear-light.md (Tests 1, 2),
first of the shader API 0.2 series (#9861).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:45:58 +01:00
AkirabaneandClaude Opus 5.5 a66f0fc57d dedicated: a durable commit point, retries after it, and the record's bounds
martipops's second review of #8730.

The commit point now covers the three files and a power loss: the
player file, advancements and statistics are each written to a new
file, synced, then renamed into place, and the folder is synced on Unix
(save::write_durable, write_dat_durable). Each write returns its error,
and the player stays held and imported when one fails. Ordinary saves
are unchanged.

A retry of `player-import` or `player-confirm` after the commit, whose
reply was lost, names the hand-over of a player already placed from it:
it is answered ok, placed, with `"retried": true`, and nothing is done
again. Another hand-over's id is still refused.

The record's health is refused over 1024 and its absorption over 2048
(max_health and max_absorption), its food exhaustion over 40
(FoodData.addExhaustion) and an effect amplifier outside 0 to 255, as
26.3's bytecode bounds them. A stack's count needs no check:
item_from_nbt already takes one outside ItemStack's 1 to 99 as 1.

docs/server.md says what a proxy does when `moved` is refused after the
commit (the freeze ran out, or an operator's /kill), and that it
confirms only while the export's `freeze_ticks_left` leaves it time.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:45:27 +01:00
AkirabaneandClaude Opus 5.5 4a5c6ab5d1 dedicated: player-import and player-confirm let a held player in
`player-import <uuid> <json>` applies a hand-over's record (from
`player-export <uuid> freeze`) to the held player on the target, whole or
in parts, and tells its client what changed. Unless the request says
`"place": false`, it then places the player as `player-confirm <uuid>
<hand-over>` does.

Placing is the hand-over's commit point: the player file, advancements
and statistics are written to disk synchronously, then the player is
placed, and only then does the reply say ok. A proxy releases the
source after that reply, so a crash of the target no longer leaves the
only current copy in memory. If the file cannot be written the player
stays held and imported.

The record's hand-over id is bound to the held player at the import: a
record without one (an export without freeze) is refused, a retry of the
same record is taken and another hand-over's is not. `player-confirm`
names it, and `player-drop <uuid> [hand-over]` may: a named drop must
name the imported record's. A held player that is leaving (dropped or
kicked, its connection not closed yet) can be neither imported nor
confirmed.

The import reads the whole compound as a file load does: the keys
Horizon does not model, the mods' attachments (given to the game's own
map) and the body's values. The body follows the parts: fall and motion
with `position` (with the saved vehicle), air, fire, freezing and food
exhaustion and timer with `health`. Values no player can have are
refused before anything changes: a position, rotation, motion, health,
absorption, saturation, experience progress or fall that is not a
number, a position past vanilla's clamp, a health of zero or less.

The client is sent the effects the import removed and the recipe book
it brought. Every player gets the arrival's list entry, since every
player is shown its entity; `announce` is the join line only. At
confirm the client is sent its position and inventory again, since what
it did since the import was not acted on.

The control endpoint refuses a request line longer than 16 MiB, which
the game would otherwise read and parse between two ticks.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:45:27 +01:00
AkirabaneandClaude Opus 5.5 6fae1fdc44 Config golden: add max_hold_seconds to the [transfer] section
The config reply pinned by api-v1-config.json lists [transfer] with its
defaults; this PR adds max_hold_seconds (60 by default), so the golden
lists it too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:45:17 +01:00
rdbtCVSandCodex gpt-6.1-sol 764795ae20 docs(server): regenerate held-player transfer setting reference
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 13:45:17 +01:00
AkirabaneandClaude Opus 5.5 62646f32ab lan: the login of an expected player is held, and player-drop lets it go
A login that used up a proxy's expectation (player-expect, taken as the
login starts) is held on the target server: it gets the whole join
sequence and the level around it, and its acknowledgements are handled,
but it is not placed. No join message, no list entry for the others, not
among the entities they are shown, not simulated, and nothing else it
sends is acted on. The world is loaded around a held player as around a
placed one. The log says "<name> is held: it arrives from another
server".

A held player nobody takes over is dropped after [transfer]'s
max_hold_seconds (60 by default, 1200 ticks), with one warning: a player
already kicked or dropped, whose connection is slow to close, is not
kicked again on every tick. horizon-server passes the setting with the
others.

A held player keeps a true picture of the others while it waits: a
player who joins meanwhile is added to its list, and one who leaves is
removed from it with its entity, or its client would drop the entity of
a player it has no entry for.

`player-drop <uuid>` forgets the player's expectation and disconnects
the held player without a save or a word to anyone. player-export
refuses a held player. Placing it is the import's work, next.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:45:16 +01:00
AkirabaneandClaude Opus 5.5 ebfab7fc89 dedicated: player-release ends a hand-over on the source server
`player-release <uuid> <hand-over> moved [announce]` takes a player that
`player-export <uuid> freeze` froze out of the world, because another
server has it now: the others see its entity and its list entry go, no
player file or record is written, the log and the leave event record the
departure ("moved to another server"), and the connection is closed. The
"left the game" line is neither sent nor logged unless `announce` is
added. `player-release <uuid> <hand-over> abort` lifts the freeze.

Both name the hand-over id the export returned, and are refused for a
player who is not in a hand-over, for another hand-over than its current
one, and for a player who died while frozen. Once the freeze has run out
the hand-over is over and a late `moved` is refused: the player has
played on since, and the target's record is older. So a stale or
repeated release cannot remove someone who is playing again.

A frozen player takes no damage (Abilities.invulnerable, which the void
and /kill still pass) and picks up no items, arrows or experience orbs,
so its record stays true until the release. A player who dies while
frozen all the same ends the hand-over with a warning.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:44:53 +01:00
Riccardo Guerriero 27f2e4ebe6 Merge origin/main into fix/placement-vibrations 2026-11-05 13:42:40 +01:00
AxxioandAntigravity b1e666ae8c game: render marker names under named banners on maps (fixes #9781)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 13:42:03 +01:00
Ludovic Marie dfba9cab74 Merge pull request #9882 from horizon-rust/fix/tab-list-cape-failure
🤖 Agent: queued. tab_list passes 1000/1000 on this head on top of main 3d9ad5214 (playhorizon); main has 964/1000 (#9834). The change brings back vanilla's SkinManager.registerTextures allOf rule: a cape or elytra that fails gives the default skin. The retry and the decode-before-cache from #9420 are kept.
2026-11-05 13:41:41 +01:00
Horizon agentandClaude Opus 5.5 fd452b052f Cosmetics: shorter caption for the single-player switch
"Cosmetics in single player: On" was cut off on the Extras button; the
Cosmetics header already names it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:41:40 +01:00
Ludovic Marie c0d3bbe914 Merge pull request #9844 from DUzzL/fix/9808-zpiglin-anger
🤖 Agent: requeued at the same reviewed code (cg-47 passed it). I pushed an empty commit only because GitHub had lost this PR's head ref.
2026-11-05 13:41:37 +01:00
Ludovic Marie 0ec8f4971e Merge pull request #9785 from UMCEKO/fix/hoglin-cannot-be-hunted-saved
🤖 Agent: queued. `python3 tools/parity.py save_roundtrip` on this head merged into main 3d9ad5214 (playhorizon): PASS, 0 unexplained differences; the hoglin's `CannotBeHunted` DIFF from the nightly audit (#9833) is gone, the 35 ignored categories are the same as on main. Matches `Hoglin.addAdditionalSaveData`, which writes the boolean unconditionally. Also closes #9833.
2026-11-05 13:41:33 +01:00
Horizon agentandClaude Opus 5.5 ed0de5149a Mod registry form: images uploaded into the registry, Edit project, no repository listings
Registry v2 (docs/design/registry-v2.md), the submission service:
- The form uploads the icon (square PNG) and up to 8 screenshots; the service
  checks them (horizon_mods::index::page) and writes them on the submission
  branch as icons/<id>.png and screenshots/<id>/<n>.<ext>, so the site and the
  launcher load them only from the registry. An icon address is refused.
- Categories and tags go into the entry; a new project needs a description of
  50 characters or more, a category and an icon. A shaderpack needs the
  submitter's statement that it is theirs to hand out.
- "Edit project" (`edit: true`, no file): an owner by GitHub ID changes the
  page without a new version; pull request edit/<id>-<time>, label owner-edit.
- The registry follows no repository any more (orchestrator decision): the
  form refuses a repository listing, and `registry review` refuses a new entry
  with `repo`. Every version is a submitted, pinned file.
- A retry takes up its own branch when every commit on it is the form's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:41:32 +01:00
Ludovic Marie 4611a56790 Merge pull request #9840 from Alysia42/travel-in-air-client
🤖 Agent: travelInAir's client branch now sees the real client side and the dimension's min Y, so a client over an unloaded chunk falls at 0.1 * 0.98. The new unit test runs with prepared resources on playhorizon.
2026-11-05 13:41:30 +01:00
Ludovic Marie 2c90d369ab Merge pull request #9829 from DUzzL/fix/9788-join-leave-team
🤖 Agent: Join and leave messages now use player_display_name, team prefix and nickname included, as PlayerList.placeNewPlayer's getDisplayName does. Chat already went through it.
2026-11-05 13:41:26 +01:00
Ludovic Marie 350ea131a7 Merge pull request #9824 from ilguerro/fix/item-used-stats
🤖 Agent: Each award sits in the branch vanilla counts in (FoodOnAStickItem's PASS, a cast but not a reel-in, trident and armor only on the server). The use_item_on harness's 400 new cases pin them.
2026-11-05 13:41:22 +01:00
Ludovic Marie 8459e4acd7 Merge pull request #9768 from arslanarm/shaderpacks/shadow-projection-planes
🤖 Agent: shadow_ortho reproduces the measured bits with JOML's setOrtho and invertAffine (MIT, credited in THIRD_PARTY_NOTICES). Horizon's own packs keep their depth range, now written as explicit planes.
2026-11-05 13:41:17 +01:00
Ludovic Marie 9414faf069 Merge pull request #9847 from senjinthedragon/lazy-flip
🤖 Agent: The second texture is decided once at load (doubled), and a single-sided buffer's B is a clone of A's view. The clear of the alt side and the alpha-1 start both skip single-sided buffers, so A is never attached twice in one pass.
2026-11-05 13:41:13 +01:00
Ludovic Marie 5990d6f3d1 Merge pull request #9755 from lightcodegit/fix/fishing-triangle-double
🤖 Agent: RandomSource.triangle is mean + spread * (nextDouble() - nextDouble()) in 26.3, and the local copy drew two floats. The cast and fishAngle now take vanilla's draws, and the change merges cleanly with #9740.
2026-11-05 13:41:09 +01:00
Ludovic Marie 8f6f2d78ca Merge pull request #9749 from lightcodegit/fix/camel-riding-sprint
🤖 Agent: A rider's sprint now goes through LocalPlayer.isSprintingPossible's vehicle branch (canSprint and local authority), so only camels keep a sprint, and boats and carts stop it as in vanilla. The released-key case is covered by drive_mount reading player.sprinting.
2026-11-05 13:41:04 +01:00
Ludovic Marie c31a5b73ff Merge pull request #9740 from Alysia42/fishing-hook-random
🤖 Agent: FishingHook.<init> draws the throw's three triangle values from the hook's own Entity.random (a fresh RandomSource), so the level's random no longer advances on a cast. #9755 sits on the adjacent lines and merges cleanly on top.
2026-11-05 13:41:00 +01:00
Horizon agentandClaude Opus 5.5 3a9c721478 Empty commit: make GitHub rebuild this PR's head ref
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:37:25 +01:00
Horizon agent cf368aa50e Merge remote-tracking branch 'origin/main' into cg47/fix-9764
# Conflicts:
#	crates/horizon-client-render/src/gfx/pack_post.rs
2026-11-05 13:37:19 +01:00
Ludovic Marie 20186e4044 Merge pull request #9479 from megabytesme/codex/issue-9407-native-mods-arm64
Build native mods on Windows ARM64
2026-11-05 13:37:13 +01:00
Horizon agent 0dfa7840d1 Merge remote-tracking branch 'origin/main' into cg47/fix-9848
# Conflicts:
#	tools/test_command_exec_parity.py
2026-11-05 13:37:09 +01:00
Horizon agent bf07ce2b17 Merge remote-tracking branch 'origin/main' into cg47/fix-9836 2026-11-05 13:36:42 +01:00
Horizon agentandClaude Opus 5.5 c4b85eaed1 Merge main into #9841 (lan_actions imports)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:34:28 +01:00
Horizon agentandClaude Opus 5.5 a388925037 Merge main into #9765 (both test modules)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:33:56 +01:00
Horizon agentandClaude Opus 5.5 8be857dcce Merge main into #9751: keep the frame-map fixture beside #9720's wolf anger
Both isolation sets, both observation ops in the record accounting, and the
expect_map guard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:33:14 +01:00
Horizon agentandClaude Opus 5.5 fd5dd184f6 Merge main into #9743 (keep both fixture flags)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:32:21 +01:00
Riccardo GuerrieroandClaude Opus 5.5 cd834b8128 Creepers, grazing, creakings, turtles, wolves, shulkers and vexes make vibrations
Creeper.tick's PRIME_FUSE, Mob.ate's EAT, Creaking's ENTITY_ACTION
(activate, deactivate, freezing, a heart-bound hit), Turtle.aiStep's and
Wolf.tick's ENTITY_ACTION, Shulker.setRawPeekAmount's CONTAINER_OPEN /
CONTAINER_CLOSE and an evoker's vexes' ENTITY_PLACE are posted, and the
host forwards them (it dropped all but entity_action and flap).

sculk gains `ignite mN` and `ate mN` actions and four scenarios, run
last since vanilla merges experience orbs by global entity id (439/439;
1/4 of the new ones on main's gameplay code). Unit tests cover the
creaking, wolf and shulker.

Part of #9776

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 13:31:45 +01:00
martipopsandClaude Opus 5.5 e954d45940 Merge 5605b86b3 into controller/settings-screen: splitscreen_e2e keeps both scenarios
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 07:30:56 -05:00
Ludovic Marie 402fb8b1f4 Merge pull request #9815 from voidsipp/fix/9154-cleanroom-default-fallback-20261105
🤖 Agent: An unsupported initializer such as double(2.0) now leaves its uniform at zero with a note at its file and line, and the program still loads. Supported defaults and conflict errors behave as before.
2026-11-05 13:29:34 +01:00
Ludovic Marie ab326e3018 Merge pull request #9811 from voidsipp/fix/9284-firework-lead-sound
🤖 Agent: The lead snap now uses the entity-bound packet on NEUTRAL and excludes nobody, as FireworkRocketItem.use's playSound(null, player, ...). The ignored game test passes with prepared resources on playhorizon.
2026-11-05 13:29:30 +01:00
Ludovic Marie 17afb222ee Merge pull request #9805 from vnsmoreira/fix-shader-horizon
🤖 Agent: The box is drawn only for a shaders/ pack's skybasic program at the far plane, so terrain stays in front and Horizon's own sky is untouched. The design comes from the public shaders.properties docs, nothing copied.
2026-11-05 13:29:26 +01:00
Ludovic Marie 9eff6af707 Merge pull request #9801 from lightcodegit/fix/tnt-explodes-gamerule
🤖 Agent: With tnt_explodes off, the overlay now reaches the acting player and the item gets PASS, as in TntBlock.useItemOn. Redstone, fire and a finished fuse are gated by the same rule as TntBlock.prime and PrimedTnt.explode.
2026-11-05 13:29:22 +01:00
Ludovic Marie 42d83f8723 Merge pull request #9796 from voidsipp/fix/9732-social-tutorial
🤖 Agent: The hint follows Minecraft.tick's isMultiplayerServer: shown on a real server, or once a single-player world is published, never just because the client uses memory networking. joinedFirstServer is saved so it shows only once.
2026-11-05 13:29:18 +01:00
Ludovic Marie 715f591b2a Merge pull request #9774 from lightcodegit/fix/spectator-loot-table-open
🤖 Agent: Spectators now open neither a block container nor a chest boat with a pending loot table (BaseContainerBlockEntity.canOpen, AbstractChestBoat.createMenu). The boat roll clears LootTable and LootTableSeed from the raw NBT before filling, as unpackChestVehicleLootTable does.
2026-11-05 13:29:13 +01:00
Ludovic Marie 6b2be8534f Merge pull request #9771 from arslanarm/shaderpacks/terrain-origin-rounding
🤖 Agent: The 65536ths rounding is applied only to section origins while a shaders/ pack draws terrain or shadow. Culling, sorting and Horizon's own rendering keep the exact camera.
2026-11-05 13:29:07 +01:00
Ludovic Marie 35e2f588fc Merge pull request #9843 from DUzzL/fix/9787-dealt-absorbed
🤖 Agent: I moved the award into hurt_by_player, which melee, sweep and spear hits all go through. tick_stats runs with the host's statistics, so a guest's absorbed damage went to the server's Game; it now goes to the attacker inside its own as_guest turn. Tenths from other kinds of hits are cleared first, so they never reach the next attacker, and tick_stats no longer walks every mob.
2026-11-05 13:29:04 +01:00
Ludovic Marie 3b8012e529 Merge pull request #9839 from arslanarm/shaderpacks/terrain-vertex-32768ths
🤖 Agent: The floor to 32768ths runs only in the pack's terrain vertex stage (hz_Vertex), so Horizon's own drawing and at_midBlock stay exact. Whole blocks and sixteenths are already on that grid.
2026-11-05 13:28:59 +01:00
Ludovic Marie 8daeb30580 Merge pull request #9832 from martipops/test/native-post-rounding
🤖 Agent: 0.75 maps to 191.25, which no driver can round two ways, while 0.5 maps to 127.5, which is exactly half way. Only the test changes.
2026-11-05 13:28:54 +01:00
Ludovic Marie db29213c59 Merge pull request #9828 from arslanarm/shaderpacks/sky-fragment-depth
🤖 Agent: The sky now writes gl_FragDepth = 1.0, so the depth test still keeps it behind the world, and gl_FragCoord.z stays the geometry's own. One cost: a fragment that writes depth loses early-Z, so a pack's sky program now runs under terrain as well (Iris pays the same, since it draws the sky first). A noperspective depth varying with the old far-plane z would keep early-Z (follow-up #9857).
2026-11-05 13:28:49 +01:00
Ludovic Marie b55d0bd8ed Merge pull request #9724 from TheMrZZ/fix/armor-stand-honey-slide
🤖 Agent: The slide check reads block cells with the 1e-5 deflated box and the 0.4375 + half-width overlap, as HoneyBlock.isSlidingDown does, and markers and NoGravity stands keep skipping it. The cost is a few cell reads per falling stand per tick.
2026-11-05 13:28:45 +01:00
Ludovic Marie f184d9fd40 Merge pull request #9823 from DUzzL/fix/9789-sweep-postattack
🤖 Agent: The sweep now ignites each swept player only after mob_hits_player lands, after the knockback, the same order doPostAttackEffects runs in Player.doSweepAttack. It uses the same ignite lookup as the direct hit.
2026-11-05 13:28:41 +01:00
Ludovic Marie b8470c60e0 Merge pull request #9720 from TheMrZZ/fix/h316-wolf-live-anger
🤖 Agent: A live /data write now restarts the wolf's anger counter from anger_end_time at the level clock, the same conversion load_mob uses, so a future end no longer saves as -1. The resource-backed game test passes on playhorizon.
2026-11-05 13:28:37 +01:00
martipopsandClaude Opus 5.5 924c7015df Controllers: one pad reader for a session, so KDE's pointer emulation stays paused
Entering a world opened the split's gilrs and closed P1's. gilrs opens a pad twice
at once, which inotify can merge into one event, and closes it twice apart. KWin
counts those events to pause its emulation, so its count could reach zero while
Horizon still held the pad, and the left stick also moved the mouse.

The router now takes P1's reader and gives it back. Its reader also gets the extra
SDL rows, which it was missing.

Refs #9730

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 07:28:34 -05:00
Horizon agentandClaude Opus 5.5 867d876b6b Mod browser: registry data packs into a world, categories and tags
Horizon's registry now serves the Data packs tab (beside Modrinth): a
`datapack` project is checked against the index like every registry file,
then goes into the world picked under the search, through the same
install_datapack flow Modrinth's use. On the registry, Filters lists the
kind's categories (horizon_mods::index::page::categories), search words
also match a project's tags, and a project's page shows its categories.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:27:03 +01:00
DUzzL b116ef75cb Apply advancement grant/revoke to each selected player 2026-11-05 13:25:20 +01:00
Horizon agentandClaude Opus 5.5 9ae108b54d Merge main into #9743: keep the namespace normalisation beside #9814's empty-default elision
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:25:20 +01:00
Ludovic Marie 85d8e283f6 Merge pull request #9814 from TheMrZZ/fix/empty-map-decoration-patch
🤖 Agent: The empty map_decorations is dropped only for minecraft:filled_map, after components_tag returns, so the stone and empty-map controls and the explicit !map_decorations removals still save as before.
2026-11-05 13:22:34 +01:00
Ludovic Marie 5c4a6885be Merge pull request #9810 from voidsipp/fix/9728-split-player-render
🤖 Agent: skin_slots.forget() runs on both renderer swaps (entering and leaving splitscreen), so the kept skin images upload again to the new atlas, the same way they do after a device loss.
2026-11-05 13:22:29 +01:00
Ludovic Marie 6a30d171e4 Merge pull request #9803 from lightcodegit/fix/cushion-name-potion-infinite
🤖 Agent: An infinite effect (duration -1) now shows effect.duration.infinite, as MobEffectUtil.formatDuration does after addPotionTooltip's endsWithin(20) check. A named cushion keeps CustomName in its NBT and on the dropped item.
2026-11-05 13:22:25 +01:00
Ludovic Marie 03237c755f Merge pull request #9798 from TheMrZZ/fix/disconnected-guest-crafting-drops
🤖 Agent: Only HostEvent::Left takes the Disconnected removal: cursor and grid inputs are dropped before the guest is saved, and the untaken result is not. A live close still puts items back in the inventory.
2026-11-05 13:22:21 +01:00
Ludovic Marie 531035e0c1 Merge pull request #9794 from voidsipp/fix/9644-modpack-row-translations
🤖 Agent: The coverage report keeps its English text through Row::plain_text, and the launcher translates the fixed sentences plus the separate waiting count. An older report without the waiting field still shows its own text.
2026-11-05 13:22:14 +01:00
Ludovic Marie 85c51b36cb Merge pull request #9773 from lightcodegit/fix/split-pad-join-controller-off
🤖 Agent: Only the joining guest gets controller.enabled = true, and only when it joined from a pad. P1's own settings are not touched.
2026-11-05 13:22:06 +01:00
Ludovic Marie 7290cadc75 Merge pull request #9770 from arslanarm/shaderpacks/shadow-model-view-angle
🤖 Agent: shadowAngle keeps its bits, and the matrix now turns by (shadowAngle - 0.25) * 360 in f32 (+360 when negative). The test pins Iris's bits at 7297, 7300, 16000 and 7303 with sunPathRotation 30.
2026-11-05 13:22:01 +01:00
Ludovic Marie 61282fe1c7 Merge pull request #9766 from arslanarm/shaderpacks/sampler-texture-unit-0
🤖 Agent: The unit-0 remap happens in bindings() at pack load, so it costs nothing per frame. The real_packs output is unchanged on all 40 packs.
2026-11-05 13:21:56 +01:00
Ludovic Marie a663667eb1 Merge pull request #9761 from Hiuoy/feat/8622-trader-template
🤖 Agent: Nothing outside the template lists changes: the content root and MobTypes::load already accept any template's species. The new test checks that a mod trader saves the same keys as a vanilla trader.
2026-11-05 13:21:49 +01:00
Ludovic Marie d0b685a570 Merge pull request #9754 from xxaxio/fix/9477-duplicate-nametag-riders
🤖 Agent: I moved the EntityAttachment.NAME_TAG doc comment back onto name_tag_at (collect_vehicle_ids had been inserted between it and its function). The vehicle set is built once per frame and stays linear in the entity count.
2026-11-05 13:21:43 +01:00
Ludovic Marie 9d53f882a2 Merge pull request #9747 from lightcodegit/fix/flaky-drop-test
🤖 Agent: The wait now ends when the held job leaves the queue, so drop(reg) always has a running job to wait for. The 10 s bound fails with a message instead of hanging.
2026-11-05 13:21:38 +01:00
Ludovic Marie 217d7453e7 Merge pull request #9739 from JulesB40/fix/glide-exit-crawl-slowdown
🤖 Agent: moving_slowly now follows LivingEntity.isVisuallySwimming, which also counts a FallFlying pose with fall_flying off. The 0.0196 against 0.00588 acceleration in the body is exactly that 0.3 factor.
2026-11-05 13:21:34 +01:00
Ludovic Marie 5a6411c864 Merge pull request #9726 from Hiuoy/feat/8622-structure-templates
🤖 Agent: The palette pick matches StructureTemplate.getRandomPalette with settings.getRandom(pos) (a LegacyRandomSource seeded with Mth.getSeed), and ids with a . or .. part are refused before template_entry reads the world's generated folder.
2026-11-05 13:21:29 +01:00
Horizon agentandClaude Opus 5.5 8cff8f8373 Breed xp test: tame both llamas (AbstractHorse.canParent)
With the vanilla data present the untamed pair never bred, so the
test failed; without it the test returned early and passed vacuously.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:21:26 +01:00
Ludovic Marie 6d6c5aa7d6 Merge pull request #9723 from GabrielNichols/codex/windows-firewall
🤖 Agent: The loopback default only applies through lab.game_env and the HORIZON_TEST_LOOPBACK=1 variable, so a player's LAN keeps binding 0.0.0.0. On Windows the launcher now copies the version folder (minus .local) into run/<home hash>/game on every start; that is a launch-time cost, not a per-frame one.
2026-11-05 13:21:24 +01:00
Ludovic Marie 3dbc8b178c Merge pull request #9714 from masa-masa-0314/feat/realms-configure-screen
🤖 Agent: The two new Realms calls go through the existing RealmsClient (GET worlds/{id}/subscription, DELETE worlds/{id}), and Delete is only offered on an expired Realm, behind its question, as RealmsSubscriptionTab does.
2026-11-05 13:21:20 +01:00
SwuarezandOpenCode f99a55995e motion: a client's own glide into a wall plays its fall sound (fixes #8906)
LivingEntity.handleFallFlyingCollisions's playSound of getFallDamageSound runs on both sides; only the hurt is the server's alone. Horizon pushed the sound event only from the server-side run, and a glider's own travel is client-side (and the server broadcast excludes the glider), so the glider never heard entity.player.big_fall / small_fall.

Co-Authored-By: OpenCode <[email protected]>
2026-11-05 20:20:58 +08:00
yahya-nusseibehandClaude Opus 5.5 e249683ac9 horizon-server update on Windows: swap the current junction safely
point() removed the `current` junction before making the new one, so a
failed mklink left the server with no current release, and std quotes an
argument only when it holds a space, so a folder like D:\mc&co\ reached cmd
unquoted and & split the command.

- The new junction is made first as current.switching; the old one is
  removed and the new one renamed into place; if that rename fails the
  old junction is put back.
- mklink runs as `cmd /D /S /C "mklink /J "link" "target""` with
  raw_arg, both paths quoted; paths holding % or " (which keep a meaning
  inside quotes) are refused with a message.

- Test (Windows only): real junctions in a folder named with `&`:
  current moves v1 -> v2 and reads each release's file, the old release
  is untouched, no current.switching is left, and a release whose path
  holds % is refused with current unchanged.

Compiled, linted (clippy -D warnings) and tested on GitHub's
windows-latest runner: all 146 horizon-server tests, the two junction
tests among them. The Unix build and tests pass too.

Split from #9129.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 14:20:43 +02:00
Horizon agent 9874105f26 Merge remote-tracking branch 'origin/main' into cg48-9479
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 13:20:05 +01:00
Ludovic Marie 31b11c7c37 Merge pull request #9871 from horizon-rust/registry-v2/schema
🤖 Agent: the compatibility point is the extension check: released launchers refuse the whole index when a version's extension mismatches its kind, which is why .zip shader-folder packs are a new 'shaderpack' kind instead of riding on 'shader'; all new entry fields stay optional until HORIZON_REV moves.
2026-11-05 13:17:45 +01:00
Horizon agentandClaude Opus 5.5 13eaff707e rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:16:16 +01:00
Horizon agentandClaude Opus 5.5 d7dce68a27 fix(skins): a failed cape or elytra shows the default skin, like vanilla
SkinManager.registerTextures joins the skin, cape and elytra downloads with
CompletableFuture.allOf, so any one failing (or a URL with no hash) fails the
profile's PlayerSkin and createLookup shows the default skin. #9420 kept the
skin when only the cape failed; tab_list caught it (36/1000 lists, #9834).
The retry of failed downloads and the decode-before-cache from #9420 stay.

Fixes #9834

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:14:59 +01:00
Arslan ArmanulyandClaude Opus 5.5 47a9eeb1ce Shader packs: fluid tops are split into triangles as Iris splits them
Vanilla's FluidRenderer puts a fluid top's corners NW, SW, SE, NE and the
quad indices join corner 0 to 2, so every top is split north-west to
south-east. Our probe packs' readings of Iris 1.11.7 fit this rule: a top
is split along the diagonal whose corners differ more in height, and
north-east to south-west when its north-west and south-east corners are
level. It is a fit, not Iris's code. Block faces keep vanilla's split in
Iris too.

Under a shaders/ pack (Mesher::shader_pack, any loaded pack), a top that
Iris splits the other way now starts from its south-west corner: same
winding, its 0-2 diagonal running south-west to north-east. Horizon's own
drawing keeps vanilla's order.

The fit covers every lava top we read, every still top but one, and 317
of 343 water tops; the misses are undiagnosed.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 17:13:44 +05:00
Ludovic Marie 3d9ad5214d Merge pull request #9804 from voidsipp/fix/9651-launcher-update-guidance
🤖 Agent: Package installs (/usr, /nix/store, /app, not /usr/local) lose Download again and get package-manager advice, and other stuck copies are told to move to Downloads. The path check now runs once per launch instead of on every sidebar draw.
2026-11-05 13:09:32 +01:00
Ludovic Marie 1af2daee6b Merge pull request #9695 from anthra123x/feature/launcher-glass-blur-and-layout-fixes
🤖 Agent: The window backdrop is baked once per generation/viewport at main's 4 px texel density with a one-texel border, so scrolling no longer rebuilds a piece per plate. The scene's animation doesn't bump generation, so idle frames do no extra work. The layout/dropdown changes were reverted (duplicate glass Sort by, design-system radii).
2026-11-05 13:09:28 +01:00
Alysia Zappavigna 7f16f6a885 Count fallFlyTicks after aiStep, as LivingEntity.tick does (fixes #9858) 2026-11-05 23:09:00 +11:00
AxxioandAntigravity 4021e7bc06 lan: preserve vehicle container stand-in while other viewers remain open (fixes #9791)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 13:07:35 +01:00
Horizon agentandClaude Opus 5.5 b3fce0c2c5 TNT: the disabled overlay reaches the acting player, and the item lights its fire
TntBlock.useItemOn sends block.minecraft.tnt.disabled to the player who
clicked (ServerPlayer.sendOverlayMessage), and returns PASS, so flint and
steel or a fire charge then lights a fire as its useOn does. The overlay
went to the server thread's own HUD, which no player sees. The overlay
helper moves from debug_stick.rs to titles.rs for both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:06:56 +01:00
Florian ERNSTandCodex 2d11bd857a Use stored entity bounds for supported box selector targets
Keep point distance and sorting, and retain the unresolved special-kind fallbacks.

Co-Authored-By: Codex <[email protected]>
2026-11-05 13:04:55 +01:00
Ludovic Marie f319701e8c Merge pull request #9684 from yahya-nusseibeh/fix/nightly-v2-signatures
Nightly: sign each build for its own version (SHA256SUMS.v2.sig)
2026-11-05 12:57:48 +01:00
TheMrZZandCodex gpt-6.1-sol 18f6f8f3c9 Populate active mob effects in entity predicate views
Project live effect IDs, amplifiers and durations into the existing view.
Cover named if/unless predicates and actual Game view construction.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 12:56:32 +01:00
Ludovic Marie eaaf9891ec Merge pull request #9862 from horizon-rust/fix/registry-zip-9
🤖 Agent: zip 2.6.1 was only the registry's test fixture writer (uploads already went through horizon-mods' 9.1.3); the new pre-check runs zip 9's validate_header_consistency, which by_name/by_index_raw skip by default, so a cloaked local header is refused before any kind's reader opens the upload.
2026-11-05 12:51:37 +01:00
Horizon agentandClaude Opus 5.5 aefbe1defb Mod registry: zip 9 and a check of the upload's zip
zip 2.6.1 (Dependabot #9 critical, #23, #24) was the registry's dev-only
fixture writer; the service already parsed uploads with horizon-mods' zip
9.1.3. Move zip to a normal dependency at 9.1.3, the version horizon-mods
and the main workspace use, so the lock holds one zip.

Before any kind's reader opens an upload, src/archive.rs now refuses a
local header that disagrees with the central directory, duplicate and
case-insensitive duplicate paths, overlapping entries, symlinks and other
special files, encrypted entries, over 100 000 entries and over 1 GiB of
declared uncompressed bytes. Tests craft each case.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:49:38 +01:00
TiggerTakkyTTTandCodex 704fde5f75 Fix Ender Dragon spawn egg
Route Ender Dragon spawn eggs through the dedicated dragon entity implementation for hand and dispenser spawns.

Fixes #9799

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-05 18:45:20 +07:00
Horizon agentandClaude Opus 5.5 ddce4effe1 Mod registry: datapack and shaderpack kinds, optional categories, tags and screenshots
Step 1 of registry v2 (docs/design/registry-v2.md): the new entry and index
fields are optional, so nothing in horizon-mods changes until HORIZON_REV
moves. Values present are checked: categories from the kind's list (1-3),
plain lowercase tags (<= 10, nothing about gambling), a description under
10,000 characters, square PNG icons and bounded PNG/JPEG screenshots served
from the registry's site like icons. Data packs (pack.mcmeta + data/) and
shaders/-folder packs get their own checks; cargo horizon-mod check tells a
zip's kind by its content. The launcher offers shaderpacks as shader packs
and keeps data packs back until it installs them into a world.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:43:46 +01:00
Horizon agentandClaude Opus 5.5 809af9425d Containers: a spectator opens no chest boat with a loot table to roll
AbstractChestBoat.createMenu returns no menu for a spectator while the
boat's lootTable is set, so the table stays for the first player who
opens it, as BaseContainerBlockEntity.canOpen does for blocks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:43:36 +01:00
Horizon agentandClaude Opus 5.5 69962b601b Signs: a plain line skips the component round trip each frame
sign_text_calls runs for every visible sign every frame. A line read as
plain text has no saved component, so it is drawn from its text at once
instead of going NBT -> JSON -> component each time.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:43:13 +01:00
Horizon agent f71ad19eb2 Merge remote-tracking branch 'origin/main' into cg48-9479
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 12:42:34 +01:00
Horizon agentandClaude Opus 5.5 f1f964f510 launcher: keep this PR to the glass backdrop bake
The version cards' radius and spacing and the mods sort dropdown are
back to main's:
- glass already draws its own Sort by beside the search
  (glass_mods_sort), so showing mods_sort_dropdown in glass gave two
  buttons sharing one sort_menu flag;
- the classic theme is square (theme::RADIUS = 0, "Blocks, not rounded
  cards" in docs/design-system.md);
- versions are panels, and panels take radius::PANEL (20).
The two Glass/Float bake branches are folded into one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:41:39 +01:00
Horizon agentandClaude Opus 5.5 fa81d70da4 launcher: resolve the package check once; /usr/local isn't package-owned
package_managed_launcher() canonicalized the update target on every
sidebar draw; the launcher doesn't move while it runs, so keep the
answer in a OnceLock. A launcher an admin copied to /usr/local/bin has
no package manager to update it, so it gets the move-it advice.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:41:20 +01:00
Ludovic Marie 46d2467fee Merge pull request #9853 from horizon-rust/registry-v2/design
🤖 Agent: docs only (docs/design/registry-v2.md); the one rollout risk it fixes is the order: optional entry fields and HORIZON_REV bump before any entry uses them, since the pinned cargo horizon-mod has deny_unknown_fields.
2026-11-05 12:41:07 +01:00
Horizon agentandClaude Opus 5.5 c17d14c739 Building boxes: no per-level clone, linear per-turn snapshot
GameLevel borrows guest_boxes outside a guest turn instead of cloning them,
and for_turn only searches the few borrowed ancestors, so a turn costs
O(players) rather than O(players^2) for each guest every tick.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:40:26 +01:00
Horizon agentandClaude Opus 5.5 36170fa8e6 Award damage_dealt_absorbed during the attack, to the attacking player
tick_stats runs with the host's statistics in place, so draining the mobs
there credited every guest's absorbed damage (and on the server thread, the
single player's own) to the server's Game instead of the player who hit.
hurt_by_player, which melee, sweep and spear hits all go through, now takes
the hit's tenths right after the damage, inside the attacker's as_guest turn.
Tenths left by hits that came another way are cleared first so they are never
credited to the next attacker, and tick_stats no longer walks every mob.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:40:14 +01:00
Horizon agentandClaude Opus 5.5 b0f885600e Stack on #9772: the new margin test's ShadowCull takes reach: None
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:39:59 +01:00
Horizon agent d7a9dd534e Merge commit 'refs/cg45/pr/9772' into cg45/9838 2026-11-05 12:39:51 +01:00
Riccardo GuerrieroandClaude Opus 5.5 e379b17f5e Placing paintings, frames, armor stands, crystals and minecarts makes a vibration
HangingEntityItem, ArmorStandItem, EndCrystalItem and MinecartItem post
ENTITY_PLACE where vanilla does; an ender chest's first opener posts
CONTAINER_OPEN (ContainerOpenersCounter.incrementOpeners).

On the same paths: each counts ItemStack.useOn's used statistic; a
painting or frame that can't hang there is CONSUME (counted) or FAIL as
in vanilla; paintings pick their variant and placed crystals their time
with their own random, not the level's; a placed armor stand falls onto
what is under it (EntityType.getYOffset with movedUp) after drawing
create's spawn yaw from the level random.

use_item_on gains placement cases (4800/4800; 4474/4800 without these
gameplay changes). The oracle's stand-in level gets hasEntities; the
fixture clears and prints crystals, paintings and frames.

Part of #9776

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 12:39:50 +01:00
Horizon agentandClaude Opus 5.5 cfa94ac87a Crafter trigger: strict box intersection, as AABB.intersects
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:39:46 +01:00
Horizon agentandClaude Opus 5.5 1bd8cc1bbf Merge origin/main into fix/nightly-v2-signatures
Resolves the launcher/lang/*.json conflicts by keeping both sides' keys.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:39:39 +01:00
Horizon agentandClaude Opus 5.5 23f78fc705 Design note: mod registry v2 (kinds, page fields, categories, updates, rollout)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:39:26 +01:00
Horizon agentandClaude Opus 5.5 061bc04462 names: keep the NAME_TAG doc on name_tag_at
collect_vehicle_ids was inserted between name_tag_at's doc comment and the
functions below it, so rustdoc gave the name-tag height text to
collect_vehicle_ids. Move it back above name_tag_at.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:39:10 +01:00
Ludovic Marie ae3ee1724b Merge pull request #9825 from horizon-rust/roadmap/server-admin-done
🤖 Agent: requeued after an empty commit; GitHub had lost this PR's head ref, so the merger couldn't fetch it.
2026-11-05 12:38:46 +01:00
Ludovic Marie 646f390b18 Merge pull request #9837 from martipops/docs/horizon-home-scope
🤖 Agent: Checked against main: HORIZON_HOME (logging::game_home) holds logs and crash reports but also telemetry/consent.json, perf reports, recordings/repro and the DX12 shader cache, while saves and options.txt follow the working directory or --game-dir (game.rs set_current_dir). I pushed 3eeb29cda to name those in the parenthesis so the rule doesn't read as "HORIZON_HOME barely matters".
2026-11-05 12:38:42 +01:00
Ludovic Marie 8343cf2595 Merge pull request #9842 from horizon-rust/fix/gl-surface-4293
🤖 Agent: the Linux fallback reuses vulkan_start_crashed unchanged and only rewrites graphicsApi when it is auto, so an OpenGL start that crashes again in Vulkan can't flip the setting back and forth.
2026-11-05 12:38:38 +01:00
Horizon agentandClaude Opus 5.5 9f7cfcd274 Retrigger: GitHub lost refs/pull/9825/head
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:37:36 +01:00
Horizon agentandClaude Opus 5.5 3eeb29cda1 AGENTS.md: list what HORIZON_HOME holds besides logs
game_home() also keeps telemetry consent, perf reports, recordings and
repro bundles, and the DX12 shader cache, so "logs and crash reports
only" undersold why the scratch HORIZON_HOME matters.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:36:02 +01:00
umcekoandClaude Opus 5.5 c926e3e034 lan_e2e: drop datapack-recipe's guard on the removed SERVER_THREAD flag
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:34:17 +03:00
591ce12184 Match a wither's single smoke particle without a redundant guard.
Clippy rejects `if count == 0` on that arm; the pattern `count: 0` is the same match.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-05 12:30:39 +01:00
Florian ERNSTandCodex 3bfb817913 Expose live guarded piglin anger UUID and remaining TTL
Co-Authored-By: Codex <[email protected]>
2026-11-05 12:27:02 +01:00
Horizon agentandClaude Opus 5.5 7dbf60d015 Cosmetics: a setting to show my cosmetics in single player
In single player the window's own server names the player by their
offline profile, which the cosmetics service doesn't know, so the
player's own cosmetics, name tag style and Horizon mark never showed
there (and the game asked the service about the offline UUID for
nothing). Cosmetics in single player (Extras > Cosmetics, off by
default, en/fr/de) asks by the signed-in account's UUID instead. Off,
the own model asks and draws nothing in single player.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:26:04 +01:00
Horizon agentandClaude Opus 5.5 28dfa8b8b0 Cosmetics: Show me as Horizon says who can ask about it
/v1/worn takes no sign-in, so anyone can learn whether a listed player
played Horizon in the last 3 hours: the tooltip (en/de/fr) and
docs/cosmetics.md now say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:24:34 +01:00
e78b42672d Play the sounds and particles vanilla already shows for fangs, withers, fireballs and level-ups.
A flat fang pitch, a powered wither's missing head particles, silent advancement loot and /xp levels, and hurting projectiles with no bubbles in water are all visible on the server-thread client.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-05 12:23:30 +01:00
Horizon agent fc4d9b758f Merge remote-tracking branch 'origin/main' into cosmetics-gem
# Conflicts:
#	crates/horizon-client-render/src/extras.rs
#	crates/horizon-client/assets/horizon/lang/de_de.json
#	crates/horizon-client/assets/horizon/lang/en_us.json
2026-11-05 12:22:34 +01:00
Horizon agentandClaude Opus 5.5 568150f63e Merge origin/main into fix/nightly-v2-signatures
Resolves the launcher/lang/*.json conflicts by keeping both sides' keys.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:21:32 +01:00
SenjinandClaude Opus 5.5 840a42cfab Shader packs: a buffer gets its second texture only when a pass flips it
Composite-style programs read a buffer's "main" side and draw into its
"alt" side; gbuffers, shadow and final programs write in place, compute
programs through images. A buffer no prepare, deferred, composite or
shadowcomp program draws into or flips never touches its B texture, so
B is now a second view of A for those buffers. The alt-side clear and
the three-channel start skip such buffers so A isn't attached twice in
one pass. At 3440x1440 this saves 35 to 224 MiB with BSL, Complementary,
Bliss and Super Duper Vanilla, most of it in shadowcolor buffers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:17:39 +01:00
DUzzL c27e16c57b Fix zombified piglin angering at the player when another mob hurts it 2026-11-05 12:15:19 +01:00
Horizon agentandClaude Opus 5.5 08fa43297e Launcher: the Vulkan crash notice in every language table
Each table gets the English text, as the other new strings do until
someone translates them. The gfx.rs comment stays as it was, so the
change touches the launcher alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:01:29 +01:00
voidsip ceb886b00f fix: prevent block placement through other players 2026-11-05 05:00:15 -06:00
Mayonesu0andClaude Opus 5.5 f01e86112c Sculk: wool and carpets dampen landings and steps (Entity.checkFallDamage)
HIT_GROUND carried no affected state, so a jump or fall on wool reached sculk
sensors and shriekers. It now carries mainSupportingBlockPos's state, as
Entity.checkFallDamage does, for players, mobs, items and armor stands.

STEP read the block 0.2 below the feet, which is the block under a carpet. It
now reads the state at Entity.getOnPos, as applyMovementEmissionAndPlaySound
does.

tools/test_server_wool_vibration_parity.py compares both servers through a
vanilla-codec client.

Fixes #8036

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:58:27 +01:00
martipopsandClaude Opus 5.5 80fc892f98 AGENTS.md: HORIZON_HOME is logs and crash reports; the game folder holds the rest
Fixes #9835

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:57:55 -05:00
Alysia Zappavigna 147c8b0a05 Bind travelInAir's client side and min Y for the player (fixes #9817) 2026-11-05 21:56:11 +11:00
martipopsandClaude Opus 5.5 7fbd35def9 Render test: smoothness off the 8-bit rounding boundary
0.5 × 255 is 127.5, which drivers can round either way: NVIDIA gives 127. The test
checks that the surface reads back exactly, so 0.75 (191) checks the same thing.

Fixes #9831

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:55:58 -05:00
Horizon agentandClaude Opus 5.5 1e553f8be7 Launcher: a Vulkan driver crash at the renderer's start switches Linux to OpenGL
On Windows a native crash in the Vulkan driver at the renderer's start
switches Auto to DirectX 12 (#746). Linux had no such rescue: since #4064,
Auto runs Mesa's hasvk on Ivy Bridge and Haswell, which Mesa calls
incomplete on Ivy Bridge (#4293), and a crash inside it would end every
start with no crash report and no fallback. The same switch now sets
OpenGL on Linux (crocus or iris through EGL, which presents to the window
since #2875). A player's own choice stays, so it never loops.

Refs #4293

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:51:42 +01:00
BPMichonandClaude Opus 5.5 97fe6557fe Native hooks: ask toast_add even with no trait mods
mods() lists trait mods only, so returning early when it was empty kept
a bundle injected at the toast_add hook point from ever running.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:48:28 +00:00
Arslan ArmanulyandClaude Opus 5.5 1f70ee39b9 Shader packs: the sky's programs read their own fragment depth
gbuffers_skybasic and gbuffers_skytextured put every vertex at the far
plane (gl_Position.z = gl_Position.w), so gl_FragCoord.z read 1.0 on every
sky fragment. Our probe packs read the sky disc's, the sunset fan's, the
stars' and (we take it) the sun quad's own perspective depth there in Iris 1.11.7.

The vertices now keep their depth, and the translator writes
gl_FragDepth = 1.0 before the pack's main (GbuffersInputs::far_depth), so
the depth test still sees the far plane: the sky shows only where the
world left nothing, and a pack's own gl_FragDepth still wins.

Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:48:26 +05:00
masa-masa-0314 ce63f1d915 Merge remote-tracking branch 'upstream/main' into feat/realms-worlds-tab 2026-11-05 18:48:24 +08:00
Arslan ArmanulyandClaude Opus 5.5 7cc22c56f1 Shader packs: the shadow pass's margin around the view is 1.125 blocks
Over 76 scenes of our probe at fields of view 30 to 110, Iris 1.11.7's
shadow maps keep every section whose box needs a growth of up to 1.1224
blocks to meet the casters' shape, and drop every one that needs 1.1252 or
more, whatever the field of view. With #9707's 1.15, main's maps held 20
columns Iris drops, in 18 of those scenes; with 9/8 they hold exactly
Iris's columns in all 76 (a fit, not Iris's code).

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:45:45 +05:00
Horizon agentandClaude Opus 5.5 2357b6d03b Roadmap: server-admin's last PRs linked, its panel and management-protocol rest split out; sync
server-admin links #9767 and #9748 (merged), so sync ships it once a release has them. The panel's later
features and vanilla's management protocol become server-panel-management (Later). sync also ships
languages (v0.2.65), end-city-banners (v0.2.67) and end-city-loot (v0.2.73).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:44:45 +01:00
Riccardo GuerrieroandClaude Opus 5.5 e0db49e280 Items count toward "Times Used" where vanilla counts them
Vanilla awards Stats.ITEM_USED in each item's own use/releaseUsing/useOn
(or in ItemStack.useOn); Horizon's ports left it out. The bow, crossbow,
trident, fishing rod (cast), eye of ender, boats, carrot and warped
fungus on a stick (no boost), empty map, equippables swapped from the
hand, books, flint and steel / fire charge on TNT, firework boosts and
the spyglass now count it, in vanilla's branches.

On the same paths: fire starters lighting a fire count the use and post
BLOCK_PLACE / BLOCK_CHANGE; sneaking with one on TNT lights a fire
instead of priming it; a placed boat posts ENTITY_PLACE; the spyglass
sound plays at the feet.

use_item_on gains used-statistic cases (4400/4400; 4054/4400 on main's
gameplay code). The oracle's stand-in player gets what these items read.

Fixes #9775

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 11:39:10 +01:00
masa-masa-0314andZCode 4a140d5629 Realms: the players tab with invites, operators and removal
The configuration screen's Players tab: the invited count, a row per player with
Op / Deop / Remove (the removal asks first), and Invite Player behind it.
RealmsInviteScreen: a name field whose blank or already-invited values are refused
before any request, the service's answer deciding otherwise.

RealmsClient gains getOwnRealm (GET worlds/{id}, the slots and players),
invite (POST worlds/{id}, OutboundPlayer's JSON), op (POST worlds/{id}/{uuid}),
deop and uninvite (DELETE worlds/{id}/{uuid} and worlds/{id}/invite/{uuid}), all
with the UUID as UndashedUuid's 32 hex digits. The tab reads again after an
operator change or an uninvite.

The Invite Codes button and its screens, and the player faces, follow in their
own PRs (the faces need the skin-face mechanism).

Co-Authored-By: ZCode <[email protected]>
2026-11-05 18:37:23 +08:00
DUzzL 27a31c391c Award damage_dealt_absorbed when a mob soaks a player's hit 2026-11-05 11:36:40 +01:00
voidsip 87f7e1ccbf test(lod): repair prepared-data fixture state and scheduling 2026-11-05 04:32:42 -06:00
TheMrZZandCodex gpt-6.1-sol 942f199d58 Apply transmute result component patches after inherited components
Retain template patch presence so explicit removals and zero-valued
components override the input without losing inherited data. Preserve
transient map postprocessing through assembly and add powered-crafter
and resource-free regression coverage.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 11:30:26 +01:00
Ente_3andClaude Sonnet 5.5 b45937b8c9 Signs: a line draws in its own colours (#9783)
A sign line kept only its plain text, so a server's {"text":"Online",
"color":"green"} drew in the sign's dark dye colour, as did `§` codes
(drawn as a raw `§`) and hex colours.

SignText now keeps each line's saved component beside the text it showed
when read, so the style survives loading, saving and the network. A line
edited since is a literal in the old line's root style, as
SignBlockEntity.setMessages makes it. List components and translate
components no longer lose their text.

sign_text_calls draws a line as one call per run of one colour: a glyph
takes its style's colour, else the sign's (Font.StringRenderOutput), with
`§` codes resolved as the font does. A line with no style is still the one
call it was. The outline of glowing text stays the dye's dark colour.

Bold, italic, underline, strikethrough and obfuscated text are kept but
not drawn yet.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 11:26:16 +01:00
Arslan ArmanulyandClaude Opus 5.5 9db60745bb Shader packs: terrain vertices sit on Iris's 32768ths of a block
Read back with a probe pack of ours, Iris 1.11.7's terrain gl_Vertex puts
each vertex's place in its section on the 32768th of a block below it:
water tops (8/9 - 0.001) at 29094/32768, plants offset by 13/15 and 0.1 at
28398/32768 and 3276/32768, on all three axes (a fit, not Iris's code).
Horizon passed the mesher's floats on, up to 1/32768 block from Iris's,
enough for a shadow-map coordinate on a texel boundary to fall the other
way. The pack's terrain vertex stage now takes the position down to that
grid; whole blocks and sixteenths are on it already.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 15:23:36 +05:00
SwuarezandOpenCode ae4a233d33 mobs: breeding experience draws from the parent's random inside the mob_drops check (fixes #8038)
Animal.finalizeSpawnChildFromBreeding draws the orb's 1-7 from the breeding parent's own random, and only when mob_drops is on. Horizon drew it first and from the level's random (llamas always, the other animals from world.random): with the rule off that consumes a draw vanilla does not make, and outside llamas, turtles and frogs the parent's and the level's later draws drift from vanilla.

Co-Authored-By: OpenCode <[email protected]>
2026-11-05 18:22:54 +08:00
voidsip a7769bd31d Keep shader programs with unsupported uniform defaults 2026-11-05 04:21:33 -06:00
martipopsandClaude Opus 5.5 6f682e763e Controllers: X and Y in their places on unmapped Xbox-style Linux pads
The kernel defines BTN_X as BTN_NORTH and BTN_Y as BTN_WEST, and gilrs' default
mapping takes those names literally, so a pad with no SDL row had X and Y crossed.
Sony and Nintendo pads send the same codes by real position and stay as they are.

Closes #9813

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:21:19 -05:00
Florian ERNSTandCodex efa0323824 Omit empty filled-map decoration defaults from saved patches
Co-Authored-By: Codex <[email protected]>
2026-11-05 11:20:57 +01:00
voidsip 8d8875c07c Bind firework lead-break sounds to the gliding player 2026-11-05 04:17:01 -06:00
DUzzL a783be5c83 Drop a Loyalty trident whose owner is dead or spectating 2026-11-05 11:12:20 +01:00
voidsip 4372977f0c Keep Microsoft account buttons inside the glass card 2026-11-05 04:11:34 -06:00
DUzzL 4c9a638a5c Use the team/nickname display name in join and leave messages 2026-11-05 11:11:27 +01:00
rison1337andCodex GPT-6 8de55d4548 Register merged client overlay and Host services as APIs 410 and 411
Generated with tools/stamp_native_hooks.py --only gui_render_post client_services. Leave the other 18 fragments pending while their contracts are reviewed.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 15:11:17 +05:00
rison1337andCodex GPT-6 82011f3c6f Allow selecting reviewed native hook fragments for stamping
Keep unselected contracts pending, preserve merge order within the selection, and reject unknown names before writing.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 15:11:17 +05:00
voidsip 5605b86b35 Reupload player skins when switching splitscreen renderers 2026-11-05 04:09:48 -06:00
DUzzL 14dde88a38 Apply Fire Aspect to players hit by a sweep attack 2026-11-05 11:09:16 +01:00
Vinícius MoreiraandCodex c603422a7d Shader packs: cover the horizon below the upper sky disc
The upper sky disc leaves directions near and below the horizon uncovered.
Shader packs keep the buffer's clear colour there, producing a sharp sky
band and pale distant terrain during fog compositing.

Draw an enclosing sky background before the existing disc. Keep its depth
at the far plane and preserve the foreground's colour and depth. Add an
original GPU probe covering 20 camera orientations and foreground occlusion.

References and provenance:
- Iris public format reference for gbuffers_skybasic:
  https://shaders.properties/current/reference/programs/gbuffers/
- Public rendering-uniform and depth-buffer references:
  https://shaders.properties/current/reference/uniforms/rendering/
  https://shaders.properties/current/reference/buffers/depthtex/
- Complementary's public user guidance, changelog and option descriptions.
- Java 26.3 + Iris 1.11.7 and Complementary Reimagined r5.9.3 were run
  as comparison inputs. The diagnostic shaders and Rust changes are original;
  no Iris or Complementary implementation source was opened or copied.
- After implementation and testing, PR descriptions and closure comments
  (including #8753) were consulted to check contribution provenance policy.
  No patch from those PRs was opened or reused. This is not a fresh-context
  or no-documentation implementation.

Validation: fast game build; 16 depth GPU tests including the new regression
on RX 9070 XT; renderer library Clippy with warnings denied; rustfmt.
Visual comparison used isolated game homes, the same world and camera,
32-chunk render distance, noon and clear weather.

Co-Authored-By: Codex <[email protected]>
2026-11-05 07:01:22 -03:00
voidsip e9fe40d2e5 Guide read-only launcher installs to the right update path 2026-11-05 03:57:42 -06:00
makscoder a1a9df806c Cushions keep their name when broken; infinite potion effects say so
Cushion.getCushionItemStackWithData copies the entity's CustomName onto
the dropped item, as the item frame beside it does; the cushion carried
no name at all. Add it, read and write it with the entity, and drop it.

PotionContents.addPotionTooltip appends MobEffectUtil.formatDuration,
which answers the infinite sign for an effect that never ends
(duration -1, from commands or datapacks); brewing's effect_lines only
appended a time past 20 ticks, so such an effect showed none.
2026-11-05 12:50:13 +03:00
Florian ERNSTandCodex 0e043eafae Drop guest cursor and crafting inputs on disconnect
Co-Authored-By: Codex <[email protected]>
2026-11-05 10:47:09 +01:00
Ludovic Marie 696d021699 Merge pull request #9767 from TiggerTakkyTTT/test/server-admin-cli-goldens
🤖 Agent: Queued after the agent fix: stopped-server ban replies now use vanilla's console lines, pinned by the golden test.
2026-11-05 10:40:48 +01:00
voidsip c320caedac fix(packs): resolve Windows folder paths before opening Explorer 2026-11-05 03:39:01 -06:00
Ludovic Marie 77e1ec40fe Merge pull request #9748 from TiggerTakkyTTT/test/server-admin-mcp-goldens
🤖 Agent: Queued. Test-only: it pins the server MCP's tool list, input schemas and error replies as a golden, so a renamed tool or field that breaks agents' calls now fails a test. Pinning that is right here, because MCP clients rely on those names.
2026-11-05 10:38:55 +01:00
Ludovic Marie 024f23e223 Merge pull request #9736 from lightcodegit/fix/splitscreen-reconfigure-race
🤖 Agent: Queued. The new queue drain runs only on split screen's present path and before a new window's surface configures, so single-window play has no extra wait. martipops's two review points are answered in 9fe888b76.
2026-11-05 10:38:51 +01:00
Horizon agentandClaude Opus 5.5 daf0206306 rustfmt lists.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:38:44 +01:00
Horizon agentandClaude Opus 5.5 a0eb1728d9 Server-admin CLI: stopped-server ban replies use vanilla's console messages
lists.rs promises vanilla's console lines; the new ban/pardon/ban-ip/pardon-ip replies used other words.
Now commands.ban.failed, commands.pardon.failed, commands.banip.success/failed/invalid and
commands.pardonip.success/failed from 26.3's en_us.json; goldens updated to match.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:38:09 +01:00
makscoder 3b3c064069 Crafter: the recipe trigger uses the player's box, as vanilla's area does
CrafterRecipeCraftedTrigger tests a ServerPlayer's bounding box against
AABB.ofSize(center, 17, 17, 17); Horizon tested the eye within 9.5 on
each axis. Use the player's box and 8.5 to each face, so the reach is
the width and height reaching into the box, not a point 9.5 away.
2026-11-05 12:37:46 +03:00
makscoder c360630194 TNT: the tnt_explodes rule gates priming and the fuse's end
With the rule off, TntBlock.prime leaves the block alone: redstone, fire
and an unstable block breaking go through the level event, which now
refuses like a native mod does (the block comes back); flint and steel
says block.minecraft.tnt.disabled; and a fuse already lit goes out
without exploding, as PrimedTnt.tick does.
2026-11-05 12:36:38 +03:00
voidsip 4bc370f36f fix(launcher): make modpack row explanations translatable 2026-11-05 03:35:48 -06:00
voidsip 0cd02931b2 Show the social interactions hint on first multiplayer session 2026-11-05 03:34:34 -06:00
makscoder 2c39790297 Chest boats: roll the loot table a boat still holds on first open
ContainerEntity.unpackChestVehicleLootTable rolls the table saved in the
boat's NBT when a player opens it. Horizon read the table nowhere, so a
boat made by a command or a mod stayed empty and kept it. Roll it into
the slots on the open (only when they are empty), clear it from the raw
NBT, and fire player_generates_container_loot for the opener.
2026-11-05 12:31:39 +03:00
voidsip 875d1c9c45 fix(launcher): expose launch settings in glass instance menu 2026-11-05 03:30:23 -06:00
umcekoandClaude Opus 5.5 96d4726a17 Hoglins always save CannotBeHunted, like vanilla
Hoglin.addAdditionalSaveData writes putBoolean("CannotBeHunted", ...) every time, so a hoglin that can be hunted saves 0b instead of dropping the tag. save_roundtrip passes again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:26:59 +03:00
makscoder bd9909a074 Storage: a spectator opens no container that still has a loot table
BaseContainerBlockEntity.canOpen refuses a spectator while the container
still has a loot table to unpack; spectator_open_menu skipped that, so a
spectator could open an unrolled chest. The pending roll lives in the
chunk's saved rolled_loot until the first open consumes it.
2026-11-05 12:21:41 +03:00
makscoder f129bbcc48 Splitscreen: a pad that joins plays with it even when P1 has controllers off
The Controller setting is P1's own. A guest copied it, so after joining
from a pad its own pad was dead - on the profile screen it joins into,
with no way to pick one. Enable the controller in the joining player's
settings when the join came from a pad.
2026-11-05 12:19:41 +03:00
makscoder 9fe888b76c Splitscreen: the drain waits for the submit thread, not the device 2026-11-05 12:13:42 +03:00
Arslan ArmanulyandClaude Opus 5.5 a75884f992 Shader packs: samplers that aren't the program's read texture unit 0
OptiFine's shaders.txt puts gtexture on texture unit 0 in gbuffers programs and
colortex0 there in deferred, composite and final; its gbuffers table has no
colortex0 to colortex3. Our probe packs, run in Iris 1.11.7, read the block atlas
in gbuffers_terrain through a sampler name of their own and through each of
colortex0 to colortex3 and gnormal, and the scene in colortex0 in deferred,
composite and final through a name of their own and through gtexture, tex and
texture, also where the program declares no colortex0.

Horizon read its white stand-in for all of these, or, in gbuffers, the buffer.
With this, PotatoShaders' blocks keep their textures and its cave and Nether
come out as bright as in Iris.

Fixes #9763
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:10:25 +05:00
TiggerTakkyTTTandCodex 6388756c60 Server-admin: edit bans while stopped
Route CLI moderation through file-backed lists when no game is running, pin vanilla-style replies, and document the stopped-server behavior.

Fixes #9753

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-05 16:09:44 +07:00
Arslan ArmanulyandClaude Opus 5.5 beb30045fd Shader packs: a built-in declared with another GLSL type reads 0
Iris 1.11.7 disables a built-in uniform whose declared type is not the one it gives
("Wrong uniform type ... Disabling that uniform"), and the program reads 0. Our probe
packs saw this for floats declared int or vec2, ints declared float, ivec2s declared
vec2, int or ivec3, vec3s declared vec2 or vec4, a mat4 declared mat3, and uint; ints
and bools declared bool or int were kept. Horizon converted the value instead.

Fixes #9758
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:07:25 +05:00
Arslan ArmanulyandClaude Opus 5.5 8794c49a28 Shader packs: the block atlas blends its two nearest levels
Our probe packs read gtexture in gbuffers_terrain, and the block atlas as a custom
texture in composite, at levels 0.5 and 1.25 in Iris 1.11.7: each read was the two
nearest levels mixed (nearest texels within a level). Horizon's sampler took the
nearest level only. The atlas now gets a sampler of its own with linear mipmap
filtering; normals and specular keep theirs.

Fixes #9760
Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 14:07:25 +05:00
martipopsandClaude Opus 5.5 ff0a9f199f horizon-ctl: the pad command's arguments name connection too
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:06:53 -05:00
HiuoyandClaude Opus 5.5 777a71b039 Content mods: a living type may copy the wandering trader
minecraft:wandering_trader joins the zombie and the villager as a living
template. It is the neutral one whose behaviour is all goals, so a native
mod's register_goals can replace every bit of it: a citizen, a guard or an
NPC, which a zombie (hostile, burns in daylight) and a villager (its brain
stays) can't be. Saving and entity data already follow the template's
class (class_id); the tests now cover a trader type too.

Part of #8622.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:06:20 +04:00
martipopsandClaude Opus 5.5 6408c7175f horizon-ctl: the pad command's usage names pad connection
The generated reference has it, so the usage it comes from has to.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:02:44 -05:00
makscoder 854db487d5 Fishing: draw the cast spread and the fish angle with vanilla's triangle
fishing.rs kept its own triangle on two next_float() calls; vanilla's
RandomSource.triangle draws two doubles, which also advances the random
differently. Use the core method.
2026-11-05 11:59:38 +03:00
AxxioandAntigravity 1d11a8b849 names: suppress default nametag when entity is ridden by text display or armor stand (fixes #9477)
Directed, architected and reviewed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 09:57:22 +01:00
i114rkandI114rk's AI agent 810d55aa22 Boot screen: paint through a memory bitmap so repainting does not flicker
Clearing the window and drawing the text straight into its DC shows the blank
frame in between, so the boot screen flickered each time the console's lines
arrived. The paint now goes to a compatible memory bitmap and reaches the
window as one BitBlt, the same frame the text was drawn into.

Co-Authored-By: I114rk's AI agent (OpenCode) <[email protected]>
2026-11-05 11:56:55 +03:00
Arslan ArmanulyandClaude Opus 5.5 cc6e8c65f7 Shader packs: shadowDistanceRenderMul keeps a cube around the camera, as in Iris
With shadowDistanceRenderMul set, the shadow pass drew only the chunks
within a circle of ceil(shadowDistance x mul / 16) chunks. Our probe pack's
Iris 1.11.7 shadow maps keep the sections whose box, grown by the same
1.15 blocks as for the casters, meets a cube of shadowDistance x mul around
the camera: out to the square's corners, a section 1 block past a side but
not 1.5, and nothing 70 blocks below a camera flying 50 blocks out. Over
12 scenes at shadowDistance 100 and mul 1, main dropped 33 columns Iris
keeps.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:53:41 +05:00
makscoder b21c3fb1ca Splitscreen: land the turn's frames before any surface reconfigure
Relands the frames on their submit threads at the top of Split::present:
a reconfigure there (a present mode switch, a new compositor, an
Outdated texture) waits for the device and errors on a submission made
meanwhile, and the compositor then copies views already submitted.
Simpler than gating on will_reconfigure, per martipops's review.
2026-11-05 11:49:16 +03:00
TheMrZZandCodex gpt-6.1-sol 067c293975 Detach old maps before adopting live frame Item data
Preserve the existing frame type during id-free live NBT adoption. Compare
count, item and components and remove the old map association before
replacement. Cover the Game command route and immutable-type adoption.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 09:47:54 +01:00
makscoder 67f66cf66b Camel: sprint works riding it, as on foot
LocalPlayer.aiStep runs its sprint decisions for a passenger too, but
the client only ran them on foot: `canSprint` and the local authority
stand in for food there. Run them for a vehicle with those conditions,
and give the ridden mount the controller's sprint state instead of the
raw key, so a double-tapped sprint starts and letting the key go keeps
it (Camel.getRiddenSpeed reads isSprinting()).
2026-11-05 11:46:47 +03:00
TiggerTakkyTTTandCodex 200716602c Server-admin MCP: pin tool contracts
Add protocol goldens for all MCP tools, schemas, safety annotations, confirmation requirements, and representative dispatcher errors.

Fixes #9741

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-05 15:46:31 +07:00
Arslan ArmanulyandClaude Opus 5.5 d652ec2f1b Shader packs: terrain is drawn from the camera in 65536ths of a block, as in Iris
Read back with a probe pack of ours, Iris 1.11.7's terrain gl_Vertex is
each block's position minus the camera with its fraction rounded to the
nearest 1/65536 block (seven cameras, up to 40000 blocks out), while
cameraPosition and the matrices keep the exact camera. Horizon drew from
the exact camera. Off by up to 2^-17 blocks, fragments on a texel boundary
of the shadow map read the other texel, and depths at the shadow test's
bias fell the other way: with this, our lit probe's depth ties at noon
go from 541 to 1 and its neighbouring-texel reads in y from 154 to 8.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:38:06 +05:00
Florian ERNSTandCodex ebc1d17ea3 Canonicalize bare map decoration component type IDs
Use the existing Identifier parser at preserved-component decode/encode boundaries. Own current Game/fresh Java focused gate94/103->103/103; unchanged full106 improves94/106->103/106 with exactly3 separate empty-elision reads retained. Core round-trip old-fail/fixed-pass,66item tests pass. Clippy, docs and content-root checks pass; existing Python release-fixture failures retained.

Co-Authored-By: Codex <[email protected]>
2026-11-05 09:33:08 +01:00
Alysia Zappavigna 6bd4443387 Fishing: draw the cast's spread from the hook's own random, as FishingHook.<init> does 2026-11-05 19:29:20 +11:00
makscoder 69d2c9c7df horizon-threads: wait for the job to start instead of sleeping past it
dropping_a_registration_waits_for_its_running_job slept 100 ms and
assumed a worker had taken the held job by then; on a busy runner it
had not, so the drop had nothing to wait for and returned early. Wait
until the job leaves the queue, and fail loudly if no worker picks it up.
2026-11-05 11:27:52 +03:00
martipopsandClaude Opus 5.5 b72ddf75c2 Controllers: a settings screen in the graphics style, and one notice per view
Graphics settings move their frame, list and key steps into settings_shell, with no change to how they behave,
and Controller Settings is its second user: General, Bindings and Device pages, search, per-row reset and Undo.
A controller notice is one slot per view rather than the split screen's own. A pad-focused options tooltip hangs
from the widget's bottom left, as the pad's hidden pointer sits mid-widget; mouse and keyboard keep vanilla's. A
focus whose widget goes away on the same screen moves beside it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:14:31 -05:00
makscoder 6e3a860916 Splitscreen: drain the render queues before reconfiguring a surface
Surface::configure waits for the device to come idle and errors when a
submission lands while it waits: with the submit thread on (Linux by
default), adding a player could crash the game in Split::present's
set_vsync, in a new compositor's first configure, or on an Outdated
texture. Compositors now say when a reconfigure is coming or owed, and
Split drains every player's queue at those points only, keeping the
overlap the submit thread is for the rest of the time.
2026-11-05 11:12:10 +03:00
Riccardo Guerriero 88b7a8af1e Merge origin/main into fix/villager-stand-up 2026-11-05 09:11:13 +01:00
Riccardo GuerrieroandClaude Opus 5.5 a36307234a Villagers waking up stand beside the bed where vanilla puts them
LivingEntity.stopSleeping asks AbstractBedBlock.findStandUpPosition for
the entity's own type. The player's port of that search moves from
game/respawn_anchor.rs to stand_up.rs, generic over the entity type
(dimensions, player, fire immune), and villager::stop_sleeping uses it
with the villager's box and yaw, and vanilla's fallback. Villagers now
step onto shelves, slabs and carpets, pick vanilla's side of the bed,
and fall back to the top of the bed instead of staying lying on it.

WorldBorder.isWithinBounds(AABB) takes 1.0E-5F (a float) off the box.

use_item_on gains villager wake-up cases: 4400/4400 match.

Fixes #9502

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
2026-11-05 09:11:13 +01:00
voidsip 9f89e738a6 Reject ambiguous trigger axes without an Xbox-style button layout 2026-11-05 02:08:30 -06:00
JulesB40andClaude Opus 5.5 7920a520ee Motion: the tick after an elytra glide ends moves at sneaking speed
LocalPlayer.isMovingSlowly is also true while the player is visually
crawling: LivingEntity.isVisuallySwimming counts the fall-flying pose a
glide that just ended still holds. Horizon only counted the swimming
pose, so on the first air tick after a glide its input took the full
0.98 x 0.02 forward acceleration where vanilla's takes 0.3 of it: GrimAC
flagged Simulation 23 times on every elytra landing with jump held, at
an offset of exactly the difference (0.01372). Vanilla's client is clean
on the same input.

Fixes #8295

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 09:01:32 +01:00
HiuoyandClaude Opus 5.5 57561e449b Native mods: structure_template, a template's blocks for builders
Part of #8622 (item 4, structure placement for builders). A new helper,
native_structure_template(id, at, rotation, mirror) (Host::structure_template),
finds a structure template as StructureTemplateManager.get does (the world's
saved structures, then the game's and the content mods' data) and answers
what /place template would place at `at`: the size after the rotation and
each block's position, mirrored then rotated state and block entity data,
in StructureTemplate.placeInWorld's order and from the palette it picks
there. Nothing is placed: a builder places the blocks a tick at a time with
native_set_block. Server only; ids with a . or .. part are refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:57:03 +04:00
makscoder 5a837668c6 Format the splitscreen pause fix 2026-11-05 10:56:06 +03:00
ViníciusandCodex 77b8780ab8 Extras: enable chunk pregeneration from the settings menu
Save an opt-in pregen switch, apply it to integrated servers immediately, and refresh command completion while preserving --pregen and permissions.

Co-Authored-By: Codex (GPT-6) <[email protected]>
2026-11-05 04:54:16 -03:00
Arslan ArmanulyandClaude Opus 5.5 da505c2b0e Shader packs: shadowProjection is Iris's for the pack's near and far planes
Read back with a probe pack of ours in Iris 1.11.7, a shadowNearPlane or
shadowFarPlane the pack sets is the orthographic projection's plane as it
is, with no 100-block offset; an unset near plane is -100.05 and an unset
far plane 156, each on its own. Horizon offset both by 100 blocks, so a
pack that sets either got another depth range. The projection's bits
(13 settings) fit JOML's setOrtho with zZeroToOne, its depth terms taken to
-1..1, in floats; the inverse is JOML's invertAffine. Before, the default
matrix's [3][2] was 4 ulps off and its inverse's signed zeros differed.

Horizon's own packs keep their depth range, now given as planes.

Adapts JOML 1.10.9's Matrix4f.invertAffine and setOrtho (MIT;
THIRD_PARTY_NOTICES.md).

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:52:10 +05:00
Arslan ArmanulyandClaude Opus 5.5 ea1fcbd314 Shader packs: sunPosition, moonPosition and upPosition are Iris's to the last bit
Read back with a probe pack of ours (25 ticks of day and night,
sunPathRotation 0 and 30, four view directions), Iris 1.11.7's sky
positions fit (0, 100, 0, 0) times gbufferModelView turned by JOML
quaternions in floats: rotationY(-90°), then for a body
rotationZ(sunPathRotation) and rotationX(its sky angle). Turned by
matrices, as Horizon did, the sun read 4e-6 where Iris has an exact 0
(e.g. at sunset), and the rest was 1 to 2 ulps off. All 50 readings of
all three now match, and shadowLightPosition with them.

Adapts JOML 1.10.9's Quaternionf.rotationX/Y/Z, Matrix4f.rotateAffine
and Vector4f.mulAffine (MIT; THIRD_PARTY_NOTICES.md).

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:51:43 +05:00
voidsip b8f05b083c Merge remote-tracking branch 'origin/main' into fix/7323-linux-triggers 2026-11-05 01:49:29 -06:00
Florian ERNSTandCodex 4efa91b049 Refresh honey dispatch with current main
Preserve both honey and pufferfish fixture options. Upstream stand changes are renderer-only and entity changes concern XP-orb sound; honey travel code is unchanged. Refreshed all-target Clippy, formatting and Python syntax pass. Runtime pair stays qualified to185156c0e.

Co-Authored-By: Codex <[email protected]>
2026-11-05 08:48:14 +01:00
Florian ERNSTandCodex 8770de9392 Apply honey side-slide movement to armor stands
Route the separately modeled stand through HoneyBlock movement after travel. Own current Game/fresh Java gate113/126->126/126, raw exact with unchanged scripts. Canonical travel regression old-fail/fixed-pass; honey suite11pass/1ignored. Broad readiness Rust/doc builds blocked by disk; formatting and Clippy pass.

Co-Authored-By: Codex <[email protected]>
2026-11-05 08:46:20 +01:00
Arslan ArmanulyandClaude Opus 5.5 a5fb848482 Shader packs: shadowModelView turns by Iris's angle between ticks
Iris 1.11.7 turns shadowModelView by (shadowAngle - 0.25) * 360 degrees in
floats, plus 360 when negative, not by the light body's own sky angle. Read
back with a probe pack of ours at 25 ticks of day and night (7295 to 7305
one by one), with sunPathRotation 0 and 30, this fits all 50 readings to
the last bit; the sky angle misses 16 of them by 1 to 6 ulps, which moves
the shadow map's texel boundaries.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 12:43:21 +05:00
GabrielNichols 1a6a403884 fix(windows): avoid repeated firewall prompts in fixtures and launcher updates
Keep same-machine test sockets on loopback and stage launcher-managed games
at a stable executable path per game home. Preserve runtime dependencies and
protect running installed/native versions from replacement and cleanup.

Refs #9685.
2026-11-05 04:42:32 -03:00
Ludovic Marie 41d5649ed5 Merge pull request #9713 from Hiuoy/feat/8622-mod-goals
🤖 Agent: with no mods the new hook costs one HOOKS.get() and a walk of an empty mod list per mob created, not per tick, and it runs before the float check, so a mod's goals::Float floats the navigation as FloatGoal does. The fragment stamps cleanly against main (test_stamp_native_hooks passes in pr_ready's Python tests). Pushed 9861c8352: living_mod_types now uses a temp folder per call, since parallel tests were deleting each other's folder. Merged with main, pr_ready --quick passes on Linux (4331 tests, launcher included).
2026-11-05 08:36:12 +01:00
makscoder 7eecf2aca1 Splitscreen: a pause menu no longer stops its window's ticks
set_paused deliberately lets the world run while splitscreen players
play, but pauses_game did not know about them, so the host window's
client ticks stopped behind its own menu: a laggy world and a rush
forward on resume. pauses_game is now false while split players are
joined, mirroring the server; scenario_pause checks both players'
tick counters against a solo game.
2026-11-05 10:34:40 +03:00
Ludovic Marie 36e1b42cb3 Merge pull request #9709 from glitchsnake/fix/9672-pufferfish-poison-tick
🤖 Agent: the sting now lands inside the fish's own tick (Pufferfish.aiStep → touch), so a target that ticks later in the same level tick counts its poison down that tick: 120→119→118, as in vanilla. Pushed 82648da3f, which removes the end-of-tick arm that could no longer run. Merged with main, pr_ready --quick passes on Linux (2863 client tests). The pufferfish contact harness is left to CI's parity run.
2026-11-05 08:28:26 +01:00
Ludovic Marie 8be47cdbca Merge pull request #9445 from martipops/fix/fake-player-close
🤖 Agent: the close paths (close_container, close_book) run while the fake inventory is still swapped in, so what a menu held goes back to the fake player and not to the server's own state. The ender chest's stand-in is put back afterwards, as a guest's turn does. Merged with main 27b9d342f (690 commits newer than its base), pr_ready --quick passes on Linux and the 4 ignored native_machines tests pass with prepared resources: trader, vehicle chests, shulker and ender lids, the restored state.
2026-11-05 08:28:22 +01:00
Ludovic Marie 8e1297dc73 Merge pull request #9711 from NerfPhaselock/perf/gpu-culling-g5
🤖 Agent: GPU culling stays off by default and DX12 frames still don't occlude, so only players who switch it on, on Vulkan, get the new passes. The new runs stamps survive a buffer regrowth (reserve_keeping copies the old buffer), so a grown table doesn't send every section back to phase 2 for a frame. On main 27b9d342f: fmt, clippy and 2419 client and render tests pass on Linux. The device tests of the box test itself aren't there, as the body says. Pushed dc3100917: a doc comment moved back onto its test.
2026-11-05 08:22:29 +01:00
Horizon agentandClaude Opus 5.5 9861c8352d living_mod_types: a temp folder per call, not per process
Tests run in parallel in one process: with three callers now (lan.rs and
two in mods.rs), one could remove the shared folder while another was
reading it (NotFound in a_mods_register_goals_replaces_its_types_goals).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 08:19:28 +01:00
TheMrZZandCodex gpt-6.1-sol 0d675f6dee Initialize wolf anger after live NBT writes
Convert the saved absolute end at the current level clock when adopting live
wolf data. Keep Bee state and the retained runtime fields unchanged. Cover
actual Game adoption and command writes with a separate NoAI drift diagnostic.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 08:19:19 +01:00
Ludovic Marie cc94ed7afe Merge pull request #9707 from arslanarm/shaderpacks/shadow-map-edge-columns
🤖 Agent: the 1.15-block margin is applied only to the caster-shape test, and the light frustum still tests the plain box, so the shadow pass can only keep more sections at the wedge's edges, never cull ones the light reaches. It costs two Vec3 ops per section test, only with a shaders/ pack's shadow culling. On main 27b9d342f: fmt, clippy and 1442 render tests pass on Linux.
2026-11-05 08:07:57 +01:00
Ludovic Marie 75fedf5a04 Merge pull request #9706 from arslanarm/shaderpacks/shadow-map-ties
🤖 Agent: the only outside code is JOML 1.10.9 (MIT, the library vanilla ships), credited in both file headers and THIRD_PARTY_NOTICES.md; Iris was only run as a black box with our own probe packs, so nothing from an LGPL source. The cost is a few float matrix ops per pack frame, and gbufferModelView now takes vanilla's Camera.getViewRotationMatrix path to the bit. On main 27b9d342f: fmt, clippy and 1443 render tests pass on Linux, the two last-bit tests included.
2026-11-05 08:07:53 +01:00
BPMichonandClaude Opus 5.5 1f866e2e51 Native hooks: ToastAddEvent (toast_add)
A client mod hears each new toast just before it is queued and may
cancel it: native_toast_add(kind, id, cancel). Kinds are advancement
(the advancement id), recipe (the first recipe id), tutorial (the
hint), system (narrator_toggle, pack_load_failure, pack_download,
low_disk_space, chunk_load_failure, chunk_save_failure) and
now_playing (empty id). Updates to a toast already queued or shown
are not adds. A cancelled toast isn't queued, shown or sounded; a
cancelled tutorial hint is kept off the slots so it isn't asked again
every frame. The startup menu's narrator toast is not covered: it
shows before Game, the hook's host, exists.

Fixes #9710

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BYf41fgTMRfUEXn3afqyTG
2026-11-05 07:03:09 +00:00
Ludovic Marie f090bb9821 Merge pull request #9689 from martipops/controller/menu-navigation
🤖 Agent: a menu frame without a controller does no new work: pad_menu_frame returns before the new resize/scroll-follow code unless the pad (or the keyboard focus) is in use, and the only always-on addition is one widgets::scroll_area push per drawn scrollbar. The superflat preset change matches vanilla (focus selects via AbstractSelectionList.setFocused, only Entry.select fills the share box). pr_ready --quick on main+PR on Linux: fmt, clippy, 4396 tests pass (the one Python failure is install.sh needing uname, absent from NixOS's PATH). tools/pad_menu_e2e.py not run: --offscreen still opens a winit window and needs a display.
2026-11-05 08:01:49 +01:00
masa-masa-0314 9bded00169 Merge remote-tracking branch 'upstream/main' into feat/realms-configure-screen 2026-11-05 14:59:42 +08:00
Horizon agentandClaude Opus 5.5 dc3100917c GPU culling tests: give records_keep_ids_page_counts_and_moves its doc comment back
the_shaders_validate was inserted between that test and its doc comment,
which then documented the new test instead.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 07:58:48 +01:00
Horizon agentandClaude Opus 5.5 82648da3f3 Pufferfish sting: drop the end-of-tick arm apply_brain_events now replaces
Every PufferfishSting comes from a mob's tick (sting_touching_mobs), and
apply_brain_events consumes it right after that tick, so handle_mob_events'
mob_hits_mob arm could no longer run. It joins StartFollowing and SchoolSize.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 07:58:28 +01:00
masa-masa-0314andZCode a7aeda8864 Realms: the owner's configuration screen and its subscription tab
The Configure button's screen was a placeholder (title, MOTD, open/close). It is now
RealmsConfigureWorldScreen: the four-tab bar (Worlds, Players, Subscription,
Settings, Settings off on an expired Realm), the footer's Play and Back, and
the Subscription tab: start date, time left, Extend, and Delete once expired
with its question.

Built on #7725's realms module and realms/client.rs, per the split on #7677.
RealmsClient.subscriptionFor (GET worlds/{id}/subscription) and deleteRealm
(DELETE worlds/{id}) join the client. The Worlds, Players and Settings tabs
follow in their own PRs (slot options, backups, invites, preferred region);
until then they show vanilla's loading line, and the open/close toggle stays
on the footer (vanilla keeps it on the settings tab).

The start date is a fixed local %Y-%m-%d where vanilla asks for the locale's
medium date (the process has no date locale), and the snapshot Realm's
subscription info line is not special-cased.

Co-Authored-By: ZCode <[email protected]>
2026-11-05 14:53:54 +08:00
NerfPhaselock 62fcccddd6 Merge remote-tracking branch 'upstream/main' into perf/gpu-culling-g5
# Conflicts:
#	docs/graphics.md
2026-11-05 01:30:43 -05:00
GabrielNichols 37ff7be994 Format the vanilla recipe source call 2026-11-05 03:30:41 -03:00
NerfPhaselockandClaude Opus 5.5 4d6f22efb6 Shader tests: the occlusion test's module is gpu_culling.wgsl's with gpu_occlusion.wgsl
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:28:50 -05:00
Ludovic Marie 27b9d342ff Merge pull request #9688 from yahya-nusseibeh/fix/registry-owner-ids
Mod registry: decide project ownership by numeric GitHub ID
2026-11-05 07:25:17 +01:00
Ludovic Marie e606196768 Merge pull request #9600 from NikitaMGrimm/hdr-light-design
Design note: linear light for HDR and shader API 0.2
2026-11-05 07:21:52 +01:00
Ludovic Marie dbf0d1c3c6 Merge pull request #9705 from horizon-rust/fix/fishing-hook-eye-spawn
🤖 Agent: at 2.5 blocks every triangle() throw enters the stand from outside on tick 2; at 3.0 a slower throw started tick 3 inside the margin box, where AABB.clip never hits.
2026-11-05 07:21:25 +01:00
MegaBytesMe eecdd28e83 Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64 2026-11-05 06:19:58 +00:00
Loki d49eb54168 Merge remote-tracking branch 'origin/main' into fix/9672-pufferfish-poison-tick 2026-11-05 15:16:32 +09:00
LokiandAntigravity c931ecb5a9 mobs: apply pufferfish sting poison during mob tick (fixes #9672)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 15:16:27 +09:00
andrescamilomartinez 1871f87982 perf(launcher): sample backdrop at main's density with 1-texel border 2026-11-05 01:14:41 -05:00
GabrielNichols 476c083e82 Send owned content recipes in the server book 2026-11-05 03:14:27 -03:00
Arslan ArmanulyandClaude Opus 5.5 416704f151 Shader packs: the shadow pass keeps the edge columns Iris keeps
Our probe pack's Iris 1.11.7 shadow maps held 1 to 3 chunk columns at
the edges of the view's wedge that the shadow pass dropped (15 columns
over 8 noon scenes). Growing each section's box by a margin before the
caster test keeps them: the 31 edge columns of 8 noon scenes allow only
a growth of 1.12 to 1.19 blocks (a fit, not Iris's code); with 1.15, 19
of our 20 scenes hold exactly Iris's columns.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:13:43 +05:00
Arslan ArmanulyandClaude Opus 5.5 06dbd97b16 Shader packs: the camera and shadow matrices are Iris's to the last bit
Our probe pack read Iris 1.11.7's matrix uniforms back exactly. Horizon's
differed in their last bits (1.0 for 0.99999994, 6e-17 for 0), and a
fragment on a texel boundary of the shadow map then read the texel on
the other side: with nearest filtering, 13,066 pixels of ow_ground_noon
were lit in one game and shadowed in the other.

- gbufferModelView and its inverse: vanilla's Camera.getViewRotationMatrix
  (JOML Matrix4f.rotation of the conjugated rotationYXZ quaternion) and
  JOML's invertOrthonormal, in floats.
- gbufferProjection and its inverse, shadowModelView and its inverse,
  sunAngle and shadowAngle: fits of our probes' readings (JOML's float
  operations in a given order, or JOML's formula worked in doubles).

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:13:11 +05:00
Ludovic Marie fb7762d6f9 Merge pull request #9704 from arslanarm/shaderpacks/custom-smooth-infinity
🤖 Agent: The guard skips only a step whose value equals the held state, which leaves a finite value unchanged anyway, while NaN still steps because NaN != NaN, so only the inf - inf case changes. It's stacked on #9701 and lands after it; the shaders tests and clippy -D warnings pass on main merged with both.
2026-11-05 07:12:04 +01:00
Ludovic Marie 3395fc16c4 Merge pull request #9703 from arslanarm/shaderpacks/terrain-cutout-alpha-test
🤖 Agent: The two parts were already the game's (terrain_parts) and drawn this way for packs with the split programs, so a pack with only gbuffers_terrain now takes the same path. That's one more pipeline switch per terrain pass, not more geometry, and only with a shaders/ pack. 0.5 matches vanilla's ALPHA_CUTOUT for cutout terrain, so foliage gaps now match both Iris and vanilla. horizon-shaders' 298 tests and the ten terrain_parts tests pass on main merged with this head.
2026-11-05 07:12:00 +01:00
NerfPhaselockandClaude Opus 5.5 b1315c5a6e GPU culling (7/7): occlusion culling in its frames, on Vulkan
GPU frames ran no occlusion culling. On Vulkan, where cull.rs's test runs
with the depth test forced early, they now run the same two-phase test, each
section's part decided on the GPU: `cull` keeps per id the frames its box was
last seen and last tested at, puts in phase 1 the sections whose last test saw
them (retested every 8 frames) and lists the other boxes. The world pass draws
phase 1, then the listed boxes against its depth, each passing fragment
stamping its id (gpu_occlusion.wgsl); count2, scan2 and scatter2 write phase
2's draws, which a second pass draws before the CPU's own. Frames occlude
under cull.rs's gate (`cull::gate`), over the hidden share that comes back
with the statistics, now 12 words copied after the last pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:08:10 -05:00
Ludovic Marie 4729c5e995 Merge pull request #9702 from arslanarm/shaderpacks/custom-uniform-wrong-type
🤖 Agent: custom_index refuses only pairs where both sides are float/int/vecN and differ, so bool on either side keeps the old conversion, and so do types outside the list (ivecN, mat). A disabled uniform simply gets no slot, so it reads 0 and shows up in the program's "defaults or zero" notes. pack_post's tests, including custom_type_tests, and clippy -D warnings pass on main merged with this head.
2026-11-05 07:06:14 +01:00
Ludovic Marie 95350c2a1c Merge pull request #9701 from arslanarm/shaderpacks/custom-uniform-clamp
🤖 Agent: Applying the upper bound first and the lower one last only changes the result when min > max, so every in-range clamp a pack uses reads the same as before (the test's two in-range rows pin that). Fitting the order to your probe readings, not to Iris's code, keeps it clean-room. horizon-shaders' 222 tests and clippy -D warnings pass on main merged with this head.
2026-11-05 07:06:10 +01:00
Arslan ArmanulyandClaude Opus 5.5 b4eabf7ecf Shader packs: smooth() in custom uniforms keeps an infinite value
smooth() stepped as val + (state - val) * k, which turns an infinite value it
already holds into NaN (inf - inf). Iris 1.11.7 keeps it: our probe packs read
smooth(1.0 / 0.0, 5, 5) as +inf and smooth(-1.0 / 0.0, 5, 5) as -inf after 3 s
to 30 s in a scene (smooth(0.0 / 0.0, 5, 5) reads NaN in both games). A value
equal to the one held now stays as it is.

Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 11:01:06 +05:00
MegaBytesMe bf764d882a Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 06:00:38 +00:00
Ludovic Marie d8800e9e4d Merge pull request #9700 from horizon-rust/fix/command-exec-fixture-leaks
🤖 Agent: a group that leaves an objective, forced chunk or gamerule behind now fails tests/test_command_fixture_state.py instead of parity-main's first Java guard.
2026-11-05 07:00:21 +01:00
Horizon agent 6e2198ef80 Merge remote-tracking branch 'origin/main' into fix/command-exec-fixture-leaks 2026-11-05 06:57:05 +01:00
Ludovic Marie bedc04f38e Merge pull request #9693 from yahya-nusseibeh/fix/launcher-import-cap
🤖 Agent: The cap counts bytes as they inflate, not the sizes the zip claims, and it lives in fill(), which import_into and import_mrpack_into both go through, so neither path can skip it. 8 GiB is well above any real .mrpack or shared instance. The launcher's fmt, clippy -D warnings --locked and all its tests (548 + 102, both bomb tests included) pass on main merged with this head.
2026-11-05 06:54:33 +01:00
andrescamilomartinez b7802f4df1 perf(launcher): optimize glass blur on scroll and polish UI layout
- Pre-bake full-window blurred background texture on resize to eliminate per-frame CPU bilinear resampling and allocation during list scrolling
- Adjust version card radius from PANEL to ROW (12px) with refined vertical padding and margins to resolve overlapping border stacking
- Add radius::CONTROL and overflow clipping to mods sort dropdown containers
2026-11-05 00:53:47 -05:00
Horizon agentandClaude Opus 5.5 3ae8128591 rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:51:45 +01:00
Ludovic Marie 3c8a2352a7 Merge pull request #9691 from yahya-nusseibeh/fix/relay-proxy-from
🤖 Agent: Only relays started with RELAY_PROXY_PROTOCOL=1 change, and neither the Dockerfile nor horizon-relay.service sets it, so a default deploy behaves exactly as before. One that has it on now refuses to start until RELAY_PROXY_FROM is set, rather than dropping every connection. fmt, clippy -D warnings and the relay's tests, including proxy_from.rs, pass on main merged with this head.
2026-11-05 06:50:47 +01:00
Ludovic Marie b8f38ec702 Merge pull request #9690 from yahya-nusseibeh/fix/telemetry-hardening
🤖 Agent: Deploying this changes nothing for Railway's address counting: the Dockerfile names x-real-ip, the header the server already read first. The website fetches /v1/aggregate?days=30 and /v1/benchmarks/summary from the visitor's browser (benchmarks-page.js), so the new 600/h read bucket counts each visitor, not one site address. pr_ready skips this workspace, so I ran it on its own: fmt, clippy -D warnings --locked and every test pass on main merged with this head.
2026-11-05 06:50:43 +01:00
Ludovic Marie dd5c4a644b Merge pull request #9687 from TheMrZZ/fix/h314-allay-liked-player-damage
🤖 Agent: This follows Allay.hurtServer, which refuses only a player whose getUUID equals the LIKED_PLAYER memory. owner_damage_uuid passes a UUID only when the acting player is the server owner, so single player on its server thread gets the exact comparison, while LAN guests and dedicated-server players keep today's refuse-any-player filter: narrower than vanilla, but not a regression. The Game-level test runs against prepared resources and passes, and parity checks the corpus.
2026-11-05 06:48:41 +01:00
Horizon agentandClaude Opus 5.5 3919125b96 command_exec: crafter map extension takes its room back
It left its stone floor at y 199, a crafter, a chest and the player in
survival: the next group's `/setblock 10 199 10 stone` guard found stone
already there and stopped the run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:48:21 +01:00
GabrielNichols 055c47c98d Merge official main into Recipes content scenario 2026-11-05 02:45:32 -03:00
yahya-nusseibehandClaude Opus 5.5 449481dd49 Relay: take PROXY protocol lines only from listed load balancers
With RELAY_PROXY_PROTOCOL=1 a PROXY line was accepted from any TCP peer,
so a client reaching the port directly could choose the address its
per-address limits count.

- RELAY_PROXY_FROM: addresses or CIDR ranges (IPv4, IPv6, IPv4-mapped
  peers handled). A connection from anywhere else is closed unanswered.
- The relay refuses to start (a clear error, exit 1) when the protocol
  is on and the list is empty, or when an entry doesn't parse, instead of
  silently dropping every connection. Config::from_env returns a Result;
  Config::check says why.
- docs/play-with-friends.md lists the setting.

Split from #9129.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 07:45:15 +02:00
yahya-nusseibehandClaude Opus 5.5 9de4d4c169 Telemetry server: refuse to start behind a proxy without the client-IP header
With HORIZON_TELEMETRY_TRUST_PROXY=1 and no
HORIZON_TELEMETRY_CLIENT_IP_HEADER, every request would count as the
proxy's address: one shared bucket for /v1/batch, deletions, the live
count and reads, so intake would stop and players' deletion requests
would be refused, with only a line on stderr to say so (martipops's
review of #9129).

- The Dockerfile sets HORIZON_TELEMETRY_CLIENT_IP_HEADER=x-real-ip, the
  header the server read first before the setting existed, so the
  address the limits count on Railway stays the same.
- TRUST_PROXY=1 without the header now refuses to start (exit 1), as the
  relay does without RELAY_PROXY_FROM: a deployment missing it fails its
  /healthz check instead of going live with one bucket.
- README: both settings, and the refusal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 07:43:48 +02:00
Horizon agentandClaude Opus 5.5 18d5d5f53b Fishing hook spawns at the eye; its entity harness no longer rolls the hit
FishingHook(Player, ...) snaps the hook's position (its feet) to the owner's
eye; Horizon put it 0.125 lower. The harness cast at a stand 3 blocks away,
which vanilla's randomised throw reaches only on ~80% of casts: Horizon's own
run missed with the same odds and was never retried. At 2.5 blocks every
throw hooks the stand and the boat on its second tick, so the harness leaves
the known-flaky list.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:43:26 +01:00
Horizon agentandClaude Opus 5.5 5aeedfa347 command_exec: crafter map extension releases its chunk; name isolated groups
crafter map extension forced chunk (0,0) and left it, which moved the next
`/forceload add` guard that covers it. The groups the full run leaves out
(reinforcements, recipe overlays) are now named once in the corpus, so the
harness and the fixture-state test skip the same ones.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:41:48 +01:00
HiuoyandClaude Opus 5.5 178bf207c6 Native mods: register_goals, a new mob's goals (Mob.registerGoals)
A trait mod's register_goals hook is asked for every new mob, after its
species added its goals and before its saved data is read. It may clear
the goal and target selectors and add Goal types of its own, which steer
the mob with its navigation (nav_move_to, create_path_to) and controls.
A mod's living type from #9667 thus gets its own AI. The hook runs before
the float check, so a mod's Float goal floats the navigation as
FloatGoal's constructor does.

The hook is a free function in the client library, so it goes through
NativeClientHooks as living_breathe does. mod_api re-exports Mob,
MobWorld, Goal, GoalSelector and Cx; the generator's dispatch tests get
a Mob stub and the catalog test knows mobs/ai.rs.

Part of #8622.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 09:41:21 +04:00
Ludovic Marie 43fdfb9de4 Merge pull request #9679 from yahya-nusseibeh/fix/reviewer-ids
CI commands: authorize trusted reviewers by numeric GitHub ID
2026-11-05 06:40:53 +01:00
Ludovic Marie 9756f9dcf7 Merge pull request #9686 from DUzzL/fix/9680-tick-freeze-visuals
🤖 Agent: This matches Minecraft.runTick and tick: the texture clock advances only inside isLevelRunningNormally, while GameRenderer.tick (the lightmap flicker) runs on every unpaused tick, and Horizon only calls tick() when !self.pause, so a paused game still freezes both. The flicker draws from FogState's own random, so moving the call doesn't shift any other draw.
2026-11-05 06:40:30 +01:00
Horizon agent c93c226ed6 Merge remote-tracking branch 'origin/main' into fix/command-exec-fixture-leaks 2026-11-05 06:38:57 +01:00
Horizon agentandClaude Opus 5.5 ff1d51d66a command_exec: mob suffocation group restores mob_griefing
mob partial block suffocation set mob_griefing false and random_tick_speed 0
and left them, so every later group ran without mob griefing: no piglin in
piglin pickups picked anything up on either side, and the gold's fate became
a race. A unit test now checks every group leaves the world rules it found.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:33:39 +01:00
Arslan ArmanulyandClaude Opus 5.5 e0e48c363e Shader packs: cutout terrain cuts at alpha 0.5 and solid terrain not at all, as in Iris
A pack without gbuffers_terrain_solid/_cutout drew all terrain in one pass
with an alpha test of 0.1, and alphaTestRef read 0.1. Iris 1.11.7, with probe
packs of ours writing one alpha per screen strip, drew solid terrain with no
alpha test (alpha 0.0 kept) and alphaTestRef 0, and cutout terrain keeping
alpha above 0.5 only (0.5 and 0.49 discarded, 0.51 kept) with alphaTestRef
0.5, through gbuffers_terrain alone and through the split programs alike.
Vanilla 26.3 cuts cutout terrain at 0.5 too (RenderPipelines.CUTOUT_TERRAIN).

Every pack now draws solid and cutout terrain apart, as packs with the split
programs already did: without them, both parts draw with gbuffers_terrain's
chain. The cutout part's default test is alpha > 0.5.

Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:33:24 +05:00
GabrielNichols 31756ebfe2 Index owned content recipes for book placement 2026-11-05 02:30:21 -03:00
TheMrZZandCodex gpt-6.1-sol aa99b2a2b0 Merge current main after owner-damage validation
Retain upstream item, recipe and rendering changes alongside the scoped
Allay identity bridge. The owner damage and Allay source paths are unchanged.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 06:27:35 +01:00
Horizon agentandClaude Opus 5.5 d6644b03ad Scores leave the board with any destroyed entity, not only mobs
ServerLevel.EntityCallbacks.onDestroyed calls Scoreboard.entityRemoved for
every entity kind. Only dying mobs did here, so an item, an area effect
cloud or a projectile that had a score kept it after it was gone, and
/scoreboard players list kept listing its UUID (command_exec's scores and
social groups after carpet farms and loaded potion clouds). Once a tick,
the UUIDs that hold a score or a team are checked against the live
entities; unloaded and travelling ones leave persist.uuids first, so they
keep theirs, as in vanilla. Nothing runs while no entity holds one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:26:29 +01:00
Horizon agentandClaude Opus 5.5 5507e73158 command_exec: keep three checks off state the two sides load differently
- entities, piglin pickups: their falling-block and item selectors spanned
  the whole world. The Rust side block-ticks a ring of terrain (--radius 4)
  that the oracle never loads, and at chunk (-5, -2) generated kelp breaks
  and gravel falls into a flooded cave there, as vanilla's own does once
  that chunk ticks (checked with both forced loaded). Those drops are no
  fixture's: the checks now look near the fixture.
- execute: `execute in the_end if block 0 64 0` expected an unloaded chunk.
  The oracle's settle keeps its tickets for the rest of the run, so since
  gateway passengers settles in the End, 0 64 0 stays loaded on the Java side
  only. The check keeps its point (another dimension, an unloaded position)
  at 1000 64 1000.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:26:29 +01:00
Horizon agent 88d9a1e58b Merge main into ci/trusted-reviewer-ids 2026-11-05 06:23:06 +01:00
Ludovic Marie 3c945f9d30 Merge pull request #9682 from glitchsnake/fix/9604-xp-orb-lava-burn-sound
🤖 Agent: As in Entity.lavaHurt, the orb draws its pitch from its own random only after a hurt that lands (the early health <= 0 return covers isInvulnerableToBase's isRemoved), so the random order matches vanilla. The sound goes through the same audio.play path as the boats' lava_hurt_sound. Orbs don't track Silent yet, so a /summon'd Silent:1b orb still sizzles; that's a small gap left for later.
2026-11-05 06:21:33 +01:00
Ludovic Marie b7235f00c3 Merge pull request #9681 from masa-masa-0314/fix/debug-options-interrupt
🤖 Agent: This follows KeyboardHandler.handleDebugKeys: an open DebugOptionsScreen closes, otherwise canInterruptScreen (shouldCloseOnEsc by default) decides, and the interrupted screen runs its own close first, so a container gives its items back before the debug screen opens. The credits keep their screen, a deliberate gap from vanilla because their close can fail; the doc comment says so.
2026-11-05 06:21:29 +01:00
Ludovic Marie 3b47b51f81 Merge pull request #9678 from NerfPhaselock/fix/gpu-culling-device-loss
🤖 Agent: The flag is set only when the last frame before the loss was GPU-culled (old.gpu_frame()), so a device lost in an ordinary CPU-culled frame still gets GPU culling offered after recovery, and the saved setting is never written. It's a Relaxed AtomicBool read once per renderer build, so it costs nothing per frame.
2026-11-05 06:21:25 +01:00
Ludovic Marie a90988dc29 Merge pull request #9677 from Hiuoy/fix/9671-world-recipe-paths
🤖 Agent: In 26.3 recipes are a registry, and RecipeManager.finalizeRecipeLoading builds the stonecutter list and unpackRecipeInfo numbers the displays by walking recipes.values(), so sorting by registration_key in every table (and in the LAN copy the guest's stonecutter picks from) gives the order vanilla shows. The three reads moved to the world's table were the only places still showing the base table's result while handing over the pack's.
2026-11-05 06:21:21 +01:00
Ludovic Marie 4e166dc57e Merge pull request #9675 from arslanarm/shaderpacks/gl-fog-density
🤖 Agent: gl_Fog.density now reads the fogDensity value Horizon already sends, which is 0 everywhere, so a pack's exp-fog maths sees what Iris gives it in the scenes your probe measured. Nothing else in the struct changes, and packs without shaders/ never read it.
2026-11-05 06:21:17 +01:00
MegaBytesMe 53eb50dd02 Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64 2026-11-05 05:15:53 +00:00
Arslan ArmanulyandClaude Opus 5.5 4c4558d84d Shader packs: a custom uniform declared with another GLSL type reads 0
A custom uniform of shaders.properties was converted to whatever type the
program's GLSL declared it with. Iris 1.11.7 disables it instead ("Wrong
uniform type for X: Iris is providing FLOAT but the program expects INT.
Disabling that uniform.") and our probe packs read 0: a float declared int,
an int declared float, a vec3 declared vec2 and a vec2 declared vec3. A float,
int, vecN custom uniform now binds only to a GLSL uniform of its own type
among those; pairs involving bool keep the conversion (a float of 2.5
declared bool read true in both games).

Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:15:34 +05:00
martipopsandClaude Opus 5.5 4828bea53c Controllers: tabs, lists, stick and crosshair reach everything in a menu
The bumpers switch every tabbed screen, and turn the recipe book's pages; its triggers change category.
The right stick only scrolls: the focus stays on its row, no value changes, and nothing scrolls under the
on-screen keyboard. The D-pad walks a list's hidden rows, stops at a list's edge without changing a value, and
a held direction stops at the edge of the slots it's on. The crosshair stays on its target as the screen changes,
A held with the D-pad spreads a stack, and a list's scrollbar says how far its rows moved.

The arrow keys share the focus step, so they walk hidden rows too, and the superflat and single-biome rows
become focus targets for them. Focus on a preset selects it and leaves the share box to a press, as vanilla does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:14:05 -05:00
yahya-nusseibehandClaude Opus 5.5 65ce92e233 Nightly: sign each build for its own version (SHA256SUMS.v2.sig)
Every Nightly was signed for the constant "horizon release nightly", and
the version a launcher installed it as came from the unsigned GitHub
title, so anyone able to edit the release could serve an older signed
Nightly as tonight's.

- nightly-build.yml signs SHA256SUMS twice with the same key(s):
  SHA256SUMS.sig for the channel, as before, for installed launchers;
  SHA256SUMS.v2.sig for nightly-<date>-<commit>, the version the title
  names (sign_release.sh ALSO_FOR). Both are verified before publishing.
- horizon-release-sign: nightly_title_version() parses the title
  strictly (no fallback to published_at); nightly_digest() takes that
  version and checks SHA256SUMS.v2.sig for exactly it; is_nightly_build();
  SIGNATURE_V2. `verify --signature <file>` checks the v2 file.
- Every installer that checks a Nightly moves in the same step: the
  launcher, horizon-server update and packaging/server/install.sh refuse
  a Nightly whose title doesn't parse and install it only with a v2
  signature for that version. install.ps1 checks no signature (only
  GitHub's SHA-256), so it has nothing to move.
- An older Nightly still installs on request as a rollback with a
  warning; the launcher's update never moves the selection back to it,
  and horizon-server prints a rollback notice for --version.
- Nightlies from before this change have no v2 signature: the refusal
  now says so and names `horizon-server rollback`, and docs/security.md
  "Nightly signatures" describes the changeover (martipops's review of
  #9129). The launcher's new messages go through trf(), in every
  language table in English until translated.
- tests/install_sh_signature.sh: a v2 Nightly installs as its version; a
  Nightly with only the channel signature, an older Nightly retitled as
  a newer one, and an unreadable title are refused (all three installed
  before this change).

Split from #9129, as martipops's review suggested.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 07:12:20 +02:00
Ludovic Marie 57ddb7c5c7 Merge pull request #9676 from yahya-nusseibeh/fix/nightly-apply-args
Nightly: call apply() with the six arguments it takes
2026-11-05 06:11:01 +01:00
TheMrZZandCodex gpt-6.1-sol 6ff0152b94 Merge main and retain synchronous command reinforcements
Keep the reinforcement request collection and post-hit drain around the
verified-owner damage adapter, and preserve all upstream corpus groups.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 06:09:51 +01:00
TheMrZZandCodex gpt-6.1-sol f8b6c21390 Compare liked-player UUIDs in owner Allay damage routes
Pass verified owner identity through the existing command and normal attack
adapters while preserving the legacy filter for unresolved damage callers.
Cover live command feedback/state and both adapter and compatibility APIs.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 06:09:06 +01:00
DUzzL a68e475a4c Hold animated textures under /tick freeze, keep light flicker running (#9680) 2026-11-05 06:08:45 +01:00
Arslan ArmanulyandClaude Opus 5.5 b67bb9d9cd Shader packs: clamp() in custom uniforms takes the lower bound when the bounds cross
A custom uniform's clamp(x, min, max) applied min first and max last, so with
min > max it gave max. Iris 1.11.7 gives min: our probe packs read
clamp(5.0, 3.0, 1.0), clamp(0.0, 3.0, 1.0) and clamp(2.0, 3.0, 1.0) as 3, and
clamp(vec2(5.0, 0.5), vec2(3.0, 0.0), vec2(1.0, 1.0)) as (3, 0.5), in three
scenes. Apply max first and min last.

Part of #9307

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 10:08:38 +05:00
Ludovic Marie ea14b8b65b Merge pull request #9646 from TheMrZZ/fix/h312-noai-allay-healing
🤖 Agent: This matches Allay.aiStep: the heal sits after super.aiStep with no NoAI gate (only isClientSide and tickCount % 10). The unit test pins both paths: NoAI heals and keeps its hold timers, and normal AI still heals only once.
2026-11-05 06:08:11 +01:00
Horizon agentandClaude Opus 5.5 806a600656 Command harness: settle refreshes the chunk tickets
Since #9482, /execute if loaded reads the ticket levels, which only a tick
recomputed. The harness's settle loads terrain without ticking, so a group
that moved the player and forced chunks (large fireball fire) still saw the
previous group's tickets: chunk column x=5 was resident and lit but not
entity-ticking. The oracle's settle takes its tickets at once (getChunk runs
the DistanceManager updates), so the probe's settle now refreshes them too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:07:46 +01:00
Horizon agentandClaude Opus 5.5 7c397e8587 /item … from block: a non-container block entity is no source
ItemCommands.getContainer rejects any block entity that is not a Container
("Source position … is not a container"). The source check only asked
whether a block entity existed, so a sign, lectern, campfire or beacon got
"The source does not have slot …" instead. Use the same container test as
the target side.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:07:46 +01:00
Ludovic Marie 33686f33ce Merge pull request #9530 from abit-foggy/codex/studio-a2a3-browser
🤖 Agent: Recording is opt-in (/studio or the control socket), filters before writing and fails closed on unknown packets. With no recording running, the network thread now pays one uncontended lock per packet instead of a copy of every payload.
2026-11-05 06:05:42 +01:00
yahya-nusseibehandClaude Opus 5.5 f2ec6394cc Launcher: cap what a shared instance or a .mrpack may unpack to
import.rs inflated every entry of a dropped .mrpack or game-folder zip
with no byte limit, so a few kilobytes of zip bomb could fill the disk
before the half-made instance was removed.

- fill() counts the bytes actually inflated across all entries (a zip's
  sizes are its own claim) and stops past MAX_IMPORT (8 GiB); the error
  removes the instance as before.
- The cap sits in fill(), which import_any() calls for both
  import_into() and main's newer import_mrpack_into() (a Java instance
  takes every path a pack carries), as martipops's review of #9129
  asked when rebasing.
- The message goes through trf(), in every language table in English
  until translated.
- Tests with a 16 KiB limit: a compressed bomb is refused and leaves
  nothing, for a shared instance and for a .mrpack; the limit is for all
  files together.

Split from #9129.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 07:05:39 +02:00
Horizon agentandClaude Opus 5.5 332c3170e8 Merge main into fix/h312-noai-allay-healing
groups(): keep main's crafter map extension group and the allay one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 06:05:05 +01:00
yahya-nusseibehandClaude Opus 5.5 337cb194af Mod registry: owners and owner_ids must name the same accounts
Once an entry has owner_ids, only an ID in them makes an owner, so adding
a co-owner to `owners` alone, the obvious edit, would silently lock them
out, and the refusal would list their own login among the owners
(martipops's review of #9129).

- Entry::check_fields refuses an entry whose owner_ids is non-empty and
  not as long as owners, with how to find the missing ID, so the registry
  CI catches it when the change is made.
- docs/api/owner-update-v2.md: the registry repository has no
  owner-update workflow yet, so nothing merges on its own today; and the
  backfill script runs only once a maintainer has read it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 07:00:11 +02:00
yahya-nusseibeh 7aceb1cbf9 Mod registry: decide project ownership by numeric GitHub ID
Owners were GitHub logins, and a login can be claimed by someone else once
its owner renames or deletes the account. For a project without a key, a
reclaimed login could publish an owner update that merges on its own.

- Entry gains `owner_ids` (#[serde(default)], omitted when empty). The
  form records the lister's ID for new projects (file and repository
  listings). The registry review notes any change to it.
- ownership(): when an entry has owner_ids, only an ID in them is an
  owner, so a matching login on another account is refused. Without
  owner_ids (older entries) the login matches, but the update always waits
  for a reviewer and the pull request says so.
- The automatic owner update requires an ID match.
- update_trailer is v2: `owner-update-v2` message with the submitter's
  numeric ID, and `format: v2` / `submitter_id:` lines in the trailer.
- tools/registry_backfill_owner_ids.py: one-off, dry run by default,
  resolves owners' logins to IDs, cross-checks them against the IDs the
  form recorded in the entry's commits, and leaves alone (and prints)
  any entry it can't resolve completely.
- docs/api/owner-update-v2.md: what the registry repository's
  owner-update workflow must check, and the deployment order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit 526bfcbbe1)
2026-11-05 06:59:35 +02:00
GabrielNichols 0e6e28109f Keep the pilot fixture focused on an original content recipe 2026-11-05 01:56:50 -03:00
Ludovic Marie 1fe029d88c Merge pull request #8941 from elythepreciouscat/perf/entity-occlusion-culling-pipeline
🤖 Agent: Entity occlusion culling stays Off by default. prepare_occlusion_culling only builds a culler when the mode isn't Off, so the default frame skips both the voxel paths and the new section-graph rejection. A budget that runs out still fails open (visible). One push from me: main's new ItemFrame.custom_name field added to the PR's bounds test, which was what broke verify. Desktop: clippy -D warnings is clean (client, client-lib, client-render) and 2835 client tests pass. Benchmarks: the author's stationary and moving runs above.
2026-11-05 05:54:51 +01:00
rdbtCVS d9340502f3 fix(telemetry): box blocking task error responses
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
(cherry picked from commit 75b4ce1062)
2026-11-05 06:54:39 +02:00
yahya-nusseibeh 6c019a2349 Telemetry server: read the client's address from one configured header
With TRUST_PROXY=1 the rate limits were keyed on X-Real-IP, else the last
X-Forwarded-For entry, whatever the platform guarantees. Railway's edge
has let clients set X-Real-IP in the past, and puts the CDN's address
there when Fastly is in the path.

- HORIZON_TELEMETRY_CLIENT_IP_HEADER names the one header the proxy
  overwrites (x-real-ip on Railway); its last comma-separated entry is
  the client. No other header is read.
- TRUST_PROXY=1 without it counts the TCP peer, and the server prints a
  warning at start saying every client then shares one bucket; a header
  set without TRUST_PROXY is reported as ignored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit bd420535e0)
2026-11-05 06:54:39 +02:00
yahya-nusseibeh 3483117549 Telemetry server: keep SQLite off the async workers, bound the totals' cost
The server runs two async workers and did every SQLite call on them, and
GET /v1/aggregate had no rate limit and cached per `days` (366 keys),
while DELETE /v1/installs/<any id> emptied every cache: a cheap way to
stall every handler.

- blocking(): batch storage (zstd inflate included), install deletion,
  the totals, the benchmarks summary, the live history, the upkeep and
  the live count's minute sample run on tokio's blocking pool.
- /v1/aggregate and /v1/benchmarks/summary take a per-address read
  bucket (READS_PER_HOUR, 600); uncached totals builds have a global
  budget (AGGREGATE_BUILDS_PER_HOUR, 600) and answer 503 past it.
- AGGREGATE_DAYS (e.g. 7,30,90,366) restricts the windows answered; unset
  keeps every value from 1 to 366. It is left unset by default because
  the website's own requests (private horizon-site) couldn't be checked.
- A deletion invalidates the cached totals only when it deleted rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit a29c16a169)
2026-11-05 06:54:38 +02:00
Horizon agentandClaude Opus 5.5 7f509a7229 Entity bounds test: give the item frame its custom_name
Main added ItemFrame.custom_name (a named frame drops with its name); the test initializer predates it and stopped verify from compiling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:52:59 +01:00
Loki 3202ebb565 Merge remote-tracking branch 'origin/main' into fix/9604-xp-orb-lava-burn-sound 2026-11-05 13:52:08 +09:00
LokiandAntigravity d199e5ece5 entities: play generic burn sound when xp orb takes lava damage (fixes #9604)
Co-Authored-By: Antigravity <[email protected]>
2026-11-05 13:52:05 +09:00
Horizon agentandClaude Opus 5.5 a9b34d89b0 Studio: record only while a recording runs; translate the browser
The privacy filter ran on every packet before the recorder was looked at, so
with no recording it still copied each payload (chunk data too) and
re-encoded every AddEntity with a fresh random UUID. It now runs only while a
recording is active, and keeps the packets it allows without copying them.

The recordings browser's title, empty state, buttons and server labels go
through the language table (gui.done, menu.singleplayer and
menu.multiplayer are vanilla's). The row's "Edit" hint is removed: this
PR has no editor to open.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:48:50 +01:00
Ludovic Marie 5f064c53b4 Merge pull request #9670 from TheMrZZ/fix/noai-pufferfish-mob-contact
🤖 Agent: The NoAI branch now calls only sting_touching_mobs, after super.aiStep's work, as Pufferfish.aiStep does without a NoAI gate, and the unit test checks that ordinary AI still stings exactly once. The 119 vs 118 poison duration your diagnostic keeps is #9672.
2026-11-05 05:48:20 +01:00
Ludovic Marie a9d017d822 Merge pull request #9669 from DUzzL/fix/9660-spectate-f5
🤖 Agent: The name tags now use posed_rotation, which camera_orientation already trusts and which is already turned round for the front view. The only change is that a spectated entity's rotation is kept even when it equals the vanilla view, so the mouse no longer turns the tags.
2026-11-05 05:48:16 +01:00
Ludovic Marie e38e3af593 Merge pull request #9667 from Hiuoy/feat/8622-living-entity-templates
🤖 Agent: class_id() is the type's own id for every vanilla type, so mob_to_nbt and mob_meta change nothing without content mods. The loot-table fix (ruby:entities/ghoul) matches EntityType.getDefaultLootTable. The 25 slow content-root tests pass with prepared resources.
2026-11-05 05:48:13 +01:00
Ludovic Marie 0fcc1c5f72 Merge pull request #9663 from arslanarm/shaderpacks/shadow-map-terrain
🤖 Agent: The caster test can only add planes to the light's box, and a plane that doesn't fit the array is dropped, which keeps more, so the shadow pass never loses terrain the old box drew for a view it can shade. It applies only to Iris-format packs with shadow.culling unset. Fitting Iris's maps from screenshots, not its code, keeps this clean-room.
2026-11-05 05:48:08 +01:00
Ludovic Marie 6e714b46ff Merge pull request #9373 from TheMrZZ/fix/crafter-map-extension
🤖 Agent: The crafter now matches map-extension recipes against the level's live saved maps (MapExtendingRecipe.matches reads the map's scale). An unknown or max-scale map no longer extends, and the lookup goes through the world's recipe table. The author's refresh has the same Rust as my tested merge, where clippy -D warnings is clean and the crafter, menu and command_harness tests pass. Its corpus builds 117 groups with no duplicates.
2026-11-05 05:48:04 +01:00
Horizon agentandClaude Opus 5.5 5c4e9a381d Merge main into fix/h312-noai-allay-healing
groups() in command_exec_corpus.py: main added the zombie reinforcement
groups where this branch adds the allay one; keep all of them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:47:46 +01:00
Horizon agent 070422e205 Merge remote-tracking branch 'origin/main' into cg42b/8941 2026-11-05 05:46:30 +01:00
GabrielNichols e76cb1f87a Use edit-box input when replacing Recipes search 2026-11-05 01:45:55 -03:00
NerfPhaselockandClaude Opus 5.5 37fcc000cd Keep GPU culling off after a device loss in one of its frames
The game rebuilds the renderer after a device loss, and the new one offered
GPU culling again: a fault in its frames could lose the device after every
recovery, until a second loss within a minute ended the session. A loss while
the last frame was GPU-culled now makes every renderer after it report
GpuCullingOffer::DeviceLost, until Horizon restarts; the switch greys out and
says why.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 23:44:57 -05:00
rdbtCVS dd63f7ba94 ci: resolve existing reviewer and owner accounts to numeric IDs
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
(cherry picked from commit 52612f0c35)
2026-11-05 06:44:12 +02:00
yahya-nusseibehandClaude Opus 5.5 c0f53a324f CI commands: authorize trusted reviewers by numeric GitHub ID
/nightly, the triage commands and /claim decided who may act by the
comment author's login. GitHub hands a renamed or deleted account's login
to whoever claims it next, so a reclaimed reviewer login could pick
unreviewed pull requests into the signed Nightly.

- trusted-reviewers.txt lines become `<id> <login>`; one shared parser
  (nightly_set.read_reviewers) for all three commands. Any other line,
  bare logins included, grants nothing and is reported on stderr.
  `paused` keeps its meaning.
- All three workflows pass AUTHOR_ID (github.event.comment.user.id);
  without it the scripts refuse. Ludovic is OWNER_ID, and his threads are
  recognised by user.id in triage.
- tools/ci/resolve_reviewer_ids.py fills in the IDs (and OWNER_ID);
  --check reports renames and deleted accounts.

No review requirement is added: a Nightly still takes any open pull
request to main, as decided on 2026-10-30 (NoReviewNeededTests).

Split from #9129, as martipops's review suggested.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 06:44:04 +02:00
masa-masa-0314 556864a921 Merge remote-tracking branch 'upstream/main' into fix/debug-options-interrupt 2026-11-05 12:43:25 +08:00
masa-masa-0314andZCode ab43fcc32e Debug options: F3+F6 interrupts every screen its Esc would close (canInterruptScreen)
Vanilla 26.3 (KeyboardHandler.handleDebugKeys, the keyDebugDebugOptions branch)
closes an open DebugOptionsScreen, else when Minecraft.canInterruptScreen()
passes closes the current screen (Screen.onClose; canInterruptWithAnotherScreen
defaults to shouldCloseOnEsc) and opens the debug options over it. Horizon only
opened over the game and the bare pause menu (Fixes #9664).

- debug_options_interrupts: the canInterruptScreen classification, one arm per
  Screen; the death screen, the level teardown, the text-box screens, the
  credits (fallible close) and a playing overlay keep theirs.
- toggle_debug_options closes the interrupted screen the way its Escape does
  (a container hands its state back, creative clears pad focus and the carried
  stack, a mod's screen hears the close) before opening.
- The F3+F4 switcher passes F3+F6 on to the debug options screen.
- A unit test pins the classification.

Co-Authored-By: ZCode <[email protected]>
2026-11-05 12:43:24 +08:00
HiuoyandClaude Opus 5.5 d1a4f0a6b2 Recipes: world table for the grid, dyes and campfire; registration order for every recipe list
- The crafting grid's result, breeding's dye mixing and the campfire's
  cooked item read the world's table, as menu_click and the campfire's
  placement already did.
- Recipes::from_sources sorts every table (crafting, cooking, mods' own
  types) in registration order, the key #9406 sorted crafting by, now
  crafting::registration_key; StationRecipes sorts stonecutting and
  smithing by it.
- RecipeIndex numbers recipe-book displays in registration order.
- The LAN copy sends the stonecutter list and numbers groups in that
  order, so a guest's stonecutter button indexes the host's list.

Fixes #9671.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 08:43:16 +04:00
MegaBytesMe e8cc426080 Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 04:42:54 +00:00
yahya-nusseibehandClaude Opus 5.5 62572a0b18 Nightly: call apply() with the six arguments it takes
command() still passed read_reviewers(reviewers).logins to apply() after
c527d60b7 dropped that parameter, so every /nightly comment that got past
authorize(), status included, ended in TypeError and was never answered.

- Drop the leftover argument.
- Test: command() run as nightly-command.yml runs it, with GitHub and the
  state branch stubbed, answers /nightly status. It fails on main with the
  TypeError.

Split from #9129, as martipops's review suggested.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
2026-11-05 06:39:53 +02:00
Arslan ArmanulyandClaude Opus 5.5 cf56244594 Shader packs: gl_Fog.density reads 0, as in Iris
The compatibility gl_Fog gave packs a density of 1. Iris 1.11.7 gives 0
there, in gbuffers_terrain and in deferred, in a cave and in the Nether,
where fogDensity also reads 0 in both games (read back with a probe pack
of our own).

Fixes #9674

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 09:38:49 +05:00
ElyTheCat 76e92e12de Merge branch 'main' into perf/entity-occlusion-culling-pipeline 2026-11-05 08:05:40 +03:30
Ludovic Marie 185156c0eb Merge pull request #9662 from horizon-rust/agent/security-fixes-from-9129
🤖 Agent: Four standalone fixes from #9129, with authorship kept. The control token now comes from getrandom. Token files are written owner-only (write_private). Ban-list dates are checked instead of taken as they are. A relay bridge frees its slot when it ends. None of them is on a hot path. The server-files, relay, server and control tests pass on playhorizon.
2026-11-05 05:33:24 +01:00
Florian ERNSTandCodex 55b2752d15 Keep map and light observations after reinforcement merge
All-target Clippy, formatting, Python options and Java oracle compilation pass at main 61afe4fa8. Captured runtime proof remains unchanged.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:31:21 +01:00
Ludovic Marie 61afe4fa8e Merge pull request #9452 from TheMrZZ/fix/command-damage-zombie-reinforcements
🤖 Agent: A zombie hit by /damage now sends its reinforcements before the command returns, as vanilla's hurtServer does inside the command, so a following command sees the reinforcements. The isolated reinforcement groups run only with --only, so the generic corpus is unchanged. The author's refresh onto main keeps both fixture isolations. Clippy -D warnings is clean and the reinforcement tests pass.
2026-11-05 05:29:09 +01:00
Florian ERNSTandCodex 470503d7a9 Preserve world recipe lookup and map context on crafter refresh
Keep H295 active world recipe selection with H298 saved-map matching and system post-process. Preserve all shared corpus groups, datapack fixtures, map copy controls and player observations. Formatting, all-target Clippy, Python and Java compile checks pass; original runtime qualifications remain unchanged.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:27:59 +01:00
Horizon agentandClaude Opus 5.5 9bc2a60c10 Remove leaked command_exec objectives and forced chunk
Java's fixture guards read counts: `/scoreboard objectives add` returns how
many objectives exist after the add, and `/forceload add` how many chunks it
newly forces. Four groups left state behind for the next ones:

- mob baby attribute reads kept h227 and allay invalid Brain retention kept
  h226, so rewrite_hit, hitstate and audit got 3 instead of 1;
- both advancement loot groups kept chunk (0, 0) forced, so item
  stack-size override merges forced 7 chunks instead of 8.

parity-main stopped on "Java fixture control failed" for each of them once
#9514 fixed the /fill floor guard (#9587). Each group now removes what it
added. A unit test replays every group's objectives and forced chunks and
checks each count guard against what earlier groups leave.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:27:39 +01:00
Florian ERNSTandCodex 5801a60185 Keep both reinforcement and recipe fixture isolation on main refresh
Formatting, all-target Clippy, Python options and Java oracle compilation pass at main 898d3e165. Preserve captured runtime qualifications.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:25:55 +01:00
Florian ERNSTandCodex f60a109738 Refresh reinforcement command fix against main
Keep reinforcement and advancement-loot corpus groups, all diagnostic options, and light/player observations. The reinforcement fixture function is byte-identical. Formatting, all-target Clippy, Python compilation/options/registration and Java oracle compilation pass. Original runtime proof remains qualified to its captured base.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:24:59 +01:00
Ludovic Marie 898d3e1654 Merge pull request #9406 from TheMrZZ/fix/crafting-recipe-selection-order
🤖 Agent: The recipe order now follows ResourceManagerRegistryLoadTask's sort by file id (path, then namespace). It runs once per load, and find_with is unchanged, so lookups cost nothing more. The prepared 26.3 recipe folder has no '-' or '.' names, so vanilla's own recipes keep their order. Main merged in, both fixture sets kept.
2026-11-05 05:24:49 +01:00
Florian ERNSTandCodex 1ab3333cef Refresh H313 with server API test coverage
The final main checkpoint changes server API tests only; the checked pufferfish source and fixture are unchanged.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:21:20 +01:00
Ludovic Marie 998b4710a4 Merge pull request #9655 from Vaspyyy/perf/collision-scan-memo
🤖 Agent: The one memo keeps vanilla's chunk requests (a missing chunk is asked again unless the world promises it stays missing), and may_reach_out, now asked once per non-empty section, is a pure query. An A/B on the desktop (dedicated server, 1500 pigs on the ground, 3 interleaved rounds, main f5565f1 against main plus this PR) gave a 5.30 ms median per tick on main and 5.33 ms with this PR: below the run-to-run noise (±0.3 ms), so your instruction counts are the finer measure. The far-move cost (+16%) is a fair trade for every mob's ground move.
2026-11-05 05:20:42 +01:00
Ludovic Marie 5c06efc139 Merge pull request #9398 from NikitaMGrimm/deterministic-capture
🤖 Agent: With HORIZON_STILL off, every new branch reduces to the old expression and the variable is read once through a OnceLock, so there's no per-frame cost. The main merge keeps the border ticking only while the level runs, as main has it. Checks: clippy -D warnings, shader tests, clock tests.
2026-11-05 05:20:37 +01:00
Ludovic Marie 30482e153f Merge pull request #9338 from NerfPhaselock/perf/gpu-culling-g4
🤖 Agent: Off by default and in no preset; with the setting off there is no readback buffer and no poll. The device-loss follow-up from the review (turn GPU culling off for the session after a loss in a GPU frame) is worth its own PR. Union build passed.
2026-11-05 05:20:34 +01:00
Horizon agentandClaude Opus 5.5 370f0314be Merge main into fix/h312-noai-allay-healing
Both sides add a group to command_exec_corpus.groups(): keep both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:19:15 +01:00
Florian ERNSTandCodex 0fcaef94e3 Refresh H313 against current mob and UI changes
Upstream changes add frog travel, player equipment visibility and UI/server work; the NoAI pufferfish caller and sting handler are unchanged. Preserve all fixture options and qualify the captured runtime pairs to d158e4473.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:19:07 +01:00
Horizon agent 8380643533 Merge commit 'refs/cg42/a9398' into cg42b/9398 2026-11-05 05:17:57 +01:00
Horizon agent e384f63540 Merge remote-tracking branch 'origin/main' into cg42b/9406
# Conflicts:
#	tools/command_exec_corpus.py
#	tools/test_command_exec_parity.py
2026-11-05 05:13:08 +01:00
Ludovic Marie 28d25be3f0 Merge pull request #9159 from TiggerTakkyTTT/test/server-api-auth-command-goldens
🤖 Agent: main merged in. The new auth/command goldens test now sits beside main's observability and config golden tests in api.rs, and both sides are kept whole. It drives the real HTTP listener, so missing/wrong token, wrong Host and malformed commands are checked over the wire. horizon-server: 150 tests pass, clippy -D warnings is clean.
2026-11-05 05:12:28 +01:00
GabrielNichols e770447686 Keep Recipes search editable after an empty result 2026-11-05 01:11:59 -03:00
Florian ERNSTandCodex 26ec6c900b Refresh H313 from main and verify overlapping cod contact
Preserve the unchanged canonical 218 and strict 220 scripts. Add an independently validated same-position pig/cod control rather than infer contact from the original cod placement. Keep landed H311 player contact and all current fixture options.

Co-Authored-By: Codex <[email protected]>
2026-11-05 05:11:44 +01:00
Horizon agent 7b0a85e775 Merge remote-tracking branch 'origin/main' into cg42b/9398
# Conflicts:
#	crates/horizon-client/src/game/remote.rs
2026-11-05 05:11:17 +01:00
Ludovic Marie 614ebb30cd Merge pull request #9661 from horizon-rust/agent/finished-8918-world-canvas
🤖 Agent: Carries #8918 unchanged, with main merged in. Only the mod_api export list conflicted, and both sides are kept. The depth-tested world text and atlas quads go through the existing native world-draw hook, so a game without mods costs nothing extra. It passed fmt, clippy and tests in union3.
2026-11-05 05:10:20 +01:00
Ludovic Marie dc5173c2bd Merge pull request #9384 from DUzzL/fix/9368-riptide-dry-use
🤖 Agent: Matches TridentItem.use: FAIL (about to break, or Riptide while dry) doesn't start using and lets the off hand try, as Minecraft.startUseItem does. game.rs's single-player use chain now also falls through on FAIL. Union build passed.
2026-11-05 05:10:16 +01:00
Ludovic Marie 5280f96229 Merge pull request #9365 from Joansitoh/t3/pig-strider-boost-sync
🤖 Agent: The boost now starts from the synced DATA_BOOST_TIME (index 18, after AgeableMob's two), as ItemBasedSteering.onSynced does, instead of the rider's client guessing it. Union build passed.
2026-11-05 05:10:12 +01:00
Ludovic Marie 3b49e970f0 Merge pull request #9362 from Joansitoh/t3/armor-stand-minecart-push
🤖 Agent: Matches ArmorStand.pushEntities: only RIDABLE_MINECARTS within distanceToSqr <= 0.2 are pushed, and the stand riding the cart is skipped as Entity.push skips a shared vehicle. Union build passed.
2026-11-05 05:10:09 +01:00
Ludovic Marie 51b70481c0 Merge pull request #9360 from Joansitoh/t3/item-frame-custom-name-drop
🤖 Agent: Matches ItemFrame.getFrameItemStackWithData: the dropped frame (glow frames too) takes CUSTOM_NAME from getCustomName, and the name now round-trips through the frame's NBT. Union build passed.
2026-11-05 05:10:04 +01:00
Ludovic Marie ab41e568b1 Merge pull request #9359 from Joansitoh/t3/frog-travel-in-water
🤖 Agent: Matches Frog.travelInWater: moveRelative(getSpeed()), move, then scale 0.9, with no gravity or water drag. Minor: the test's `let _ = ran;` passes silently without vanilla data. Union build passed.
2026-11-05 05:10:00 +01:00
Ludovic Marie 3f61f093a5 Merge pull request #9351 from Joansitoh/t3/fix-mob-visibility-head-range
🤖 Agent: Matches LivingEntity.getVisibilityPercent: each worn stack's mob_visibility applies only when its equippable slot matches and the looker's type is listed, then Mth.clamp(0, 10). The chest/legs/feet fields cost three Arc clones per PlayerView. Union build passed.
2026-11-05 05:09:56 +01:00
Ludovic Marie 153d4f654d Merge pull request #9344 from MSedgeMC/test/explosion-victims
🤖 Agent: The non-player victims carry NoGravity so the blast's push is the only motion compared, and Pos/Motion are compared at 9 decimals as server_knockback does. Union build passed.
2026-11-05 05:09:52 +01:00
Ludovic Marie 3325da4253 Merge pull request #9340 from Joansitoh/fix/9336-honey-landing
🤖 Agent: HoneyBlock.fallOn plays the slide sound (the guest's own client plays it for the guest) and broadcasts event 54 to everyone, the lander included. Union build passed.
2026-11-05 05:09:48 +01:00
Ludovic Marie 508f933a63 Merge pull request #9331 from BPMichon/feat/health-hud-state-helpers
🤖 Agent: native_health_hud_state hands out &mut HealthHud, so a mod calling layout twice a frame double-advances the heart blink; the docs say once a frame, which is the right contract. Stamp test and union build pass.
2026-11-05 05:09:44 +01:00
Ludovic Marie 7cfa9f9f7c Merge pull request #9319 from lightcodegit/fix/peaceful-despawn
🤖 Agent: Matches Mob.checkDespawn: peaceful discards a disallowed type on its own despawn check, even when persistent; Horizon's check_despawn already does that first, so the three instant purges were the only difference. Union build passed.
2026-11-05 05:09:40 +01:00
DUzzL 7658eb5a2d Billboard name tags and camera-facing effects to the spectated camera entity 2026-11-05 05:09:23 +01:00
Horizon agent adaf038bd3 Merge remote-tracking branch 'origin/main' into cg42b/9159
# Conflicts:
#	crates/horizon-server/src/api.rs
2026-11-05 05:07:26 +01:00
Ludovic Marie 6c5eece90d Merge pull request #8700 from Akirabane/feat/7484-control-read
🤖 Agent: main merged in after #9062. The config golden now lists the new [transfer] section with its defaults (enabled false, expect 10 s, max 60 s, 64 players, freeze 30 s). horizon-server tests: 149 pass.
2026-11-05 05:04:28 +01:00
Ludovic Marie 3a919c7d32 Merge pull request #9381 from gigamans442/feat/weather-depth-contract-20261104
🤖 Agent: An explicit rain.depth=false now skips the weather depth write; omitted and malformed values keep main's behaviour. Spectrum and Luculent are MIT and disclosed. Union build passed.
2026-11-05 05:04:24 +01:00
Ludovic Marie 524aa03ee9 Merge pull request #9176 from arslanarm/shaderpacks/terrain-solid-cutout
🤖 Agent: Each part clips the other's quads in the vertex stage, so packs with gbuffers_terrain_solid/_cutout pay double terrain vertex work (noted in the earlier review); packs without them take the old single draw. Union build with 44 PRs passed, including horizon-shaders and render tests.
2026-11-05 05:04:20 +01:00
Ludovic Marie a6937b0eae Merge pull request #9652 from voidsipp/fix/9382-sound-entity-lookups
🤖 Agent: Mob sounds now return the mob-index state before level_entity runs, so the dragon's metadata Mob can't shadow its Dragon, and silence and removal still come from the Mob. The once-per-tick cache is keyed by id inside one call, and a removed sound never revives when its id is reused.
2026-11-05 05:04:16 +01:00
Ludovic Marie a09f16910f Merge pull request #9659 from horizon-rust/fix/9239-review-fixes
🤖 Agent: queued; a chunk unload now drops only that chunk's sleepers, so idle furnaces elsewhere stay asleep instead of all ticking again.
2026-11-05 05:04:11 +01:00
Ludovic Marie 049c9d25db Merge pull request #9658 from horizon-rust/fix/9271-review-fixes
🤖 Agent: queued; a dolphin now drowns if kept under water past 4800 ticks, as in vanilla (it is not in #can_breathe_under_water).
2026-11-05 05:02:12 +01:00
Ludovic Marie 8c45bbac89 Merge pull request #9656 from horizon-rust/fix/9222-9229-review-fixes
🤖 Agent: queued; the drop_tool key is the machine's own position, so a neighbour broken in the same call no longer rolls the machine's tool or its silk touch.
2026-11-05 05:02:09 +01:00
Horizon agentandClaude Opus 5.5 8ea4669674 Config golden: add the [transfer] section
#9062 pinned the config reply before this PR added [transfer]; the GET reply now lists it with its defaults.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:02:07 +01:00
Ludovic Marie ea93759d41 Merge pull request #9457 from voidsipp/fix/9382-sound-port-ledger
🤖 Agent: Ledger only: SoundInstance::tick and the bound-entity note match what #9382 merged. Nothing else found.
2026-11-05 05:02:04 +01:00
Ludovic Marie c015efcdc3 Merge pull request #9645 from HrafnaValkyrja/fix/8589-raid-sweat
🤖 Agent: entity_event_burst is now the only list, and no other branch of the EntityEvent handler draws for 38 or 42, so nothing draws twice. The cost is the existing mob search, now for every entity event instead of eight ids; entity events are rare enough that this doesn't show.
2026-11-05 05:01:59 +01:00
Ludovic Marie 42417e3d4b Merge pull request #9453 from GabrielNichols/codex/stale-harness-verdict
🤖 Agent: The old verdict is removed before carry mutates content, and an interrupted harness can no longer leave a stale green one. Union build passed.
2026-11-05 05:01:55 +01:00
Ludovic Marie 6c2c0f9c5b Merge pull request #9441 from NikitaMGrimm/windows-exact-sin
🤖 Agent: Measured on the desktop: exact::sin + cos takes 110 ns a pair against libm's 10 ns (11x). The 27 jmath sin/cos call sites are per mob or per feature, never per vertex, so that is microseconds a tick, on Windows only. Union build passed.
2026-11-05 05:01:50 +01:00
Ludovic Marie 3497ab4654 Merge pull request #9429 from itarqos5/perf/far-light-one-span-a-run
🤖 Agent: A previewed far-terrain column spends one span; the dampening is per run, measured −13 % far sampling memory. Union build passed.
2026-11-05 05:01:46 +01:00
Ludovic Marie a9b1bc2649 Merge pull request #9420 from phexuss/fix/skins-transient-failures
🤖 Agent: A failed cape no longer drops the skin, a failed download retries after 60 s instead of never, and only bodies that decode reach the disk cache. Union build passed.
2026-11-05 05:01:42 +01:00
Ludovic Marie d4fe63cbb7 Merge pull request #9415 from DUzzL/fix/9390-game-master-break
🤖 Agent: Matches ServerPlayerGameMode.destroyBlock: a GameMasterBlock stays without canUseGameMasterBlocks. Vanilla also sends a block update there; the guest's break acknowledgement covers that here. Union build passed.
2026-11-05 05:01:38 +01:00
Ludovic Marie 7d8176bc00 Merge pull request #9394 from oxi-does-dev/fix/control-chat-client-commands
🤖 Agent: The control socket's chat and command go through handle_chat_input and the client mod command path, the same as typed chat, so client mods see them. Union build passed.
2026-11-05 05:01:35 +01:00
Ludovic Marie 1d5ec41a8a Merge pull request #9393 from TheMrZZ/fix/h294-mob-nbt-hit-cooldown
🤖 Agent: Entity.load mutates the existing living entity and never reads its damage cooldown or last hurt amount, so a /data merge no longer resets them. The corpus case covers it against Java. Union build passed.
2026-11-05 05:01:30 +01:00
Ludovic Marie 67d37c698e Merge pull request #9388 from phexuss/fix/png-size-guard
🤖 Agent: The header's width × height is checked before decoding, so a crafted PNG can no longer ask for a huge allocation. Union build passed.
2026-11-05 05:01:26 +01:00
Ludovic Marie 62242ccd02 Merge pull request #9372 from arslanarm/shaderpacks/smooth-untaken-branch
🤖 Agent: if() now evaluates conditions up to the first true one plus that branch only, so a smooth() in an untaken branch keeps its state. Iris was only run with your own probe packs, which our rules allow. Union build passed.
2026-11-05 05:01:23 +01:00
Ludovic Marie 51a1af2436 Merge pull request #9366 from Joansitoh/t3/dragon-flying-hit-knockback
🤖 Agent: Matches EnderDragon.knockback: LivingEntity.knockback only when the phase isn't sitting, power 0.4F from dealDefaultKnockback, and the vertical stays (noPhysics never sets onGround). Arrow hits still don't push, as before. Union build passed.
2026-11-05 05:01:19 +01:00
HiuoyandClaude Opus 5.5 d579acfa15 Content mods: living entity types from a zombie or villager template
A content mod's entity type could only copy minecraft:snowball, so a mod
had no way to add a mob. It may now copy minecraft:zombie or
minecraft:villager, as NeoForge's EntityType.Builder.of(Zombie::new, ...)
makes a type of a vanilla class, with its own default attribute values
(EntityAttributeCreationEvent) in an `attributes` map, for attributes the
template has. A thrown type still names its item; a living one has none.

The mod's row in entity-types.json is its template's with `template`
added and its attribute bases in place of the template's, every other
value kept as the template's text. MobTypes::load makes a row naming a
template into that species' MobType under the mod's id, so registry and
network ids, /summon and remote spawns, which already went through the
table and the registry, pick it up. What a class saves and syncs of its
own (a zombie's IsBaby, CanBreakDoors and conversion times, its entity
data, the villager's) follows MobType::class_id, the template's id for a
mod type; the saved and sent id stays the mod's.

A mob's default loot table, and /loot ... kill, now keep the type's
namespace (EntityType.getDefaultLootTable): ruby:ghoul rolled
minecraft:entities/ruby:ghoul, and rolls ruby:entities/ghoul. Nothing
changes without content mods: no row has a template, and class_id is the
type's own id.

First slice of #8622; goals, navigation and structure placement stay
open.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 08:00:38 +04:00
Nikita Maximilian GrimmandClaude Opus 5.5 052e130539 Merge origin/main into deterministic-capture
Keep main's world border rule from #9396 (it ticks only while the level
runs, which already covers HORIZON_STILL's hold) instead of the
branch's hold gate.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 05:00:25 +01:00
Ludovic Marie 1802dc9713 Merge pull request #9301 from degepie/fix/custom-enchantment-names
🤖 Agent: The synced enchantment table is built once per registry sync, not per tooltip; a literal-text description of a data-pack entry now shows instead of the raw id. Union build passed.
2026-11-05 04:57:22 +01:00
Ludovic Marie 4cc927ec8e Merge pull request #9199 from Mayonesu0/fix/8779-powered-wither-wind-charge
🤖 Agent: Matches WitherBoss.hurtServer: a powered wither refuses a direct WindCharge, and a breeze's BreezeWindCharge (a sibling class) still hurts it; the test pins exactly half health. The only parity failure was #9511, fixed by #9514 (merged), so parity runs again.
2026-11-05 04:57:18 +01:00
Ludovic Marie e7f54ff0da Merge pull request #8924 from Delete228/create-owned-sounds-pr
🤖 Agent: Merged in a 45-PR union build (fmt, clippy, tests pass). Ticking::Native only changes who advances a sound instance; vanilla's SoundEngine.tickInGameSound order is unchanged for every non-native sound.
2026-11-05 04:57:14 +01:00
Ludovic Marie 3747a6c2cb Merge pull request #9421 from phexuss/fix/wardrobe-session-reuse
🤖 Agent: I merged main (wardrobe tests on both sides kept, 95124d4). The session is reused until the service answers 401, then signs in once more; Mojang's join is made once per service session instead of on every refresh. Union build passed.
2026-11-05 04:57:10 +01:00
Ludovic Marie 4a67ce5b0c Merge pull request #9427 from JulesB40/perf/surface-rule-once
🤖 Agent: I merged main and carried main's new OreVein positive_y through fold_biomes (3bbcda7); it depends on the rule, not the chunk. The measured win is in the carvers stage (−16 to −18 % ms/chunk), with snapshots identical to main. Union build passed.
2026-11-05 04:57:07 +01:00
Ludovic Marie f52f7edf1b Merge pull request #9364 from Joansitoh/t3/firework-elytra-leash-break
🤖 Agent: Matches FireworkRocketItem.use: dropAllLeashConnections(null) while fall-flying, then LEAD_BREAK when something dropped. I fixed the ignored resource test, which no longer compiled after main made the audio capture a struct (86d09bf). Union build passed.
2026-11-05 04:57:02 +01:00
Horizon agent 10209ee6bb Merge remote-tracking branch 'origin/main' into cg42b/8700 2026-11-05 04:57:00 +01:00
Ludovic Marie f9d353f860 Merge pull request #9450 from DUzzL/fix/9437-consume-no-swing
🤖 Agent: The tamable arm was the wrong way round: Wolf/Cat/Parrot.mobInteract answer SUCCESS for sitting down, and for food that heals a hurt tame wolf or cat or tames a cat, so those swing. AbstractHorse.fedFood and Panda answer SUCCESS_SERVER (no client swing). Fixed in a pure mob_swings with a test (0d8d49d), main merged. Union build passed.
2026-11-05 04:56:58 +01:00
Ludovic Marie baa7e75c90 Merge pull request #9405 from degepie/fix/dp-block-nbt
🤖 Agent: I merged main. block_entity_nbt now reads a live moving piston first, as #9617 made /data do, so block predicates see the same NBT (de85911). Union build passed.
2026-11-05 04:56:54 +01:00
Ludovic Marie 993843b164 Merge pull request #9403 from degepie/fix/dp-entity-tags
🤖 Agent: I merged main and changed one thing: a registry whose pack tags fail to load now keeps the game's tags. Before, it stored an empty table, so even vanilla's #tags matched nothing in advancement predicates (167f156). Union build passed.
2026-11-05 04:56:49 +01:00
Ludovic Marie 50c220db84 Merge pull request #9304 from degepie/fix/dragon-death-vibration
🤖 Agent: Matches EnderDragon.kill and tickDeath: remove, then gameEvent(ENTITY_DIE). I merged main, which now asks is_fight_dragon before the dragon is taken (0b6621e). Union build passed.
2026-11-05 04:56:46 +01:00
Ludovic Marie 9c09a461b8 Merge pull request #9349 from TheMrZZ/fix/h293-mob-team-command-names
🤖 Agent: I merged main (corpus groups from both sides kept, every def checked present). Mob command names now take the team's prefix, suffix and colour, as Entity.getDisplayName via PlayerTeam.formatNameForTeam. Union build passed.
2026-11-05 04:56:42 +01:00
Ludovic Marie e8b8a0103f Merge pull request #9356 from TheMrZZ/fix/advancement-world-loot-rewards
🤖 Agent: Advancement loot rewards now read the world's data-pack tables, as ServerAdvancementManager's reward resolution does through the reloadable registries. Union build passed.
2026-11-05 04:56:38 +01:00
Ludovic Marie 9c3c363f0d Merge pull request #9397 from NikitaMGrimm/entity-own-phase
🤖 Agent: Item bob and end crystal phases come from the entity's own random, as the ItemEntity and EndCrystal constructors draw them, instead of a shared stream. Union build passed.
2026-11-05 04:56:34 +01:00
Arslan ArmanulyandClaude Opus 5.5 a66f8fa25a Shader packs: the shadow pass draws only terrain that can shade the view
Our probes' Iris 1.11.7 shadow maps (shadow.culling unset) fit keeping
only the sections whose shadow can fall into the camera's view: under a
noon sun a wedge of chunks in front of the camera, with a low sun ahead
nothing behind it. Horizon's held every section in the light's box.

For Iris-format packs, the shadow pass now also culls sections with the
camera's frustum stretched without end toward the light (the faces turned
toward the light, plus a plane along the light through each silhouette
edge), and tests whole 16-block sections there, a guess that matched our
probe with the camera looking straight up. shadow.culling=false and the
voxel distance of shadow.culling=reversed keep everything, as before.
Horizon's own packs are unchanged.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 08:56:30 +05:00
Ludovic Marie 244e5320b0 Merge pull request #9396 from NikitaMGrimm/frozen-sky-flash
🤖 Agent: Matches ClientLevel.tick: the sky flash fades every tick, and the world border lerps only while the level runs. Union build passed.
2026-11-05 04:56:30 +01:00
Ludovic Marie 2eb57c2ad6 Merge pull request #9151 from TiggerTakkyTTT/test/server-api-observability-goldens
🤖 Agent: Test-only: the uptime line is masked to <dynamic>, the rest of the metrics text stays exact. Union build passed.
2026-11-05 04:56:26 +01:00
abit-foggy 6d295f04b8 Merge remote-tracking branch 'upstream/main' into codex/studio-a2a3-browser 2026-11-04 17:53:09 -10:00
Ludovic Marie f5565f1a3c Merge pull request #9075 from TiggerTakkyTTT/test/server-api-moderation-goldens
🤖 Agent: Test-only: pins the moderation replies' status codes and bodies with independent literals. Union build passed.
2026-11-05 04:50:53 +01:00
Ludovic Marie 740d472acb Merge pull request #9072 from TiggerTakkyTTT/test/server-api-event-goldens
🤖 Agent: Test-only: the event replies' goldens are written by hand, so a serializer change has to show up here. Union build passed.
2026-11-05 04:50:49 +01:00
Ludovic Marie 437ff774c7 Merge pull request #9062 from TiggerTakkyTTT/test/server-api-config-goldens
🤖 Agent: Test-only: the config replies are pinned against literal JSON, not the serializer under test. Union build with 44 other PRs passed.
2026-11-05 04:50:45 +01:00
abit-foggyandCodex bb61e27267 fix(studio): harden recording privacy
Exclude packets that carry player identity or private text, randomize entity UUIDs in recorded AddEntity packets, and fail closed on unrecognized packets. Keep recording metadata free of server addresses and player names. Correct the recordings browser tooltip to match its current behavior.

Co-Authored-By: Codex <[email protected]>
2026-11-04 17:48:42 -10:00
Florian ERNSTandCodex 9fbc1c6398 Dispatch puffed pufferfish mob contacts with NoAI
Reuse the existing contact helper from the NoAI Mob.tick branch without changing ordinary-AI order or accepted-hit handling. Add actual Mob.tick and guarded Game/Java contact regressions; retain the independent poison-duration diagnostic.

Co-Authored-By: Codex <[email protected]>
2026-11-05 04:47:11 +01:00
Ludovic Marie 36134aa077 Merge pull request #9640 from voidsipp/fix/9346-sculk-setup
🤖 Agent: Ten inactive ticks in a row is longer than any vibration in flight takes to arrive inside the 8-block range, so the walk starts from a quiet sensor. The movement and activation checks on both clients are unchanged, and the three trace tests cover the vibration still travelling, a second vibration and a setup that never settles.
2026-11-05 04:46:09 +01:00
VaspyyyandClaude Opus 5.5 1174737b8f Collision scan: one memo for the last cell's chunk and section
#9598 made far moves cheap by passing empty sections and missing
chunks a row segment at a time, but every cell of a mob's box paid for
it: a second cache search on top of the reach cache, and more state in
the cell loop. On the ground a mob's move cost about 7.5% more
instructions than before #9598.

The scan now keeps one memo, the last cell's chunk with its section's
facts (empty, may reach out), so a cell in the same chunk and section
costs one comparison. Both facts live in one eight-section cache,
asked once per section. Where the world's chunks stay as they are for
the scan (missing_chunks_stay_missing), it also keeps the chunk before,
so a row across a chunk border goes back and forth without asking
again. Worlds without that promise ask as vanilla's BlockCollisions
does, including again after a missing chunk.

Fixes #9653

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:45:11 +01:00
Ludovic Marie cfe6b9ef76 Merge pull request #9638 from KBroneZ/claude/fix-9629-coverage
🤖 Agent: The headline is counted from the rows' own status, class and tier, so it can't disagree with the numbers under it, and the launcher's tr() lines are tested against Summary::lines. The per-row plain sentences are still English report data, like `reason`; translating them is #9644.
2026-11-05 04:42:16 +01:00
Ludovic Marie 4649ff391b Merge pull request #9637 from KBroneZ/claude/fix-9628-copy-from
🤖 Agent: The CurseForge rows now reuse #9583's curseforge_instance_target (minecraftinstance.json first, manifest.json only if it has no version), so copy-from and `instance others` read the same triple the pack import does. A loader Horizon doesn't name, or one without a version, stays none, as before.
2026-11-05 04:42:10 +01:00
Ludovic Marie d594f449d8 Merge pull request #9639 from KBroneZ/claude/fix-9630-errors
fix(launcher): say what to do next in three common errors
2026-11-05 04:41:10 +01:00
Horizon agentandClaude Opus 5.5 266d52a6cd Mobs: dolphins hold their breath, copper golems breathe under water
Dolphins ran WaterAnimal's air rule, which set their air to 300 every tick
in water: air never fell under 140, so they never surfaced for air nor
drowned, and #9271 now sent that 300 to every viewer. Dolphin's
handleAirSupply is empty and it isn't in #can_breathe_under_water, so it
now takes LivingEntity.baseTick's air from its 4800 (and stops taking
drowning damage on land on top of drying out). The breathing list follows
the tag: copper golems in, ghasts, blazes and snow golems out (no override
keeps their air; the iron golem's decreaseAirSupply does). A saved mob
without an Air tag loads with its species' maximum (Entity.load), not 300.
From martipops's review of #9271.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:33:32 +01:00
Horizon agentandClaude Opus 5.5 7218bd0680 rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:33:32 +01:00
Ludovic Marie bd054c2682 Merge pull request #9648 from horizon-rust/fix/9311-review-leftovers
🤖 Agent: verified on playhorizon merged with main: launcher fmt, clippy -D warnings, 541 lib + 102 binary tests. The failure file's tag line is what keeps a launcher already at that release from showing a stale 'update failed'; files from older installers have no tag and still show.
2026-11-05 04:33:32 +01:00
Horizon agentandClaude Opus 5.5 37477f99ec Sleeping block entities: tests run the real tick on every idle case; chunk unloads keep sleepers asleep
The idle tests restated the predicate. The furnace, brewing stand and
campfire tests now run the tick the block runs on each idle case and
check nothing changed, and that an awake one changes, so an idle() that
drifts from its tick fails. Unloading chunks used retain, which wakes
every sleeping block entity of the level, so with moving players most
idle machines ticked anyway: unload removes only the gone ones. From
martipops's review of #9239.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:33:13 +01:00
voidsip ba1e658704 Resolve bound sounds from live indexed entity state 2026-11-04 21:31:59 -06:00
Horizon agent 6b54c6f50b Merge remote-tracking branch 'origin/main' into cg40b/review-fixes-launcher-update 2026-11-05 04:31:12 +01:00
Ludovic Marie 7ac27e71f0 Merge pull request #9473 from horizon-rust/fix/known-packs-as-vanilla
🤖 Agent: #9298 merged; main merged in (clean, nothing since the verified tree touches net.rs, remote.rs or the wire tools). Verified on playhorizon at the stack's top 72f38687b: all five vanilla_wire_e2e rules pass against vanilla 26.3 with the Login held 1 s, known-packs-as-vanilla and known-data-used included; fmt, clippy -p horizon-client --all-targets -D warnings, 910 horizon-client tests.
2026-11-05 04:30:35 +01:00
Ludovic Marie c626fc0b1a Merge pull request #9428 from martipops/fix/launcher-checks
Launcher: run the binary's tests in PR checks
2026-11-05 04:30:07 +01:00
Horizon agentandClaude Opus 5.5 5890a75541 docs: Host carries play_sound and send_particles; change_mod_block_entity on a client is local
Also fixes the borrow in drop_resources_now from the machine-tool commit
(the target check is a bool now, so popping can borrow the level).
From martipops's review of #9222.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:29:55 +01:00
Horizon agent f03a34e508 Merge remote-tracking branch 'origin/main' into cg40b/9473b 2026-11-05 04:28:56 +01:00
Horizon agentandClaude Opus 5.5 d160024a85 Launcher: an update failure isn't shown once its release is in; a data move drops a staged update
The failure file now starts with the release that failed, and a launcher
already at it or newer (another copy, the command line or the site put it
in) doesn't say its update failed. Moving the data folder quit the window
with an update still staged, so the installer could start while the
relocation worker copied the folder holding the stage; the move now
drops the staged update and its stage, and the restarted launcher checks
again. From martipops's review of #9311.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:27:42 +01:00
Ludovic Marie fb683da6ac Merge pull request #9631 from TheMrZZ/fix/pufferfish-player-contact
🤖 Agent: The contact now follows Player.aiStep (inflate 1/0.5/1, or the vehicle union inflated 1/0/1) and Pufferfish.playerTouch (doTeamsAllowDamage, hurt first, then poison with the fish as source), and it runs per guest even with a frozen level. One gap left for later: vanilla sends PUFFER_FISH_STING to the player only when the hurt lands, while the sting sound here still comes from the fish AI every 10 ticks.
2026-11-05 04:26:16 +01:00
Ludovic Marie 89ea0a8c81 Merge pull request #9627 from Vaspyyy/fix/launcher-mod-list-paths
🤖 Agent: Every place that removes or renames a listed file now goes through Entry::path, which accepts only plain names under the five install folders (mods, resourcepacks, shaderpacks, horizon-mods, horizon-native-mods), and imports from packs or other launchers can no longer bring their own mod-browser.json. Data packs never enter that list, so nothing the browser installs today is dropped.
2026-11-05 04:26:12 +01:00
Ludovic Marie a12a1a156d Merge pull request #9626 from Vaspyyy/fix/friends-relay-guess-limit
🤖 Agent: The wrong-guess total and the retired-code count now last for the whole sharing session, and only as many relayed key exchanges run at once as wrong guesses are left, so re-registering no longer resets the relay's chances (at most 50 guesses at 20-bit secrets). Counting every failed exchange as wrong is the right call: the host sends its confirmation first, so a peer that hangs up after checking it has had its guess.
2026-11-05 04:26:08 +01:00
TheMrZZandCodex gpt-6.1-sol b696d86470 Merge current main while retaining Allay healing coverage
Keep all upstream corpus groups and restore the Allay fixture area after its measurements.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 04:26:05 +01:00
ElyTheCat be4c480e9c Merge branch 'main' into perf/entity-occlusion-culling-pipeline
# Conflicts:
#	crates/horizon-client/src/game/item_draw.rs
2026-11-05 06:53:56 +03:30
Ludovic Marie 86a2f2516e Merge pull request #9298 from horizon-rust/fix/client-information-as-vanilla
🤖 Agent: #9297 merged; main and its harness fix merged in (clean). Verified on playhorizon on the stack's top (#9473, which carries this head): the five vanilla_wire_e2e rules against vanilla 26.3 with the Login held 1 s pass, including brand-before-information and no-play-information from this PR; fmt, clippy -p horizon-client --all-targets -D warnings, 910 horizon-client tests.
2026-11-05 04:22:48 +01:00
Ludovic Marie d26c7fd7de Merge pull request #8498 from utophii/fix/2282-musl-build
feat: add musl linux release build
2026-11-05 04:21:41 +01:00
ElyTheCatandCodex gpt-6.1-sol e62b4a1eae perf(entity-culling): hide entities in sections the graph never reached
Reject an entity whose padded render bounds lie entirely in terrain sections the renderer's visibility graph cannot reach, before any per-entity occlusion query runs; the graph must know the camera's own section, and unknown cells fail open to the existing paths. Add tests for the graph's answer and the bounds-to-section span, and tidy the fully-qualified occlusion calls in the culling tests.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 06:51:14 +03:30
HrafnaValkyrjaandClaude Sonnet 5.5 051322cd22 Entity events: the particle table decides which ones draw
The client forwarded an entity event to the particle code only if its id was
in a hand-written list in remote.rs. The table that says what each event draws
(entity_event_burst) already had rows for villager 42 (raid sweat) and dolphin
38, but the list did not, so neither showed. The list repeated the table and
had drifted from it. It is removed: entity_event_particles already looks the
event up in the table and draws nothing when there is no row.

Fixes #8589

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 22:19:45 -05:00
Horizon agentandClaude Opus 5.5 af82f65855 mod API: scenes shade and cull block entity quads as the world does; a stale state id can't crash
Mod quads, cubes, items and text come with their colour baked; the scene
shaded them a second time (an east face of a cube at 0.30 against the
block model's 0.6, glowing text dimmed). It now shades only what
entity.wgsl shades (|normal.w| >= 0.5) and culls only what it culls
(CULL_BACK, SHADED_CULL_W), so a flat quad or text seen from behind and a
mirrored part draw as in the world. A degenerate quad takes its vertex
normal. A state the registry doesn't know no longer panics in the face
culling. The docs list the scene's limits. From martipops's review of
#9229.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:19:25 +01:00
Horizon agentandClaude Opus 5.5 9f83f20c74 mod API: chunk tickets refuse chunks past the world's edge; held chunks generate between ticks
A ticket past /forceload's 30,000,000-block edge is refused (the ring's
arithmetic overflowed near i32::MAX). pump_chunks kept the workers fed
between ticks for /forceload only; a mod's held chunks get the same. The
docs say where tickets differ from NeoForge's and what they can't do yet
(lazy Nether/End, pausing servers, free owners, cost). From martipops's
review of #9226.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:18:41 +01:00
Ludovic Marie 41ace47be5 Merge pull request #9433 from martipops/fix/staged-update-install
🤖 Agent: verified merged with main (81dad4693, with #9428 on top) on playhorizon: launcher fmt, clippy --all-targets --locked -D warnings, 538 lib tests and 101 of 102 binary tests (the one failure is main's stale docs/launcher-commands.md, fixed on #9428). One point checked by hand: an older window hands the new installer no HORIZON_LAUNCHER_UPDATE_SHA256 only if the file in place changed under it, and then already_in_place returns Ok before the empty hash is looked at, so a mixed-version pair restarts instead of failing. Real placement on macOS and Windows is still unrun.
2026-11-05 04:18:07 +01:00
Horizon agentandClaude Opus 5.5 00922e5c8d mod API: a machine's tool rolls only its own block; no ore experience with silk touch
drop_tool and drop_into applied to every dropResources inside the
machine's with_level, so blocks popping off with the break (grass on the
dirt) dropped with the machine's tool and went into its list. They now
drop as with no tool and pop, as Level.destroyBlock does for neighbours.
A silk touch tool no longer collects ore experience
(EnchantmentHelper.processBlockExperience, after the xpRange draw), which
made an unlimited experience farm. From martipops's review of #9223.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:18:05 +01:00
Horizon agentandClaude Opus 5.5 2ec49e8bc2 mod API: Host::equipment(MainHand) is the selected slot in an off-hand pass
held() is the acting hand: during the off-hand pass of a use it is the
off hand, so equipment(MainHand) answered the off-hand stack.
LivingEntity.getItemBySlot doesn't depend on the hand acting. From
martipops's review of #9222.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:17:18 +01:00
MegaBytesMe 6e3073863e Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64 2026-11-05 03:17:05 +00:00
Ludovic Marie 9540aaeb86 Merge pull request #9435 from martipops/fix/data-remove-loot-table
🤖 Agent: verified merged with main 81dad4693 on the desktop: clippy -p horizon-client --lib --tests -D warnings, 29 command tests, and the ignored loot_table_tests run against prepared 26.3 resources (passes; with take_loot_table's old bare retain put back it fails with the table still in the saved chest, so the test pins the fix). Matches vanilla: RandomizableContainer.tryLoadLootTable loads a tag without LootTable as a plain, empty container. The run-parity label adds opened_loot as asked.
2026-11-05 04:15:17 +01:00
Horizon agentandClaude Opus 5.5 1f6cd059ce docs: regenerate launcher-commands.md (game-architecture key)
The reference test lives in the launcher's binary, which --bins now runs in
CI: main's copy missed the game-architecture setting from #8274, so this
PR's own check would fail on it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:14:14 +01:00
Horizon agent 7dc38db5bd Merge remote-tracking branch 'origin/main' into cg40b/9428 2026-11-05 04:14:13 +01:00
voidsip 50cad6717c Wait for setup vibrations before checking player sculk movement 2026-11-04 21:14:00 -06:00
KBroneZandClaude Sonnet 5.5 9cf673ffb9 fix(launcher): fewer language-table lines for #9630
Insert the new keys before each table's last entry (no comma changes), fold
the instance-name sentence into one key, and give copy-from its --as hint as
plain text after it.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 05:11:21 +02:00
yahya-nusseibeh 0785a988df Relay: reserve a bridge slot at the match, give it back on every exit
max_bridges compared against bridges already running, so joins waiting
(up to attach_wait) for their host's attach all passed the check and a
burst could exceed RELAY_MAX_BRIDGES.

- BridgeSlot, taken under the lookup lock when a guest is matched with
  its host, counts toward max_bridges from then on; dropping it gives it
  back, so every way out of a join (no attach, a refused or forged
  attach, an error inside bridge(), a panic) releases it.
- counts().bridges now includes matched joins awaiting attach.
- tests/bridge_slots.rs: a waiting join holds its slot (a second guest
  gets Full); the count returns to 0 after a timeout, after a forged
  attach, and after a bridge closes; refusals never take one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit 91d35fcae0)
2026-11-05 04:11:20 +01:00
yahya-nusseibeh e42e8b5497 Ban lists: checked arithmetic for hand-edited dates
parse_date multiplied and added the fields of banned-players.json and
banned-ips.json dates as plain i64, so a huge year, month, day or time
panicked in a debug build and wrapped in a release one, turning a ban's
expiry into an arbitrary date. Every step is now checked, years are bounded
before days_from_civil, and a date out of range is None, like one that
doesn't parse. Lenient month overflow (month 13) still works.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit 184ea41194)
2026-11-05 04:11:20 +01:00
yahya-nusseibeh 0b974a2f9c Write token files through a private temporary file and a rename
write_private (the control API's .horizon/api.json, the game's control
endpoint, the management protocol's generated key) opened the path with
create+truncate and mode 0600, which applies only when the file is new:
an existing, wider-readable file kept its permissions and got the new
token, and a link at the path was followed.

- horizon_server_files::private_file::write_private, shared by
  horizon-server and horizon-client: writes a create_new 0600 temporary
  file beside the path, syncs it and renames it over the path (a link
  there is replaced, not followed); the temporary file goes on failure.
- Tests: an old 0644 file comes out 0600; a link's target is untouched.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit 9cb9d76054)
2026-11-05 04:11:20 +01:00
yahya-nusseibeh 55345332a3 Game control endpoint: draw its token from the OS random generator
The TCP control endpoint's token (it gates full control of the running
game) was two SipHash outputs of RandomState over the time and the process
ID. RandomState's keys are OS-seeded, but std doesn't promise it is a
cryptographic generator. It now comes from getrandom::fill, as
horizon-server's and the relay's tokens do (getrandom 0.3, already in the
lockfile, added to horizon-client-lib, which compiles control.rs).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit aa89153fc0)
2026-11-05 04:11:20 +01:00
TheMrZZandCodex gpt-6.1-sol e34841176c Heal Allays without enabling their AI
Keep passive healing on the ten-tick entity cadence for NoAI Allays. Reuse the existing heal operation and preserve normal-AI ordering and the independent duplication countdown.

Add guarded command-oracle coverage for both AI modes and health-cap boundaries, plus a reachable Mob tick regression.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 04:10:38 +01:00
Horizon agentandClaude Opus 5.5 86d09bfcb7 Read the captured lead snap through CapturedSound's fields
Main made the audio capture a struct; the ignored resource test still
compared tuples and no longer compiled.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:08:08 +01:00
Horizon agent eb7e1af600 Merge remote-tracking branch 'origin/main' into cg42b/9364 2026-11-05 04:07:48 +01:00
Horizon agent 95124d4a75 Merge remote-tracking branch 'origin/main' into cg42b/9421
# Conflicts:
#	launcher/src/wardrobe/tests.rs
2026-11-05 04:07:33 +01:00
KBroneZandClaude Sonnet 5.5 a4c0f82c7a fix: read the recorded target when --folder names one CurseForge instance
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 05:06:05 +02:00
Horizon agentandClaude Opus 5.5 3bbcda7ad5 Carry an ore vein's positive_y through the shared rule's biome fold
Main gave CompiledRule::OreVein the y bounds of its positive density; the
fold copies them, as they depend on the rule and not on the chunk.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:05:37 +01:00
Horizon agent a434181f20 Merge remote-tracking branch 'origin/main' into cg42b/8918
# Conflicts:
#	crates/horizon-client/src/mod_api.rs
2026-11-05 04:01:46 +01:00
Ludovic Marie 0c62a47e9a Merge pull request #9297 from horizon-rust/fix/client-tick-end-needs-level
🤖 Agent: requeued after martipops's harness finding: vanilla_wire_e2e now records the held Login when the client gets it. On playhorizon against vanilla 26.3 with a 1 s hold, this head passes no-play-before-login and the same tree with main's r.joined condition fails it with 27 client_tick_end before Login. Merged with main 81dad4693 (impl Remote conflict, both sides kept): fmt, clippy -p horizon-client --all-targets -D warnings, 910 horizon-client tests.
2026-11-05 04:01:32 +01:00
Horizon agent ba520f9237 Merge remote-tracking branch 'origin/main' into cg42b/9450
# Conflicts:
#	crates/horizon-client/src/game/remote_mob_use.rs
2026-11-05 04:00:26 +01:00
Horizon agent 72f38687bd Merge branch 'cg40b/9298' into cg40b/9473 2026-11-05 04:00:26 +01:00
Horizon agent af15fa9bba Merge branch 'cg40b/9297' into cg40b/9298 2026-11-05 04:00:15 +01:00
KBroneZandClaude Sonnet 5.5 f6e407e16c fix(launcher): say what to do next in three common errors
No version installed: name `horizon-launcher install latest`.
Instance name taken: ask for another name; `instance copy-from` names `--as`.
Play gate: name where to sign in (Settings in the window, `account sign-in` on the CLI).
No change in behaviour; messages go through tr()/trf().

Fixes #9630

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 04:59:42 +02:00
Horizon agent 5ef75a930e Merge remote-tracking branch 'origin/main' into cg42b/9427
# Conflicts:
#	crates/horizon-world/src/worldgen/material_stage.rs
2026-11-05 03:58:07 +01:00
Horizon agent 7c3e86ec82 Merge remote-tracking branch 'origin/main' into cg42b/9349
# Conflicts:
#	tools/command_exec_corpus.py
2026-11-05 03:53:56 +01:00
Horizon agentandClaude Opus 5.5 de859115ed Merge origin/main into #9405; block_entity_nbt reads a live moving piston first
#9617 made /data read a live moving piston's block entity before the ordinary
ones. The shared getter keeps that order, so block predicates see it too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:53:15 +01:00
Horizon agentandClaude Opus 5.5 167f156560 Merge origin/main into #9403; a registry whose pack tags fail keeps the game's own
A load_tags error used to store an empty table for that registry, so every
#tag of it, vanilla's included, matched nothing in advancement predicates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:52:41 +01:00
KBroneZandClaude Sonnet 5.5 32a64fc70c feat(launcher): say in plain words whether a modpack will work
The coverage report read "0 ported, 52 partial, 264 not yet" with reasons such
as "needs mod API 0.4". A player could not tell what comes over.

coverage.md and the Modpacks page now open with a headline counted from the
report's own rows and tiers: how many of the mods that need porting come over,
how many more Port can carry over, how many need mod support Horizon does not
have yet, and how many are not sorted yet; plus what "partial" means. Each row
gets its reason in plain words (`plain` in coverage.json, a last column in
coverage.md, a line above the technical one on the page). Counts, statuses and
the technical `reason` are unchanged. An older coverage.json without `plain`
still reads.

The three headline lines go through tr() with English values in the language
tables; the per-row sentences are English, like the reasons they sit beside.

Fixes #9629

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 04:52:39 +02:00
Horizon agent 0b6621e274 Merge remote-tracking branch 'origin/main' into cg42b/9304
# Conflicts:
#	crates/horizon-client/src/game/dragon.rs
2026-11-05 03:52:07 +01:00
Ludovic Marie a295df1fd7 Merge pull request #9277 from martipops/controller/menu-actions
🤖 Agent: verified merged with main 81dad4693 on the desktop: fmt, clippy -D warnings on the four client crates (all targets) and the --no-default-features server build, 2750 horizon-client/-lib tests and 1358 render tests pass. One point checked by hand: with no glyph a button's label is centred exactly where it was (x + floor(w/2) equals the old x + 2 + floor((w - 4)/2)), so mouse players see vanilla's buttons unchanged. pad_menu_e2e not rerun here (windowed, no display over ssh).
2026-11-05 03:51:41 +01:00
Florian ERNSTandCodex 81c9870f01 Keep pufferfish and moving-piston test options together
Refresh from main without changing the H311 runtime fixture or contact implementation.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:50:06 +01:00
KBroneZandClaude Sonnet 5.5 663c1efb70 fix: copy-from reads a CurseForge instance's Minecraft version and loader
The CurseForge app's instances were listed with no version and no loader, so
`instance copy-from` demanded --minecraft and the copy lost its loader. They
now use curseforge_instance_target (minecraftinstance.json, else manifest.json).
--minecraft still overrides; the Modrinth App path is unchanged.

Fixes #9628

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 04:47:54 +02:00
Florian ERNSTandCodex 79271db8c0 Refresh crafting-priority branch from main
Keep isolated recipe worlds, advancement controls and current shared command fixture routes together.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:47:08 +01:00
Horizon agentandClaude Opus 5.5 0d8d49d825 Swing for a tame wolf, cat or parrot sitting down, and for food that heals or tames
Wolf.mobInteract and Cat.mobInteract answer SUCCESS for sitting down, for
food that heals a hurt tame one and for food that tames a cat, before
Animal.mobInteract is reached. AbstractHorse.fedFood and Panda answer
SUCCESS_SERVER, which swings on the server only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:46:03 +01:00
Florian ERNSTandCodex 50fd4a8d1f Refresh world-loot reward branch from main
Preserve the focused, full inline, named admission and valid-empty diagnostics alongside current shared command fixtures.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:44:35 +01:00
Horizon agentandClaude Opus 5.5 5a10e12a10 vanilla_wire_e2e: hold the Login before recording it
The proxy wrote the Login's line and then slept, so whatever the client sent
during the hold landed after the Login in the capture, where
no-play-before-login stops looking: a client sending tick ends for the whole
hold could pass. Sleep first, then record (martipops's review on #9297).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:41:43 +01:00
Horizon agent 042c8b4b44 Merge remote-tracking branch 'origin/main' into cg40b/9297
# Conflicts:
#	crates/horizon-client/src/game/remote.rs
2026-11-05 03:41:42 +01:00
Ludovic Marie d158e44736 Merge pull request #9632 from DUzzL/fix/9607-scaffolding-sight
🤖 Agent: Line of sight now clips with the viewer's collision context, as LivingEntity.hasLineOfSight's ClipContext(COLLIDER, entity) does, so scaffolding is open from below and solid from above. I ran the new test against prepared resources too: it builds the world and passes, not vacuously.
2026-11-05 03:40:07 +01:00
abit-foggyandCodex ba7f8a3f40 Merge upstream/main into codex/studio-a2a3-browser
Co-Authored-By: Codex <[email protected]>
2026-11-04 16:39:48 -10:00
Florian ERNSTandCodex 123d271505 Refresh crafter map branch while preserving shared item-use fixtures
Retain map metadata and max-scale controls alongside current fixture groups and explicit boat-use packet actions.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:37:57 +01:00
Florian ERNSTandCodex 37b303bb1d Keep reinforcement and moving-piston fixture options together
Co-Authored-By: Codex <[email protected]>
2026-11-05 03:33:55 +01:00
Florian ERNSTandCodex becdc3d7d3 Refresh reinforcement command branch from main
Keep current shared oracle and corpus fixtures alongside the unchanged H300 room, light observations and immediate/once diagnostics.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:33:35 +01:00
Ludovic Marie 81dad4693e Merge pull request #9617 from TheMrZZ/fix/h309-live-piston-data
🤖 Agent: Moving-piston NBT is read from the live tick entry with the existing piston serializer, and writes are still refused before any detached block entity exists. The ordered pretty-print context applies only to a live piston's requested subtree, so other /data get output is unchanged.
2026-11-05 03:32:32 +01:00
Ludovic Marie 122149ae5c Merge pull request #9625 from Vaspyyy/fix/relay-hosts-per-address
🤖 Agent: The per-address host cap mirrors the existing rate limits (off when the relay can't see addresses; IPv6 counted per /64 with a 4x /48 cap). HostSlot gives the slot back on every way out of host(), and the integration test holds connections open to prove it.
2026-11-05 03:32:29 +01:00
Ludovic Marie 8a8b5674db Merge pull request #9624 from Vaspyyy/fix/client-malformed-server-data
🤖 Agent: The tree check follows ClientboundCommandsPacket.validateEntries: one pass over redirects (canBuild) and one over children (canResolve), so vanilla's own execute-run-to-root redirect still passes. It is iterative, so a long chain cannot overflow either.
2026-11-05 03:32:25 +01:00
Ludovic Marie 8edbc6dbe5 Merge pull request #9623 from Vaspyyy/fix/dialog-url-scheme
🤖 Agent: Dialog open_url now goes through the same untrusted_http_url check as chat click events (ExtraCodecs.UNTRUSTED_URI: http/https only), so file:, UNC and protocol-handler links never reach open_uri.
2026-11-05 03:32:22 +01:00
Ludovic Marie 76a26cc628 Merge pull request #9622 from Vaspyyy/fix/lan-owner-login
🤖 Agent: The owner's window always joins its integrated server through memory, so refusing the owner's UUID over the network costs the host nothing and closes the offline-name takeover. Splitscreen seats already had this rule.
2026-11-05 03:32:18 +01:00
Ludovic Marie 24f537709a Merge pull request #9621 from Vaspyyy/fix/item-nesting-depth
🤖 Agent: The depth guard is a thread-local counter around read_added and effect_details, released on drop, so an error leaves nothing counted. 64 levels still decode, and one guarded Cell per component costs nothing next to the decoding itself.
2026-11-05 03:32:13 +01:00
Ludovic Marie 926b36ee9d Merge pull request #9611 from Vaspyyy/perf/ore-sphere-loop
🤖 Agent: Along each axis the rounded squares fall and then rise, so the cells inside a sphere form one run per row. Skipping the rest changes no draw or query (vanilla only draws for inner cells), and your snapshots of 4 decorated worlds match byte for byte.
2026-11-05 03:32:09 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 acdf8d5304 docs: HDR design note: decisions for Ludovic with recommendations
Turn the open questions into three decisions, each with options, a
recommendation, the reason and the step it blocks. State what the HDR
pass does with a shaders/ pack's alpha today, and move the full-screen
peak and light-source info for post stages to the later additions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:29:53 +01:00
MegaBytesMe a0a3ac2524 Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64
# Conflicts:
#	docs/native-mods.md
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 02:28:56 +00:00
Florian ERNSTandCodex 6328ba1dc5 Refresh H311 after the current proof batch
Preserve all landed fixture routes and one declaration of the shared scratch/reference options. Runtime pair remains qualified to 21c7f4597.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:27:17 +01:00
Ludovic MarieandClaude Opus 5.5 a82b1427c6 Merge #9617 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:22:37 +01:00
Ludovic Marie c20465a997 Merge pull request #9597 from TheMrZZ/fix/boat-item-exact-hit
🤖 Agent: Boat items now clip like Item.getPlayerPOVHitResult (float view vector, OUTLINE, Fluid.ANY) through the shared ray code instead of 0.05-block fluid sampling; the new boat_item_hit harness runs in CI parity.
2026-11-05 03:22:09 +01:00
Florian ERNSTandCodex 6ae2f17402 Dispatch pufferfish player contact from the live player tick
Keep accepted-hit gating and attach the actual fish to poison effect criteria, independently of fish AI and frozen world ticks.

Co-Authored-By: Codex <[email protected]>
2026-11-05 03:21:58 +01:00
Ludovic Marie 2cd9e2c910 Merge pull request #9598 from Vaspyyy/perf/collision-scan-skip-missing
🤖 Agent: The same hits come in the same order, with the budget kept (both tests compare against the cell-by-cell walk). Measured: +0.07 ms per tick with 1500 ground mobs (palette check per section), noise for flying mobs, and the far-teleport worst tick goes 2290 → 160 ms.
2026-11-05 03:17:36 +01:00
VaspyyyandClaude Opus 5.5 d342803768 Dialogs: open_url takes only http and https links
A server dialog's open_url action, from a show_dialog packet or a
show_dialog click in chat, took any string. A click went to open_uri, which
hands it to ShellExecute on Windows and to xdg-open or open elsewhere. A
button naming file:///C:/.../cmd.exe, a UNC path to an .exe on the
attacker's share, or a custom protocol handler could start a program. The
link prompt is on by default but shows only the raw string; with "Prompt on
Links" off it opened at once.

Vanilla's ClickEvent.OpenUrl takes an ExtraCodecs.UNTRUSTED_URI (http and
https only). Chat components already applied that check; dialogs now share
it, and a refused link leaves the button with no action.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:16:27 +01:00
Ludovic MarieandClaude Opus 5.5 47fb24cdfb Merge main into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:15:52 +01:00
DUzzL 7588ad80ea Use the viewer's collision context for mob line of sight (scaffolding) 2026-11-05 03:13:44 +01:00
Ludovic Marie 24008dfaa4 Merge pull request #9608 from KBroneZ/claude/fix-9585-trapdoor
🤖 Agent: Vanilla's trapdoor ladder rule is instanceof TrapDoorBlock, so a tagged block with no open or facing is now just not a trapdoor. A real ladder still needs valid facing, and the tests cover the three non-trapdoor shapes.
2026-11-05 03:08:50 +01:00
Ludovic Marie ffd9ffb995 Merge pull request #9601 from rison1337/native-client-services
🤖 Agent: The client services are owned snapshots with no authoritative writes, and every lifecycle hook is empty without a mod. The menu_background result is clamped to a finite 1..64, so a mod cannot hand the blur a bad radius. I dropped the enable_older_servers helper (see my comment).
2026-11-05 03:08:46 +01:00
TheMrZZandCodex gpt-6.1-sol 5f4cff260b Merge current main while preserving piston command coverage
Keep upstream corpus groups and opt-in loaded-fixture controls alongside the live-piston contract and full presentation diagnostic.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 03:05:13 +01:00
VaspyyyandClaude Opus 5.5 ac66f3bdf9 LAN: the owner joins only through memory
A host without a Microsoft account opens the world in offline mode, where a
player's UUID is offline_uuid(name). The owner's UUID is offline_uuid of the
host's name too, and the login gate let that UUID in from anywhere. So a LAN
or friends player who logged in under the host's name passed the gate, the
duplicate-login handling kicked the host's own window, and the guest played
on as the owner: operator level 4, the owner's player data, and the
movement checks the owner skips.

Apply the splitscreen seats' rule to the owner: the owner's UUID is let in
only over the in-memory connection. Vanilla never reaches this state because
IntegratedServer always authenticates LAN guests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:03:33 +01:00
VaspyyyandClaude Opus 5.5 d9b42b1267 Protocol: refuse item data nested past 64 levels
Item stacks can hold stacks in their components (bundle contents, charged
projectiles, use_remainder, container), and a mob effect can hold a hidden
effect. The decoders called each other with no depth limit. A payload a few
hundred thousand levels deep fits in one 2 MiB frame and overflows the
decoding thread's stack. In Rust that aborts the whole process instead of
failing the packet: one creative-slot packet from any player stopped a
dedicated server or a LAN host (with the host's own window), and one slot
packet from a server stopped the client. Vanilla's decoder turns the
StackOverflowError into a disconnect.

Count nesting per thread in read_added, which every stack-in-a-stack passes
through, and in effect_details, and return an error past 64 levels. The
packet then fails like any other bad packet and only that connection
closes. Survival bundles nest about 16 deep at most.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 03:03:33 +01:00
MegaBytesMe 2daf7fd6fe fix(launcher): statically link ARM64 Rust unwinder 2026-11-05 01:58:53 +00:00
abit-foggyandCodex 185826582d feat(studio): add saved recordings browser
Co-Authored-By: Codex <[email protected]>
2026-11-04 15:58:15 -10:00
VaspyyyandClaude Opus 5.5 98cec87e34 relay: cap the hosts one address holds open
The relay only rate-limited host registrations (6 a minute per
address) against the global `max_hosts` (1000). A host that answers
pings keeps its slot as long as it likes, so one client could register,
hold every control connection open and fill all slots: in under three
hours from one IPv4 address, in seconds from an IPv6 /48 (limits count
by /64, and a /48 holds 65536 of them). Every real host then got
`Full`.

Now the limiter also counts the hosts each address key holds open:
at most 4 (`RELAY_MAX_HOSTS_PER_ADDRESS`), and an IPv6 /48 at most four
times that, however many /64s it spreads over. A registration past the
cap gets the existing `Full` reply. The count goes up when a host
registers and back down through an RAII guard (`HostSlot`) when its
connection ends, on every way out of `host`, panics included. Like the
other per-address limits it is off with `RELAY_PER_ADDRESS_LIMITS=0`.

Filling the default 1000 slots now takes 250 IPv4 addresses or 63 IPv6
/48s, not
one client.

Tests: an integration test registers the cap's worth of hosts from one
address, sees the next refused with `Full`, closes one and gets its
slot back; a limiter test covers the /64 and /48 caps, a freed slot and
the limits switched off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:58:08 +01:00
VaspyyyandClaude Opus 5.5 4f682369c4 friends: bound a relay's guesses at the short code's secret
A short code's secret is 20 bits, and the docs promise the relay can't
get into the world. But the host gave a relay (or anyone driving its
joins) unlimited online guesses:

- after five wrong secrets the code was retired, but the wrong count
  was reset on every registration and `run_relay` registered again at
  once, so each cycle bought five fresh guesses for one round trip;
- a guess was only counted when the guest's confirmation failed to
  verify. The host sends its own confirmation first, so a peer could
  check it against its guess offline and hang up: an uncounted guess;
- the five-guess limit was checked after the fact, so joins sent all
  at once ran any number of key exchanges in parallel.

Now the host keeps the wrong guesses for the whole sharing session
(`Guesses`): a count per code (reset when it registers), a total that
never resets, and a reservation for each key exchange in progress, so
only as many run at once as wrong guesses are left. Every relayed key
exchange that ends without a confirmation counts. After a retired code
the host waits 1 s, 2 s, 4 s ... (at most 5 minutes, `retire_delay`)
before registering again, and after 50 wrong guesses in one session it
leaves the relay (`HostEvent::RelayStopped`; the game says so through
a new translation key). The full code keeps working directly.

The math: each short code has a fresh secret, so every guess hits with
probability 2^-20 and guesses don't narrow anything down.
- With the cap, a relay gets at most 50 guesses per sharing session:
  50 / 2^20, about 1 in 21,000. Spending them takes at least
  1+2+...+256 s = 511 s of waits.
- With the backoff alone (capped at 5 min, 5 guesses a cycle), the
  steady rate is 60 guesses an hour; a 50 % chance needs
  ln 2 * 2^20 = ~727,000 guesses, about 12,100 hours (~505 days) of
  continuous sharing. Before: one round trip a guess, hours.

Tests: the per-code count starts over at each registration while the
total stays, the waits double (1 s ... 256 s) until the host stops at
50; exchanges in progress count against the limit; `retire_delay`
doubles up to 5 minutes. The relay's tunnel test still sees a new code
after five wrong secrets (now after 1 s).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:57:12 +01:00
VaspyyyandClaude Opus 5.5 4cdbd962a3 Ore placement: visit only the cells inside each sphere
OreFeature.doPlace scanned each sphere's whole box and divided for every
cell, although a cell's square along one axis depends on that coordinate
alone. Each axis's squares are now worked out once per sphere with
vanilla's expression, so every sum is bit for bit the same, and each row
visits only its run of cells inside the sphere: along an axis the squares
fall and then rise, so the passing cells form one run around the smallest.

The cells tested, the world queries and the random draws stay the same
and in the same order. A test compares the walk with vanilla's full box
scan over 20,000 spheres, including ones whose edge sums to exactly 1.

Fixes #9609

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:56:53 +01:00
VaspyyyandClaude Opus 5.5 6b1d342294 Client: refuse light layers, command trees and dimension shapes vanilla refuses
Three things a server sends were decoded without vanilla's checks, and each
let a server crash or stall the client:

- A light layer shorter than 2048 bytes: the column-top sky light indexes the
  layer directly, so an empty layer panicked the main thread. Vanilla's
  DataLayer constructor refuses any other length; Layers now does too.
- A command tree whose children (or redirects) loop: the usage hint followed
  the loop when the player typed "/" and overflowed the stack. Commands now
  runs ClientboundCommandsPacket.validateEntries' check ("Server sent an
  impossible command tree"), as an O(n) depth-first walk so a long chain
  can't make the check itself slow.
- A dimension type's min_y and height went unchecked into the chunk format,
  so a height of 2^31 made the client allocate gigabytes for light. A shape
  outside DimensionType's codec (multiples of 16, at least 16 high, inside
  -2032..=2031) now keeps the named type's own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:55:57 +01:00
Ludovic MarieandClaude Opus 5.5 6d54bb04f8 Merge main into native-client-services
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:52:21 +01:00
Ludovic MarieandClaude Opus 5.5 1f8f9f8467 Native client services: leave the older-server switch to HORIZON_OLDER_SERVERS
The older-server adapters are off by default while they are incomplete, and
only the environment turns them on. A Host helper that turns them on for the
whole process would let any installed mod do that for its players; it is
left out of this change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:52:20 +01:00
Ludovic Marie 78a7e6a712 Merge pull request #9562 from degepie/fix/player-predicate-stats
🤖 Agent: The player predicate now tests stats, advancements, recipes and level, and player_entity_for_any stays &self, so #9494's compound is added only by the &mut self caller.
2026-11-05 02:49:43 +01:00
Ludovic Marie 743aad7dc3 Merge pull request #9482 from TheMrZZ/fix/execute-loaded-ticket-status
🤖 Agent: /execute if loaded now also needs the entity-ticking status and saved entities applied, which matches ExecuteCommand.isChunkLoaded (FullChunkStatus.ENTITY_TICKING && areEntitiesLoaded). The simulation permission tracker is untouched.
2026-11-05 02:49:39 +01:00
Ludovic Marie 354cf20b02 Merge pull request #9592 from janibert1/host-send-map
🤖 Agent: This is a fragment only: Host::send_map wraps the existing native_send_map, and tests.test_stamp_native_hooks passes with it.
2026-11-05 02:48:41 +01:00
Ludovic Marie 32e5a325a7 Merge pull request #9563 from xxaxio/feat/9547-sources-dawn-client
🤖 Agent: Dawn Client is only one more place to find the player's own 26.3 jar and assets (cache/minecraft under .dawn). The tests check that nothing is fetched from Mojang.
2026-11-05 02:48:37 +01:00
Ludovic Marie 15be9febca Merge pull request #9534 from Joansitoh/fix/9528-server-pack-replace-loop
🤖 Agent: A replaced pack becomes a new entry and the old one is unloaded, as ServerPackManager.pushNewPack marks it SERVER_REPLACED, so the reload ends instead of looping.
2026-11-05 02:48:33 +01:00
Ludovic Marie 9207297b65 Merge pull request #9589 from DUzzL/fix/9569-ghast-seats
🤖 Agent: The four seats match the passengerAttachments of HAPPY_GHAST in 26.3's EntityTypes, and the index clamps as EntityAttachments.getClamped does.
2026-11-05 02:48:30 +01:00
Ludovic Marie 5efec909a5 Merge pull request #9519 from Delete228/data-driven-damage
🤖 Agent: No-pack worlds keep the existing preload. Only an overlay with damage-type definitions seeds SyncData at world entry, so the default path is unchanged.
2026-11-05 02:48:26 +01:00
VaspyyyandClaude Opus 5.5 cb9205a34f launcher: keep the mod browser's list from deleting files outside the game folder
The mod browser records what it installed in `mod-browser.json` in the instance folder, and
Update, Remove and turning a mod off act on each entry's `file` through `home.join(file)`.
Nothing checked that path: an absolute path replaces `home`, and `..` climbs out of it. A Java
modpack (`.mrpack` with a loader) copies every relative path from `overrides/` into the new
instance, so a pack could plant its own `mod-browser.json` with an entry such as
`"file": "/home/me/.ssh/id_ed25519"`, and the launcher would delete or rename that file when the
player clicked Update, Remove or the on/off toggle on the fake entry. Copying a Prism/MultiMC
instance folder could plant the same file.

- `Installed::load` now leaves out (and logs) any entry whose `file` isn't a relative path of
  plain names inside a folder the browser installs into (`mods/`, `resourcepacks/`,
  `shaderpacks/`, `horizon-mods/`, `horizon-native-mods/`), with at least one name below it.
- `Entry::path` applies the same check where files are removed or renamed (update, remove,
  toggle, Java ports, registry installs), so an entry that didn't come from `load` can't reach
  outside either.
- A modpack import never writes `mod-browser.json` (any case), and the other-launcher copy skips
  it.

Tests: a planted list with an absolute path, a `..` path, a path climbing out through `mods/`
and a bare `horizon-mods` keeps only the valid `mods/x.jar` entry; Remove and toggle leave a
file outside the instance untouched. A Java `.mrpack` with `overrides/mod-browser.json` imports
its mods but not the list.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:48:10 +01:00
Ludovic MarieandClaude Opus 5.5 f87de22790 Merge main into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:46:31 +01:00
Ludovic Marie 3242adcfbb Merge pull request #9596 from xxaxio/fix/9391-native-mods-conflicting-pins
🤖 Agent: The mod id is read from the mixins.incoming/<id>/ path that stage_mixins writes, so the culprit named for E_MODULE_CONFLICT is right. I removed the Fixes lines for #9016 and #9414, which this PR does not fix.
2026-11-05 02:43:17 +01:00
Ludovic Marie 3c41dc04b8 Merge pull request #9525 from valkdzx/perf/behaviour-snapshot-key
🤖 Agent: The snapshots now key on a build.rs hash of the world sources, so a relink no longer throws them away, while a change to those sources still does.
2026-11-05 02:43:13 +01:00
Ludovic Marie 8ab7482d2f Merge pull request #9595 from Mayonesu0/fix/9465-launcher-game-tools-fallback
🤖 Agent: The launcher's own endpoint still wins when both games answer. A crashed game's file no longer counts (a TCP endpoint must accept its token), and a change of endpoint restarts the horizon-ctl child, whose Drop kills the old one.
2026-11-05 02:43:09 +01:00
Florian ERNSTandCodex 2ab3fcd728 Refresh H311 paired-proof checkpoint
Co-Authored-By: Codex <[email protected]>
2026-11-05 02:42:28 +01:00
Florian ERNSTandCodex 8738762519 Refresh H311 fixture branch from main
Co-Authored-By: Codex <[email protected]>
2026-11-05 02:38:45 +01:00
Ludovic Marie 21c7f45978 Merge pull request #9544 from dfrnoch/fix/atlas-animated-any-path
🤖 Agent: Animated strips are now cut to their first frame for every atlas directory source, not only item/ and block/, so a pack's own folder (wynn/) animates as vanilla's does.
2026-11-05 02:38:39 +01:00
Florian ERNSTandCodex 5bdab6c046 Add a frozen-player pufferfish contact fixture
Co-Authored-By: Codex <[email protected]>
2026-11-05 02:36:59 +01:00
Ludovic Marie 7716494c9a Merge pull request #9512 from CocofireHD/fix/9434-mcpr-crash-recovery
🤖 Agent: Recovery keeps only whole frames: the crashed .tmcpr.tmp is cut at its last complete packet record before it becomes an .mcpr, and a writer that may still be alive (its pid is checked) is left alone.
2026-11-05 02:35:29 +01:00
Ludovic Marie 249f21e8ba Merge pull request #9476 from itarqos5/perf/far-detail-off-the-frame
🤖 Agent: Applying detail now stops at DETAIL_BUDGET (3 ms) per frame and the rest waits in detail_backlog; with #9515 the widths reduction runs on the worker, so neither the burst nor its first read lands on the frame.
2026-11-05 02:35:24 +01:00
Ludovic Marie 6804926c56 Merge pull request #9483 from voidsipp/fix/9284-server-entity-sounds
🤖 Agent: Captured server sounds keep their entity, category and excluded player and go out between the entity's add and remove, so a client always knows the emitter when the sound arrives.
2026-11-05 02:35:20 +01:00
Ludovic Marie 6cdb23ac34 Merge pull request #9561 from degepie/fix/entity-killed-player
🤖 Agent: entity_killed_player fires from the guest's death with the blow read before the death message, as ServerPlayer.die does; the killer view stays on &self on top of #9494.
2026-11-05 02:35:15 +01:00
Ludovic Marie f8b542ecd3 Merge pull request #9478 from voidsipp/fix/end-gateway-passengers
🤖 Agent: End gateways now keep a same-level vehicle live and teleport it with its passengers. As you note, #9461's temporary mob_can_use_gateway restriction is no longer needed with this.
2026-11-05 02:35:11 +01:00
Ludovic Marie dd31fddcbf Merge pull request #9474 from voidsipp/fix/passenger-dimension-transitions
🤖 Agent: The whole vehicle tree moves through Entity.teleportCrossDimension's calculatePassengerTransition, with UUIDs and mount links restored, so riders keep their vehicles across /execute in … run tp.
2026-11-05 02:35:07 +01:00
Ludovic Marie 651e4de726 Merge pull request #9462 from voidsipp/fix/carpet-farm-regression
🤖 Agent: The coral-fan carpet fixture is coverage only (all 96 observations already matched on main), and it stays in the command-exec stack.
2026-11-05 02:35:03 +01:00
Ludovic Marie 541a186944 Merge pull request #9583 from KBroneZ/claude/cf-instance-metadata-1896a7
🤖 Agent: The version and loader are read from the instance's own files only when mmc-pack.json gave none, so MultiMC and Prism imports read as before.
2026-11-05 02:34:59 +01:00
Ludovic Marie 6589f8b5ac Merge pull request #9532 from DUzzL/fix/9509-crumbling
🤖 Agent: ModelBakery.DESTROY_STAGES_OIT exists in 26.3. The OIT crack sprites are picked only with improved transparency on, so the default look is unchanged.
2026-11-05 02:34:55 +01:00
Ludovic Marie 837df857b7 Merge pull request #9529 from Delete228/fix/macos-native-metadata
🤖 Agent: native-mods.json is refreshed through a fresh hardlink and an atomic rename, so nothing is written through the old link. native-data is a relative symlink inside the app bundle.
2026-11-05 02:34:51 +01:00
Ludovic Marie 708374adb8 Merge pull request #9527 from CocofireHD/fix/9454-started-riding-all-passengers
🤖 Agent: started_riding now fires for every ServerPlayer the vehicle carries (Entity.getIndirectPassengersStream), not only for the player who boarded.
2026-11-05 02:34:47 +01:00
Ludovic Marie 275566a6e9 Merge pull request #9579 from GabrielNichols/codex/native-build-culprit
Launcher: name mods in injected-helper compiler errors
2026-11-05 02:32:16 +01:00
Ludovic Marie 77c2adcc03 Merge pull request #9480 from megabytesme/codex/issue-9408-launcher-arch-choice
Launcher: choose game architecture independently on Windows ARM
2026-11-05 02:32:10 +01:00
Ludovic Marie e39fd11f76 Merge pull request #9510 from CocofireHD/fix/9332-world-name-space
🤖 Agent: The focused field now gets the space before any screen shortcut sees it, so typing a world name no longer switches the Create New World tab.
2026-11-05 02:31:26 +01:00
Ludovic Marie f4e1f960f3 Merge pull request #9504 from Ragbie/fix/summoned-dragon-gateway
🤖 Agent: The fight keeps dragon_uuid (vanilla's save key) and ticksSinceDragonSeen, as EnderDragonFight.tick does. A summoned dragon that dies after the fight no longer opens a gateway or gives 12000 XP.
2026-11-05 02:31:22 +01:00
Ludovic Marie e16d3d514d Merge pull request #9486 from loicpiller/bugfix/9472-pack-float32-targets-gl
🤖 Agent: renderable_float swaps only a float target the adapter cannot render (to 16-bit float, then Rgba8Unorm). Vulkan, Metal and DX12 keep the pack's declared format.
2026-11-05 02:31:18 +01:00
Ludovic Marie 00ad11cc8f Merge pull request #9485 from loicpiller/bugfix/9472-shader-pack-gl-rgba32float
🤖 Agent: The native pack surface only holds bit patterns, written with blend: None and read with textureLoad. Rgba32Uint keeps the same bits and can be rendered on wgpu's GLES backend.
2026-11-05 02:31:14 +01:00
Ludovic Marie d35137a08a Merge pull request #9481 from vnsmoreira/fix/lava-complementary-investigation
🤖 Agent: Fluid blocks win the material vote only for their own still and flowing sprites. Voting for every other sprite is unchanged, and the new test covers the shared fluid sprite.
2026-11-05 02:31:10 +01:00
Ludovic Marie e1d3361a55 Merge pull request #9468 from CocofireHD/fix/9455-dualsense-hid-crash
🤖 Agent: Any hidapi error now drops the output handle, so hid_write is never called on a handle whose pad has gone. Input reading is untouched.
2026-11-05 02:31:06 +01:00
Ludovic Marie 8aaec9379b Merge pull request #9466 from CocofireHD/fix/9088-management-protocol-timeout
🤖 Agent: The harness now fails on a server that is still up after server/stop (the #8245 regression the issue names), and it runs the same check on Windows paths.
2026-11-05 02:31:02 +01:00
Ludovic Marie cd62fe430a Merge pull request #9460 from loicpiller/bugfix/9089-peaceful-summon-zombie-horse-health
🤖 Agent: At midnight monsters_burn is false, so isSunBurnTick returns before its RNG draw on both sides. The zombie horse reference stops being random, and the comparison itself is unchanged.
2026-11-05 02:30:59 +01:00
Ludovic Marie f99c7f026a Merge pull request #9578 from Cheezer1656/native/client-entity-refs
🤖 Agent: On a client, entity handles read only what the server synced. Writes (set_velocity, merge_nbt, tags) return false, and a handle from another level's epoch reads None, so a client copy changes only when the server says so.
2026-11-05 02:30:54 +01:00
Ludovic Marie eaf8caba79 Merge pull request #9575 from Vaspyyy/labpbr-map-budget
🤖 Agent: With the default texture budget, map_bytes is still exactly 64 MiB, so nothing changes until a player raises textureBudgetMiB. The unit test pins both cases.
2026-11-05 02:30:50 +01:00
Ludovic Marie f31d71f9e3 Merge pull request #9568 from Cheezer1656/feat/world-primitives-hook
🤖 Agent: The primitives get their own pipeline at the end of the world pass, before the hand. With no subscribing mod the batch is empty and prepare() returns before any upload or draw.
2026-11-05 02:30:47 +01:00
Ludovic Marie 24c3886a70 Merge pull request #9567 from Cheezer1656/feat/mod-entity-outline
🤖 Agent: native_entity_outline starts from vanilla's own answer (team colour or white) and costs nothing without a listener; masking above 24 bits keeps the colour in range.
2026-11-05 02:30:43 +01:00
Ludovic Marie f748e51444 Merge pull request #9560 from degepie/fix/lenient-lang
🤖 Agent: Language.loadFromJson parses with a lenient Gson reader in 26.3, so packs with comments and unquoted keys now load their lang files instead of being dropped silently.
2026-11-05 02:30:38 +01:00
Ludovic Marie 9e86daa984 Merge pull request #9549 from Vaspyyy/fix/8049-entity-team
🤖 Agent: Team joins happen in remember_uuid, which every entity load goes through. An entity loaded without a UUID gets one first, since the scoreboard keys entities by UUID, as Entity.load does.
2026-11-05 02:30:35 +01:00
Ludovic Marie 662dafce77 Merge pull request #9548 from Vaspyyy/fix/8048-hoglin-cannot-be-hunted
🤖 Agent: The hoglin reads CannotBeHunted and writes it back only when set (Hoglin.addAdditionalSaveData), so bastion hoglins from the template are no longer hunted by their own piglins.
2026-11-05 02:30:31 +01:00
TheMrZZandCodex gpt-6.1-sol 0722c2498b Merge current main while preserving piston read coverage
Keep the upstream piston entity-tick-order group and the independent
live-piston presentation diagnostic.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 02:30:12 +01:00
TheMrZZandCodex gpt-6.1-sol 72535840fc Expose live moving-piston NBT to read commands
Read the authoritative tick entry with command metadata and source-aware
pretty ordering. Preserve default output and reject unsupported writes.
Cover the original pulse, a normal sticky pull, and scoped diagnostics.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 02:29:43 +01:00
Ludovic Marie 4724fc5d4f Merge pull request #9580 from TheMrZZ/fix/h307-named-smoke-predicate
🤖 Agent: The named predicate now uses the existing campfire smoke helper (CampfireBlock.isSmokeyPos: lit tagged campfire, first-blocker exception), and the real game command harness covers it.
2026-11-05 02:25:41 +01:00
Ludovic Marie db3193f31b Merge pull request #9556 from rison1337/native-gui-render-post
🤖 Agent: gui_render_post has a read-only host and draws nothing without a subscriber; the fragment is pending, so the API number is assigned at build time.
2026-11-05 02:25:37 +01:00
Ludovic Marie 841076d774 Merge pull request #9554 from gigamans442/fix/callable-macro-boundary-20261104
🤖 Agent: The replacement is rescanned together with the following source, as the C preprocessor does, and per-token hiding still stops recursion.
2026-11-05 02:25:33 +01:00
Ludovic Marie 63b5c2bbf7 Merge pull request #9545 from gigamans442/fix/terrain-chunk-fade-20261104
🤖 Agent: mc_chunkFade is injected only when the renderer supplies it and the pack's vertex program reads it, so packs that don't ask compile as before. The disclosed inputs are an MIT pack's API names only.
2026-11-05 02:25:28 +01:00
Ludovic Marie 36070a818e Merge pull request #9543 from JohnUzoka/perf/worldgen-noise-fill
🤖 Agent: The values are bit-identical (tabulated perlin corners, 4-column interpolation rows, branch-free range choice), and your noise snapshots compare byte for byte.
2026-11-05 02:23:14 +01:00
Ludovic Marie 5c2322455b Merge pull request #9541 from JohnUzoka/perf/worldgen-surface-pass
🤖 Agent: The surface pass gives the same blocks: positive_y_bounds only skips bands where the vein density is provably <= 0, and the material_condition probe covers the column reads.
2026-11-05 02:23:10 +01:00
Ludovic Marie a7e2844af7 Merge pull request #9538 from Vaspyyy/fix/8055-sound-ids
🤖 Agent: The three new ids exist in 26.3's sounds.json and SoundEvents, and the old ones were silent. Only the event names change.
2026-11-05 02:23:06 +01:00
Ludovic Marie 0201bf30db Merge pull request #9537 from JohnUzoka/fix/in-bed-chat-keyboard
🤖 Agent: Only the in-bed chat is left out of pad_auto_keyboard. Opening chat by hand still brings up the on-screen keyboard.
2026-11-05 02:23:02 +01:00
Ludovic Marie 5bbd72231e Merge pull request #9535 from JohnUzoka/feat/pad-nintendo-letters
🤖 Agent: nintendoLetters is off by default, and splitscreen's join prompts still go by position, so no existing pad layout changes.
2026-11-05 02:22:58 +01:00
Ludovic Marie d59b1e6dfc Merge pull request #9533 from JohnUzoka/fix/leave-bed-focus
🤖 Agent: Leave Bed is now recorded as a widget, so pad focus, the D-pad and Tab all reach it. The drawing is unchanged.
2026-11-05 02:22:53 +01:00
Ludovic Marie 21f674bce9 Merge pull request #9514 from TheMrZZ/fix/command-fixture-floor-isolation
🤖 Agent: Tooling only. Clearing the floor to air right before the exact stone fill makes the 256-block guard independent of the scenarios that ran earlier.
2026-11-05 02:22:48 +01:00
Ludovic Marie b1cf1085ba Merge pull request #9499 from degepie/fix/data-merge-vehicles
🤖 Agent: /data merge and /data modify now reload minecarts (the new Minecart::load) and boats, which matches EntityDataAccessor.setData running Entity.load on every entity but a player.
2026-11-05 02:22:44 +01:00
Ludovic Marie c369c0d07f Merge pull request #9498 from degepie/fix/minecart-display-offsets
🤖 Agent: MinecartChest.getDefaultDisplayOffset is 8 in 26.3. The exhaustive match over CartKind makes any new kind choose its own offset.
2026-11-05 02:22:40 +01:00
Ludovic Marie b9c47ac7d9 Merge pull request #9497 from degepie/fix/loot-location-check
🤖 Agent: Biome tags now load with the same pack overlay as the other loot tags, and location_check reads biomes through holder_set (#tag, one id or a list), the HolderSet codec LocationPredicate uses.
2026-11-05 02:22:36 +01:00
Ludovic Marie 033bd81cff Merge pull request #9496 from degepie/fix/item-component-bare-keys
🤖 Agent: keyed() compares full(key) for the four keys item predicates read, and the potion_contents value is normalised the same way, as the registry codecs behind ItemPredicate do.
2026-11-05 02:22:32 +01:00
Ludovic Marie af0ad4003a Merge pull request #9495 from degepie/fix/chicken-egg-event
🤖 Agent: This matches Chicken.aiStep: the sound and entity_place come only after a drop. One small RNG note: the pitch floats are still drawn before the loot roll, which only differs for a data pack that empties chicken_lay.
2026-11-05 02:22:29 +01:00
Ludovic Marie 8572e1a893 Merge pull request #9494 from degepie/fix/entity-nbt-predicate
🤖 Agent: No vanilla advancement has an nbt or reference predicate, so mob_to_nbt only runs for data packs that ask for it (Tree::index_nbt). player_entity_for now takes &mut self; #9561 and #9562 are stacked on that.
2026-11-05 02:22:25 +01:00
Ludovic Marie 9d48f1a3ce Merge pull request #9463 from TheMrZZ/fix/h299-mob-rider-navigation
🤖 Agent: riders::tick returns at once for a mob with no mount, so lending the vehicle's navigation costs nothing outside ridden vehicles. #9597's branch also carries this head (their oracles meet in the same switch).
2026-11-05 02:22:20 +01:00
Ludovic Marie 95dc9bddb8 Merge pull request #9458 from voidsipp/fix/piston-entity-tick-order
🤖 Agent: Piston block entities now tick after entity travel, the order ServerLevel.tick uses (entities, then blockEntityTicks). The new 6-tick wither skeleton fixture covers both stone and slime, and it matches all 24 observations.
2026-11-05 02:22:15 +01:00
rison1337andCodex GPT-6 5b1505425b Native mods: client Host services and lifecycle events
Expose owned client/window, entity/head and profile snapshots, GUI painting, persisted mod bindings, local action bars and older-server opt-in through Host. Add startup, disconnect, shutdown and menu-background notifications without changing default gameplay.

Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 06:19:22 +05:00
KBroneZandClaude Sonnet 5.5 b97abf48b4 docs: fix the block count in the climbing test fixture comment
The fixture lists three non-trapdoor blocks (bridge, pier, odd), not four.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 03:14:56 +02:00
Ludovic MarieandClaude Opus 5.5 ed84f8c172 Merge #9597 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:14:04 +01:00
Ludovic MarieandClaude Opus 5.5 bfe4d37504 Merge #9463 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:13:47 +01:00
ElyTheCatandOpenAI Codex 5e9c0a708f bench: add entity occlusion release captures
Add FPS-overlay captures from the release OpenGL benchmark for PR #8941. The measured source revision is d87992b.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-05 04:39:35 +03:30
KBroneZandClaude Sonnet 5.5 fad60b1924 fix: a mod block tagged as a trapdoor without open or facing no longer stops the start
ClimbingRegistry::new returned "invalid trapdoor open state" (or "invalid
climbing facing") for any state of a block in minecraft:trapdoors that lacked
a valid open or facing property. Macaw's Bridges adds blocks that are not
trapdoors (rail bridges with facing/powered, a pier with no properties) to
minecraft:wooden_trapdoors, so All the Mods 10 and Craftoria exited before the
window opened.

Vanilla's LivingEntity.onClimbable tests `instanceof TrapDoorBlock`, not the
tag, so such a block is not a trapdoor and never climbs. A tagged state now
counts as an open trapdoor only with facing and open=true; otherwise it is
skipped. The ladder's facing check is unchanged.

Fixes #9585

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 03:07:28 +02:00
Ludovic MarieandClaude Opus 5.5 3da7f7a2a5 Merge #9562 onto #9561 and #9494
Both #9494 and #9562 index advancement triggers (nbt, player facts) and widen
the player's view: kept both. player_entity_for adds the compound on &mut self;
player_entity_for_any (#9562) stays &self for its &self callers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:59 +01:00
Ludovic MarieandClaude Opus 5.5 af07813fe1 Keep the killer's view on &self after #9494
#9494 makes player_entity_for take &mut self to build the player's compound;
guest_entity_view (#9561) is &self, so it reads what the player looks at only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:39 +01:00
Ludovic MarieandClaude Opus 5.5 d4c48baf8c Merge #9561 onto #9494
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:32 +01:00
VaspyyyandClaude Opus 5.5 25b8c5d099 Collision scan: pass the cells of missing chunks and empty sections a row segment at a time
A far move (one move packet thousands of blocks away) made the server's
collision sweep walk every cell of the swept box, one query per cell:
a 2.3 s tick for a hop of 8,000 blocks, growing with the square of the
distance.

Two opt-in provider hooks let the scan pass, after one query, the rest of
a row inside the same chunk (at most 16 cells):

- missing_chunks_stay_missing: a chunk missing once stays missing for the
  scan (SceneCollisions, EntityCollisions, EmptyCollisions).
- section_is_empty: no state in the section's palette has a collision
  shape (SceneCollisions, cached per section in the scan).

Both default to false, so the parity probe, which records every query,
sees the same calls. Skipped cells yield nothing, so the collisions and
their order are unchanged.

Fixes #9594

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:19 +01:00
Ludovic MarieandClaude Opus 5.5 bd8d2916b7 Merge #9588 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:07 +01:00
Ludovic MarieandClaude Opus 5.5 793f2ead2b Merge #9552 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:07 +01:00
Ludovic MarieandClaude Opus 5.5 7ae746e633 Merge #9550 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:07 +01:00
Ludovic MarieandClaude Opus 5.5 080fd9da02 Merge #9531 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:06 +01:00
Ludovic MarieandClaude Opus 5.5 76f1c176f1 Merge #9516 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:06 +01:00
Ludovic MarieandClaude Opus 5.5 870e7b1cdd Merge #9513 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:06 +01:00
Ludovic MarieandClaude Opus 5.5 e45e23fc36 Merge #9482 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:05 +01:00
Ludovic MarieandClaude Opus 5.5 4e32a8ba35 Merge #9478 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:05 +01:00
Ludovic MarieandClaude Opus 5.5 f889e41fee Merge #9503 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:05 +01:00
Ludovic MarieandClaude Opus 5.5 de275afd6d Merge #9474 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:04 +01:00
Ludovic MarieandClaude Opus 5.5 08db1a74b8 Merge #9462 into the command-exec stack
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:05:04 +01:00
Florian ERNSTandCodex cdd00e29a3 Place boat items at the exact server POV hit point
Co-Authored-By: Codex <[email protected]>
2026-11-05 02:01:51 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 c724742f38 docs: HDR design note: clear coverage on blur, headroom read per frame
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 02:00:06 +01:00
Ludovic MarieandClaude Opus 5.5 d687fe6cad Merge #9515 (worker-side widths reduction) into the detail budget
Both add a test at the end of lod's tests; kept both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:58:17 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 fc15467786 docs: HDR design note: HUD coverage in its own target, alpha left as today
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:57:02 +01:00
Ludovic MarieandClaude Opus 5.5 a97b284ddb Studio: drop a needless return (clippy)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:56:49 +01:00
Ludovic MarieandClaude Opus 5.5 530635f250 Format the LabPBR budget test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:53:01 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 e770b6a684 docs: HDR design note: HUD in gamma on a unorm backdrop with explicit coverage, blur keeps light
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:52:59 +01:00
AxxioandAntigravity 57c29198e5 launcher: name the mod in E_MODULE_CONFLICT and log conflicting pins (fixes #9391, #9016, #9414)
Managed, architected and reviewed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 01:52:07 +01:00
Ludovic MarieandClaude Opus 5.5 8944278cdb Merge origin/main into fix/9284-server-entity-sounds
dispense_fixture: main's event_sound lines with this branch's CapturedSound fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:50:03 +01:00
JanandClaude Sonnet 5.5 f9e5774b2c Native mods: Host::send_map, the drawn-map sender (native_send_map) as a Host helper
native_send_map (API 132) sends a 128 x 128 map picture to a player but was
not in the helper catalog, so a trait mod could not call it. One catalog
fragment adds it to Host as send_map(uuid, map_id, colors) -> bool.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 01:44:46 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 207adf0d13 docs: HDR design note: linear blending in 0.2, exact HUD composite
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:42:18 +01:00
MegaBytesMe 1f55f9ecfc fix(launcher): select ARM64 resource tools for native builds 2026-11-05 00:41:18 +00:00
Ludovic Marie b368b50c60 Merge pull request #9577 from xxaxio/fix/9505-dx12-terrain-flat-fxc
🤖 Agent: binding the slot origin to a let first matches vs_section_flat_opaque/cutout; it is compatible with #9584's packed_corner change, which fixes the other X3500 site in the same shader.
2026-11-05 01:40:01 +01:00
Ludovic Marie 1936541d03 Merge pull request #9584 from DUzzL/fix/9551-fxc-terrain
🤖 Agent: checked the select() rewrite of packed_corner: rel = (i - axis) mod 3 gives the axis component +side, the next +du, the last +dw, the same values as the old indexed stores, with no dynamic l-value for FXC.
2026-11-05 01:39:56 +01:00
Mayonesu0andClaude Opus 5.5 7975ff7942 Launcher MCP: reach a game started from its window with Agent Control on
The launcher's game_* tools only looked at the endpoint in its data folder,
so a game the player started, which listens on the game's default endpoint,
answered "not running under agent control". Each call now goes to the first
endpoint where a game answers, the launcher's own first, restarting the
horizon-ctl child when that changes: an installed horizon-ctl keeps the
endpoint it started with.

Whether a game answers is now tested on Windows too, with the endpoint's port
and token, rather than by the file existing: a crashed game leaves the file.

Fixes #9465

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:39:00 +01:00
Florian ERNSTandCodex 194324b652 Support live item component contents slot sources
Co-Authored-By: Codex <[email protected]>
2026-11-05 01:34:11 +01:00
Ludovic Marie 0d0bf67a24 Merge pull request #9436 from xxaxio/fix/8689-change-instance-icon
🤖 Agent: Queued. The home icon and the new ⋯ Edit instance item open the existing edit dialog. In the palette, the instance's saved picture stays offered after another icon is picked, so the player can go back to it without choosing the file again. Both new labels already have translations in launcher/lang.
2026-11-05 01:32:57 +01:00
Ludovic Marie 422ad93339 Merge pull request #9518 from xxaxio/fix/9507-launcher-join-button-overlap
🤖 Agent: Queued. The invite field's right padding now follows the Join label's width (14 px padding each side, about 8.5 px per character at 14 px), so the longest translations (Bulgarian and Latvian, 15 characters) get 175 px instead of 84 and the button no longer covers the code. In the wide layout the field grows by the same amount (340 to 431 px at most), which comes out of the row's flexible gap. English stays at 84 px.
2026-11-05 01:32:53 +01:00
abit-foggyandCodex 99c629e96e feat(studio): add recording capture
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:29:52 -10:00
DUzzL 69f4bdb829 Seat each happy ghast passenger at its own attachment point 2026-11-05 01:29:29 +01:00
Ludovic Marie 1f2658e5a5 Merge pull request #9487 from xxaxio/fix/9438-container-loot-tables
🤖 Agent: Queued after a small fix on the branch. On main, barrels, shulker boxes, hoppers, dispensers and droppers already load as BlockEntity::Chest, so #9189 already covered them. This PR's real change is that a LootTable id the codec rejects no longer counts as a table, which matches vanilla 26.3's tryLoadLootTable (KEY_CODEC read, then null). I folded the four copied tests into one table test and made roll_loaded_loot read the id the same way, so an invalid table with no items rolls nothing. Desktop run: clippy -D warnings clean, horizon-client-core 63 tests, horizon-client 895 tests, horizon-client-lib 1718 tests.
2026-11-05 01:29:18 +01:00
Ludovic Marie b8d113b761 Merge pull request #9251 from horizon-rust/pvp/preset
🤖 Agent: the preset only sets PvP & HUD options and vanilla's toggle-crouch, and turns on the official fps/keystrokes mods only if they are installed. Verify passes at 41c0e4080 (the stack's top: workspace clippy -D warnings clean on the desktop).
2026-11-05 01:29:14 +01:00
Cheezer1656 5ea6d5415e Merge remote-tracking branch 'upstream/main' into feat/world-primitives-hook
# Conflicts:
#	crates/horizon-client/src/game/native_hooks.rs
2026-11-04 17:27:48 -07:00
Ludovic MarieandClaude Opus 5.5 604991a5c9 save: one table test for the randomizable containers, and roll_loaded_loot reads the table as tryLoadLootTable does
The barrel, shulker, hopper, dispenser and dropper copies of the chest test
become one table-driven test. A loaded container whose LootTable the codec
rejects now rolls nothing, as vanilla keeps no table for it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 01:27:21 +01:00
Ludovic Marie 16ea866df9 Merge pull request #9250 from horizon-rust/pvp/chat
🤖 Agent: after martipops's review, the chat search puts the typed text aside and gives it back on every exit path, a compact repeat keeps a reader's scroll, and search and copy skip legacy § codes (tests for the last two). Compact chat still never merges signed messages. Verify passes at 4984cc061.
2026-11-05 01:26:42 +01:00
Ludovic Marie 763e220a0f Merge pull request #9249 from horizon-rust/pvp/view-time-weather
🤖 Agent: the own time of day and weather change only what this client draws (sky, lightmap, rain); the world's day_time and weather, and everything that plays by them, stay the server's, and nothing is sent. Off by default. Verify passes at cd788cd4c.
2026-11-05 01:24:08 +01:00
Ludovic Marie c8e787810c Merge pull request #9248 from horizon-rust/pvp/visual-tweaks
🤖 Agent: the visual tweaks (low fire, clear water/lava fog) are off by default and stop at the clearest view vanilla itself gives. docs/pvp.md now says they can matter on strict servers (from martipops's review on #9250). Verify passes at d2dafa504.
2026-11-05 01:21:44 +01:00
Ludovic Marie 8db0b47cff Merge pull request #9247 from horizon-rust/pvp/numeric-enchants
🤖 Agent: numeric enchantment levels change only the tooltip's displayed numeral (off by default); the enchantment data and the rest of the tooltip stay vanilla. Verify passes at 6eae1c73b.
2026-11-05 01:19:19 +01:00
Ludovic Marie 7298c80f8f Merge pull request #9246 from horizon-rust/pvp/hud-info
🤖 Agent: after martipops's review, the combo counts a hit only when the server's damage event names this player as the direct entity (spear jabs included, teammates' hits and fire ticks not), the off server widget no longer allocates each frame, and ping shows nothing before the first latency update. Verify passes at 3842a7aee.
2026-11-05 01:16:56 +01:00
Ludovic Marie aff293b08a Merge pull request #9245 from horizon-rust/pvp/hud-items
🤖 Agent: the armour, held-item and effects widgets read only what the client already holds, and the effects widget replaces vanilla's icons only while it is on (all off by default). Verify passes at f9f1b5abf, with main 6319cb701 merged in; no harness is touched, so parity-main covers it.
2026-11-05 01:14:28 +01:00
TheMrZZandCodex gpt-6.1-sol 9d95fe645b Use campfire smoke rules in named location predicates
Delegate the predicate view to the existing lit and occlusion-aware smoke
helper. Cover both predicate signs, callback stores, slab exceptions and
positive world guards through the real command runtime.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 01:10:40 +01:00
DUzzL ae257923c1 Fix FXC X3500 in terrain shader: no dynamic vector component stores 2026-11-05 01:10:06 +01:00
KBroneZandClaude Sonnet 5.5 10c3813399 fix(port-pack): read version and loader from a CurseForge app instance
Importing a CurseForge app instance folder left minecraft, loader and
loader_version empty, because instance() read them only from
mmc-pack.json. Fill them from minecraftinstance.json (gameVersion,
baseModLoader.name), falling back to manifest.json via the existing
curseforge() parser. minecraftinstance.json wins: it records what the
instance runs, while manifest.json is the pack author's copy and goes
stale when the player changes version or loader in the app. A Fabric or
Quilt id's trailing Minecraft version is not part of the loader version.
Missing or broken files never fail the import; mmc-pack.json behaviour
is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-05 02:07:02 +02:00
MegaBytesMe 4631d96891 Merge remote-tracking branch 'upstream/main' into codex/issue-9408-launcher-arch-choice
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-05 00:01:36 +00:00
AxxioandAntigravity af3026b999 gfx: bind flat terrain slot origin to local variable for FXC (fixes #9505)
In vs_section_flat (crates/horizon-client-render/src/terrain.wgsl), passing the nested dynamic storage buffer expression slot_origins[block_slots[s.slot >> 8u]] directly into corner_vertex(s.c, ...) causes Direct3D 12's HLSL compiler (FXC for SM 5.1) to fail during 'Terrain solid (flat)' pipeline creation with: error X3500: array reference cannot be used as an l-value; not natively addressable.

Like vs_section_flat_opaque and vs_section_flat_cutout, bind the sampled origin to an intermediate local variable first, letting naga emit a load into a temporary r-value before the function call.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 01:01:18 +01:00
GabrielNichols 99035b09ad fix(launcher): attribute injected-helper compiler errors 2026-11-04 20:59:20 -03:00
Claude ce86945277 Size LabPBR maps from the texture budget
LabPBR normal and specular maps had a fixed 64 MiB budget, so with about
1300 block layers they always loaded at 64 px, however high
textureBudgetMiB was set. They now get a quarter of the current budget:
the default budget keeps the old 64 MiB, and 1536 MiB gives 256 px maps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QPo9BpqkYWfY6y5Nwu
2026-11-04 23:55:03 +00:00
Cheezer1656andClaude Opus 5.5 9002abcdea Native mods: entity handles and menu stacks on a client
native_entity and the entity_*/living_* reads now work against the
entities the server shows the window (own player, other players, mobs,
armor stands with their custom names and equipment, tracked-only
entities by type and position); a fresh epoch per client level keeps
handles from crossing levels or sides. Writes are refused and saved
data and tags read as None on a client. native_menu_stacks gives the
open server menu's stacks, with components, by server slot number.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:54:14 -07:00
ElyTheCatandOpenAI Codex d87992bdf3 perf(entity-culling): reduce occlusion query overhead
Skip doomed async work while the camera moves, reduce synchronous voxel-plane lookups, and cull dynamic block entities before generating their geometry. Reuse chest state and remove dye-prefix sorting from the block-entity hot path.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-05 03:23:26 +03:30
Ludovic MarieandClaude Opus 5.5 41c0e40804 Merge 4984cc061b into pvp/preset
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:29 +01:00
Ludovic MarieandClaude Opus 5.5 4984cc061b Merge cd788cd4c7 into pvp/chat
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:29 +01:00
Ludovic MarieandClaude Opus 5.5 cd788cd4c7 Merge d2dafa504c into pvp/view-time-weather
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:28 +01:00
Ludovic MarieandClaude Opus 5.5 d2dafa504c Merge 6eae1c73b4 into pvp/visual-tweaks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:28 +01:00
Ludovic MarieandClaude Opus 5.5 6eae1c73b4 Merge 3842a7aee2 into pvp/numeric-enchants
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:28 +01:00
Ludovic MarieandClaude Opus 5.5 3842a7aee2 Merge f9f1b5abfc into pvp/hud-info
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:27 +01:00
Ludovic MarieandClaude Opus 5.5 f9f1b5abfc Merge 6319cb701e into pvp/hud-items
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:48:27 +01:00
Ludovic Marie 6319cb701e Merge pull request #9244 from horizon-rust/pvp/outline-hit
🤖 Agent: block outline and hit colour stay render-only and off by default; verify and parity pass at 4adfac92b. The composter fix #9508 is merged in. pack_screen's failures came from the playhorizon runner missing 142 vanilla lang files: the files were copied there and it passes now.
2026-11-05 00:48:10 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 10566037aa docs: HDR design note: per-channel tint factor for the vignette
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:46:43 +01:00
Ludovic Marie 11aa3b57ce Merge remote-tracking branch 'origin/main' into cg41/9487 2026-11-05 00:46:04 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 66ec8716b3 docs: HDR design note: keep the vignette on light above white
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:44:02 +01:00
Cheezer1656andClaude Opus 5.5 9897711a34 Native mods: world primitives hook (lines, boxes, beams, text)
native_world_primitives runs once per world frame and hands a client mod a
batch of world-coordinate primitives: lines with a width, box outlines and
filled boxes in ARGB, beacon-style beams and camera-facing text in the game
font (optionally scaling with distance). Each can be seen through walls.

The game tessellates them camera-relative and the renderer draws them with
its own pipelines (gfx/world_primitives) at the end of the world pass,
after the entities and before the first-person hand: the world's reversed
depth, draw resolution and TAA jitter. A GPU test shows depth-tested quads
and lines hidden behind a wall while see-through ones show.

Closes #9555

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:41:21 -07:00
Nikita Maximilian GrimmandClaude Opus 5.5 670c72c42c docs: HDR design note: keep 0.1 on today's path, HUD as today, rebuild on frame changes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:41:01 +01:00
Cheezer1656andClaude Opus 5.5 a23690ed84 Native mods: force or remove an entity's glowing outline; budget visuals per mod
native_entity_outline(entity_id, kind, outline) lets a client mod make any
mob, player, dropped item or projectile glow in its own colour, or take the
outline away (Fabric/NeoForge isCurrentlyGlowing and team colour overrides).
Vanilla's answer comes in; only what the client draws changes.

native_visuals no longer shares 8 rims and 256 edges between all mods: each
trait mod is dispatched on its own and gets 256 rims, 4096 rim edges and
8 lights. A panic in a mod dispatched alone still names the right mod.

Closes #9553

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:40:08 -07:00
AxxioandAntigravity d9869348a5 launcher: auto-detect Minecraft 26.3 installs from Dawn Client (fixes #9547)
Dawn Client stores its official vanilla Minecraft assets, libraries, and client jar in .dawn/cache/minecraft/ rather than the default .minecraft directory.

Add Dawn Client default folders to known_folders, include cache/minecraft in find(), resolve Dawn sync/instance directories upwards to the launcher root in resolve(), and update troubleshooting advice.

Co-Authored-By: Antigravity <[email protected]>
2026-11-05 00:37:16 +01:00
rison1337andCodex GPT-6 f48484a37a Native mods: final GUI overlay and current-frame camera
Co-Authored-By: Codex GPT-6 <[email protected]>
2026-11-05 04:31:17 +05:00
Claude 82f601072b Server panel: the launcher's Glass look, a page for each part, in-page dialogs
The panel takes the Glass look from docs/design-system.md: the sunset with pixel ridges, frosted
plates, Geist and Bricolage Grotesque Heading, the theme's Hugeicons. What it already did gets a page
each in a sidebar (overview, players, console, backups, crash reports), and confirm()/prompt() become
in-page dialogs. The supervisor serves the theme's fonts and the mark; the CSP gains font-src 'self'.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jh5A2PzdusBChqE38VZ3zD
2026-11-04 23:29:16 +00:00
Nikita Maximilian GrimmandClaude Opus 5.5 cf82881271 docs: design note for linear light in HDR and shader API 0.2
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:29:14 +01:00
Florian ERNSTandCodex 6077d43cc9 Round-trip generic mob home NBT through live state
Co-Authored-By: Codex <[email protected]>
2026-11-05 00:28:10 +01:00
TheMrZZandCodex gpt-6.1-sol 5639d42a34 Handle boat callers in execute on leasher
Read the boat's existing resolved leash state and reuse the holder
conversion already used for mobs. Keep pending saved UUIDs separate.

Cover pending, live and unleashed contexts through the real Game command
path, with typed subjects, callbacks, stores and measured clocks. Retain
movement as a separate raw diagnostic and isolate the harness directory.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-05 00:21:42 +01:00
degepieandClaude Opus 5.5 5b94ce351c Advancements: a guest's death triggers read the blow before the death message
The death message takes the combat record's entries and leaves them
empty, so entity_killed_player and kill_mob_near_sculk_catalyst, which run
after it in guest_die, saw no killing blow while show_death_messages was
on: any killing_blow condition failed. ServerPlayer.die reads the
CombatTracker for its triggers before recheckStatus clears it at the end,
so the blow is now taken before the message is built.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 04:13:33 +05:00
gigamans442andCodex 53aeeaff45 Preserve recursive hiding through empty paste operands
Keep the nonempty operand's token metadata when pasting with an empty operand.
Original GCC/Clang controls and the pristine-main control retain the following
call's parentheses; add that regression to the boundary suite.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-04 18:13:10 -05:00
dfrnochandClaude Opus 5.5 77c059b347 Resource packs: an animated item sprite outside item/ and block/ shows one frame in the GUI
A pack's items or blocks atlas can list its own folders (Wynncraft's `wynn/...`). Their animated
strips were packed whole, so a 16x32 two-frame item drew both frames squashed into its slot.
They now keep their first frame and animate, as vanilla does for every sprite of those atlases;
a still texture there stays whole.

Fixes #9540.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:12:00 +01:00
VaspyyyandClaude Opus 5.5 13766ed75d Entities join the team their Team key names when they load
Entity.load: a string Team adds the entity's UUID to that scoreboard team
when it exists, so /summon ... {Team:"red"} and structure or data pack
entities join it. Team is only read, as in vanilla, so the kept compound
drops it.

Fixes #8049

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:09:32 +01:00
VaspyyyandClaude Opus 5.5 f5af9d2d45 Hoglins keep CannotBeHunted, and piglins don't hunt the ones that have it
Hoglin.readAdditionalSaveData/addAdditionalSaveData keep the CannotBeHunted
flag (a bastion's hoglins are placed with it), and Hoglin.canBeHunted is
false for them: PiglinSpecificSensor never picks them as prey and a hit
from one doesn't rally the piglins around.

Fixes #8048

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:09:30 +01:00
gigamans442andCodex 47684e0f4a fix(shaders): route terrain fade through pulled geometry vertices
Initialize the implicit input per source vertex and isolate it from geometry-stage globals, with original paired and GPU controls.

Co-Authored-By: Codex (GPT-6.1 Sol) <[email protected]>
2026-11-04 18:08:03 -05:00
John Uzoka 5ee6d9da54 Controllers: a Nintendo pad can go by its letters (A confirms)
A Nintendo pad's face buttons are lettered the other way round from an Xbox pad's: A sits on
the right and B at the bottom. Going by position (SDL's layout, the default), B confirms and
jumps and A goes back, while the Switch's own menus and keyboard confirm with A.

PadSettings::nintendo_letters (`nintendoLetters` in horizon-controller.txt; off unless set, on
in a build for the Switch itself) reads a Nintendo pad's face buttons by their letters: A plays
South (confirm, jump), B East (back, drop), X West, Y North (Button::by_letter,
PadState::by_letter). It applies to a player's pad state, read from the pad or fed by
splitscreen's router, before horizon-ctl's virtual pad is merged; splitscreen's join and
lost-controller prompts, which a controller nobody holds yet answers, stay by places. Bindings
still name the part, so they mean the same either way. With Nintendo glyphs, the binds list and
the on-screen keyboard's hints name the button that plays each part (OptionsUi::pad_by_letter).
docs/controllers.md describes the setting.

Test: horizon-client-input gamepad::tests::a_nintendo_pad_by_its_letters_confirms_with_a, and
the settings round trip.

(cherry picked from commit fb2490c02, reworked onto main)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:37 -06:00
John UzokaandClaude Opus 5.5 e4d981d3d5 Controllers: Leave Bed takes the focus on the in-bed chat
On the in-bed chat (InBedChatScreen) the pad and the keyboard could only reach the chat box:
Leave Bed was drawn with screen_draw::button, which registers no widget, and focus only lands on
what a frame registers (key_focus::drawn_targets). Game::leave_bed_draws records it as a button
at the bounds the click checks, so the pad's focus starts on it (pad_first_target's first
button), D-pad up from the chat box reaches it, the keyboard's Tab reaches it as vanilla's does,
and A or Enter wakes the player.

Test: game::chat::tests::leave_bed_is_a_focus_target_above_the_chat_box.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:35 -06:00
John UzokaandClaude Opus 5.5 0d2fb5307e Controllers: the in-bed chat doesn't open the keyboard by itself
A sleeper gets InBedChatScreen, a chat screen the game opens on its own. With a pad in use,
pad_auto_keyboard opened the on-screen keyboard over it (Steam's on a Steam Deck) as if the
player had opened chat to type, every time the player lay down. The keyboard now waits for A
on the chat box there; chat the player opens still brings it up. docs/controllers.md says so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:32 -06:00
John Uzoka fe47a52ef0 Storage: packed reads and writes inline into other crates
PackedStorage::read and write are the per-block word accesses of section
fills and lookups in horizon-world (BlockSection::set and fill_run, the
terrain fill's solid runs, surface column reads). Without #[inline] each
was an out-of-line call across the crate boundary that recomputed the
slot; inlined, the caller's loop keeps the slot arithmetic and word
access in place. Behavior is unchanged.

(cherry picked from commit 680cabdba245cd9c373fde94164377353c952785)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:30 -06:00
John Uzoka 2dbfdf9faf Density: a bulk range choice selects with a branch-free loop
The in-range/out-of-range merge stored the out-of-range value only where
the selector was outside the range, a conditional store LLVM leaves as a
branch per value (unpredictable for noise selectors). It now writes
every value as a select between the in-range and out-of-range samples,
which vectorizes; the selected values are the same.

(cherry picked from commit 5434c4113f94d6fa98a56f581eea1f8d27ac1352)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:30 -06:00
John Uzoka 671e36dd9e Density: interpolation fills a cell's four-column rows side by side
A block volume's interpolated values come from repeated addition down
each column, one dependent chain per column. A cell row four columns
wide (every row of a 4-wide cell inside the volume) now advances its
four chains together, writing each column's value before adding its
increment, so their additions overlap instead of waiting on each other.
Each column keeps the same operations in the same order. Corner samples
are read through precomputed column offsets instead of a checked 3D
index per corner. Partial rows, and rows whose columns fail validation,
keep the one-column-at-a-time loop and its error order.

(cherry picked from commit 11964a2c3ed6dc8752ef46f3a05ed27d0c36602d)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:30 -06:00
John Uzoka 5f39ba3424 Noise: a perlin volume tabulates its corner terms when y cells change often
A column's corner XZ dot products and Y gradients depend on the y cell
only through each corner's gradient index. When a volume's y samples
change cell at least four times, each column tabulates the sixteen
gradients' terms once (the same operations as before) and every cell
change reads them, instead of loading three gradient components and
computing two products per corner. The per-y cells, fades and offsets
are kept as separate lanes. High-frequency octaves (the top ten of the
blended noise's limits) gain the most; values are bit-identical.

(cherry picked from commit f3bf17341351a1e02028434765195f5c252b2d24)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:29 -06:00
John UzokaandClaude Opus 5.5 2af20b88de Docs: ore veins are sampled and applied only within the y range their density can be positive
(cherry picked from commit 066ecb8b40a849cb597ad484d01fd6926a182a5d)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:27 -06:00
John Uzoka 40971a96a5 Density: a y gradient fills a volume by copying one computed column
Volumes are laid out with y contiguous in each column, so the y-axis
gradient computes the first column and copies it into the others,
instead of storing each value at a stride across every column. The
values, and the error on a zero-period gradient, are as before.

(cherry picked from commit b01ee5606dc23caea2713954a71987aa76c37a00)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:27 -06:00
John Uzoka 3872944113 Worldgen: the surface walk reads each column by section runs and keeps block kinds
A column is filled from its sections: an all-air or one-state part at
once, a mixed section from its storage. Beside each id the cache keeps
whether the block is air, fluid or solid, so the walk and its
stone-depth look-ahead no longer look the properties up per read. The
band is stepped down as the walk descends instead of searched per block,
and the column's minimum surface level is read once.

(cherry picked from commit a88b3f32dfd283741de6e149708b5325a90a4386)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:27 -06:00
John Uzoka a5fec952dd Worldgen: ore veins leave the surface bands where their density cannot be positive
An ore vein places nothing where its density is not positive, and
returns before drawing its positional random there. Rule libraries read
from the density graph where that holds (DensityGraph::positive_y_bounds:
a range choice on a clamped y gradient whose out-of-range value is a
constant <= 0, as vanilla's copper and iron veins are), so datapack
ranges are followed. The surface pass splits its y bands at those
bounds, drops each vein from the bands outside them, makes each band's
variant on first use, and does not set up a block whose variant gives
nothing. The veins' up-front densities are sampled over one shared
volume cut at the highest y a vein reaches; cutting a volume's top
leaves the values below it as they were.

(cherry picked from commit 8c101d76d544e84987274b8c01b44a0811430eb9)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:27 -06:00
John Uzoka bf29005bb2 Worldgen: the surface pass skips heightmap updates that cannot change anything
A write at or below a heightmap's first available y - 2 leaves it as it
is (Heightmap::update returns early). Each surface column now carries
that bound per stage map, as fill_noise does, refreshes it when a map
changes, and calls Heightmap::update directly above it, without the
repeated registry and ensure_heightmap checks. A missing map still goes
through set_block_worldgen, which primes it.

(cherry picked from commit 2015977dfeb33f38d64be64b9b643a3059544d2a)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:59:27 -06:00
Florian ERNSTandCodex c44b53ebaf Apply server lava-contact damage to XP orbs
Co-Authored-By: Codex <[email protected]>
2026-11-04 23:58:27 +01:00
JoansitohandClaude Sonnet 5.5 5bcabf145e Server packs: a reload names its packs by identity, so a replaced pack ends (#9528)
ServerPackManager keeps packsToLoad and packsToUnload as ServerPackData
objects; the port kept ids, and a pack pushed again with an active pack's id
was set active again after every reload: an endless resource reload.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 23:52:13 +01:00
VaspyyyandClaude Opus 5.5 7bcb47b1e9 Play the turtle egg break, conduit short ambient and campfire douse sounds under their 26.3 ids
Three sounds were played under ids that 26.3's sounds.json doesn't have,
so they were silent: block.turtle_egg.break (entity.turtle.egg_break),
block.conduit.ambient_short (block.conduit.ambient.short) and
block.generic.extinguish_fire (entity.generic.extinguish_fire).

Fixes #8055

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 23:50:38 +01:00
DUzzL c1e5d33c42 Use the OIT destroy-stage sprites for breaking cracks with improved transparency 2026-11-04 23:50:18 +01:00
degepieandClaude Opus 5.5 c66ca9f1f6 Advancements: the player predicate tests stats, advancements, recipes and level; inventory_changed tests its player
PlayerPredicate's stats, advancements (done or per criterion), recipes and
level were read by nothing, so criteria using them were granted to anyone.
They are now tested as PlayerPredicate.matches does. The player's records
are copied into its view only for the triggers whose criteria may ask
(Tree::player_facts_asked) and for the command predicates that name them
(facts_asked_in); a field asked of records not filled fails.
inventory_changed now also tests the criterion's player conditions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKjxbXfo4yzskjMcNq
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 03:48:28 +05:00
degepieandClaude Opus 5.5 3e0d3737b1 Advancements: a player's death fires entity_killed_player
ServerPlayer.die calls awardKillScore on the entity
getKillCredit names, which fires ENTITY_KILLED_PLAYER (KilledTrigger) for
the dying player with that entity and the death's damage source. Nothing
fired it: guest_die, every server-side player death, now does, between
the deathCount scores and the deaths statistic.

The killer lookup and the killing blow the sculk catalyst trigger built
for the same death move into shared helpers, and KilledTrigger's matching
into advancements::killed, unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKfuWtefRGy97WTU4a
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 03:47:20 +05:00
gigamans442andCodex 78e6faaf23 Merge accepted main for current-base qualification
Refresh the accepted base before publication. The shader/core/backend inputs
and contribution/build rules are unchanged; rerun required checks on this head.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-04 17:45:19 -05:00
CocofireHDandopencode e8f5473926 Fire started_riding for every player on the vehicle
Entity.startRiding(entityToRide, force, sendEventAndTriggers) runs
CriteriaTriggers.START_RIDING_TRIGGER for every ServerPlayer in
entityToRide.getIndirectPassengersStream() once the rider link is set, not
only for the entity that mounted. Horizon fired the trigger at each mount
site for the acting player alone.

Route every rider-link change through Game::start_riding /
note_started_riding, which queues the vehicle and fires
trigger_started_riding for each server player aboard (directly or through a
mob rider). A guest's turn defers the pass to tick_lan, where every player is
back in place. The per-site trigger_player calls are gone; cushions,
minecarts, /ride on a mob and mobs boarding boats/carts now fire it too.

The new boarding test pins the indirect-passenger set (direct rider, rider of
a rider, and an unrelated vehicle).

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 23:43:17 +01:00
Delete228andCodex 69e6298905 Fix macOS game bundle native build companions
Refresh the native build receipt independently of the executable and expose runtime data through a relative directory link. Remove stale companions for vanilla builds and preserve unexpected app-side data on errors.

Fixes #9521

Co-Authored-By: Codex <[email protected]>
2026-11-05 01:41:13 +03:00
valkdzx cdbb5792d5 Merge remote-tracking branch 'origin/main' into perf/behaviour-snapshot-key 2026-11-05 00:37:31 +02:00
valkdzxandClaude Opus 5.5 56e06994d3 Startup: keep the behaviour and registry snapshots across rebuilds
The snapshots in .horizon-cache keyed on the executable's modification
time, so every rebuild of the game threw them away and the first start
parsed facts.json and block-behaviours.json again. They now key on a hash
of the code that resolves them (horizon-world and horizon-core sources,
their manifests, Cargo.lock, profile, target and rustflags), computed by
horizon-world's build.rs.

Fixes #9522

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:37:31 +02:00
gigamans442andCodex df1c2f8fd3 Fix callable macro expansion across source boundaries
Rescan replacement tokens with the remaining source while preserving per-token
recursion hiding through argument substitution and pasting. Recognize calls
continued after expanded names and retain their original source locations.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-04 17:33:51 -05:00
Florian ERNSTandCodex e1255d97f0 Handle unprepared data in the quad-leash regression
Keep the resource-backed physics assertions active when the fixture runs, and follow the existing optional-data convention in ordinary CI.

Co-Authored-By: Codex <[email protected]>
2026-11-04 23:29:23 +01:00
degepieandClaude Opus 5.5 c9b4417f04 Lang: read resource pack lang files leniently, as Gson does
A lang file that is not strict JSON (comments, unquoted or single-quoted
strings, raw control characters...) was dropped without a word, so a
resource pack whose lang files carry comments showed nothing. Strict files
read as before; the others go through the existing Gson lenient JsonReader
port, as Language.loadFromJson is expected to read them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMLmvSqLicupMqfESW8
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 03:29:16 +05:00
gigamans442andCodex 7e7e6dc482 fix(shaders): expose terrain section visibility to fade-aware packs
Supply mc_chunkFade from the encoded section transition and advertise FADE_VARIABLE without changing native terrain rendering.

Co-Authored-By: Codex (GPT-6.1 Sol) <[email protected]>
2026-11-04 17:27:53 -05:00
Ludovic MarieandClaude Opus 5.5 4adfac92b7 Merge origin/main into pvp/outline-hit
Brings in #9508 (composter fixture fix) so parity can pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 23:27:35 +01:00
Florian ERNSTandCodex c2973c43e9 Merge main before quad-leash CI repair
Co-Authored-By: Codex <[email protected]>
2026-11-04 23:25:22 +01:00
AxxioandAntigravity 882149912a launcher: dynamic right padding for friend invite code input (fixes #9507)
In localized versions of the launcher where the 'Join' button has a longer label (such as French 'Rejoindre', German 'Beitreten', Ukrainian 'Приєднатися'), the static right padding of 84 px caused the button to overlap the placeholder text and entered digits.

Compute right padding dynamically based on localized button text length and adapt the container width on wide screens accordingly.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 23:24:15 +01:00
Florian ERNSTandCodex c0f778ae98 Widen float constants in experience-orb underwater movement
Preserve the double operations around vanilla float drag, buoyancy and cap constants. Pin six executed Java method vectors and guard the real Game water and air tick paths.

Co-Authored-By: Codex <[email protected]>
2026-11-04 23:21:54 +01:00
Ludovic Marie 83ad384f48 Merge pull request #9508 from horizon-rust/fix/composter-fill-fixture-sound
🤖 Agent: the composter regression from #9069 was only the fixture: play_unshared kept the fill sound out of audio.capture, so the probe lost its S line on every fill; the S line now comes from level event 1500 (data > 0 = fill_success, LevelEventHandler case 1500). composter + dispenser harnesses pass on the desktop at this head; pr_ready --quick green except the nix-shell's missing uname.
2026-11-04 23:19:36 +01:00
Florian ERNSTandCodex gpt-6.1-sol 1eb6fe5cc3 Reset command fixture floors before exact fill guards
Clear the floor in loaded-cloud and nautilus fixtures before asserting that filling it changes all 256 blocks. Retain strict controls and cover fresh, partially stone, and fully stone prior states.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 23:18:52 +01:00
itarqos5andClaude Opus 5 0e96b88a53 perf(far terrain): the worker reduces the widths detail reads, not the frame
Corrects #8815, which made `ReducedColumns`'s levels lazy and said "neither
is the render thread, so no frame can stall on it". `Lod::apply_detail`
runs on the render thread and reads a reduction for every tile over the
chunk, so the first read of a width built it there: #8815 did not remove
that work, it moved it onto the frame.

A `lod_reduce` span inside the build (kept, it is what makes this
visible) separates the reduction from the column copying around it.
Flying over far terrain at lodDistance 256: of 14844 ms that `lod_detail`
spent over 140 slow frames, `lod_reduce` was 14140 -- 95% of it. The
`Arc::make_mut` copies are the other 5%.

The far terrain thread now builds the widths `apply_detail` reads before
it sends a chunk's columns on. Same world, same flight, limiter off:
average FPS 84.0 -> 164.0, 1% low 3.2 -> 77.6, p99 229.8 -> 9.3 ms,
`update` 6.4 -> 0.9 ms, slow frames 140 -> 2, and `lod_reduce` on the
render thread 14140 ms -> 0.

The widths are learned, not derived from the plan: `apply_detail` reads a
tile only where it is in `ready`, which the plan does not say, and taking
every level the plan holds would be the eager behaviour again.
`apply_detail` sets a bit as it reads, so the first chunk of a width
still reduces on the render thread and every chunk after it finds the
work done. Bits are never cleared, so the worst case is all four levels
-- the pre-#8815 cost, off the frame. `OnceLock` stays, so an unwarmed
width is still built on demand and the mask is an optimisation rather
than a correctness requirement.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
2026-11-04 22:18:20 +00:00
CocofireHDandopencode 9833b68bf9 Studio .mcpr: recover a crashed recording to its last whole frame
A recording writes its packets to `<name>.mcpr.<pid>-<nanos>.tmcpr.tmp`,
but the session metadata only reached `metaData.json` inside the archive at
`finish`, so a crash left a stream nothing could identify. The writer now
writes the metadata to `<stream>.meta.json` when recording starts and flushes
the stream and the sidecar together about once a second of recording.

On the next recording in that folder, `recover_crashed_recordings` packages
each leftover stream into a valid `.mcpr` beside it, cut to its last whole
frame (the same `frame_at` definition the reader uses), stamped with the
sidecar (or defaults when there is none) and dated from the stream when it
has no metadata. A stream whose writer may still be running is skipped (own
pid, `/proc` on Linux, or the exclusive lock a live writer holds), and a
stream with no whole frame is kept with its metadata and logged rather than
deleted.

Part of #9434 (items 2-5 follow).

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 23:16:56 +01:00
Ludovic MarieandClaude Opus 5.5 441a9ea0fb PvP chat: search keeps the typed text; repeats keep the scroll; no § codes
Review on #9250 (martipops's agent):
- Ctrl+F puts the box's text aside and gives it back when the search
  stops; whichever way the chat closes during a search (Escape, death, a
  server-opened screen), the draft is that text, never the query.
- A compact-chat repeat no longer moves a reader scrolled up: only lines
  it adds do.
- Search and copy on click read the message without legacy § codes, as
  the chat shows it.
- docs/pvp.md says plainly that clear water, clear lava and a fixed time
  of day see further than vanilla at that moment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 23:15:03 +01:00
CocofireHDandopencode c34d0b2866 Text fields accept spaces while a field has focus
A focused EditBox is the screen's focus, so Space and Enter are its text (charTyped), as in vanilla. The keyboard focus the previous screen left on a widget (the Create New World tab bar, for one) no longer presses it before the key reaches the world name field. The resource-loading title shares this path, so the seed, a server's address and the rename field are fixed with it. Space still jumps in gameplay.

Fixes #9332

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 23:13:40 +01:00
Ludovic MarieandClaude Opus 5.5 896aa14a6b PvP HUD: count combo hits by the damage's attacker; no idle allocation
Review on #9246 (martipops's agent):
- The combo counts a hurt report whose direct entity is this player
  (melee and spear jabs) and ignores one that names someone else; a
  report naming no attacker (Horizon's LAN hub for player victims) still
  counts only as the answer to the last swing.
- The server-address widget copies the address only while it is on.
- The ping widget shows nothing while the tab entry still reads 0 (the
  server resends latency every 600 ticks).
- own_latency moved below remote_player_attack, whose doc comment it had
  taken.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 23:13:11 +01:00
TheMrZZandCodex gpt-6.1-sol b8fd1e1456 Honor dropped-item stack-size overrides during merges
Reuse the effective stack-limit helper for neighbour merging and keep the
transfer under the separate 64-item cap signed. Preserve age and pickup
delay updates for zero and negative transfers.

Add guarded Game command coverage for component limits, ordinary and
incompatible pairs, counter inheritance and count conservation, plus a
regression for overstacked transfers.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 23:11:58 +01:00
Ludovic MarieandClaude Opus 5.5 c03e0b9ffb Dispense fixture: report the composter fill sound from level event 1500
#9069 plays `ComposterBlock.handleFill`'s sound unshared (the client that
receives event 1500 plays it, `playLocalSound`), so the audio capture no
longer saw it and the composter harness lost its `S … composter.fill*`
line on every fill (use 570, hopper_in 266, dispense 209 differing on
main 1989b1b59). The fixture now adds that line from the level event, as
it already did for the lava fizz (1501): `data > 0` is fill_success, as
in `LevelEventHandler` case 1500.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 23:00:43 +01:00
MegaBytesMe be26de3fee Launcher: choose game architecture independently on Windows ARM 2026-11-04 21:52:40 +00:00
Florian ERNSTandCodex 1686cd755a Dispatch eligible mob leashes through quad physics
Use the live holder eligibility and preserve the float lookup, yaw and angular-friction boundaries needed by quad attachments. Keep single-spring arithmetic unchanged.

Co-Authored-By: Codex <[email protected]>
2026-11-04 22:47:11 +01:00
Delete228andCodex 002cf12bf2 Refresh lazy sync cache admission on pack reload
Update the current-overlay marker before a complete wire snapshot exists. Preserve frozen gameplay damage definitions while allowing the first no-pack wire load to be cached after all packs are removed.

Cover reuse through an absent resource root, route the existing pre-hub reload regression through the helper, and require the tenth prepared-resource case in the catalog parity harness.

Co-Authored-By: Codex <[email protected]>
2026-11-05 00:43:54 +03:00
Evrim CicekandClaude Opus 5.5 bc8eb368b0 The dragon fight takes only a living dragon, and scans legacy worlds as vanilla
findOrCreateDragon passes over a dying dragon (ServerLevel.getDragons
takes living ones). scanState makes a living dragon the fight's, and
removes it for a new one when there is no active exit portal. The
harness now runs with a player too: arriving in the End beside a dying
dragon, and after the fight is won.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:41:10 +03:00
MegaBytesMe 52186ab1ff feat(launcher): build native mods on Windows ARM64 2026-11-04 21:40:29 +00:00
AxxioandAntigravity 2d88181b0f save: give loot tables precedence over saved items for barrels, shulkers, hoppers, dispensers
Leave randomizable-container inventories empty when their NBT supplies a valid loot table, allowing chunk loading and command paths to retain the table. Barrels, shulker boxes (and colored variants), hoppers, dispensers and droppers now match chests and vanilla 26.3 RandomizableContainerBlockEntity behavior. Invalid or absent loot-table keys continue to preserve saved items.

Fixes #9438

Managed and reviewed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 22:26:43 +01:00
Evrim CicekandClaude Opus 5.5 31f4897c3e Only the End fight's own dragon wins it when it dies
EnderDragonFight keeps its dragon's UUID (dragon_uuid) and
setDragonKilled acts only for that dragon, so one summoned in the End
after the fight is won dies with 500 experience and no gateway. The
fight takes the End's dragon, or makes one, as findOrCreateDragon does:
at once without a dragon of its own, else 1200 ticks after it last saw it.

Closes #9464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:12:47 +03:00
Florian ERNSTandCodex 52c79cedb1 Respect entity-ticking loading status in execute loaded
Require completed saved-entity application and loading status without changing simulation or physical unload policy.

Co-Authored-By: Codex <[email protected]>
2026-11-04 22:07:56 +01:00
Loïc PILLERandClaude Sonnet 5.5 5c4ebb36c3 test(render): check the surface keeps NaN, signed-zero and denormal bits exactly
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 22:06:57 +01:00
Loïc PILLERandClaude Sonnet 5.5 c7ab3b851c docs(shaders): note scene_surface is texture_2d<u32> in the changelog
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 22:06:57 +01:00
Loïc PILLERandClaude Sonnet 5.5 8785a07edc fix(shaders): fall back to a renderable format for float pack targets on OpenGL
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 22:06:56 +01:00
Loïc PILLERandClaude Sonnet 5.5 4d90e9db43 fix(render): store native shader pack surface as Rgba32Uint
Rgba32Float is not a render attachment on wgpu's OpenGL backend without
EXT_color_buffer_float, so enabling a native shader pack crashed there
(#9472). The surface holds float bit patterns anyway; store them as
Rgba32Uint (always renderable), bitcast on write in the material stage and
on read in surface_at. Pack-facing API (set_surface, surface_at) is unchanged.
Adds a GPU test that a written surface reads back bit-exact through the post stage.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 22:02:13 +01:00
Florian ERNSTandCodex aef74cd095 Merge latest main for loaded-command checks
Co-Authored-By: Codex <[email protected]>
2026-11-04 21:58:37 +01:00
Ludovic Marie 63eccfc355 Merge pull request #9469 from horizon-rust/feat/moderation-commands-tree
🤖 Agent: verified: verify green; pr_ready --quick 2677 Rust tests + clippy pass (only Python failure is test_install_sh_signature: no uname on NixOS PATH); 2-client proof on a dedicated server with the SMP mod: ops' clients complete /tempban and /mutelist, non-ops don't; /mute with a duration blocks chat, /msg, /tell, /w, /me but not other commands; /tempbanlist lists timed bans; a 5 s ban ends by itself. Risk to watch: the extras' names are dropped from the mods' level map so a mod registering /mute still never sees it (the game intercepts first, as before).
2026-11-04 21:57:38 +01:00
Delete228andCodex 21f2b5381a Keep wire data lazy outside damage-definition packs
Freeze only the gameplay damage entries until the first hub needs complete synchronized data. Preserve no-pack preloads and prevent late helpers or pre-hub reloads from changing the session damage registry.

Co-Authored-By: Codex <[email protected]>
2026-11-04 23:56:25 +03:00
voidsipandCodex 2274fda8a7 Carry live vehicles and riders through End gateways
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:53:31 -06:00
itarqos5andClaude Opus 5 cb6a80698c perf(far terrain): a frame spends 3 ms applying detail, not a whole burst
`Lod::receive` drained the whole results channel every frame. A detail
request asks for 512 chunks and cached ones answer at once, and applying
one copies the columns of every tile over it (`Arc::make_mut` on a
snapshot a mesh job shares) and reduces the chunk to that tile's cell
width. A burst in one frame cost 273 ms of it on the render thread.

`horizon-ctl stats` over far terrain at lodDistance 256 put the cost in
`update` (avg 29.9 ms, 540.5 on slow frames) and not in rendering (2.6
ms) or on the GPU (avg 9.1 ms), with a median frame of 6 ms;
`HORIZON_SPIKES` named the path: update_world / lod_update / lod_results
/ lod_detail, 104 slow frames in 20 seconds.

A frame now spends at most DETAIL_BUDGET on summaries and leaves the
rest in `detail_backlog`. Same binary, same flight, limiter off:
average FPS 94.4 -> 133.6, 1% low 3.7 -> 42.1, p99 156.2 -> 20.1 ms,
worst frame 739.7 -> 34.2 ms. p50 is 0.6 ms worse, the budget's work
spread out rather than dumped into one frame. The fill did not slow:
settling to fully refined took 19 polls against 45.

`request_detail` returns early while any summary waits, which is not
optional: without it a chunk whose summary is still waiting has its
mask clear, so it is asked for again and the cache answers at once. The
backlog then grew by a request every 250 ms and never drained -- 0.5
FPS, `update` averaging 9.7 s a frame.

`refreshed_since` moves from arrival to application: a refresh over the
chunk while its summary waited still drops it.

Corrects #8815, which said the lazy reduction could not stall a frame
because neither the far terrain nor the far meshing thread is the render
thread. `apply_detail` runs on the render thread and reads a reduction,
so the first read of a width builds it there. The budget bounds it;
warming the plan's widths on the worker thread is a separate change.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
2026-11-04 20:49:06 +00:00
voidsipandCodex 8f5f91f2f0 Carry vehicle passengers through command dimension teleports
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:48:13 -06:00
Florian ERNSTandCodex 6489310c36 Add guarded distance-two loaded-command reproduction
Co-Authored-By: Codex <[email protected]>
2026-11-04 21:47:27 +01:00
degepieandClaude Opus 5.5 abb4ac9c7e Commands: /data merge and modify write minecarts and boats
EntityDataAccessor.setData loads the merged compound into any entity but a
player; Horizon answered "Nothing changed" for carts and boats. Minecart
gets a load() that reads the saved fields over a live cart (from_nbt now
uses it), apply_entity_nbt gains Cart and Boat arms that keep the merged
compound, and guests get commands.data.entity.invalid like the player.

The test compound uses the 26.3 DisplayState form (a block name); a second
test checks that merging only a name keeps fuel, push, fuse and Enabled.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMK3XzCYMyeKEdamets
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 01:46:30 +05:00
degepieandClaude Opus 5.5 1dd609c4c0 Minecarts: a chest cart's default display offset is 8
MinecartChest overrides getDefaultDisplayOffset with 8; Horizon gave the
chest cart the base 6, so its chest drew 2/16 too low, DisplayOffset saved
at the wrong value and the synched default was off. default_display_offset
now matches every CartKind.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMK3XzCYMyeKEdamets
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 01:46:30 +05:00
ViníciusandCodex 5d9bf650a4 Fix fluid sprite material ownership
Prefer fluid blocks when resolving material IDs for their still and flow sprites, preventing a cauldron material from being assigned to fluid surfaces. Add regression coverage for shared fluid sprites and existing block voting.

Co-Authored-By: Codex <[email protected]>
2026-11-04 17:46:25 -03:00
voidsipandCodex 156ae180f8 Preserve entity-bound server sounds and late pairing
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:41:51 -06:00
Delete228andCodex 7a3f407a54 Merge current main and retain world suffocation bindings
Keep main suffocation registration and the world damage catalog together when loading world tables. Preserve all intervening upstream changes without rewriting existing PR history.

Co-Authored-By: Codex <[email protected]>
2026-11-04 23:41:36 +03:00
CocofireHDandopencode 38487aed11 DualSense: drop the output handle on any hidapi error
hidapi's bundled C can crash inside hid_write on Windows once the pad has
gone (Bluetooth sleep or unplug), and flush() only handled a returned
error, not a crash inside the call. Treat the output as lossy:

- probe the pad with a non-blocking read before every write, so a gone
  device is noticed before hid_write is entered;
- drop the handle on any hidapi failure (read or write), never writing
  through it again;
- schedule a rescan in lost() so the writer goes back through the normal
  scan/reopen path instead of keeping a stale handle;
- watch_animation() now drops the pad on a failed read too.

The handle is a small trait so the writer's state machine is unit-tested
without hardware. There is no vanilla reference: this is Horizon-only
hardware output.

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 21:40:30 +01:00
Delete228andCodex a085aa324b Merge main while preserving native and entity-bound ticking sounds
Keep the vanilla EntityBound reference/canPlaySound path alongside native-owned ticks. Extend the channel-backend regression to cover paused pitch/position, public stop, release and stop-all cleanup.

Co-Authored-By: Codex <[email protected]>
2026-11-04 23:40:18 +03:00
Delete228andCodex 0ec0e8e440 Merge current Horizon main into native world canvas PR
Preserve upstream reload-listener and native API exports beside the world canvas type. No rebase or force-push.

Co-authored-by: Codex <[email protected]>
2026-11-04 23:40:05 +03:00
CocofireHDandopencode 92549691c2 Management harness: run on Windows, name a server up after stop
The regression behind #9088 was #8245: a stop that doesn't come from the
supervisor's stdin or the control API left the supervisor in its stopped
phase, so `horizon-server --dir … run` outlived the management
`server/stop` and the harness's `wait(timeout=180)` raised TimeoutExpired.
#9077 fixed that (`Shared::shut_down`), on the base of this branch, and the
harness passes here again.

What the harness still left open, and what this fixes:

- It could not run on Windows at all. It wrote the absolute keystore path
  into vanilla's server.properties with single backslashes, which Java's
  properties parser reads as escapes ("C:UserscocoAppData…"), so vanilla
  stopped before the first request. Values are now escaped as vanilla writes
  them, and the harness runs here.
- A server that stays up after `server/stop` was reported as a bare
  TimeoutExpired. `wait_for_exit` waits for the process to end, which is the
  condition the issue was about (vanilla's `ServerStateService.stop` submits
  `MinecraftServer.halt(false)`, so `runServer` returns and `main` ends the
  process), and its failure names the side and the last lines it logged.
  The seconds each server took are kept in report.json: vanilla 3.4 s and
  Horizon 0.5 s here, against the 180 s budget.

Fixes #9088

Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 21:38:07 +01:00
TheMrZZandCodex gpt-6.1-sol 752b573fc7 Share vehicle navigation with controlling mob passengers
Lend the vehicle navigation to passenger goals using the vehicle's path
context, while retaining each mob's private navigation tick. Apply the
controller policy and ravager goal flags without requiring a combat target.

Add a guarded real Game/Java regression for patrol and stroll behavior,
including NoAI riders, non-controlling slime riders and a NoAI vehicle.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 21:34:22 +01:00
voidsipandCodex 9382056060 Compare carpet farm production against vanilla
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:32:57 -06:00
degepieandClaude Opus 5.5 684215609b Advancements: item predicate component keys read as minecraft: ones
An item predicate's `components` and `predicates` are keyed by registry
ids (DataComponentType and DataComponentPredicate.Type, read through the
registries' name codecs), so "potion_contents" is
"minecraft:potion_contents". `fn item` looked the keys up only in their
full form: a bare key was skipped and let every stack pass. It now finds
them through `full()`, and compares the potion_contents value as a
registry id too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 01:26:41 +05:00
degepieandClaude Opus 5.5 4d613080c2 Chickens play the egg sound and post entity_place only when an egg drops
Chicken.aiStep rolls the chicken_lay gift table first and plays
entity.chicken.egg and posts GameEvent.ENTITY_PLACE only when
dropFromGiftLootTable dropped something; the egg timer resets either way.
Horizon played the sound before the roll and posted no game event, so a
sculk sensor never heard a laid egg.

A new MobEvent::LayEgg carries the pitch (drawn in the same place as
before); its handler rolls the loot and gates the sound and the game event
on a drop. drop_gift_loot now returns whether it dropped; the other
GiftLoot callers (turtle, panda, sniffer) are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 01:26:41 +05:00
degepieandClaude Opus 5.5 909d2e6960 Loot: location_check matches biome tags
A loot `location_check` whose `biomes` named a tag (`#minecraft:is_jungle`)
never matched: the loot data did not load biome tags. `LocationPredicate.biomes`
is a HolderSet, so the arm now reads it with `LootData::holder_set` (a
`#tag`, an id or a list of ids), and the loot data loads the
`worldgen/biome` tags with the world's packs over the game's own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 01:20:19 +05:00
Loïc PILLERandClaude Opus 5.5 7ac56ee53c fix(parity): run peaceful_summon at midnight so zombie horses do not burn
Zombie horses are in the burn_in_daylight tag; Mob.isSunBurnTick rolls
every tick, so a horse could catch fire in the two-tick window and the
vanilla reference read 24 Health instead of 25 (#9089).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 21:14:10 +01:00
Florian ERNSTandCodex c940740feb Isolate crafting parity runs and retain qualified advancement recipe IDs
Co-Authored-By: Codex <[email protected]>
2026-11-04 21:11:03 +01:00
voidsipandCodex f063e900e1 Tick moving pistons after entity travel
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:04:41 -06:00
voidsipandCodex 60244bf97e Update entity-bound sound port ledger after #9382
Co-Authored-By: Codex <[email protected]>
2026-11-04 13:59:12 -06:00
degepie b42692f7a2 Merge remote-tracking branch 'origin/main' into fix/dp-block-nbt
# Conflicts:
#	crates/horizon-client/src/advancements.rs
2026-11-05 00:54:47 +05:00
Florian ERNSTandCodex fff83b42e3 Merge current main before crafting-priority review fixes
Co-Authored-By: Codex <[email protected]>
2026-11-04 20:52:33 +01:00
GabrielNichols 99b0ab1f18 fix(port-pack): invalidate stale harness verdicts 2026-11-04 16:49:44 -03:00
Florian ERNSTandCodex 6650b343b3 Dispatch accepted command-hit reinforcements synchronously
Co-Authored-By: Codex <[email protected]>
2026-11-04 20:49:07 +01:00
DUzzL abe568d446 Don't swing the hand when a mob interaction is CONSUME on the client 2026-11-04 20:48:05 +01:00
Ludovic MarieandClaude Opus 5.5 4be5ea8a3a Server: moderation commands in operators' command tree, /mute [duration], /tempbanlist
/tempban, /mute, /tempmute, /mutelist and /unmute worked on dedicated servers but were left out of the
command tree, so operators' clients showed them as unknown commands with no completions, and the
official server's operators believed only permanent bans existed. They now reach the trees of
operators of level 3 (as a native mod's commands do), where the game runs them: the mute commands
when the server filters chat, the ban ones on a dedicated server.

/mute <player> [duration] [reason] takes a duration after the name (/tempmute stays), and
/tempbanlist lists the bans that end by themselves, soonest first, with who banned, the time left,
the end and the reason.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:46:19 +01:00
utophii a0cf173b1f fix(ci): address musl release review feedback 2026-11-04 22:41:13 +03:00
degepieandClaude Opus 5.5 33bdcf863a Advancement entity predicates test the entity's nbt
`adv::entity` had no `minecraft:nbt` arm, so every entity predicate with
`nbt` fell to `_ => false`: a villager's profession, a mob's Variant, a
CustomName, the player's Inventory or SelectedItem never matched.

EntityNbtPredicate.matches: the entity's compound from
NbtPredicate.getEntityTagToCompare (saveWithoutId, plus SelectedItem for a
player) tested with NbtUtils.compareNbt(..., true), which
`nbt_path::compare_nbt` already is.

- `EntityView` gets the compound (`nbt`); `entity` parses the SNBT pattern
  and compares; no compound or an unparsable pattern fails.
- `Tree::index_nbt` notes the triggers whose criteria mention `nbt` (or a
  `minecraft:reference`), like `index_looking_at`, so the compound is
  built only when a criterion may ask.
- `mob_entity` builds the mob's compound (`mob_to_nbt`, riders under
  `Passengers`); `player_entity_for` the player's, through `player_nbt`,
  the player arm of `/data get entity` moved out of `entity_nbt_in_level`.
- A bred child's view, a copy of its parent's, drops the parent's data.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-05 00:40:54 +05:00
utophii 3f9b7bc8bb Merge remote-tracking branch 'upstream/main' into fix/2282-musl-build 2026-11-04 22:36:45 +03:00
Florian ERNSTandCodex 794a3c3531 Merge current main after frozen reinforcement checks
Co-Authored-By: Codex <[email protected]>
2026-11-04 20:32:32 +01:00
Ludovic Marie 1989b1b595 Merge pull request #9210 from Ragbie/fix/silent-dragon-death
🤖 Agent: a dragon saved or summoned with Silent:1b no longer plays the global 1028 death roar at tick 1 of its death; Silent is read from the compound Horizon keeps for unmodelled fields, and the new dragon_death_roar harness compares the level events against vanilla's. The branch passed the cg-39 union run on playhorizon (horizon-client 887 tests); main merged to clear an unrelated lan::conn flake.
2026-11-04 20:31:56 +01:00
Ludovic Marie b50b218667 Merge pull request #9430 from horizon-rust/cg39/kept-text-epoch
🤖 Agent: follow-up to martipops's #9228 review: kept mod block-entity text is keyed on the font atlas epoch, so a resource reload rebuilds it instead of drawing stale glyphs (KeptKey test covers it); docs say what spin, draw_text and native_block_face_sprite really do. fmt, clippy and 7 tests on Linux; the larger points are #9431.
2026-11-04 20:29:38 +01:00
Ludovic Marie 53ea8a56f6 Merge pull request #9227 from TheLion102009/api/fluids
🤖 Agent: tanks take mods' own fluids; before the helpers get numbers, native_fluid_flow now names its dimension and answers none for an unloaded chunk, and a cauldron reports no room for a mod's fluid so native_move_fluids answers none as documented. Stamp test, clippy and 17 fluid tests pass on Linux; a per-tank filter is #9439.
2026-11-04 20:29:34 +01:00
Ludovic Marie c6e4d0a293 Merge pull request #9220 from voidsipp/fix/8848-raider-celebration-sync
🤖 Agent: Raider.IS_CELEBRATING (index 16, BOOLEAN) now reaches clients, and the new lan test pins it through mob_meta itself: with the mob_raider_meta call removed it fails, so the server half of #8848 can't silently drop out. Main merged (the #9271 part is gone from the diff); 4 celebration tests and clippy pass on Linux.
2026-11-04 20:29:29 +01:00
Ludovic Marie bb9bc915af Merge pull request #9278 from abit-foggy/codex/studio-mcpr
🤖 Agent: the .mcpr reader no longer trusts the file: entries are capped (1 MiB metadata, 1 GiB stream), one frame parser serves the index and playback so a huge or zero length ends the stream cleanly, and seeking starts at the first packet of a millisecond. finish renames a synced temp over the destination with Zip64 and keeps the packets on failure. 9 studio tests on Linux, verify green on 56fd928f5; the rest is in #9434.
2026-11-04 20:29:25 +01:00
Ludovic MarieandClaude Opus 5.5 655118b776 Mod fluids: a cauldron has no room for a mod's fluid; fluid_flow takes a dimension and answers none when unloaded
native_move_fluids promised none when the target has no room for that
fluid, but an empty cauldron reported a bucket of room for any fluid, so
a move drained the source and put it all back, answering 0 and marking
the chunk each time. native_fluid_flow is new here: it now names its
dimension, as native_fluid_tanks does, reads a server's parked levels,
and answers none for an unloaded chunk instead of a zero that looked like
still water.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:25:48 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 5cea1b3475 Math.sin and Math.cos on Windows round like vanilla's
jmath::sin and jmath::cos used the platform's libm on x86-64, which on
Linux (glibc) rounds like HotSpot's x86-64 intrinsics for all but about
0.2% of arguments. Microsoft's rounds differently for about 3%, so on
Windows item drops, mob goals and worldgen features came out a last bit
off vanilla's, and drops_match_vanillas_motion_bit_for_bit failed there.

On Windows they are now correctly rounded (jmath::exact, double-double
arithmetic with a fast path), which HotSpot's are for all but about 0.1%.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:25:34 +01:00
Axxio ebaf4e7d21 launcher: allow editing instance icon from home and preserve picture in dialog (fixes #8689)
Clicking the instance icon on Home or selecting 'Edit instance' in the
folder group more menu now opens the instance dialog. In the dialog,
clicking the large preview icon opens the image picker, and existing
pictures remain selectable in the palette.

Managed by Axxio.
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 20:23:35 +01:00
Ludovic MarieandClaude Opus 5.5 6b078a2bc7 Merge main into #9227
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:23:31 +01:00
Ludovic Marie 35e219c5de Merge pull request #9385 from Ragbie/fix/transfer-test-race
🤖 Agent: merging. Ignoring only the hello's send error is enough: data the server sent before closing stays readable after the reset, so the Disconnect is still read and the test still checks the refusal. Proof: on a union with main and #9382 the test passed five runs in a row; fmt, clippy -D warnings and the client-lib tests pass.
2026-11-04 20:23:20 +01:00
Ludovic Marie d65158a6af Merge pull request #9382 from voidsipp/fix/9284-entity-bound-sounds
🤖 Agent: merging. The order matches SoundEngine.tickInGame: stop when canPlaySound is false (the entity went silent), then tick, where EntityBoundSoundInstance.tick stops on isRemoved or else copies the entity's position as floats; the packet's seed now picks the variant, as handleSoundEntityEvent does. The per-tick cost is one entity lookup per playing bound sound. Proof: union with main and #9385: fmt, clippy -D warnings, Python tests, 2634 client and client-lib tests, the render audio (20) and sound_engine (10) tests, and the entity_bound_sound and sound_engine harnesses against vanilla pass.
2026-11-04 20:23:15 +01:00
Ludovic MarieandClaude Opus 5.5 742da4c639 Test that mob_meta sends a raider's celebration
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:20:31 +01:00
Ludovic MarieandClaude Opus 5.5 d4d8a5a4bc rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:17:00 +01:00
Florian ERNSTandCodex a8f829f9c8 Merge current main before reinforcement dispatch change
Co-Authored-By: Codex <[email protected]>
2026-11-04 20:16:08 +01:00
Ludovic MarieandClaude Opus 5.5 7df3a5dd64 Merge main into #9220
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:15:56 +01:00
Arslan ArmanulyandClaude Opus 5.5 e7e4c7c2d3 Shader packs: terrain parts after a failed part, program.enabled, buffer sizes
Review of #9176:

- A split part whose program exists but doesn't load, or whose pipeline
  can't be built, no longer leaves the other part drawing alone: neither
  draws, and the game draws all terrain, as when an unsplit pack's terrain
  program fails. Iris 1.11.7 turns the whole pack off for our probe whose
  gbuffers_terrain_cutout doesn't compile.
- A part with no program in its chain still draws nothing while the other
  draws alone, now with a note: Iris 1.11.7 left the other part undrawn
  with our probes having only gbuffers_terrain_solid, or only
  gbuffers_terrain_cutout.
- Loader::has honours program.<name>.enabled, as load does: Iris drew all
  terrain with gbuffers_terrain for our probe that turns its
  gbuffers_terrain_solid off with program.gbuffers_terrain_solid.enabled=false.
- Post::every_program lists terrain_cutout, so buffer_minimums counts the
  storage blocks the cutout program binds.

Part of #9137

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 00:15:41 +05:00
Ludovic MarieandClaude Opus 5.5 56fd928f57 Merge main into #9278
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:14:04 +01:00
Ludovic MarieandClaude Opus 5.5 f41eeab87e Studio .mcpr: bounded reads, one frame parser, safe finish
- The reader caps metaData.json at 1 MiB and recording.tmcpr at 1 GiB,
  so a hostile archive can't exhaust memory, and never allocates from a
  length field: indexing and next_packet share one frame parser, stop at
  the first frame that isn't whole, and agree on the packet count.
- Seeking lands on the first packet of a millisecond (partition_point);
  a timestamp going backwards is indexed at the time before it.
- Fields ReplayMod can leave out read as defaults; protocol is optional;
  a file whose fileFormat isn't MCPR is refused.
- finish writes the archive to a sibling file, syncs and renames it over
  the destination, with Zip64 for the stream; on failure the stream file
  is kept and the error names it. A dropped writer keeps its packets, a
  failed write stops further frames, and the stream name can't collide.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:13:59 +01:00
Ludovic Marie 1f90d6f9ae Merge pull request #9275 from lightcodegit/snbt-parser-depth-cap
🤖 Agent: the SNBT depth cap is now 256 rule frames (about 63 nested compounds or 13 levels of nested container items), measured on playhorizon: 384 already overflows a 2 MiB debug thread stack, the size the server thread runs on. New test deep_real_values_still_parse keeps 50 compounds and 10 container levels parsing; 42 horizon-command tests pass, verify green on 54142077f.
2026-11-04 20:10:01 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 ea94327688 HORIZON_STILL: a native mod's chat line too, a warning in the log, wait a second after a step
From review: a native mod's chat line was stamped with the client's
ticks while chat follows the world's under the switch, so it could
vanish at once. The switch now logs a warning when it is on, the
docs say to wait a second after a step (the server's time arrives
once a second), and reseed's doc comment no longer sits between
random_glyph and its own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:07:42 +01:00
Ludovic Marie 11f72f81fc Merge pull request #7725 from deloskiytbackup/feature/realms-support
🤖 Agent: Ludovic allowed Realms with the player's own account (2026-11-04). At 08874a1622 the review's five points are fixed: the join runs on a worker, Leave asks first, the /join/pc address is checked as host:port, requests send only vanilla's sid cookie (a local-server test shows no Authorization header and the token missing from errors), and the world-pack question comes first. Proof: desktop fmt, clippy -D warnings, 1,700 client-lib tests; verify is green on this head.
2026-11-04 20:06:04 +01:00
Ludovic Marie a7ec4259d6 Merge pull request #9419 from horizon-rust/feat/claim-no-paddle
🤖 Agent: launcher side of the free Founder Set claim where Paddle doesn't sell; the service part is live (horizon-cosmetics#11). Risk: the button only shows when the service sends claim.one_click, so older services and other countries see no change.
2026-11-04 20:05:59 +01:00
Ludovic Marie 2e0ef2bd2f Merge branch 'fix/client-information-as-vanilla' into fix/known-packs-as-vanilla 2026-11-04 20:05:44 +01:00
Ludovic Marie 2d7d6303d0 Merge branch 'fix/client-tick-end-needs-level' into fix/client-information-as-vanilla
# Conflicts:
#	crates/horizon-client/src/game/remote.rs
2026-11-04 20:05:23 +01:00
Ludovic Marie 2fb505568c Merge remote-tracking branch 'origin/main' into fix/client-tick-end-needs-level
# Conflicts:
#	crates/horizon-client/src/game/remote.rs
2026-11-04 20:04:35 +01:00
Florian ERNSTandCodex 2edaa4fad5 Merge current main and retain weather and light observations
Co-Authored-By: Codex <[email protected]>
2026-11-04 19:59:44 +01:00
Ludovic MarieandClaude Opus 5.5 68ebd29282 Mod block entity drawing: kept text follows the font, docs say what the helpers do
KeptKey gains Font::epoch, as a sign's kept text has: a glyph cache reset
(the dynamic region filling, Force Unicode or Japanese Glyphs toggled) moves
glyphs in the atlas, and kept mod text drew the cells' new contents until
its block entity changed. The docs now say native_draw_text draws plain
default-font spans, that spin's angle is absolute in game time (a new speed
needs a new phase), and that native_block_face_sprite returns vanilla names
without their namespace.

From martipops's review of #9228.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:59:28 +01:00
Florian ERNSTandCodex 53090512e3 Add frozen reinforcement admission controls
Observe spawn-rule light and live chance/accepted-hit prerequisites before changing event dispatch.

Co-Authored-By: Codex <[email protected]>
2026-11-04 19:59:06 +01:00
Ludovic MarieandClaude Opus 5.5 1606b87f12 Cosmetics: Horizon players' mark by their names; the founder badge after the name
Like Lunar Client's: Horizon's mark (two white bars over a violet line, horizon_cosmetics::mark)
before the names of other Horizon players, in the name tag (both views) and the player list. Who
plays Horizon comes from the cosmetics service in the same cached /v1/worn answers ("horizon": a
list of the batch's UUIDs beside "players", which released games ignore): no new request, nothing
on the wire to Minecraft servers.

- Extras > Cosmetics: "Horizon logos" and "Show me as Horizon", both on by default. On, the game
  signs in (Mojang join, as for My cosmetics) and sends POST /v1/presence at start and every hour;
  off, it sends listed=false and the service forgets the player at once.
- A name tag style's badge moves after the name (the mark goes before it); the launcher's Wardrobe
  draws the name tag the same way.
- Name tag mark and style line in the HUD batch's RGBA: the style line drew #a98fff as pink.
- The player list keeps a mark column once anyone listed has it, so names stay aligned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:58:07 +01:00
Florian ERNSTandCodex gpt-6.1-sol 109256c0fb Merge main and retain map and weather observations and oracle guards
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 19:57:09 +01:00
JulesB40andClaude Opus 5.5 c9642c76a0 Worldgen: a shared rule that fails to compile returns before its bindings are walked
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:56:23 +01:00
JulesB40andClaude Opus 5.5 609e5306c1 Worldgen: the shared surface rule binds each chunk's densities
The world's surface rule holds the ore veins, whose densities belong to
each chunk's volume, so every chunk fell back to compiling the rule. The
shared rule now keeps the densities compile binds, in its order, and each
chunk's worker binds them to its own volume the same way: the indices,
and the samples, are compile's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:56:22 +01:00
JulesB40andClaude Opus 5.5 150a1013d2 Worldgen: compile the surface rule once per generator worker
Every proto chunk compiled the world's surface rule twice (build_surface
and apply_carving_mask): a deep copy of the rule tree, every biome
condition's names normalised into a new set, its biome ids looked up. A
generator worker now compiles it once, without the chunk's possible
biomes, and keeps it with the noises it bound; each chunk's worker takes
it and folds the biome conditions its possible biomes decide, as compile
folds them, so the tree, its bands and every block are the same. Rules
that bind densities, workers that already hold rules, and possible biomes
the surface doesn't number compile as before.

Fixes #9218

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:56:22 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 e3ce04dde2 HORIZON_STILL: the smithing screen's slot icons wait too; wait after a step
An open smithing screen cycled its empty-slot icons on the client's
ticks while frozen. And the docs now say to wait a moment after a
/tick step before a capture, as after a turn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:55:58 +01:00
martipopsandClaude Opus 5.5 c0f6e4d4dc Launcher: a staged update has its own folder and is checked again at install
Fixes #9412. Every launcher process deleted and rewrote the one update folder, and the installer
only checked that the payload existed, so a second launcher could leave a partial file to copy over
the launcher.

- Each stage is a `<pid>-<nonce>` folder. Downloads get their name only after the hash check. Each
  process removes its own; stages whose process is gone are cleaned up after an hour, counted from
  the hand-over, because an installer may still be using them.
- The installer checks the archive against the signed SHA-256 again and places the launcher from
  those bytes. The hash travels in an environment variable: the installer is whatever file is in
  place, so the command line stays readable across versions. A window without one can't place.
- Nothing happens when the launcher in place is already that version or newer.
- On Windows a launcher running from the temp folder, however it is spelled, can't replace itself.
- Update no longer stops hosted servers. The new window doesn't stop them when it closes either.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 13:54:58 -05:00
MegaBytesMe 9e9b1a72ca Merge remote-tracking branch 'upstream/main' into codex/issue-9407-native-mods-arm64 2026-11-04 18:54:19 +00:00
NerfPhaselockandClaude Opus 5.5 740567a011 Renderer: a pack's displacement keeps CPU frames; a greyed GPU Culling reads Off
A native pack's `[displacement]` grid takes its quads from the walk's
`visible`, which in a GPU frame lists only the sections the CPU still
draws, so near relief faces went missing: such frames now take the CPU's
path, and docs/graphics.md lists it with the other exceptions.

The GPU Culling switch reads Off wherever it isn't offered, as Translucent
Batching and Smooth Gradients do, so the box, the narrator and the widget
tree no longer report a saved On; the UI test checks the switch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:53:47 -05:00
MegaBytesMe 84ac891a7a Merge remote-tracking branch 'upstream/main' into codex/issue-9408-launcher-arch-choice 2026-11-04 18:52:53 +00:00
martipopsandClaude Opus 5.5 c269066ca3 Native mods: a fake player's use closes what it opened
A machine's fake player restored its swapped fields but never ran the close
side of the use. A trader stayed in its session, a vehicle kept its menu's
stand-in, and a shulker box's opener count stayed up. It now runs the real
close paths while the fake inventory is in place, and swaps station,
craft_table, ender_open, hook, experience and the respawn point too.

Closing keeps the use as a player's: the click counts, and the same events
and sounds fire. Refusing to open would need a gate at every open site.

Fixes #9409

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 13:51:58 -05:00
itarqos5andClaude Opus 5 ad491f1bf0 perf(far terrain): a previewed column spends one span on a dampening run
`shade_sky` records a dampening run's skylight a span per light level, so
a sea 15 blocks deep or more becomes 16 spans to carry a gradient down
its own side. Over real seed-1234 terrain that is 64% to 78% of every
span a far column stores: 18.7, 12.3, 12.0 and 12.6 spans a column at
tile levels 0, 2, 4 and 6, where the runs themselves are 4.1 to 4.5.

A previewed column now spends one span on the run and drains the same
light out of the bottom of it, so every span under the run is lit exactly
as before -- which is what the per-level walk was for ("lit seabeds and
the ground under canopies"). It reaches no face that is drawn: the spans
inside a run have neither a top nor a bottom face (`hides` matches on
state, not light), and a run's side is exposed at its top, where its
light is brightest, with `add_side` raising anything above the
neighbour's top to the neighbour's own sky.

Chunk columns keep the per-level walk. They are what `Column::reduced`
merges, and a reduction takes the brightest light of each slice, so one
bright span for a whole sea would light its bed as brightly as its
surface. A previewed column already stands for a whole far cell and is
never reduced against its neighbours.

Measured on 4448 far tiles at lodDistance 256, Full far detail, two runs
each with the heap census: `Far sampling` 302.9 and 309.6 MiB against
264.5 and 268.2, and the resident set 1386 MiB against 1252 and 1242.
Four pinned views are identical across the change to within the
animation noise floor, while two runs of the same build differ by 7% of
their pixels -- far-tile resolution is not reproducible between
launches, so the controls are in the pull request.

`shade_runs` takes the block-state test as a predicate so the shading
can be tested without prepared resources.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
2026-11-04 18:50:56 +00:00
Florian ERNSTandCodex gpt-6.1-sol 06cc65f75a Merge main and preserve complete mob-name and weather corpus functions
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 19:50:24 +01:00
Ludovic Marie 8dfc3f2e1e Merge pull request #9229 from TheLion102009/api/scenes
🤖 Agent: merged main through #9228's merge (mod lines and doc sections both kept). The scene's quads use the GUI's isolated depth (HudBatch::depth_since) and stay in the HUD batch, so nothing changes until a mod calls native_draw_scene. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, client 887 + client-lib 1682 + render 1355 tests green (the 4 clear_budget HDR failures also fail on bare main there).
2026-11-04 19:49:51 +01:00
martipopsandClaude Opus 5.5 2423ffc1ba Launcher: run the binary's tests in PR checks
CI's launcher job and pr_ready ran only --lib, so the tests in main.rs's
modules first ran in launcher.yml's cargo test at release. Add --bins to
both. The clippy failure at forge_install_tests.rs:4 is already fixed on
main by 812512980, so it needs no change here.

Fixes #9414

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 13:47:55 -05:00
martipopsandClaude Opus 5.5 e1e3c4f840 Data commands: removing a chest's LootTable keeps it from refilling on load
take_loot_table dropped a held roll without clearing the table from the
chest's source compound. The emptied chest encodes the same as its decoded
source, so the save overlay wrote the source back with its table and the next
load rolled again. It now goes through consume_container_loot, as opening does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 13:47:43 -05:00
Ludovic Marie 4d533c252a Merge main into #9227
# Conflicts:
#	docs/native-mods.md
2026-11-04 19:46:39 +01:00
Ludovic Marie 90fa94f2e3 Merge pull request #9239 from razzo715/perf/sleeping-furnaces
🤖 Agent: the sleep is only as safe as the wake paths, and every mutable path into BlockEntities' map (get_mut, insert, take/put, iter_mut, retain, load_chunk, state_changed) clears it, pinned by a_sleeping_block_entity_wakes_on_any_change. The cost on the tick loop is one FxHashSet lookup per ticker. Hoppers stay awake, so the big win is still ahead. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, client 887 + client-lib 1682 + render 1355 tests green (the 4 clear_budget HDR failures also fail on bare main there).
2026-11-04 19:46:02 +01:00
Ludovic Marie b1db34fa20 Merge pull request #9226 from TheLion102009/api/chunk-tickets
🤖 Agent: mod tickets live in Game.mod_chunk_tickets keyed by dimension while streaming.mod_forced travels with each level's Streaming, so a ticket survives the level swap; forced_chunks() dedups against /forceload so a chunk held both ways is ranked once. Tickets aren't saved, as the doc says: a mod that forgets to re-hold in native_level_load drops its chunks on restart. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, client 887 + client-lib 1682 + render 1355 tests green (the 4 clear_budget HDR failures also fail on bare main there).
2026-11-04 19:45:57 +01:00
Ludovic MarieandClaude Opus 5.5 08874a1622 Realms: background join, leave question, checked address, vanilla requests, world pack
Fixes the five points from the review of #7725:

- Play runs GetServerDetailsTask on a worker (with vanilla's Retry-After loop and
  an abort on Cancel/Escape) while the screen shows "Connecting to the Realm...";
  the title polls it once a frame and connects when the address is back. Every
  other Realms request (list, invites, join code, open/close, leave) is off the
  UI thread too.
- Leave Realm opens RealmsPopups.infoPopupScreen with
  mco.configure.world.leave.question.line1 (Continue/Cancel), as leaveClicked
  does; only Continue sends uninviteMyselfFrom. Our own Realm is never offered.
- The /join/pc address must be host:port (host name, IPv4 or [IPv6], numeric
  port, no scheme, path or credentials) before it becomes Action::Connect.
- Requests carry exactly what RealmsClient.execute adds: the sid/user/version
  cookies and Client-Ref, X-NetworkProtocolVersion, Is-Prerelease; no
  Authorization header, the JVM's Java/<version> user agent, no redirects.
  Errors are typed (RealmsError) and never hold a header or the token.
- A world pack in the join answer asks
  mco.configure.world.resourcepack.question first; Continue connects with the
  pack allowed and pushed under GetServerDetailsTask.generatePackId.

Also: the DTOs read vanilla's camelCase field names (daysLeft, ownerUUID,
resourcePackUrl... were silently dropped), ownership compares ownerUUID, the
legacy invite-link fallback endpoint is gone (vanilla only uses the frontend
invite-code service), links go through Action::OpenUri with vanilla's
CommonLinks, the Realms title button follows the multiplayer gate, and every
visible string is a vanilla mco.* key (one Horizon key for "not signed in").
No session (offline, expired token) means no request at all.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:45:45 +01:00
Ludovic Marie 8231317207 Merge main into #9210 2026-11-04 19:45:38 +01:00
phexuss e49d129642 fix(launcher): reuse the Wardrobe session until it expires 2026-11-04 19:44:18 +01:00
Ludovic Marie 54142077f2 Merge main into #9275 2026-11-04 19:43:20 +01:00
Ludovic MarieandClaude Opus 5.5 3f529b7d80 SNBT depth cap: 256 rule frames, and a test for deep real values
128 frames let only 31 nested compounds, 17 levels of a component's
`extra` list and 6 containers in containers parse. 256 doubles that and
still stays under a 2 MiB debug thread's stack overflow (384 overflows the
400-deep tests in debug); release frames are smaller still.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:43:19 +01:00
Ludovic Marie 581fad0d32 Merge pull request #9310 from TheMrZZ/fix/h287-named-weather-predicate
🤖 Agent: `weather_check` now compares each present field with `Level.isRaining` (rain level > 0.2) and `isThundering` (rain × thunder > 0.9, dimension-aware), as `WeatherCheck.test` does; an omitted field doesn't constrain. We merged main twice so your corpus group runs after #9288's and #9309's; verify is green on that merge. Nothing else found.
2026-11-04 19:43:14 +01:00
Ludovic Marie 9a5a471297 Merge pull request #9321 from dontstopgorkingit/fix/4683
🤖 Agent: on Linux the stream now asks ALSA for a fixed 1024-frame buffer (about 23 ms at 44.1 kHz) instead of cpal's ~100 ms default. We pushed adf2038d7 so a device that refuses a fixed buffer falls back to the default one rather than playing nothing. Not measured on real hardware here; fmt, clippy and the client tests pass on a union with main.
2026-11-04 19:43:09 +01:00
Ludovic Marie 2599f46c1f Merge pull request #9311 from horizon-rust/fix/launcher-update-prompt
🤖 Agent: the launcher now offers "A new version is available" with Update, and copies that can't replace themselves say so instead of failing silently; Linux end to end on the ThinkPad (Update to 0.2.40 restarts, a read-only copy shows the can't-replace card), pr_ready --quick green.
2026-11-04 19:39:41 +01:00
Ludovic Marie 95247e709f Merge pull request #9225 from TheLion102009/api/container-accepts
🤖 Agent: pushed a fix: container_accepts.json listed native_hooks.rs in `comments`, which the stamp tool stamps by itself and refuses (test_stamp_native_hooks failed on this head). The hopper path pays nothing for vanilla containers, since mod_container_accepts returns at the Kind::Mod check. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green.
2026-11-04 19:39:35 +01:00
Ludovic Marie 52268eec25 Merge pull request #9223 from TheLion102009/api/machine-breaking
🤖 Agent: use_as_player swaps about 30 Game fields by hand and restores them. A per-player field added to Game later and not listed there would leak the fake player's state into the server shell, so one swap struct would be safer next time. Health, food and effects aren't swapped, which only works because a single use never finishes eating. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green.
2026-11-04 19:39:30 +01:00
Ludovic Marie a815f2f89d Merge pull request #9222 from TheLion102009/api/host-bridge
🤖 Agent: merged main (the mod_api use list now also has NativeReloadListener). native_sneaking reads KeyCode::ShiftLeft as use_hand does, so on a server it is the acting guest's only inside as_guest; from a block-entity tick it reads the server's empty key set. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green, stamp tests OK.
2026-11-04 19:39:26 +01:00
Ludovic Marie 57487ddd4a Merge pull request #9262 from voidsipp/fix/8702-breeze-fishing-deflection
🤖 Agent: merged main (its minecart_hook_targets and pull_hooked tests sit beside the breeze ones, and the test module imports Hook once). The deflect path matches on Species::Breeze rather than #deflects_projectiles, which is the same set with default tags but not with a data pack that adds to the tag. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green.
2026-11-04 19:39:21 +01:00
Ludovic Marie b2842e8f68 Merge pull request #9271 from voidsipp/fix/8279-mob-air-sync
🤖 Agent: checked max_air_supply against the 26.3 bytecode: only Dolphin (4800) and Axolotl (6000) override getMaxAirSupply, and Nautilus returns the default 300, so the two arms are complete. #9220 now carries this branch and shares its mob fixture. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green.
2026-11-04 19:39:16 +01:00
Ludovic Marie 3cbad717b2 Merge pull request #9264 from yosifdheef/fix/7992-shulker-open-support
🤖 Agent: shulker_support costs one block-tag lookup per sturdy/rigid/center query before the cached path, the same shape as moving_block_supports there. Not in this PR: vanilla's ShulkerBoxBlockEntity also calls doNeighborUpdates when the lid moves, so a torch already on an opening lid only drops at the next neighbour update. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green, the new level tests included.
2026-11-04 19:39:10 +01:00
Ludovic MarieandClaude Opus 5.5 949facfa7e Wardrobe: the shop tests' claim has one_click
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:35:51 +01:00
Ludovic Marie 262bdc0fef Merge pull request #9206 from degepie/fix/looking-at-all-triggers
🤖 Agent: checked every new trigger id against the trigger it guards; damage_view's two KilledTrigger ids are right, since its only callers (creatures.rs last_blow, dragon.rs) feed trigger_killed. Commands keep player_entity(), so a predicate= that names looking_at still sees the ray. union of 15 contributor PRs on main 6ddd14e72 (playhorizon): fmt, clippy -D warnings, horizon-client 887 + client-lib 1682 + world/command tests green.
2026-11-04 19:35:26 +01:00
Ludovic MarieandClaude Opus 5.5 bcdb34dcdd Multiplayer: answer the server's known packs with vanilla's core pack, as vanilla
KnownPacksManager.trySelectingPacks answers the server's SelectKnownPacks
with the packs the client has; vanilla's client names minecraft:core of its
version, and the server then leaves that pack's registry data out. Horizon
answered with no pack, so every server sent it all registries in full and
saw a client unlike vanilla's. Horizon has the core pack's data in its
prepared resources: it now names it, and fills the entries that come
without data from data/<namespace>/<registry>/<path>.json, as
RegistryDataCollector does from the client's own pack.

tools/vanilla_wire_e2e.py gains known-packs-as-vanilla and known-data-used
(a /say shows as the say_command chat type formats it).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:34:56 +01:00
Florian ERNSTandCodex 13864e6417 Merge latest main before H295 submission
Co-Authored-By: Codex <[email protected]>
2026-11-04 19:34:11 +01:00
Ludovic Marie 498168e904 Merge pull request #9346 from voidsipp/fix/9303-hdr-clear-fixture
🤖 Agent: merging. On playhorizon (llvmpipe adapter) four of the six clear_budget tests fail on main with "fixture buffer 2 must have RGBA16F"; with this head all seven pass, the new adapter-free fixture check included, so CI without a GPU now catches a broken fixture too. Proof: cargo test -p horizon-client-render --lib clear_budget on main (4 failed) and on a union with this head (7 passed); fmt and clippy -D warnings pass on the union.
2026-11-04 19:33:11 +01:00
Ludovic Marie 2d7dd77d1d Merge pull request #9345 from xxaxio/fix/9074-title-wither-skeletons
🤖 Agent: merging. clear_entities runs when a world opens and in finish_close_world, after the closing save has already written every parked level (save_parked_levels), so the new levels.clear() drops nothing unsaved; leave_server now also resets persist and entity attachments, which a client never saves. Proof: union with main, #9341 and #9346: fmt, clippy -D warnings, Python tests and 2624 client and client-lib tests pass.
2026-11-04 19:33:07 +01:00
Ludovic Marie f92e450401 Merge pull request #9341 from Ragbie/fix/dragon-no-ai
🤖 Agent: merging. The 26.3 bytecode of EnderDragon.aiStep checks isNoAi right after Mth.wrapDegrees(getYRot()), sets flapTime to 0.5 and returns, which is where tick_dragon and the remote tick now stop; NoAI is bit 1 of Mob.DATA_MOB_FLAGS_ID (15), the index the host sends. Proof: union with main, #9345 and #9346: fmt, clippy -D warnings, Python tests and 2624 client and client-lib tests pass; CI parity runs the new dragon_no_ai harness against a vanilla server.
2026-11-04 19:33:03 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 84ccda46f1 A lightning flash fades in a frozen level, and its border waits
ClientLevel.tick fades the sky flash on every tick, and moves the world
border's size change only while the level runs normally. The client did
it the other way round: after a /tick freeze, a flash that had just
struck lit the sky for as long as the level stayed frozen, and a border
change went on shrinking or growing on this side while the server's
held.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:31:03 +01:00
phexuss e2c6c17bd7 fix(skins): keep the skin when only its cape fails, and retry failed textures 2026-11-04 19:30:18 +01:00
DUzzL 05a8366017 Refuse breaking game master blocks without game master permission 2026-11-04 19:29:55 +01:00
Ludovic Marie 0d7704f3f3 Merge pull request #9243 from horizon-rust/pvp/crosshair
🤖 Agent: rebased on main e0798a4224 (main made cosmeticsOthers optional in Extras::serialize; the crosshair keeps main's string and appends the pvp lines). Desktop: clippy -D warnings and rustfmt pass on the stack's top; before this rebase pr_ready passed 3946 tests on this commit (fails: main's 4 gfx:: clear_budget GPU tests on lavapipe, which CI passes, and NixOS's missing uname in a Python install.sh test). CI verify green on this head. Off by default; the crosshair mask is rebuilt only on a settings change.
2026-11-04 19:28:30 +01:00
Florian ERNSTandCodex f234c3adc3 Merge main and isolate crafting-priority fixture worlds
Preserve advancement and recipe fixture catalogs; the standalone parity runner prevents overriding recipes from affecting generic command-count controls.

Co-Authored-By: Codex <[email protected]>
2026-11-04 19:27:59 +01:00
degepie 5cdb0d73b4 Merge remote-tracking branch 'origin/main' into fix/dragon-death-vibration
# Conflicts:
#	crates/horizon-client/src/game/dragon.rs
2026-11-04 23:27:16 +05:00
Nikita Maximilian GrimmandClaude Opus 5.5 5d9e84e649 HORIZON_STILL: a frozen scene draws the same frame in every run
A dev switch for before/after pixel diffs (#8750). The world starts frozen
before its first tick, and what the frame shows of time follows the
world's ticks: animated textures, the glint and world border scroll, the
darkness pulse, the dragon's crystal beam, the player model's idle swing,
the HUD (hearts, chat, titles, the locator bar, the tab list), a shader
pack's clock and randoms (smoothed uniforms at their targets, custom
uniforms' random, cave mood, obfuscated text, a spinning clock or compass)
and the camera's partial tick. While frozen, the client's own entity
effects and countdowns wait for the world too, as a frozen level's
entities don't tick. `/tick step N` moves the scene on by whole ticks.
Off by default; nothing changes without it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:24:23 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 26860a60df Dropped items and end crystals draw their phase from their own random
ItemEntity's bobOffs and EndCrystal's time come from the entity's own
random in vanilla. The client drew them from the level's random, shared
with splashes, eating and other client effects, so the same dropped item
could bob and spin from a different phase from run to run. Each now draws
from a random seeded from its id and position, as a client mob's is
(Mob::new, now mobs::entity_random).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:24:17 +01:00
Florian ERNSTandCodex gpt-6.1-sol 5c7c4b07fe Merge main and retain advancement fixtures alongside the crafter map copy
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 19:21:20 +01:00
Florian ERNSTandCodex 8c0f8fe031 Select crafting recipes in resource-file registration order
Query the active world table from crafters and retain first-match path/namespace priority after recipe replacement and filtering.

Co-Authored-By: Codex <[email protected]>
2026-11-04 19:18:36 +01:00
Ludovic Marie 771541d109 Merge pull request #9378 from horizon-rust/fix/native-build-name-the-mod
🤖 Agent: verified b5c41f4 on playhorizon (launcher fmt, clippy -D warnings, 51 native_mods tests; the new test fails before the fix) and CI is green. Only the one-line form of horizon-mixins' diagnostic changes: it leads with mod_id (injection_id) when the tool names one; E_MODULE_CONFLICT carries no mod_id and reads as before.
2026-11-04 19:17:56 +01:00
Ludovic Marie 6b75c9cb79 Merge pull request #9280 from voidsipp/fix/9224-opened-loot-save
🤖 Agent: opening a rolled chest now also drops `LootTable`/`LootTableSeed` from its saved source compound, so an emptied chest no longer re-rolls after a reload; `consume_container_loot` touches nothing when the chest had no rolled marker. The 576 harness cases vary seed and table, but the result never depends on either: three modes would cover the same thing. Proven in our union run (client tests, desktop).
2026-11-04 19:17:51 +01:00
Ludovic Marie 2baf3a4847 Merge pull request #9300 from degepie/fix/minecart-display-sync
🤖 Agent: entries 11 (`DATA_ID_CUSTOM_DISPLAY_BLOCK`, optional block state, 0 = none) and 12 (`DATA_ID_DISPLAY_OFFSET`, default from the kind) now reach clients and are read back, so single player and vanilla servers show a cart's DisplayState. One gap noted in your body and left for later: `default_display_offset` gives 6 for a chest cart where 26.3's `MinecartChest` says 8. Proven in our union run (client tests, desktop).
2026-11-04 19:17:47 +01:00
TheMrZZandCodex gpt-6.1-sol e98ab97b73 Preserve mob damage protection across NBT edits
Keep the internal damage cooldown and previous-hit amount when a data
command reconstructs a mob, matching an in-place vanilla entity load.
Continue loading explicitly edited Health and HurtTime values from NBT.

Add guarded command regression cases for equal hits, stronger hits,
unchanged writes, fresh actors and measured cooldown expiry.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 19:17:26 +01:00
Florian ERNSTandCodex gpt-6.1-sol bfdd6af67f Merge main and install function and loot advancement fixtures together
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 19:16:14 +01:00
Ludovic Marie b46279d5b5 Merge pull request #8274 from megabytesme/feat/windows-arm64-support
feat(windows): add native ARM64 build support
2026-11-04 19:14:26 +01:00
Ludovic MarieandClaude Opus 5.5 7f19c70db3 Wardrobe: "Claim for free" where Paddle doesn't sell
Players in countries Paddle doesn't serve (Russia, Belarus...) can't use the
shop's 0 CHF checkout. The cosmetics service now says so per request
(`claim.one_click` in /v1/me, decided by country on the service), and the
Wardrobe then shows a "Claim for free" banner that calls POST /v1/claim with
the existing Mojang-proven session. Everyone else keeps the shop link.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:13:25 +01:00
Ludovic Marie 80d4e1c9f2 Merge remote-tracking branch 'origin/main' into cg38/9310
# Conflicts:
#	tools/command_exec_corpus.py
2026-11-04 19:12:25 +01:00
Ludovic Marie cee89b5498 Merge pull request #9309 from TheMrZZ/fix/advancement-reward-functions
🤖 Agent: `AdvancementRewards.grant`'s function now runs as the player with suppressed output and GAMEMASTER permission, and restores the outer callback so a reward's `return 9` can't replace the grant command's result. We merged main so your group runs after #9288's bell group in the corpus; verify is green on that merge.
2026-11-04 19:11:47 +01:00
oxi-does-dev 487d76b3f2 Run client mod commands from the control socket
The control socket's chat line went straight to the server on a connected world, skipping the client mod commands, the Chat option's restrictions and the recent list that typed chat goes through. It now uses handle_chat_input like the chat screen, so /modcmd opens the mod's screen on a server and the rest of the line is sent only when no mod owns it.

The control socket's command line also tries a client mod's command before the server console: the console cannot see a mod that is compiled into the client, so hudplus or another client mod's command failed there.

AI agent: oxi-does-dev via OpenCode, GLM 5.3.
2026-11-04 21:10:01 +03:00
Ludovic MarieandClaude Opus 5.5 1394ce1659 PvP & HUD: a PvP preset button
One click on the PvP & HUD page (and on Horizon Extras) turns on the custom
crosshair, the armour and effects widgets, vanilla's toggle sprint and toggle
sneak, lower fire, and the official FPS counter and keystrokes mods when they
are installed. It keeps each option's own settings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 c58cc9369c PvP & HUD: your own time of day and weather
Two options on the PvP & HUD page, off by default: a time of day (morning,
noon, sunset, midnight) and a weather (clear, rain, thunder) that only this
window draws. The sky, its light, the rain and its fog follow them; the
world's day time and weather, and the gameplay that reads them, stay the
server's. Nothing is sent to the server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 5468cd1c3b PvP & HUD: lower fire, shield and totem size, glint colour, clear water and lava
Visual tweaks on the PvP & HUD page, each off by default:
- Lower fire moves the first-person fire overlay down (0-80% of its height).
- Shield size (50-100%) and totem animation size (25-100%).
- Enchantment glint colour: the glint's brightest channel over a solid colour,
  in entity.wgsl and hud.wgsl, carried in free uniform lanes.
- Clear water (vanilla's 30-second water vision from the start) and clear lava
  (vanilla's Fire Resistance lava fog), never past what vanilla shows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 ff77855f6f PvP & HUD: enchantment levels as numbers
An option on the PvP & HUD page, off by default: enchantment names show their level
as a number ("Sharpness 5", and past vanilla's numerals, "Sharpness 255") wherever
the client names one: tooltips and the enchanting table's hint. A one-level
enchantment still has none. Enchantment.getFullname reads a process-wide switch the
game sets each frame from the option.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 441d4da364 PvP & HUD: armour, effects and held item count widgets
Three HUD widgets on the PvP & HUD page, each off by default, in a corner of its own
beside the minimap and mods' boxes (docs/pvp.md):
- Armour: each piece worn, head first, with its uses left as a number or a percent,
  coloured like its durability bar.
- Effects: icon, name and level, time left, in place of vanilla's top-right icons.
- Held item count: how many of the held item the inventory holds; arrows with a bow
  or crossbow.

pvp_hud::Widget keeps each widget's lines and rebuilds them only when what it shows
changes, so a frame that changes nothing allocates nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 46f7457082 PvP & HUD: block outline colour, thickness and fill; hit colour
Two more groups on the PvP & HUD page, off by default (docs/pvp.md):
- Block outline: a solid colour at 10-100%, 1-4 times vanilla's width, and an
  optional fill that tints the targeted block's shape boxes at 25%. Its first
  settings are vanilla's 0x66000000 stroke. With high contrast on, the colour
  replaces the bright stroke.
- Hit colour: the hurt overlay's colour and how much of it covers the entity.
  Its first settings give OverlayTexture's 0xB2FF0000 texel. The texel rides in
  colored_count.y of the frame uniforms, so it needs no pipeline rebuild.

FrameInputs gets one pvp::Frame field the later PvP options extend.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 2209fd9df0 PvP & HUD: chat timestamps, compact chat, copy on click and search
Four chat options on the PvP & HUD page, off by default:
- Timestamps: the local time before each message.
- Compact chat: a message repeated in a row shows once with its count (x3).
- Copy on click: a click on a message with no click action copies its text.
- Chat search: Ctrl+F in the open chat filters the messages by the box's text.

They only change how the kept messages are laid out, so turning one off
shows the chat as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 6ab8720792 PvP & HUD: ping, coordinates, server address and combo widgets
Four more HUD widgets on the PvP & HUD page, off by default (docs/pvp.md):
- Ping: the latency the server's player list gives this player. Multiplayer only.
- Coordinates: block position and facing (Direction.fromYRot), hidden whenever F3
  is reduced (option, game rule or server), so it never shows what a server hides.
- Server address: as net::shown_address shows it (invite codes hidden).
- Combo counter: hits in a row, each counted when the server reports the attacked
  entity hurt within a second of the swing; reset when this player is hurt or after
  3 s without a hit.

Each widget rebuilds its text only when its value changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
Ludovic MarieandClaude Opus 5.5 b208196224 PvP & HUD page: a custom crosshair
Horizon Settings > PvP & HUD... is a new page of options a PvP player sets, all off by
default (docs/pvp.md). The first is the crosshair: shape (cross, T, X, circle, square,
dot), size, gap, thickness, colour (inverted as vanilla, or solid), outline, centre dot,
and another colour over a living entity (vanilla's crosshairPickEntity). Its first
settings draw vanilla's 15-pixel cross; the attack indicator stays vanilla's.

The pixels are worked out once per settings change (CrosshairCache), so a frame only
adds their quads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:06:35 +01:00
gigamans442andOpenAI GPT-6.1 Sol b6763cc941 Honor explicit weather depth settings in shader packs
Carry rain.depth through Post into weather pipeline depth writes.
Preserve prior omitted/invalid behavior pending external default evidence.
Add original property and hardware GPU regression coverage.

Co-Authored-By: OpenAI GPT-6.1 Sol <[email protected]>
2026-11-04 13:03:21 -05:00
Ludovic MarieandClaude Opus 5.5 4bced57e08 Merge origin/main into feature/realms-support
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:01:37 +01:00
phexuss 22ca656476 fix(render): check a fetched PNG's size before decoding it 2026-11-04 19:01:29 +01:00
Evrim CicekandClaude Opus 5.5 cc613a02fd lan::conn test: a refused transfer's hello may find the connection closed
With transfers off the server turns a transfer away at the handshake and
closes (ServerHandshakePacketListenerImpl.handleIntention), so
transfer_login's hello raced the close and its unwrap failed with
"broken pipe" now and then (seen in CI). The hello's send error is now
ignored; the disconnect is still read. With a 200 ms pause before the
hello the test failed every time; with this change it passes.

Fixes #9376

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 21:01:01 +03:00
Ludovic MarieandClaude Opus 5.5 b5c41f49d8 Native builds: a horizon-mixins error starts with the mod that doesn't apply
A player's native build on 2026-11-04 failed with "horizon-mixins failed
(exit status: 1)" on Horizon 998b1d8, the commit fast_items 0.1.0 pins.
fast_items applies there; built together with a mod made for an older
Horizon (14 of the 72 registry native mods don't apply at 998b1d8, among
them appleskin, flair, mod_menu and freecam), the other mod's hook target
is gone. The diagnostic named that mod only deep in a file path. It now
leads with the mod and its injection: "appleskin (hud): E_TARGET_NOT_FOUND".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:00:22 +01:00
DUzzL 41be700c66 Fail dry Riptide trident use without dipping the hand (TridentItem.use FAIL) 2026-11-04 19:00:01 +01:00
f19df01c7c Keep the dragon hit helpers that only tests call out of the server build.
The game hits through hurt_part_from, so the no-attacker wrappers were dead code without cfg(test).

Co-authored-by: Cursor <[email protected]>
Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 18:56:29 +01:00
Florian ERNSTandCodex gpt-6.1-sol 80646f9a18 Merge main and preserve both corpus scenario additions for #9349
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 18:56:02 +01:00
Florian ERNSTandCodex eee1ffee60 Merge current main and preserve loot and bell fixtures
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:55:13 +01:00
Ludovic Marie e0798a4224 Merge pull request #9281 from lightcodegit/fix/execute-parse-depth
🤖 Agent: caps `parse_nodes` recursion at 128 where `execute … run` redirects to the root, so the 400-deep chain from #9252 fails to parse instead of overflowing the server thread; the 400-deep test would abort the binary without it. Chosen over #9320, which capped execution only. Nothing else found.
2026-11-04 18:54:32 +01:00
Ludovic Marie 4921de8282 Merge origin/main into feat/windows-arm64-support
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	.github/workflows/launcher.yml
2026-11-04 18:54:00 +01:00
Florian ERNSTandCodex 9f5d2ea594 Merge current main shulker-space fix
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:53:08 +01:00
Florian ERNSTandCodex 1c3dae2a17 Merge latest main before H298 submission
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:52:48 +01:00
Ludovic Marie b319334189 Merge pull request #9292 from voidsipp/fix/9269-shulker-teleport-collision
🤖 Agent: attachment now uses the entity-context collision shape (`entityCanStandOnFace`), so soul sand only takes a shulker under it and scaffolding follows the shulker's height. We pushed a89eb944c, a full-cube fast path, because `canStayAt` runs every tick per shulker. Proven with the client tests on a union with main (desktop).
2026-11-04 18:52:19 +01:00
Ludovic Marie 555c69406f Merge pull request #9296 from sirrayi/fix/teleport-rider-dismount-and-turn
🤖 Agent: the dismount on teleport matches `Entity.teleport`'s `stopRiding()`. We pushed 23a886d93: a relative yaw is a delta in vanilla's transition, so riders turn by the vehicle's turn either way, and `facing` leaves them unturned. The ignored rider game test passes on a union with main (desktop), with clippy and the client tests.
2026-11-04 18:52:14 +01:00
Ludovic Marie 900416b606 Merge pull request #9305 from degepie/fix/dragon-breath-mobs
🤖 Agent: mobs now stop a dragon fireball and take its breath, through the lingering clouds' `cloud_affect` path (undead healed). We pushed 01634eab6 so the per-tick End scans gather mobs only while a fireball or breath cloud exists. Proven with fmt, clippy and the client tests on a union with main (desktop).
2026-11-04 18:52:10 +01:00
voidsipandCodex 164b298d35 Follow entity-bound sounds with their emitting entity
Keep the packet seed and category, update the bound position each tick,
and stop the old object when an entity is removed or its id is reused.
Compare the sound-engine lifecycle against the owned vanilla client.

Co-Authored-By: Codex <[email protected]>
2026-11-04 11:51:50 -06:00
Florian ERNSTandCodex 7f0f1a93ec Merge latest main and retain both parity corpus groups
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:51:40 +01:00
Ludovic Marie 8045ecd4e7 Merge pull request #9259 from jaszkus/optimization-launcher
Launcher: a Performance mode for slower computers (and an accent that changes at once)
2026-11-04 18:51:39 +01:00
Ludovic Marie 63219718ff Merge remote-tracking branch 'origin/main' into cg38/9310
# Conflicts:
#	tools/command_exec_corpus.py
2026-11-04 18:51:19 +01:00
Ludovic Marie c5fb2ba820 Merge remote-tracking branch 'origin/main' into cg38/9309
# Conflicts:
#	tools/command_exec_corpus.py
2026-11-04 18:50:26 +01:00
4af07cfcf1 Drop the needless return at the end of the frog water-travel test.
Clippy rejects a return that only ends a unit test.

Co-authored-by: Cursor <[email protected]>
Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 18:49:09 +01:00
Arslan ArmanulyandClaude Opus 5.5 0758947658 Shader packs: smooth() in an if branch not taken keeps its value, as in Iris
Custom uniforms evaluated every argument of if(), so a smooth() in a
branch not taken still moved toward its value each frame. Our probe pack
in Iris 1.11.7 (if(fmod(frameTimeCounter, 4) < 2, smooth(sq, 10), 0.5)
over a 16 s square wave) reads values that fit "frozen while the branch
isn't taken" within 0.008, and miss "updated every frame" by up to
0.14. if() now evaluates only the conditions up to the first true one
and that branch.

The same probes also time smooth() over a square wave from if(),
eyeBrightness, worldTime and rainStrength, fades 0.5 to 100 and the
default: their Iris values fit a half-life of 0.1 s per fade unit, as
custom.rs already has (a fit to our probes, not Iris's code). A test
pins worldTime steps and the default fade.

Fixes #9371

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 22:48:53 +05:00
c131ee3fee Drop constant asserts that clippy rejects in the armor-stand shove test.
The 0.2 gate is already checked by whether the cart is shoved.

Co-authored-by: Cursor <[email protected]>
Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 18:47:14 +01:00
ElyTheCat 3ec390a6b8 Merge branch 'main' into perf/entity-occlusion-culling-pipeline 2026-11-04 21:14:37 +03:30
Ludovic Marie 795024bfc4 Merge pull request #9263 from masa-masa-0314/chore/ci-bump-actions-node24
CI: run GitHub Actions on Node 24 (checkout v7, artifacts v7/v8, setup-python v7, setup-java v6)
2026-11-04 18:43:53 +01:00
Ludovic Marie 036a2a2cb6 Merge pull request #9328 from voidsipp/fix/9231-inline-mod-install
🤖 Agent: merging. The row button takes the detail card's own checks (installed, checking, busy, support.installable) and calls the same mods_install with the known project, so native-mod consent still comes first; stop_propagation keeps the click from selecting the row. Proof: union with main, #9198 and #9314: launcher fmt, clippy -D warnings and 492 launcher tests pass.
2026-11-04 18:42:51 +01:00
Ludovic Marie 55501878f1 Merge pull request #9314 from voidsipp/fix/4350-client-arrow-fluid-parity
🤖 Agent: merging with one change. The client tick now emitted the arrow's hit, splash and extinguish sounds and played them, but Entity.playSound passes no player to Level.playSound and ClientLevel.playSeededSound plays only for its own player (26.3 bytecode), so the server's packets already play them: every arrow hit sounded twice. tick_client now drops its Sound events after the tick, so the random draws stay in vanilla's order; the new test fails without it. The rest matches AbstractArrow.tick: the whole startFalling/tickDespawn branch is inside !isClientSide. Proof: union with main, #9198 and #9328: fmt, clippy -D warnings, Python tests and the client and client-lib tests pass; arrows tests pass on this head.
2026-11-04 18:42:45 +01:00
Ludovic Marie 815a3434fe Merge pull request #9198 from voidsipp/fix/8892-zombie-conversion-sounds
🤖 Agent: merging. Zombie.convertVillagerToZombieVillager and ZombieVillager.finishConversion send level events 1026 and 1027 only when not silent, and handleEntityEvent(16) draws volume before pitch, as here. I merged main and put 1026/1027 into main's new level_event_except (no excluded player, like the other null-player level events), and gave the probe's hand-built MobType main's new item_stack_limits field. Proof: union with main, #9314 and #9328: fmt, clippy -D warnings, Python tests and the client and client-lib tests pass; CI parity runs the new zombie_cure_sound harness.
2026-11-04 18:42:40 +01:00
Ludovic Marie 6be843b43c Merge pull request #9288 from TheMrZZ/fix/headless-bell-server-tick
🤖 Agent: bells now tick on the headless server before the renderer guard, matching `BellBlockEntity.serverTick`/`makeRaidersGlow`. Small cost to know: while any bell is in `be_anims.bells` the raiders tag set is cloned each tick, and server bells stay in the map until their block changes. Proven with the client tests on a union with main (desktop).
2026-11-04 18:42:32 +01:00
Ludovic Marie f8de9f502d Merge pull request #9293 from xxaxio/fix/9207-control-summon-ticking
🤖 Agent: `summon` now goes through `server.call` + `in_owner_level` like `mobs` already does, so the mob ticks on the server thread and reaches the window through the tracker; the default position is computed from the window's own player first. Nothing else found.
2026-11-04 18:42:23 +01:00
Ludovic Marie 3cb1f356ab Merge pull request #9294 from Ragbie/fix/dragon-death-outside-end
🤖 Agent: `self.dim == Dim::End` is the level being ticked (levels swap per dimension), so a summoned overworld dragon now gives 500 and builds no exit portal, as `EnderDragon.tickDeath` with no `dragonFight` does; the new harness checks it against a vanilla server. Nothing else found.
2026-11-04 18:42:18 +01:00
Ludovic Marie c6d447748c Merge pull request #9302 from degepie/fix/dolphin-trail
🤖 Agent: matches `Dolphin.tick` on a client (NoAI return, in water, lengthSqr > 0.03, four particles at ±0.3 of the yaw, one `1.2 - nextFloat()*0.7` draw a tick from the mob's own random). It runs only on the window's copies, so the server's random sequence is untouched. Nothing else found.
2026-11-04 18:42:14 +01:00
Ludovic Marie 92694f6d2c Merge pull request #9306 from degepie/fix/illusioner-blindness-mobs
🤖 Agent: matches `SpellcasterIllager.customServerAiStep` (Illusioner has no override) and `IllusionerBlindnessSpellGoal.performSpellCasting` blinding any LivingEntity target. One gap left: `EffectMob` carries no source entity where vanilla passes the illusioner; it only matters for effect attribution. Proven with the client tests on a union with main (desktop).
2026-11-04 18:42:10 +01:00
Ludovic Marie a226114661 Merge pull request #9323 from martipops/fix/wardrobe-mark-cache
🤖 Agent: `Marks` keys each mark by item id, the catalogue's hash and the window scale, and hands every replaced image to `drop_image` once per frame next to `sky.free_replaced`, so the atlas stops growing on redraws; the tests cover reuse, a scale change and a new file. Nothing else found.
2026-11-04 18:42:05 +01:00
Florian ERNSTandCodex 55a00b627a Merge latest main for H298 readiness
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:40:16 +01:00
Ludovic Marie 84e9ba93b5 Merge pull request #6652 from rdbtCVS/revert-ram-limit
🤖 Agent: Ludovic decided on 11-04 to remove Horizon's RAM limit, so this goes in. A correction: earlier agent comments here said he had decided to keep the opt-in limit. That was wrong; he never decided that. One concrete point, checked: a Java instance's -Xmx stays settable in both layouts. Classic: main.rs:3281 renders memory_row when not glass, and memory_row returns None for non-Java instances (launch_page.rs:250). Glass: the Edit dialog renders glass_memory_row (glass_instance.rs:678), and memory_instance filters to Java instances. java_launch.rs:427 still passes -Xmx{memory_mb}M. Our 13:57Z review was wrong about this. I merged main (d571d80a06) and found no stale HORIZON_MEMORY_*, /ramlimit or lang keys. Proof on playhorizon: fmt; clippy -D warnings on client/client-lib/render/threads (default) and on horizon-client with --no-default-features; the server build with --no-default-features; tests for threads 17, client-lib+render 2693, client 878; the launcher's fmt and clippy, plus 571 launcher tests. Thanks rdbtCVS.
2026-11-04 18:38:43 +01:00
Ludovic MarieandClaude Opus 5.5 decba13c64 Drop a client arrow's own sounds: the server's packets play them
Entity.playSound passes no player to Level.playSound and
ClientLevel.playSeededSound plays only for its own player, so the
client's copy of an arrow plays no hit, splash or extinguish sound;
the server's sound packets carry them. Playing them as well doubled
every arrow hit in single player and on servers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:37:50 +01:00
Florian ERNSTandCodex 53fef18ae4 Extend crafter maps using live saved-map context
Reuse map post-processing before dispensing and register the derived saved map. Cover actual map use, scheduled crafting, and backed/missing/max-scale controls.

Co-Authored-By: Codex <[email protected]>
2026-11-04 18:37:42 +01:00
31ef855776 A flying ender dragon takes LivingEntity knockback from a melee hit.
EnderDragon.knockback skips that push while the dragon is sitting, including a hit that makes it take off.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 18:28:34 +01:00
820b8fe8f5 Start a ridden pig or strider's boost when the client receives its total.
The rider's client simulates the mount, and ItemBasedSteering.onSynced starts that timer from the synced int. Without it the boost factor stays 1.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 18:28:09 +01:00
Ludovic MarieandClaude Opus 5.5 c41444f589 Merge feat/windows-arm64-support (DirectX 12 first on Windows ARM64) into the release-safety change
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:27:48 +01:00
28778ffc3b client(fireworks): free leads when a glide boost starts
A firework used while fall-flying has to drop every lead on the player and play the snap, or the mob stays tied and nobody hears it.

Co-authored-by: Cursor <[email protected]>
Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 18:27:23 +01:00
0cfb9167a5 Shove a rideable minecart that overlaps an armor stand.
ArmorStand.pushEntities moves a plain minecart inside the stand when it is within 0.2, and the stand tick never did, so the cart stayed put.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 18:25:42 +01:00
34f506d0d1 A named item frame drops with its custom name.
ItemFrame.getFrameItemStackWithData copies the entity name onto the frame item before dropItem spawns it. Glow item frames use that same stack.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 18:25:28 +01:00
553d0d26e9 Swim frogs with Frog.travelInWater instead of generic fluid travel.
A frog in water was using LivingEntity.travelInFluid, so its speed and drag did not match vanilla.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 18:22:59 +01:00
Florian ERNSTandCodex 6a0ac7a198 Merge latest main for H292 readiness
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:19:48 +01:00
1c615062f6 Apply worn mob_visibility when mobs measure detection range.
A skeleton skull, zombie head, creeper head or piglin head should halve how far that mob notices the player. getVisibilityPercent multiplies by the item's component, and the targeting paths only scaled sneaking and invisibility.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 18:17:34 +01:00
Ludovic MarieandClaude Opus 5.5 c844022a28 Windows ARM64: a failed ARM64 build never holds the other platforms' release
- game-build.yml and launcher.yml: the windows-arm64 matrix entries are
  continue-on-error, so the sign and publish jobs that need them still run.
- nightly-build.yml: horizon-windows-arm64.zip goes into SHA256SUMS and the
  Nightly only when it was built; otherwise a ::warning:: says it is left out.
- launcher.yml: the release warns when the ARM64 zip or setup is missing (its
  sign and publish steps already take dist/horizon-launcher-* by glob).
- Test: an ARM64 launcher skips a launcher release without its own zip and
  never takes the x64 one (launcher_update_from).
- actionlint: windows-11-vs2026-arm is GitHub's image, newer than its label list.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:15:21 +01:00
TheMrZZandCodex gpt-6.1-sol f8dc1e773b Apply scoreboard team formatting to mob command names
Reuse the existing mob team lookup and team formatter when command feedback
names a mob. Preserve its custom name or translated type and the player paths.

Extend the real command corpus with guarded zero-tick join, custom-name,
prefix/suffix clearing, color-only and leave controls against Java 26.3.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 18:12:27 +01:00
voidsipandCodex d93fa85595 Repair HDR clear-budget fixture format hints
Co-Authored-By: Codex <[email protected]>
2026-11-04 11:08:56 -06:00
Ludovic MarieandClaude Opus 5.5 ae0453ebba Merge main into chore/ci-bump-actions-node24
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:08:39 +01:00
MegaBytesMe a59c1ae8b0 Merge the latest Windows ARM64 review changes 2026-11-04 17:08:37 +00:00
Ludovic Marie 23bbd81179 Merge pull request #9342 from horizon-rust/cosmetics-default-off
🤖 Agent: a player with no cosmetics and no saved choice now sees none on others and the game never asks the service about them; owners see them as before, an explicit choice wins. Proof: a gameplay test against a fake service (non-owner sends no request), 27 cosmetics + 78 render + 9 wardrobe tests and clippy -D warnings green on the desktop.
2026-11-04 18:07:12 +01:00
Ludovic Marie 87a5fc6ad0 Merge pull request #9343 from horizon-rust/automation/native-hook-stamp-1104
🤖 Agent: ten merged fragments numbered through API 409 by the repo's own stamper; the workflow now warns instead of failing, because the setting that lets Actions open PRs would also let it approve them.
2026-11-04 18:07:08 +01:00
AxxioandAntigravity a807755773 Title screen: clear wither skeletons and all level mobs on world exit (fixes #9074)
When disconnecting or leaving a server/single player world, leave_server() in remote.rs cleared only top-level mob arrays, leaving GPU skin models, skin frame vertex queues, parked dimension levels, remote objects, and entity draw spans allocated. These cached models could persist into the title screen renderer, causing Nether mobs such as Wither Skeletons to render and wander across the main menu panorama.

- Delegate leave_server() entity cleanup to clear_entities().
- Extend clear_entities() to also clear remote objects, projectiles, parked levels, render vertex buffers, draw spans, entity motion, and reset skin models and frame draws.
- Implement Levels::clear().
- Expose SkinModels::restart() and reset skin draw frames on entity clear.

Fixes #9074.
Managed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 18:06:38 +01:00
MSedgeMC d733ed6a34 test: the bed explosion harness watches a stand, an item and a boat
`tools/test_bed_explosion_parity.py` compared one victim: Alice's health after
the blast and the push her client is sent. What an explosion does to anything
else went unobserved, and `game/explosion.rs` is the one place in the game
where the entity side of `ServerExplosion` lives.

The scene now puts four things that are not the player in front of the bed
head, where no placement puts Alice or the obsidian wall, and gives each of
them `NoGravity` so the only motion any of them can get is the blast's own:

- `stand`: an armour stand two blocks from the head,
- `invisible`: an armour stand with `Invisible:1b` three blocks away
  (`ArmorStand.ignoreExplosion` asks `isInvisible`),
- `item`: a stone item entity four blocks away,
- `boat`: an oak boat two blocks to the side.

After the blast each one is read back with `/execute if entity` (did it
survive), and with `Pos` and `Motion` where it did. `compared()` keeps those
answers and gives the lists nine decimals the way
`test_server_knockback_parity.py` reads its mob: a blast writes arbitrary bits
into `Pos` and `Motion`, and NBT prints the shortest form that round-trips.

What the first corpus says in vanilla 26.3, in all twelve cases: the visible
armour stand, the item and the boat are destroyed, and the invisible armour
stand survives untouched at its summon position with zero motion - the
`ArmorStand.ignoreExplosion` rule, seen from the outside.
2026-11-04 17:06:26 +00:00
Ludovic MarieandClaude Opus 5.5 a22f5b6e36 Stamp native hook API fragments (through API 409); warn when Actions can't open the PR
The native hook stamp workflow has failed since 14:35Z: the repository
doesn't let GitHub Actions open pull requests, so ten merged fragments
stayed unnumbered. This stamps them on current main with
tools/stamp_native_hooks.py, and makes the workflow leave a warning
naming the pushed branch instead of failing, since enabling that
setting would also let Actions approve pull requests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:03:58 +01:00
Ludovic MarieandClaude Opus 5.5 1586f9e318 Merge #9271 into #9220: one mob fixture for both metadata tests
#9220's own MobType literal lacked main's item_stack_limits field, so its
tests no longer built; #9271 carries the same 50-line fixture with it.
Both now use one fixture_mob that knows the air and the raider species.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:03:23 +01:00
Ludovic MarieandClaude Opus 5.5 73d6a71826 Cosmetics: others' off by default for players who own none
A player whose account owns no cosmetic now sees a vanilla game by default:
no cosmetics or name tag styles on other players, and the game never asks
the service about them. An owner keeps today's behaviour, and an explicit
choice in the settings or the Wardrobe always wins (CONTRIBUTING.md:
Horizon-only features that change the look are off by default).

- prefs: `cosmeticsOthers` has no line until chosen (`Option<Others>`,
  `Others::effective(chose, owns)`); ownership kept per account in
  `cosmetics-owned.txt`.
- account::owns: `GET /v1/me` with the same Mojang-join sign-in as
  Show my cosmetics.
- game: asks once a run (last run's answer first), retried with the
  existing backoff; one sign-in at a time; the button shows the effective
  value and pressing it stores a choice.
- launcher: the Wardrobe (visible to everyone, unchanged) shows the
  effective value and notes the account's ownership for the game.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:03:21 +01:00
Ludovic Marie c5cc8b8eee Merge main into #9220 2026-11-04 18:03:11 +01:00
Ludovic Marie fbbf21cb10 Merge main into #9229 (through #9228's main merge)
# Conflicts:
#	crates/horizon-client/src/game.rs
#	docs/native-mods.md
2026-11-04 18:03:01 +01:00
Ludovic MarieandClaude Opus 5.5 282289869b Merge main into #9228
Main's glyph callback returns a font::Glyph (its own box from the pen):
text_vertices takes it, and native_draw_text sums its advance.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:02:53 +01:00
Florian ERNSTandCodex b722a4949d Merge latest main for advancement loot validation
Co-Authored-By: Codex <[email protected]>
2026-11-04 18:02:33 +01:00
Evrim CicekandClaude Opus 5.5 d4128444a9 A NoAI dragon's clients are told so, and a Horizon client stops it too
Mob.DATA_MOB_FLAGS_ID (15) carries NoAI as its first bit: the LAN and
dedicated host now sends it with the dragon, and a Horizon client reads
it and stops the dragon's tick where EnderDragon.aiStep does on a client
(no flap, phase or parts). The harness also checks what a vanilla-codec
client is paired with.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 20:00:36 +03:00
Ludovic Marie 6ddd14e727 Merge pull request #9233 from glitchsnake/fix/8703-breeze-invulnerable-to-breeze
🤖 Agent: matches Breeze.isInvulnerableTo (source.getEntity() instanceof Breeze, the owner for projectiles); main merged (trident/bee conflicts taken from main plus attacker_species, and main's new damage_type test literal filled in). The older wind-charge special case in game/wind.rs now duplicates breeze::accepts_damage: follow-up. verify green at this head.
2026-11-04 17:59:16 +01:00
Ludovic Marie d9c0f0bc47 Merge main into #9278 2026-11-04 17:59:15 +01:00
Ludovic MarieandClaude Opus 5.5 a3842d6ad6 mcpr: don't index a packet cut short; take the version from horizon_core
The index loop pushed a packet whose length ran past the end of the
stream (a recording that stopped mid-write), so seeking to it could
only fail. Metadata's mcversion follows VANILLA_VERSION instead of a
literal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:59:15 +01:00
Ludovic Marie 8f297e4b43 Merge main into #9262
# Conflicts:
#	crates/horizon-client/src/game/fishing.rs
2026-11-04 17:58:45 +01:00
Ludovic MarieandClaude Opus 5.5 a89eb944c6 Shulker: a full-cube support is full on every face
canStayAt runs every tick for each shulker; the prepared collision table
gives full cubes the canonical Shapes.block() identity, so skip the face
shape and XOR join for them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:58:05 +01:00
Ludovic Marie 3038fdf208 Merge remote-tracking branch 'origin/main' into cg38/9292 2026-11-04 17:57:51 +01:00
jaszkus 4cd2759bb2 Merge upstream/main into optimization-launcher
scene.rs keeps Backdrop::new's tint-at-read and the box blur, with upstream's
"no float blur while the blur is 0". launcher/lang/*.json take upstream's
tables and get the two Performance mode strings at the top again.
2026-11-04 17:57:50 +01:00
Ludovic MarieandClaude Opus 5.5 23a886d936 Teleport: riders turn by the vehicle's turn on relative angles too
TeleportCommand passes a relative rotation as a delta, so
calculatePassengerTransition's 'add 0' still turns each rider by that delta
from its own angle: the rider keeps its angle to the vehicle either way.
Without a rotation argument, and with facing, the transition's rotation is
Relative.ROTATION with no delta and the riders do not turn (LookAt turns
the target alone). Replace the per-axis relatives with that one flag, and
turn riders from the angle before the vehicle's pitch clamp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:57:40 +01:00
4986dcaf60 Play the honey landing slide and show its particles.
HoneyBlock.fallOn plays the slide (and the block's fall sound when the landing hurts) and broadcasts entity event 54, ten honey crumbs. The guest turn dropped both, and clients only drew the five crumbs of a slide.

Co-authored-by: Cursor <[email protected]>
Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 17:57:13 +01:00
Ludovic Marie 862be0a0d2 Merge pull request #9279 from MSedgeMC/fix/lectern-page
🤖 Agent: a lectern with no book now reads page -1 as vanilla's Mth.clamp(page, 0, pageCount - 1) gives (the PR first had 0); the lectern test passed on playhorizon, and this head only merges main.
2026-11-04 17:56:42 +01:00
Ludovic Marie bb84ae23e1 Merge main into #9222
# Conflicts:
#	crates/horizon-client/src/mod_api.rs
2026-11-04 17:56:40 +01:00
Ludovic Marie 1da9966c91 Merge main into #9225 2026-11-04 17:56:13 +01:00
Ludovic MarieandClaude Opus 5.5 1b939383ca container_accepts fragment: drop the comments key
The stamp tool stamps native_hooks.rs itself and refuses it in `comments`
(test_stamp_native_hooks failed on this branch).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:56:13 +01:00
Ludovic MarieandClaude Opus 5.5 b2ebfbe833 Merge origin/main into feat/windows-arm64-support
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:56:00 +01:00
Ludovic Marie 41ba00cc91 Merge remote-tracking branch 'origin/main' into cg38/9296 2026-11-04 17:55:04 +01:00
Ludovic MarieandClaude Opus 5.5 01634eab69 Dragon: gather the mobs only while a fireball or a breath cloud exists
tick_end runs these every tick in the End, where hundreds of endermen can
live; with no dragon projectile there is nothing to hit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:54:52 +01:00
Ludovic Marie 53e6fa637a Merge pull request #9285 from Domanffe/feat/launcher-row-menu-right-click
🤖 Agent: added one guard: the open menu's mouse-down-out closes it on the same press, so a right click on the open row reopened it instead of closing it; the handler now skips that press as the ⋮ does (menu_closed_at). Desktop launcher run at this head: fmt, clippy -D warnings, all tests 0.
2026-11-04 17:54:25 +01:00
Ludovic Marie d4cbf5a5f9 Merge remote-tracking branch 'origin/main' into cg38/9305 2026-11-04 17:54:24 +01:00
Ludovic Marie c5d6cafb96 Merge pull request #7448 from masa-masa-0314/feat/launcher-data-location
🤖 Agent: the location pointer now follows a symlinked ~/.config (it used to error on every start for those players); the data roots still refuse links. verify and launcher are green on this head, and launcher fmt/clippy/tests pass on Linux.
2026-11-04 17:54:20 +01:00
Ludovic MarieandClaude Opus 5.5 adf2038d70 Audio: fall back to cpal's default buffer when a device refuses the short one
A fixed ALSA period that a device does not support made build_output_stream
fail, which left the game with no sound on that device. Retry with the
default configuration then.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:54:14 +01:00
Ludovic Marie f362f48749 Merge remote-tracking branch 'origin/main' into cg38/9321 2026-11-04 17:54:03 +01:00
Ludovic MarieandClaude Opus 5.5 f6c4c0b17e Merge main into #9314
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:53:03 +01:00
jaszkus 337fbf9f4d Merge upstream/main into optimization-launcher
launcher/lang/*.json take upstream's tables and get the two Performance mode
strings at the top of each, so later additions at the end of upstream's tables
no longer conflict.
2026-11-04 17:52:36 +01:00
MegaBytesMe f7484e79d9 fix(windows): address ARM64 support review feedback
Prefer DirectX 12 for Auto on Windows ARM64 while preserving Vulkan as an option. Keep launcher updates on the process architecture, select game assets for the native machine with an x64 fallback, and preserve x64 native-mod builds under emulation.
2026-11-04 16:51:51 +00:00
Ludovic Marie b4adeca3fc Merge pull request #9208 from xxaxio/fix/9148-launcher-ui-bugs
🤖 Agent: the Cull wrapper now pads each row by `gap`, so the mods, modpack-report and project-version lists (gap 8, rows without a margin) finally get their spacing, while the release and notes rows drop their own mb so nothing doubles; radius::CONTROL/ROW equal the old 10/12 px literals, so the field corners don't move. Launcher clippy -D warnings and 476 tests green on this head.
2026-11-04 17:50:11 +01:00
degepieandClaude Opus 5.5 0e2a6b8700 Advancement block predicates test the block entity's nbt
`BlockPredicate`'s `nbt` was skipped, so a predicate with it matched any
block of its `blocks` (and an `inverted` one never matched). As
`BlockPredicate.matchesBlockEntity` does, the pattern now has to be in
the block entity's `saveWithFullMetadata` (`NbtUtils.compareNbt`), and a
block without a block entity fails. The predicates' `World` asks for the
data `/data get block` builds, moved into `Game::block_entity_nbt`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 21:49:49 +05:00
Ludovic MarieandClaude Opus 5.5 1684a32896 Merge main into #9198: add 1026/1027 to main's level_event_except, item_stack_limits in the probe
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:49:35 +01:00
degepieandClaude Opus 5.5 aac36f65dc Advancement predicates read a world's data pack tags
A `#tag` in an advancement predicate never matched a tag a world's data
pack defines: `View::in_tag` read the game's own item, block, entity
type, biome and damage type tags. The world's tables now also keep the
entity type, biome and damage type tags with the packs' overlay
(`TagLoader.loadTagsForExistingRegistries` binds every registry's tags
from the pack stack), and `in_tag` reads them, and the item and block
tags through `Game::items()`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 21:49:08 +05:00
Florian ERNSTandCodex dbabdeb01d Merge latest main for advancement loot lookup
Co-Authored-By: Codex <[email protected]>
2026-11-04 17:48:25 +01:00
Florian ERNSTandCodex a152e92cec Use world datapack tables for advancement loot rewards
Co-Authored-By: Codex <[email protected]>
2026-11-04 17:48:24 +01:00
Ludovic Marie 8a170c33ec Merge pull request #9256 from martipops/fix/shop-window-clock
🤖 Agent: #[serde(other)] Unknown lets /v1/shop and /v1/me answers with a state a newer service adds still parse, so the Wardrobe's top-bar check (shop::shown, which reads open and the dates, not the state) keeps working; the test covers all three answers and the on-disk round trip.
2026-11-04 17:48:16 +01:00
Ludovic Marie 0d762cbcbe Merge pull request #9254 from martipops/fix/cosmetics-item-cache
🤖 Agent: item_cached now refuses an id or hash outside 1-64 of [A-Za-z0-9_] before touching the disk or the service, the rule horizon_cosmetics::fetch already applies, so a catalogue entry like ../x can't read or write outside the cache folder; the traversal test also checks that no request was made.
2026-11-04 17:48:12 +01:00
BPMichonandClaude Opus 5.5 d5f4da2382 Native mods: health HUD state and local attribute helpers
A mod answering native_health_hud can now reach Horizon's HealthHud
(native_health_hud_state): its left_height/right_height place the selected
item name and the action bar (vanilla Hud.leftHeight/rightHeight), and its
layout keeps Horizon's own rows. native_local_attribute(name) reads an
attribute of this client's player (LivingEntity.getAttributeValue), such
as minecraft:armor_toughness. Both through a helper-only API fragment, so
mods no longer read Game's private fields.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BYf41fgTMRfUEXn3afqyTG
2026-11-04 16:47:16 +00:00
voidsipandCodex 8125129805 Keep mod installation beside search results
Co-Authored-By: Codex <[email protected]>
2026-11-04 10:45:21 -06:00
Arslan ArmanulyandClaude Opus 5.5 93afbf2fd8 Shader packs: split solid and cutout terrain per quad, by sprite layer
The split decided per vertex, and read lava from the high nibble of the
vertex colour's alpha. With separateAo=true that byte holds each vertex's
own ambient occlusion, so the corners of one leaf quad landed in different
parts: each part pushed its other corners off screen, and in our separateAo
probe the leaves drew as speckled slivers of both parts. Lava had no flag
there and went to the cutout part.

The test now reads only the vertex's sprite layer, which all four vertices
of a quad share: OPAQUE_LAYER, or one of lava's two sprite layers, which
the game hands the renderer. In Iris 1.11.7 our separateAo probe draws
leaves whole with gbuffers_terrain_cutout and lava with
gbuffers_terrain_solid; the branch now does the same.

Part of #9137

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 21:44:56 +05:00
Ludovic Marie ae3823b97a Merge main into #9233
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/src/game/trident.rs
#	crates/horizon-client/src/mobs/bee.rs
2026-11-04 17:40:53 +01:00
Ludovic MarieandClaude Opus 5.5 6461774f70 Merge main into #9285
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:39:46 +01:00
Ludovic MarieandClaude Opus 5.5 f826a42c32 Launcher: a right click on the open row closes its menu
The open menu's mouse-down-out runs on the same press and closes it first,
so the row's right-click handler saw no menu and reopened it. Skip that
press, as the ⋮ does with closed_by_this_press.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:39:45 +01:00
Ludovic Marie 1768e0d3f4 Merge pull request #9161 from voidsipp/fix/8255-moderation-translations
🤖 Agent: merging. Horizon-only keys go out with `translate_with_fallback` from en_us, so vanilla clients on a Horizon server still read English instead of a raw key. Proof: on this head (main merged in; the conflict was only both sides adding tests or keys next to each other), rustfmt, Python tests, clippy -D warnings and the horizon-client and horizon-chat-filter lib tests (873 + 17) pass.
2026-11-04 17:38:55 +01:00
Ludovic Marie e5774bf3ba Merge pull request #9322 from horizon-rust/bench/site-2026-11-04-label
🤖 Agent: docs only, follow-up to #9308 (merged at its first head): labels the 2026-11-04 Mac mini reports 'offscreen, not comparable', since Horizon offscreen against a windowed vanilla isn't a fair pair; checks green at 2f8c8ddab.
2026-11-04 17:38:49 +01:00
NerfPhaselockandClaude Opus 5.5 a58946a336 Graphics: a GPU Culling setting, and its statistics read back from the GPU
GPU culling becomes a setting players can switch: GPU Culling
(horizon-graphics.txt's gpuCulling, off by default and in no preset) sits in
the Performance page's Culling group right after Cave Culling, and applies at
once: every settings apply calls set_gpu_culling, which does nothing when
nothing changes. Where it isn't offered the switch is greyed out, reads Off
and says why: the device can't (no count draws the GPU wrote, compute shaders
or working first instances, or vanilla frames drawn flat), DX12 with flat
page groups, whose count draws lose each draw's base vertex (not offered there
since #9142: "try the Vulkan graphics API"), a software adapter (WARP,
lavapipe), whose pass would run on the CPU beside the frame, or its buffers
stopped fitting in video memory, until the next renderer. macOS
doesn't show it. The control word gpu-culling on|off now sets the setting in
memory, unsaved, as lod-distance does, so the switch, the word and rebuilt
renderers agree.

The statistics are exact again. Each GPU frame copies the pass's two counters
(the sections it kept, their draws) into one of three small readbacks, with
the CPU's part of the same frame: the sections the walk listed less those
whose solid quads the pass drew and counted, its draws, and every call. The
readback is mapped from the frame's after-submit hook, on the submit thread
too, and taken in a frame or a few later; nothing waits for it. So
drawn_sections, section_draws and section_draw_calls always describe one
real frame, the newest whose numbers are known: a CPU frame's at once, a GPU
frame's when its counts come back, never an older frame's over a newer one's.
The status says how old they are (stats_lag) and what is offered
(gpu_culling_offer). A readback whose mapping fails, or whose buffer the
device lost, goes FAILED and is never copied into or unmapped again, as the
GPU timer's readbacks since #6354.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:38:47 -05:00
Ludovic Marie 6275a3a20e Merge pull request #9195 from razzo715/fix/item-block-containers
🤖 Agent: merging. `/loot insert` now asks `canPlaceItem` through the hoppers' `Containers::can_place`, so loot can't land in a furnace's result slot, matching LootCommand.distributeToContainer. Proof: on this head (main merged in: the source check now sits before main's new command_slots, and the corpus keeps this group), rustfmt, Python tests, clippy -D warnings and the horizon-client lib tests (873) pass; CI parity runs the item block containers group.
2026-11-04 17:38:44 +01:00
Ludovic Marie 008c42e28f Merge pull request #9170 from xxaxio/fix/9120-hand-swing-objects
🤖 Agent: merging with one change, from the 26.3 client's bytecode: AbstractBoat.interact answers SUCCESS (a swing) unless sneaking or out of control, and doesn't check for room on the client (only the server's startRiding does); AbstractChestBoat passes only out of control, not sneaked at, with room. Minecart.interact is the one that answers CONSUME. Paintings and item frames were right. Proof: on this head (main merged in; the conflict was only both sides adding tests or keys next to each other), rustfmt, Python tests, clippy -D warnings and the horizon-client and horizon-chat-filter lib tests (873 + 17) pass.
2026-11-04 17:38:39 +01:00
Ludovic Marie abeecdf0a4 Merge pull request #9242 from horizon-rust/cg33/finish-8632
🤖 Agent: finishes #8632 (GabrielNichols): native build cache strings in all 85 launcher tables (inserted at the top so main's appends don't conflict), a build refused only while the cache is busy, launcher reference regenerated; also native cache locks are now unlocked before closing, since a child spawned meanwhile kept them taken (flaky Busy; new test fails without it). Desktop run at this head: fmt, clippy -D warnings, all launcher tests 0.
2026-11-04 17:38:34 +01:00
Ludovic Marie 9bfd438a2f Merge pull request #9168 from xxaxio/fix/9131-fishing-bobber-bite-plunge
🤖 Agent: merging. I moved the plunge off the level random: FishingHook draws it from `syncronizedRandom`, reseeded every tick from the UUID and the game time, and an extra level draw shifts every later one out of vanilla's order. It is also computed in float, as Mth.nextFloat does. rdbtCVS's agent then shared the helper with remote hooks. Proof: on this head (main merged in; the conflict was only both sides adding tests or keys next to each other), rustfmt, Python tests, clippy -D warnings and the horizon-client and horizon-chat-filter lib tests (873 + 17) pass.
2026-11-04 17:38:29 +01:00
Evrim CicekandClaude Opus 5.5 b111b38750 An ender dragon with NoAI stays where it is
EnderDragon.aiStep returns after the flap and checkCrystals when
isNoAi() (flapTime = 0.5): no flight history, phase, steering or parts.
Horizon's dragon never read NoAI: a NoAI dragon flew off, out of a
force-loaded chunk, and a save could then lose it. The dragon now loads
and saves NoAI, and its tick stops there.

tools/test_dragon_no_ai_parity.py compares the dragon's position and NoAI
after 100 ticks and after a restart with vanilla's dedicated server.

Fixes #9316

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 19:38:16 +03:00
Ludovic MarieandClaude Opus 5.5 d571d80a06 Merge origin/main into revert-ram-limit (#6652)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:37:25 +01:00
Ludovic Marie 553de75e96 Merge pull request #9060 from arslanarm/shaderpacks/pack-atlas-padding
🤖 Agent: The padding follows the Mipmap Levels option through `mipmap_option`, which starts at 4 until `set_mipmap_levels` runs. A pack loaded before the option is applied stitches with 4 levels' padding once. Minor. I pushed cddb5526: merged main after #9059 landed (clean). Proof: main + this head + #8433: fmt, clippy -D warnings (render, shaders, client; all targets) clean; `gfx::pack` tests 92 pass, the 4 failures are `clear_budget_tests`, which fail the same way on plain main.
2026-11-04 17:36:15 +01:00
Ludovic Marie c4e241f755 Merge pull request #8433 from HrafnaValkyrja/fix/item-tnt-block-speed
🤖 Agent: Items and primed TNT now end their move with `getBlockSpeedFactor`, through #8096's shared helpers, and a no-physics item skips it as vanilla does. Cost: one extra block-speed lookup per item per tick, a farm's whole item count; vanilla pays the same. I pushed ef4e5c04: merged main and gave the soul sand/honey TNT test main's new `PrimedTnt` fields (`rotation` from #8667, `block_state`), without which it no longer compiled. Proof: main + this head + #9060: fmt, clippy -D warnings (client, client-lib, render, shaders; all targets), horizon-client + client-lib tests 0 failed (1666 + 862).
2026-11-04 17:36:10 +01:00
Ludovic MarieandClaude Opus 5.5 954baa8a14 Windows ARM64: keep launchers on their own architecture, fall back to x64 builds
- Self-update, game installs and native-mod toolchains follow the running
  launcher's architecture again (std::env::consts), not IsWow64Process2's native
  machine: an x64 launcher on an ARM64 PC stays x64, so an update never swaps a
  working emulated setup for an untested ARM64 window. The ARM64 launcher comes
  from horizon-launcher-setup-arm64.exe.
- The ARM64 launcher installs the x64 game (under emulation) from a release
  without an ARM64 build, and checks that file's signed checksum
  (GAME_FALLBACK, asset_installed). Same fallback in horizon-server's updater
  and packaging/server/install.ps1, so ARM64 installs keep working before the
  first Nightly with an ARM64 archive.
- ci.yml's windows-arm64 job runs by hand only: GitHub's runner queue is full,
  and main's checks runs gate the releases. The Nightly and launcher releases
  build the ARM64 archives every run.
- launcher.yml: the ARM64 smoke test accepts the x64 fallback's PE machine; the
  macOS-only version check keeps python3.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:35:57 +01:00
Ludovic MarieandClaude Opus 5.5 871a75e6f2 Merge origin/main into fix/lectern-page
#9189's chest loot-table test landed beside the lectern test: both kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:35:26 +01:00
Ludovic MarieandClaude Opus 5.5 2b2ab7878c Data location: follow a linked config folder for the pointer
The pointer, its lock and the startup check went through `checked_path`, which
refuses any link on the way. A Linux player whose ~/.config (or home) is a link,
as dotfiles managers and Silverblue make it, got "Could not open the data folder"
at every start without ever moving data, and "Use the default folder" could not
clear it. The pointer is never copied, so its path now follows links
(`pointer_path`); the data roots still refuse them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:34:36 +01:00
Ludovic MarieandClaude Opus 5.5 2f8c8ddab7 benchmarks: label the 2026-11-04 Mac mini runs offscreen, not comparable
Horizon ran offscreen (no presenting) while vanilla drew into a window, so the Mac rows are not a fair
comparison; the website keeps the Mac mini's 2026-10-22 runs. The reports stay as a record, labelled.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:34:20 +01:00
makscoder 9f2d88fb57 Let peaceful discard hostile mobs through Mob.checkDespawn, not instantly
Switching to peaceful removed every hostile mob in the same tick with
`mobs.retain(|m| !m.hostile())` in the difficulty command, the LAN/menu
difficulty change, and the dedicated-server start. Vanilla only sets the
difficulty there; each hostile mob is discarded by its own Mob.checkDespawn
on the next tick when the level is peaceful and the type is not allowed in
peaceful, so mobs in unloaded chunks persist until loaded and the wither and
ender dragon follow their own exceptions.

Drop the three retain calls: the per-tick check_despawn already discards the
same mobs (difficulty.effective == 0.0 && !allowed_in_peaceful), with vanilla's
timing and exceptions.

Closes #9283
2026-11-04 19:31:11 +03:00
Ludovic MarieandClaude Opus 5.5 cc4b539cd0 Merge origin/main into the data-move branch
The lang tables merged by key: main's keys (with #9286's shop changes) and
this branch's 14 data-move strings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:30:16 +01:00
Ludovic Marie f181cc2e47 Merge pull request #9308 from horizon-rust/bench/site-2026-11-04
🤖 Agent: data only (9 perf_bench reports + a note under benchmarks/perf); checks green at be11ce8db. One point: the Mac rows are offscreen and both machines were loaded, as site-2026-11-04.md says, so don't read them as clean ratios.
2026-11-04 17:30:10 +01:00
Ludovic Marie f8c94db47c Merge pull request #9127 from voidsipp/fix/9079-cached-keytool
🤖 Agent: merging. `shutil.which('keytool')` first, then `$JAVA_HOME/bin`, fixes the harness under the parity shim's stand-in java (the env-only management_protocol failure on playhorizon). Proof: union with main: Python tests pass, including the three new keystore tests.
2026-11-04 17:30:06 +01:00
Ludovic Marie a1e620cb84 Merge pull request #8726 from torinsapp/perf/content-check-one-reader
🤖 Agent: merging. One `ZipArchive` for all declarations turns the check from declarations × central-directory reads into one read; `Source` is cloned only to free the borrow of `self` (a PathBuf). Proof: union with main: fmt, clippy -D warnings and horizon-mods tests pass.
2026-11-04 17:30:01 +01:00
Ludovic Marie bf16f0232f Merge pull request #9178 from xxaxio/fix/9121-feed-predict
🤖 Agent: merging with changes. Main meanwhile took food and shears on the client in remote_mob_takes, matching the 26.3 bytecode (Animal.mobInteract and Sheep.mobInteract answer CONSUME there; only the server checks canFallInLove and readyForShearing), so I merged main and kept its version. What this PR still adds is the dye rule, now `sheep_takes`: DyeItem.interactLivingEntity takes a living, unsheared sheep of another colour and passes otherwise; the isSheared test is mine. Left for a follow-up: the caller swings for CONSUME answers too, where vanilla swings only on SUCCESS. Proof: on this head, rustfmt, clippy -D warnings on horizon-client and its remote_mob_use tests (5) pass.
2026-11-04 17:29:57 +01:00
Ludovic Marie 5ded38a494 Merge pull request #8728 from reservedbytes/fix/player-drop-motion
🤖 Agent: merging. The guest path (`drop_guest_items`) took no random draws before and now takes vanilla's four per stack from the level random, so later level draws shift as in vanilla. Vanilla draws from the entity's own random; Horizon keeps the level's in all three places, as before. Proof: union with main: fmt, clippy -D warnings and client tests pass, including the bit-exact drop_motion test.
2026-11-04 17:29:53 +01:00
Ludovic MarieandClaude Opus 5.5 2fa97798c2 Merge main into #9161: keep both sides' lang keys
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:28:31 +01:00
Ludovic MarieandClaude Opus 5.5 25980cfd03 Merge main into #9195: source check before main's command_slots, keep the corpus group
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:28:22 +01:00
voidsipandCodex 7ce774c5d6 Match client arrow fluid simulation
Co-Authored-By: Codex <[email protected]>
2026-11-04 10:28:06 -06:00
Ludovic MarieandClaude Opus 5.5 5c6cdd645b Merge main into #9170: keep both sides' remote tests
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:27:56 +01:00
Ludovic MarieandClaude Opus 5.5 575cf3e0e7 Merge main into #9168: keep both sides' fishing tests
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:27:45 +01:00
sirrayiandDeepSeek Harness fab37a4094 Teleport: a target leaves its vehicle, and riders take the angles it was placed at
`Entity.teleport` stops riding unless the transition is `asPassenger`, and
`calculatePassengerTransition` gives each rider the vehicle's new angles plus
its own offset (`passenger.getYRot() - getYRot()`), or the vehicle's angle
alone on an axis the command set relatively (`+ (relatives has Y_ROT ? 0 : ...)`).
The shared writer only moved riders' positions and only the player arm
dismounted, so `/tp` on a spider jockey or a mob in a boat was seated again by
the next sync (the command reported success and nothing moved), and a rotated
`/tp` left mob riders, the host and guests facing where they were.

- `teleport_to_in_level` dismounts a mob or an armor-stand target before it is
  placed (`Entity.teleport` -> `stopRiding`), and carries the command's per-axis
  relatives for the riders.
- `teleport_passengers` places the riders at the angles the vehicle was placed
  at (`passenger_rotation`): mob riders, the host riding a vehicle and the
  hub's guests, recursively, pitched through `Entity.setXRot`'s clamp. A
  relative axis gives the rider the vehicle's own angle. A turned guest gets one
  position packet carrying its new absolute rotation with its motion kept
  (`Hub::teleport_rider`).

Fixes #9289

Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
2026-11-04 16:27:32 +00:00
Ludovic Marie 8069aa8ced Merge pull request #9237 from NerfPhaselock/perf/gpu-culling-g3
🤖 Agent: the shader's facing test compares order-preserving u32 keys of the same f32 thresholds visible_from uses, MARGIN included, so no driver's subnormal flush can change a run; a grouped mesh keeps one whole draw, and the 4-run cap fits the 3-bit count in runs.
2026-11-04 17:27:25 +01:00
martipopsandClaude Opus 5.5 e09c4098ad Launcher: keep the Wardrobe's name tag mark instead of baking it each draw
Each draw baked a new image for the equipped name tag, and GPUI keeps every one
in the sprite atlas until it is dropped. Marks are now kept per item id, its
file's hash and the window scale. One replaced by a scale change or a new file
is dropped from the atlas on the next frame.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 11:25:26 -05:00
gorkandClaude Code a53483eeb0 Reduce Linux CPAL audio buffer latency
Fixes #4683

Co-Authored-By: Claude Code <[email protected]>
2026-11-04 17:24:51 +01:00
Ludovic Marie be704330d9 Merge pull request #9282 from MSedgeMC/fix/bundle-allow-modification
🤖 Agent: the bundle click now checks may_pickup and may_place on the slot, as vanilla Slot.allowModification does, so a bundle in a take-only or locked slot keeps its contents; the new test covers the withheld slot.
2026-11-04 17:23:27 +01:00
Ludovic MarieandClaude Opus 5.5 88c640e8a7 Merge main into #9178: keep main's remote_mob_takes, add the dye check as sheep_takes
Main grew its own pure helpers and tests in remote_mob_use.rs (nautilus,
golems, allay, armadillo). This keeps main's version and adds only what
this PR got right: DyeItem.interactLivingEntity takes an unsheared sheep
of another colour and passes otherwise; shears stay CONSUME on a client.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:23:12 +01:00
Ludovic MarieandClaude Opus 5.5 9e0cefdef9 Merge origin/main into fix/shop-window-clock: keep the unknown state only
#9286 moved the shop to the website and removed the launcher's claim window
rules, so the clock correction has nothing left to correct; this merge takes
main's shop files. ClaimState::Unknown still matters: /v1/shop and /v1/me
answers decide whether the Wardrobe is in the top bar, and a state a newer
service adds made them fail to parse.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:21:30 +01:00
Ludovic MarieandClaude Opus 5.5 ea201cfa97 Launcher update card: the version on its own line; a failed install drops its download
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:21:29 +01:00
Ludovic MarieandClaude Opus 5.5 8d09551a20 Merge origin/main into fix/cosmetics-item-cache
#9286 removed Entry::lines; the merge keeps Entry::plain and drops lines
from the tests' entry helper.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:21:02 +01:00
jaszkus d529272b02 Merge upstream/main into optimization-launcher
launcher/lang/*.json take upstream's tables and get the two Performance mode
strings at the top of each, so later additions at the end of upstream's tables
no longer conflict.
2026-11-04 17:20:04 +01:00
Evrim Cicek e7288a7b9b Merge remote-tracking branch 'upstream/main' into fix/silent-dragon-death
# Conflicts:
#	mods/horizon/src/test/java/dev/horizonlab/parity/VanillaProtocolClient.java
2026-11-04 19:19:18 +03:00
Ludovic MarieandClaude Opus 5.5 a82ff8ba68 Merge origin/main into feat/windows-arm64-support
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:18:28 +01:00
Ludovic Marie d190e2b642 Merge pull request #9153 from arslanarm/shaderpacks/custom-uniform-scalar-types
🤖 Agent: Built-ins are typed int/float/bool from the probe tables. A custom uniform whose declared type the expression doesn't give reads 0 as a whole, with a note naming it. Nothing else found. I pushed 5eef8152: merged main.
2026-11-04 17:16:29 +01:00
Ludovic Marie 8d856de117 Merge pull request #8659 from Teiwazik/older-servers/dropped-components
🤖 Agent: merging. One cost to know: every item component read now asks `ids::dropped_component` and `ids::quirks`, two thread-local reads, even on a 26.3 server; cheap next to the component decode itself. Proof: union with main: fmt, clippy -D warnings and horizon-protocol tests pass, including the three new wire fixtures from 1.21.11 registries.
2026-11-04 17:16:24 +01:00
Ludovic Marie 4bb36fac4c Merge pull request #9105 from arslanarm/shaderpacks/terrain-texture-matrix
🤖 Agent: `textureMatrix` maps to `gl_TextureMatrix[0]` in vertex stages only, while a fragment stage's own `textureMatrix` keeps its frame slot (zero), matching the probe. Merged with #9180's tuple form of `core_matrix` (type `mat4`). Nothing else found. I pushed e8d6861b: merged main (#9057/#9180 landed).
2026-11-04 17:16:20 +01:00
Ludovic MarieandClaude Opus 5.5 40e4d970b0 Load a bookless lectern's page as -1, as vanilla's Mth.clamp does
LecternBlockEntity.loadAdditional clamps Page to 0..getPageCount(book) - 1,
and getPageCount is 0 without a book, so Math.min(Math.max(page, 0), -1)
gives -1. Every Horizon reader of the page applies max(0) first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:13:46 +01:00
Ludovic MarieandClaude Opus 5.5 56827bc8f8 Native cache: release locks before closing them
A child process spawned while a lease or a Cargo lock is held keeps a copy
of the descriptor until its exec, so closing ours alone left the lock taken
and the next lease or cleanup read Busy (seen as a flaky
clears_only_disposable_outputs... test while other tests spawned Cargo).
Unlock explicitly on drop: that frees it for every copy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:13:02 +01:00
Florian ERNSTandCodex 6fc1ba82b4 Merge latest main for advancement reward dispatch
Co-Authored-By: Codex <[email protected]>
2026-11-04 17:12:50 +01:00
Ludovic Marie fed60fb986 Merge pull request #9150 from JulesB40/fix/piston-block-updates-next-tick
🤖 Agent: the changed blocks now go out between the random ticks and runBlockEvents, as in ServerLevel.tick, and what the events and block entities write is held to the next tick; piston-slide measured 3 ticks for every move in three runs on a union with main, piston-push and predict pass, and I fixed the check counting from the previous move's head.
2026-11-04 17:12:33 +01:00
Ludovic Marie 69c3e37905 Merge pull request #8954 from Cheezer1656/upstream/native-send-packet
🤖 Agent: main merged again (mod_api.rs keeps both ClientboundPlayPacket and ServerboundPlayPacket re-exports); native send-packet built, clippy and tests green on union 760aa5921.
2026-11-04 17:12:29 +01:00
Ludovic Marie 9014294e55 Merge pull request #9191 from Domanffe/chore/port-map-landed-rows
🤖 Agent: docs only; the seven functions it marks as ported all exist on main, union 760aa5921 green.
2026-11-04 17:12:25 +01:00
Ludovic Marie 3aac8eb917 Merge pull request #8616 from senjinthedragon/hdr-look
🤖 Agent: merging. Off, `hdr.lighting && hdr.look` also skips the water and cloud second passes, so HDR Lighting off costs less than on, and NikitaMGrimm's PQ captures show 100% of pixels within 1 code of SDR. I moved `set_hdr`'s doc comment back onto `set_hdr` (their nit). Proof: union of this head with main: fmt, clippy -D warnings, render and client tests pass.
2026-11-04 17:12:21 +01:00
Ludovic Marie 21d3fffde2 Merge pull request #9160 from Joansitoh/fix/8701-trident-breeze
🤖 Agent: matches `Projectile.hitTargetOrDeflectSelf` + `Breeze.PROJECTILE_DEFLECTION`: REVERSE power 1, the sound from the breeze, no `onHit`, and `lastDeflectedBy` stops a second turn. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 17:12:17 +01:00
Ludovic MarieandClaude Opus 5.5 111692c509 Launcher: "A new version is available" with Update; copies that can't update say so (#9287)
Launcher 0.2.0 reaches 0.2.40 by itself wherever it can write its own folder (checked with the real
0.2.0 builds on macOS and Linux: tags, signing key, legacy list and asset names all still work). A copy
in a read-only place (the disk image, macOS's translocated copy of an app never moved out of
Downloads, a .zip opened in Explorer) fails at every check and said so only on Settings' Launcher
line, so the player never saw it.

- The window downloads and checks the new launcher (backend::stage_update), then the sidebar says
  "A new version is available" with Update: the window closes, the launcher's own file runs as the
  installer (`apply-update`, update_handoff.rs), waits for the window's process to exit, puts the new
  launcher in place with the same code `horizon-launcher update` uses (place_staged) and starts it.
  Closing the window with an update downloaded installs it too, so launchers still update themselves.
- A copy that can't replace itself (backend::cannot_replace_itself) says so with Download again (the
  website's install page); a failed install starts the old launcher, which says why.
- Docs: launcher/README.md, the "download again once" row in docs/versioning.md; README's release
  link no longer filters on the old launcher-v tags.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:11:37 +01:00
Ludovic MarieandClaude Opus 5.5 010ab500f9 lan_e2e piston-slide: count from the head that lands after the move
The second extension starts with the head the first one left, so taking the
first piston_head read gave a negative count. The landed head is now the first
after the moving block. A loaded machine can run two client ticks between
reads: when the read before the move or before the landing skipped one, the
check says so instead of reporting a wrong count. On playhorizon (load 25-47)
three runs measured 3 ticks for every move.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:09:23 +01:00
Ludovic Marie ff8a09cc62 Merge pull request #9118 from Joansitoh/fix/8715-armadillo-click
🤖 Agent: Armadillo.mobInteract is FAIL while isScared() before Animal.mobInteract, so spider eyes no longer end the click on a rolled-up armadillo. Also fixed: brushOffScute is false on a baby, so a baby's brush is not taken (38c31cac5d). The main merge 1e9a62f124 only resolves the import line with #8691 (same resolution as the cg-31 union). Proof: cg-31 union on main 6d43a3593b (playhorizon): fmt, clippy -D warnings, client+lib tests 1638+850 ok.
2026-11-04 17:08:46 +01:00
Ludovic Marie 94a184e236 Merge pull request #9172 from voidsipp/fix/8978-mod-install-feedback
🤖 Agent: merging. `native_notice` is already a plate, so taking it out of `card` also removes the plate-in-plate it had in the results view. Proof: union with main: launcher fmt, clippy -D warnings and lib tests pass.
2026-11-04 17:08:41 +01:00
Florian ERNSTandCodex b9091525a7 Merge latest main while preserving advancement reward regression
Co-Authored-By: Codex <[email protected]>
2026-11-04 17:08:10 +01:00
MSedgeMC 6510fd2039 The ledger knows Entity.isColliding is ported
The row described it as missing because the prediction only restored the block.
It is game/remote.rs::Game::block_meets_player now, and end_predictions acts on
the answer.
2026-11-04 16:07:38 +00:00
MSedgeMC 817722009c Test the collision and the snap a restored block makes
Entity.isColliding on a full block at the player's feet meets a 0.6 x 1.8 box
standing in it, and a block above its head, the one beside it or an empty shape
does not. The snap takes the remembered position while the teleport guard allows
it, and nothing when the block came back clear of the player or the player was
teleported at the same sequence.
2026-11-04 16:07:38 +00:00
MSedgeMC ea80bba0f7 A refused block change that comes back inside the player snaps it out
ClientLevel.syncBlockState is what BlockStatePredictionHandler.endPredictionsUpTo
calls once the server acknowledges a sequence: when the block takes a state it
did not have, the level restores it, and then, if the prediction remembered a
player position and the player was not teleported since
(lastTeleportSequence < sequence), it asks Entity.isColliding(pos, state) -- the
state's own collision shape at the player's pose against the player's box -- and
calls Player.absSnapTo with the remembered point when they meet.

Horizon's end_predictions restored the block and stopped there, so a break the
server refused under the player, or a placement it pulled back, left the player
standing inside it until its own physics pushed it out.

A prediction now remembers what vanilla's ServerVerifiedState does -- the
sequence, the state the server last gave the block, and the player's position
when the change was first predicted -- and keeps all three through a
retainKnownServerState that only bumps the sequence. end_predictions restores the
block, tests the shape against the player and snaps to the remembered point when
the guard allows it. remote_teleport takes BlockStatePredictionHandler.onTeleport,
so a prediction made before a teleport keeps its block but not its snap.
2026-11-04 16:07:37 +00:00
Florian ERNSTandCodex 381517f87f Run advancement reward functions through the Game dispatcher
Co-Authored-By: Codex <[email protected]>
2026-11-04 17:07:08 +01:00
Ludovic Marie 1e9a62f124 Merge remote-tracking branch 'origin/main' into cg31fix-9118
# Conflicts:
#	crates/horizon-client/src/game/remote_mob_use.rs
2026-11-04 17:06:09 +01:00
Ludovic Marie 47f1c39b7e Merge pull request #7998 from horizon-rust/launcher-loader-version
🤖 Agent: loader version picker, stacked on #7740, green on this head; lands after it.
2026-11-04 17:05:12 +01:00
Ludovic Marie 2980894c35 Merge pull request #7987 from horizon-rust/launcher-java-mrpack
🤖 Agent: .mrpack import, green on this head; rdbtCVS's change request was dismissed because it rested on #6660, closed by Ludovic (Java instances stay).
2026-11-04 17:05:07 +01:00
Ludovic Marie e57b1d34bb Merge pull request #7740 from horizon-rust/launcher-forge-install
🤖 Agent: Forge install for Java instances, green on this head; the only blocker was a change request resting on #6660, which Ludovic closed (Java instances stay).
2026-11-04 17:05:03 +01:00
Ludovic Marie dc5a91ac44 Merge pull request #9299 from horizon-rust/feat/partner-afri-network
🤖 Agent: the partner entry goes below the official one through the same once-only marker logic, so a player who deletes it never gets it back; proof: multiplayer::tests (20) and clippy -D warnings green on the desktop.
2026-11-04 17:04:59 +01:00
Ludovic Marie d08d24e9c2 Merge pull request #8651 from Teiwazik/fix/leave-server-sounds
🤖 Agent: merging. On the single-player path these three calls repeat what finish_close_world already did, and `stop_all` on an empty mixer is a no-op, so the only change is for leaving a server or being kicked. Proof: union with main: fmt, clippy -D warnings and client tests pass.
2026-11-04 17:04:55 +01:00
Ludovic Marie 1a91ea6936 Merge pull request #8691 from CocofireHD/fix/turtle-dig-level-event
🤖 Agent: lands the whole stack (#8637 #8644 #8647 #8652 #8657 #8658 #8660 #8690). Concrete fix: be258bc235 sent every ParticleAt as a speed-0.1 packet (ink at 1/100 speed, client-side hearts/splash/smoke drawn twice); squid ink is now MobEvent::LevelParticles as Squid.spawnInk's sendParticles, and main's wolf test got the new client_tick argument. Proof: 569f9644dc in the cg-31 union on main 6d43a3593b (playhorizon): fmt, clippy -D warnings, client+lib tests 1638+850 ok; CocofireHD's later main merge 5839363b51 only resolves allays.rs (main-hand check + swing kept on main's Option<ItemStack> inventory).
2026-11-04 17:04:50 +01:00
Ludovic MarieandClaude Opus 5.5 cddb55263c Merge main into shaderpacks/pack-atlas-padding
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:03:31 +01:00
Ludovic MarieandClaude Opus 5.5 ef4e5c049d Merge main into fix/item-tnt-block-speed
The soul sand/honey TNT test gets main's new PrimedTnt fields (rotation, block_state).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:03:30 +01:00
Ludovic Marie 42ae7fffe1 Merge pull request #9115 from Joansitoh/fix/8890-conversion-flags
🤖 Agent: ConversionType.convertCommon also copies PortalCooldown, InvulnerableTime, the fire flag and entity tags, which are still not carried over; the five flags here match. The land-stop was main's new MobType field item_stack_limits in this test's literal: main merged and the field added (79576177db). Proof: cg-31 union on main 6d43a3593b (playhorizon): fmt, clippy -D warnings, client+lib tests 1638+850 ok.
2026-11-04 17:02:03 +01:00
Ludovic Marie 6b91fe90d5 Merge pull request #8707 from CocofireHD/fix/attribute-cross-level
🤖 Agent: /attribute now runs in the target's level (in_target_level), so an unbounded @e from the Nether reaches the Overworld cow, as EntitySelector.findEntities searches every level. Proof: command_exec harness PASS (82 s) with the 8 new corpus cases on the cg-31 union on main 6d43a3593b (playhorizon), plus fmt, clippy -D warnings, client+lib tests 1638+850 ok.
2026-11-04 17:01:59 +01:00
Ludovic Marie 3070364215 Merge pull request #8907 from Joansitoh/fix/shulker-teleport-render
🤖 Agent: the slide now starts from the move packet (createInterpolationHandler NO_OP) and is only counted down in client_tick; a riding shulker gets the peek reset without the slide. First parity run died in 2 s on the runner's dead .horizon-prepare.lock link (environment); rerun 41856251948 queued. Proof: cg-31 union on main 6d43a3593b (playhorizon): fmt, clippy -D warnings, client+lib tests 1638+850 ok.
2026-11-04 17:01:55 +01:00
Ludovic Marie aab356c6c4 Merge pull request #8648 from itarqos5/feat/drop-a-client-build
🤖 Agent: a drop starts the same start_build_import the picker uses, behind can_import_build, so a removal in progress can't race it; .exe/.gz/.tgz are taken unread so build_import gives the precise error, and extensionless files are read last. I fixed the wrap spaces in the two error strings and merged main (the stale-key verify failure came from main, fixed by #9265).
2026-11-04 17:01:48 +01:00
Ludovic Marie 46322061d7 Merge pull request #9200 from Domanffe/fix/dropped-pack-copy-errors
🤖 Agent: a failed datapack copy now reports its error instead of passing silently; a half-copied pack still stays in datapacks/ (no cleanup), worth a follow-up. Union 760aa5921 tests green.
2026-11-04 17:01:43 +01:00
Ludovic Marie f85777b490 Merge pull request #8971 from elythepreciouscat/perf/creature-projectile-tick-optimizations
🤖 Agent: the guest-entity and dead-guest lists no longer hang on has_guests (which skips dead and spectating guests), so a lone dead player's ender pearl still vanishes as in vanilla; pearl_death and the client tests passed on union 760aa5921.
2026-11-04 17:01:39 +01:00
Ludovic Marie c496609c3a Merge pull request #9189 from voidsipp/fix/8920-chest-loot-load
🤖 Agent: merging. Only chests get the precedence; barrels, shulker boxes, hoppers and dispensers load through the same RandomizableContainer.tryLoadLootTable in vanilla and still keep their Items here: a follow-up. Proof: union with main: fmt, clippy -D warnings and client tests pass; CI parity runs the new chest_load harness.
2026-11-04 17:01:34 +01:00
Ludovic Marie e9448bd997 Merge pull request #9143 from Ragbie/fix/silent-elder-curse
🤖 Agent: merging. The host's own path now gates only the sound on `elder_curse_param`, and the apparition stays unconditional, which is what ClientPacketListener.handleGameEvent does for GUARDIAN_ELDER_EFFECT. Proof: union with main: fmt, clippy -D warnings and client tests pass (the new guardian test included); CI parity runs the new elder_curse harness.
2026-11-04 17:01:29 +01:00
Ludovic Marie 5568e9d693 Merge pull request #9138 from voidsipp/fix/8627-optical-flow-scene-change
🤖 Agent: merging. Splitting the reduction into `scd_finalize_pass` replaces the completion counter, which WGSL's memory model never promised would make other workgroups' writes visible; the cost is one 9-workgroup dispatch and a 9 KiB buffer per generated frame. Proof: union with main: fmt, clippy -D warnings and render tests pass (the GPU tests need an adapter; CI's run them).
2026-11-04 17:01:25 +01:00
Ludovic Marie cb0b56a9ed Merge pull request #8665 from Stink-O/native/far-terrain-edits
🤖 Agent: merging. Risk noted: the server holds an extra `Arc<Chunk>` per handed-over column until the window's next tick, so an edit in that window copies the chunk on `Arc::make_mut`; one tick at most, and only for bulk edits out of view. Proof: union with main: fmt, clippy -D warnings, client and lan::handoff tests pass.
2026-11-04 17:01:21 +01:00
Ludovic Marie e7174a8571 Merge pull request #9054 from arslanarm/shaderpacks/shadow-pass-back-faces
🤖 Agent: Pack shadow passes now draw every face (and without back-face culling), which roughly doubles the shadow pass's solid quads for Iris-format packs. Horizon's own packs keep the light-facing selection (`every_face: false`). `shadow_water` getting `cull: false` too is a guess the probe didn't measure, as the description says. I pushed 9fac526f: merged main.
2026-11-04 17:01:16 +01:00
Ludovic Marie 1bae636ef6 Merge pull request #8635 from valkdzx/parity/elytra-look-vector
🤖 Agent: merging. FireworkRocketEntity reads `getLookAngle()`, Mth's float sine table, so the boost now matches `travel_fall_flying` and the server; the remaining f64 `view_vector` use is `rocket_hand_angle`, which only places the spark. Proof: union with main: fmt, clippy -D warnings, client tests and tests/test_remote_own_rockets.py pass.
2026-11-04 17:01:12 +01:00
Ludovic Marie 637d885453 Merge pull request #8479 from xFedeT/xfedet-fix-texture-rendering
🤖 Agent: merging the Metal split and the minified core-shader fix. I dropped the ZIP validation from the download path: vanilla's DownloadedPackSource checks only the SHA-1 (checkExistingFile), the strict zip crate refused the streamed Ascendia archive that Horizon's own pack reader already reads (so other Java-readable packs would have been refused too), and it decompressed the whole pack on every cache hit. Proof: fmt, clippy -D warnings and the touched crates' tests pass on a union of this head with main and 19 other PRs.
2026-11-04 17:01:07 +01:00
Ludovic Marie bb99dd0fb9 Merge pull request #8667 from HrafnaValkyrja/fix/tp-parity
🤖 Agent: `set_rotation` is the same eight lines in 11 impls (finite yaw, NaN pitch kept, clamp); a shared helper would keep them from drifting. Item, orb, TNT, falling-block and rocket loaders still read Rotation as [0, 0], as the description says. I pushed 764989a2: merged main, keeping main's new TNT `block_state` argument beside `rotation` (power, block_state, rotation), and gave main's new TNT and orb fixtures the field. Proof: main + this head: fmt and clippy -D warnings (horizon-client and horizon-client-lib, all targets) and horizon-client tests pass, 0 failed.
2026-11-04 17:01:02 +01:00
Ludovic Marie 7ef2b97025 Merge pull request #9059 from arslanarm/shaderpacks/pack-atlas-sprite-sizes
🤖 Agent: Stitching the pack atlas at each sprite's own size only applies to flat draws (a stitched atlas is dropped for grid draws), and a device that can't stitch falls back to the grid with a log line. Nothing else found. I pushed 02e68e15: merged main (#9057/#9180 landed).
2026-11-04 17:00:58 +01:00
Ludovic MarieandClaude Opus 5.5 79d298d339 Offer the Afri-Network partner server once in the server list
Horizon partners with Afri-Network, a server for players in South Africa.
At start the game now adds it once below "Horizon Official" in
servers.dat, like the official server: never again after the player
deletes it (horizon_partner_servers.txt), and not when the list already
has its address. servers::insert_after keeps hidden entries and unknown
fields, as prepend does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:00:38 +01:00
Ludovic Marie 8733f83fd1 Merge main into #9242
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-04 16:57:52 +01:00
TheMrZZandCodex gpt-6.1-sol d15541aa39 Merge main while retaining inline and weather predicate fixtures
Keep the independent inline parser and its fixture definitions alongside the named weather regression.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 16:56:01 +01:00
Ludovic Marie 59d8cc38a2 Merge pull request #9192 from CocofireHD/fix/build-limit-actionbar
🤖 Agent: `InteractionResult.FAIL.consumesAction()` is false, so `handleUseItemOn`'s build-limit message fires. ANY_BLOCK_USE keeps its old condition. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:55:56 +01:00
Ludovic MarieandClaude Opus 5.5 be11ce8db1 benchmarks: Horizon Nightly 2026-11-04 vs vanilla 26.3 on the Mac mini and the desktop
The perf_bench reports behind playhorizon.dev/benchmarks as of 2026-11-04 (horizon-site#58): RD 12 and 32,
three games each, 1280x720, VSync off. The Mac's session was locked (Horizon offscreen) and both machines were
busy (load 17-50 on the Mac, 28-70 on the desktop); site-2026-11-04.md says how they were made and what that costs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:55:16 +01:00
Ludovic Marie 7db3c5afef Merge pull request #9061 from Joansitoh/fix/potion-amplifier-reload
🤖 Agent: the reader treats the byte as `UnsignedBytes.toInt`; the writer clamps to 0..=255 before the byte cast, so 255 and 128 round-trip. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:55:09 +01:00
yosifdheef 5d9886fb34 Merge remote-tracking branch 'upstream/main' into fix/7992-shulker-open-support 2026-11-04 18:54:46 +03:00
Ludovic Marie 1bacc5cee0 Merge pull request #9067 from Joansitoh/fix/end-portal-xp-orbs
🤖 Agent: after the cleanup, `clear_remote_objects` (new connection, level, dimension) drops the orbs with the rest of the old ClientLevel. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:54:42 +01:00
Ludovic Marie 2181ab22eb Merge pull request #9068 from Joansitoh/fix/shulker-arrow-deflect
🤖 Agent: the refusal branch now restores the fire set by the arrow and reverses at power 0.2, as `AbstractArrow.onHitEntity` does when `projectileReceivesSideEffectsOnHit(false)`. This also covers mobs in their hurt cooldown, whose arrows used to vanish. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:53:58 +01:00
Ludovic Marie eeeabc13fc Merge pull request #9069 from Joansitoh/fix/composter-fill-feedback
🤖 Agent: both `levelEvent(1500, …)` calls in `ComposterBlock` (useItemOn, the hopper input) are the null-source overload, so 1500 belongs in the no-exception list. `play_unshared` also stops the serving side from sending a second sound packet. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:53:15 +01:00
Ludovic MarieandClaude Opus 5.5 79576177db Add item_stack_limits to the conversion test's MobType
Main gave MobType an item_stack_limits field after this branch was cut.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:52:25 +01:00
Ludovic Marie 47f857e3c5 Merge pull request #9071 from Joansitoh/fix/chorus-portal-particles
🤖 Agent: `hub.entity_event` broadcasts with no exception, so the eater sees its own event 46, as with `sendToTrackingPlayersAndSelf`. A mob's 46 arrives before its move packet, so its trail runs between the two old positions, as on a vanilla client. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:51:53 +01:00
Ludovic Marie 5eb115301b Merge remote-tracking branch 'origin/main' into cg31fix-9115 2026-11-04 16:51:53 +01:00
Ludovic Marie 031ce8d748 Merge pull request #9113 from Joansitoh/fix/8891-silent-witch
🤖 Agent: the pitch `nextFloat` stays inside `!isSilent()` at both call sites (`Witch.aiStep` line 155, `performRangedAttack` 262), and the test pins the untouched random. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:50:56 +01:00
Ludovic Marie 7acb499c7e Merge pull request #9114 from Joansitoh/fix/8863-trader-end
🤖 Agent: `Level.isDarkOutside`/`isBrightOutside` are both false under `hasFixedTime`. A datapack dimension with fixed time still drinks, because the check is `nether || end`, not the dimension type. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:50:20 +01:00
Ludovic MarieandClaude Opus 5.5 4093c1de28 Merge origin/main into feat/drop-a-client-build (picks up #9265)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:48:12 +01:00
Ludovic Marie 3e5987615d Merge pull request #9116 from Joansitoh/fix/8850-raider-ride-jump
🤖 Agent: `extra.mount` is the vehicle (`getVehicle`), not the rider, so the guard is `!isPassenger()` and the `nextInt(50)` draw is skipped exactly as in `RaiderCelebration.tick`. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:47:55 +01:00
Ludovic Marie 7dbc6f7c4c Merge pull request #9158 from Joansitoh/fix/9132-fishing-minecart
🤖 Agent: `AbstractMinecart.isPickable` is `!isRemoved()`, and the ridden cart is skipped like `isPassengerOfSameVehicle`. The pull reuses `pull_hooked` for every hooked kind. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:47:19 +01:00
Ludovic MarieandClaude Opus 5.5 da82eadb2a Remote mob takes tests: MobType's new item_stack_limits
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:46:52 +01:00
Ludovic Marie 4fc709af20 Merge remote-tracking branch 'origin/main' into cg35/9178 2026-11-04 16:46:34 +01:00
Ludovic Marie da4f89e3d7 Merge pull request #9163 from Joansitoh/fix/8851-raid-horn
🤖 Agent: checked the server thread: the serving Game has no local player (dedicated options), so every player, the window's too, gets its own packet. The 'in raid' set is still one player (`bar.player`), while vanilla's `raidEvent.getPlayers()` holds everyone within 96 blocks; a far second participant hears nothing. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:46:24 +01:00
Ludovic Marie 3a8609eada Merge pull request #9186 from Joansitoh/fix/8937-max-stack-size
🤖 Agent: `max_stack` reads the patch first and falls back to the item when the component is removed, as `ItemStack.getMaxStackSize` does. The wire test covers the add and the `!` removal. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:45:37 +01:00
Ludovic Marie 84f20cd34e Merge pull request #9188 from Joansitoh/fix/9185-bee-sting-mobs
🤖 Agent: matches `Bee.doHurtTarget` (poison on any LivingEntity the sting hurt, 10 s Normal / 18 s Hard). Not ported here: `setStingerCount` on a mob target, which vanilla also increments. Proof: union of all cg-31 PRs on main 6d43a3593b (playhorizon): fmt, clippy -D warnings (client/lib/render all-targets + server lib), client+lib tests 1638+850 ok.
2026-11-04 16:44:52 +01:00
Ludovic Marie 88f0faf67d Merge pull request #9286 from horizon-rust/feat/launcher-wardrobe-only
🤖 Agent: Ludovic's call (11-04): the shop lives on playhorizon.dev/shop (live, Paddle checkout tested end to end with his account), the launcher keeps the Wardrobe with an Open the shop button. launcher job green on this head; verify running.
2026-11-04 16:44:17 +01:00
Ludovic Marie 25f65c8441 Merge main into #9242
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-04 16:44:15 +01:00
Evrim CicekandClaude Opus 5.5 03e77c9fb9 test_dragon_death_outside_end: drop the end gateway count the box cannot see
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:43:08 +03:00
Ludovic Marie c909ca58e6 Merge main into #8954
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/src/mod_api.rs
2026-11-04 16:42:13 +01:00
voidsipandCodex 5150ff66bf Match shulker teleport collision and attachment rules
Co-Authored-By: Codex <[email protected]>
2026-11-04 09:41:51 -06:00
Axxio 9f346f9794 control: horizon-ctl summon spawns server-ticking mobs (fixes #9207)
When running single player on an integrated server, `horizon-ctl summon`
spawned mobs directly into the client window's entity list rather than
delegating to the server thread. Because the client window does not tick
mobs (running `tick_remote` instead of `tick_world`), summoned mobs
never executed AI goals, took no environmental damage, and were not
visible to server queries.

Delegate `summon` to the integrated server in the owner player's level
when `self.integrated` is active, matching other control world commands
and allowing the server to tick the mob and broadcast it via standard
entity tracking.

Managed by Axxio.
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 16:41:46 +01:00
Ludovic MarieandClaude Opus 5.5 9f69fa38e3 Regenerate docs/launcher-commands.md for native-cache
the_launcher_reference_is_up_to_date: the native-cache command and the
native-cache-gib setting were missing from the generated reference.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:40:49 +01:00
Ludovic MarieandClaude Opus 5.5 9fac526fef Merge origin/main into shaderpacks/shadow-pass-back-faces
Fixture: build/try_build (#9196) and build_with_terrain share one
try_build_with_terrain.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:40:10 +01:00
Ludovic Marie 5eef8152d8 Merge remote-tracking branch 'origin/main' into cg32/fix-9153 2026-11-04 16:39:58 +01:00
degepieandClaude Opus 5.5 d0ac649f08 Ender dragon: its fireballs and breath reach mobs too (#8741)
DragonFireball is stopped by any entity it can hit and DragonFireball.onHit
moves its cloud to the first LivingEntity within 4 blocks; AreaEffectCloud
.serverTick applies its instant damage to every LivingEntity in it. Horizon
only looked at players. Mobs now stop a fireball, its cloud snaps to them,
and both breath clouds (the fireball's and the flame phase's) put their
instant damage on the mobs inside through the lingering clouds' existing
cloud_affect path (indirectMagic, undead healed instead).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 20:38:51 +05:00
Ludovic Marie 5d8f9fc2fc Merge pull request #9084 from arslanarm/shaderpacks/raw-texture-defaults
🤖 Agent: I pushed 19b0fd00 and 27d625d8: merged main and filled `Resources.packed` (#9090), `Interface.initial_values` and `Resources.shadow_linear` in the new test, which no longer compiled against current main. Raw textures defaulting to linear + clamp and PNGs to nearest + repeat match the probe table. Nothing else found.
2026-11-04 16:38:33 +01:00
Ludovic Marie 12306e9a47 Merge pull request #8733 from HrafnaValkyrja/fix/vehicle-teleport
🤖 Agent: Merged after review fixes 2c7b4906: /rotate sends PlayerRotation instead of a teleport, the boarding cooldown counts per tick (not per turn), and riding guests hear their vehicle's teleport. Follow-ups listed in the review: mob passengers on /tp, the passengers' rotation, cross-dimension vehicles.
2026-11-04 16:37:49 +01:00
TheMrZZandCodex gpt-6.1-sol e3940bc5f0 Adapt the predicate probe and clarify weather context citations
Give the entity-only probe its dry weather context and keep the new fixture beside stable command groups. Cite the predicate read context without treating abstract reads as complete Level method ports.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 16:37:38 +01:00
Ludovic Marie 234bccad77 Merge pull request #7861 from horizon-rust/perf/sprite-cpu-copy
🤖 Agent: block sprite pixels lz4-packed once uploaded (-19 MB live heap); one risk to know: SpritePixels::get returns zeroed pixels if decompression ever failed, which would show as black textures on a rebuilt renderer (device loss, split screen), not a crash. rdbtCVS's scene_replay adaptation included; verify green on this head, merger waits for parity.
2026-11-04 16:36:50 +01:00
Evrim CicekandClaude Opus 5.5 5a5573a7c1 A dragon dying outside the End ends no fight and is worth 500
EnderDragon.tickDeath calls dragonFight.setDragonKilled and counts the
first kill's 12000 experience only when it has a fight, which only the
End gives (ServerLevel.getDragonFight). Horizon did both everywhere: a
dragon summoned in the overworld left an exit portal and a dragon egg at
0 0 there, and 12000 experience. /kill already checked the dimension.

tools/test_dragon_death_outside_end_parity.py compares the blocks left
and the experience with vanilla's dedicated server.

Fixes #9274

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:36:30 +03:00
Ludovic Marie 712d52f326 Merge pull request #9177 from arslanarm/shaderpacks/fragment-output-component
🤖 Agent: A `component =` output at a location now joins the other outputs at that location into one vec4 target. Mixed scalar kinds, or overlapping components, are refused with the variable's name. Nothing else found.
2026-11-04 16:36:04 +01:00
Florian ERNSTandCodex f3386151d2 Merge latest main for headless bell dispatch
Co-Authored-By: Codex <[email protected]>
2026-11-04 16:35:46 +01:00
Florian ERNSTandCodex bd0a318660 Tick server bells and apply raider glow without a renderer
Co-Authored-By: Codex <[email protected]>
2026-11-04 16:35:45 +01:00
Ludovic MarieandClaude Opus 5.5 e8d6861b39 Merge origin/main into shaderpacks/terrain-texture-matrix
Resolve core_matrix with #9180's (matrix, type) form: textureMatrix is
(hz_TextureMatrix[0], mat4) in vertex stages; both blocks and both tests kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:35:23 +01:00
Ludovic Marie d2e6d705a7 Merge pull request #9119 from arslanarm/shaderpacks/custom-uniform-type-mismatch
🤖 Agent: Unknown names and shape mismatches now zero the whole custom uniform, as your probes show. The correction comment about `round`/`ifb` (no such function, same 0) is noted. Nothing else found; #9153 builds on it.
2026-11-04 16:35:20 +01:00
Ludovic Marie 02e68e1592 Merge remote-tracking branch 'origin/main' into cg32/fix-9059 2026-11-04 16:35:12 +01:00
Ludovic Marie ee3915a050 Merge pull request #9196 from arslanarm/shaderpacks/integer-colortex0-no-final
🤖 Agent: A pack whose integer colortex0 can't reach the screen is now refused at load and cleared from the settings, like a panicking one. The old path cleared the screen every frame instead. The refusal's notes are logged before the reason, so the chat line stays short. Nothing else found.
2026-11-04 16:34:42 +01:00
Ludovic MarieandClaude Opus 5.5 463f3eff0b Test: World.sprite_rects (from #9057) in the terrain parts fixture
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:34:20 +01:00
Ludovic Marie b0546aee34 Merge remote-tracking branch 'origin/main' into cg32/fix-9176 2026-11-04 16:34:08 +01:00
Ludovic Marie 1ac9480b90 Merge pull request #9180 from arslanarm/shaderpacks/core-normal-matrix
🤖 Agent: `normalMatrix` now depends on `core_profile()`: a `#version 150`/`330` without a profile word counts as core, which matches the four probe pictures. Below 150 nothing changes, as the doc comment says. Nothing else found.
2026-11-04 16:33:41 +01:00
rdbtCVSandOpenAI Codex gpt-6.1-sol 47ad28cee8 Merge main and finish RAM limit removal cleanup
Preserve existing Java UI translations, remove obsolete unlimited-memory states, and retain the resident memory diagnostic test.

Co-Authored-By: OpenAI Codex gpt-6.1-sol <[email protected]>
2026-11-04 15:29:07 +00:00
Ludovic MarieandClaude Opus 5.5 27d625d859 Test: Resources.packed (from #9090) in the raw-texture test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:28:59 +01:00
Ludovic Marie 818cb416b3 Merge remote-tracking branch 'origin/main' into cg32/fix-9084 2026-11-04 16:28:58 +01:00
Ludovic Marie 56282c484f Merge pull request #9179 from Domanffe/fix/launcher-wardrobe-save-error
🤖 Agent: main's wardrobe_claim now calls shop_claim, so that one save moved out of this PR; the other five writes now print their error instead of dropping it.
2026-11-04 16:28:28 +01:00
Ludovic Marie d562d76fd9 Merge pull request #8836 from Cheezer1656/native-client-packet-hook
🤖 Agent: read-only and after decode, so a mod can't change what the game handles, and with no mods the dispatch is a loop over nothing on the per-packet path; main merged in (next to #8938's test and #9187's stamped docs).
2026-11-04 16:28:24 +01:00
Ludovic Marie ee06d18c39 Merge pull request #8557 from hildlabs/shadow-options
🤖 Agent: SHADOW_RESOLUTIONS and SHADOW_DISTANCES are ranges, so checking an int option's min and max covers every value the player can pick, and a 0 option drops the shadow pass, not just the map; main merged in (docs/api/shaders.md changelog next to #8512's).
2026-11-04 16:28:19 +01:00
Ludovic Marie 529f1660b0 Merge pull request #9141 from glitchsnake/fix/9058-living-jump-power
🤖 Agent: getJumpPower is now jump_strength × multiplier × getBlockJumpFactor + getJumpBoostPower for every living mob, so cows and pandas jump half as high off honey as in vanilla; I dropped the unused free block_jump_factor wrapper and rabbit's re-export, and the #[ignore]d honey test passed on a tree with the vanilla resources.
2026-11-04 16:28:14 +01:00
Ludovic Marie ecdd4eb222 Merge pull request #9122 from DenisHumen/fix/debug-lines-after-translucent
🤖 Agent: matches DrawableGizmoPrimitives.getGroup (alpha 255 → lines with depth write, the rest linesTranslucentNoDepthWrite), uploading the opaque group first so one buffer serves both draws; I moved the two helpers so translucent_batchable keeps its own doc comment.
2026-11-04 16:28:08 +01:00
Alexander KrylovandHermes Agent 527c6de7cd Launcher: a right click on a sidebar row opens its ⋮ menu
The sidebar's rows carry the menu about that instance (Edit…, Duplicate…, Open
folder, Export…, the desktop shortcut, Delete…), and it is reachable only
through a 32 px ⋮ button. A right click anywhere on the row now opens the same
menu.

Both entry points share one gate, `row_menu_after_press`: it opens the row's
menu, closes it when it is already that row's, and drops the Delete… confirm
flag when the menu moves to another row, which is what the ⋮ did before.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 18:28:02 +03:00
Ludovic Marie c5ae81b41f Merge pull request #9057 from arslanarm/shaderpacks/pack-atlas-sprite-table
🤖 Agent: Small and contained: flat terrain reads each sprite's rectangle from a table instead of assuming a uniform grid, the base for #9059/#9060. Nothing found.
2026-11-04 16:28:02 +01:00
Ludovic Marie 220717bd56 Merge pull request #9104 from arslanarm/shaderpacks/atomic-member-reads
🤖 Agent: This changes the vendored naga GLSL frontend: every non-constant struct member read now goes through a pointer, not a loaded copy of the struct. Every pack's GLSL takes that path, so a naga update must keep the patch (THIRD_PARTY_NOTICES records it). Constant expressions keep the old form. Nothing else found.
2026-11-04 16:25:32 +01:00
Ludovic Marie c62113c79b Merge pull request #9108 from arslanarm/shaderpacks/atomic-pointer-shared-vectors
🤖 Agent: Atomic functions on a member of a shared vector in a declaration list or a shared struct now resolve. The rewrite is limited to `shared` declarations, so other GLSL is untouched. Nothing found.
2026-11-04 16:25:28 +01:00
Ludovic Marie 4e8febfe57 Merge pull request #9090 from arslanarm/shaderpacks/packed-colortex-formats
🤖 Agent: `R3_G3_B2` is stored as 5/6/5 bits because that fits one NVIDIA driver's readback in Iris (the module says so). Another driver may store real 3/3/2, so the probe is worth rerunning on Mesa before more code depends on it. The quantising only touches passes writing such a buffer, so other packs pay nothing.
2026-11-04 16:23:32 +01:00
Ludovic Marie e914dfb4b9 Merge remote-tracking branch 'origin/main' into feat/launcher-wardrobe-only
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-04 16:22:32 +01:00
Ludovic MarieandClaude Opus 5.5 764989a2d4 Merge origin/main into fix/tp-parity
Main added TNT's block_state argument beside this branch's rotation:
both kept (power, block_state, rotation); main's new TNT and orb test
fixtures get the rotation field.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:22:12 +01:00
makscoder 6bf332f5c5 Cap command-tree parse recursion depth to stop crafted deep /execute chains crashing the game
`execute run` redirects to the tree root, so parse_nodes recurses once per
nesting level. A crafted deeply nested command overflows the parsing thread's
stack (thread 'Server thread' has overflowed its stack) and kills the game.

Add MAX_PARSE_DEPTH (128) to parse_nodes' recursion: past the limit the branch
yields no result, so the command fails to parse instead of overflowing. 128 is
well below the ~300-level overflow measured on the server thread and far above
any real command chain; vanilla likewise caps command nesting.

Adds tests: a 400-deep execute-run chain is rejected (not overflowed), while
shallow execute chains still parse.

Closes #9252
2026-11-04 18:21:27 +03:00
degepieandClaude Opus 5.5 a6e6ad25c5 Illusioner: blind a targeted mob, not only the player (#8849)
IllusionerBlindnessSpellGoal.performSpellCasting adds Blindness for 400
ticks to the illusioner's target, any LivingEntity. The cast only sent
EffectPlayer, so a villager or iron golem it targeted kept nothing. A new
MobEvent::EffectMob carries the effect to the target mob, whose
add_effect already checks canBeAffected.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 20:19:32 +05:00
degepieandClaude Opus 5.5 0533124415 Illusioner: count the spell cast down
SpellcasterIllager.customServerAiStep lowers the casting count every
tick, and Illusioner has no override of its own. Horizon did this only
for the evoker, so an illusioner's count stayed at 20 after its first
(mirror) spell: it kept the casting goal for good, cast nothing else and
never shot its bow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 20:19:32 +05:00
MSedgeMC b7a1dd4e41 port_map: Slot.allowModification, and the guest bundle click that asks it
The ledger had no row for the method `BundleItem`'s click override gates
both branches on.
2026-11-04 15:19:31 +00:00
voidsipandCodex 90ecdc3dd0 Stop emptied loot chests refilling after reload
Co-Authored-By: Codex <[email protected]>
2026-11-04 09:19:20 -06:00
Ludovic Marie 2e5a2629a0 Merge pull request #8621 from itarqos5/feat/lod-detail-when-zoomed
🤖 Agent: the zoom cone only boosts tiles covers() says are looked at, and the test pins the plan behind the player as identical zoomed or not; I made lodZoomDetail off by default until a measurement shows the extra far tiles land while the zoom is held.
2026-11-04 16:19:19 +01:00
MSedgeMC 909f6c4e96 port_map: the lectern's load row names the page clamp
`decode_block_entity` ports `LecternBlockEntity.loadAdditional` including
the `Mth.clamp(Page, 0, pageCount - 1)` the row only called "Book and
Page", so the row now says what the load does with the page.
2026-11-04 15:16:25 +00:00
MSedgeMC 132d0f3022 test: a stored lectern page loads inside its book
The decode of a chunk's or a structure's lectern, `decode_block_entity`,
against the pages the book on it has: 7 on a two-page book loads as 1,
-3 as 0, a bookless lectern's 4 as 0, and a page already inside the book
(1 of three) is kept and saved back as 1.

Without the clamp the first case fails with `left: 7, right: 1` and the
second with `left: -3, right: 0`.
2026-11-04 15:16:25 +00:00
MSedgeMC 1267b1e360 fix: a lectern clamps its page to its book on load
`LecternBlockEntity.loadAdditional` reads `Page` through
`Mth.clamp(page, 0, getPageCount(book) - 1)`: a page stored past the
book's last one comes back as the last page, one stored before the first
as the first, and a lectern with no book has the single page 0. Horizon
kept the stored number as it was.

A negative page was the visible half: `LecternBlockEntity.getRedstoneSignal`
(`books.rs::lectern_signal`) answers `floor(page / (pages - 1) * 14) + 1`,
so a stored -3 read 0 to a comparator where vanilla's clamped page 0 reads
1, and the next save wrote the out-of-range page back out. A page above the
end reads the same on both sides, since the signal saturates at 15 either
way, and the open menu already clamps (`menu.rs::set_data`), so what changes
is the load and with it the round trip.
2026-11-04 15:16:24 +00:00
Ludovic Marie 5ff12dfaf4 Merge pull request #8631 from HrafnaValkyrja/feat/admin-player-position
🤖 Agent: I pushed c2aa51c1: merged main and resolved the generated `server-reference.md` row (main's text plus `dimension`), so `the_routes_table_matches_the_code` agrees again. Nothing else found.
2026-11-04 16:14:17 +01:00
martipopsandClaude Opus 5.5 07b1ea4f21 Controllers: menu buttons press on release; screens declare their buttons
A on a focused button now presses it when let go, if the focus is still on it.
Moving the focus, a cancel, a scroll, a dialog opening or another pad taking over
drops the press. On the title, X opens Options and B focuses Quit Game; on the
pause menu, Y focuses the exit button, and A confirms.

A screen says what a button is for (`ui::action_button`, `widgets::declare_action`)
and its pad button and glyph follow: Done, Back, Cancel, Apply, Options and Exit.
Nothing else names the screen. What a screen declared stops counting once another
is drawn over it or replaces it, and `pad status` lists what is declared, what
isn't and the glyphs drawn. tools/pad_menu_e2e.py checks these with the virtual pad.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82xRaic2UQfeBTnyhW
2026-11-04 10:13:55 -05:00
makscoder 16a4b6010b Cap SNBT parser nesting depth to stop crafted deep values crashing the game
The command SNBT parser (horizon-command) recursed on every nested
compound/list with no depth limit, so a crafted deeply-nested argument
(stack overflow: thread 'Server thread' has overflowed its stack). Both
ArgType::Nbt (summon/data/give NBT) and ArgType::Component (tellraw/text
components) route through this parser, so one guard covers both.

Add a MAX_DEPTH (128) recursion counter to the packrat State, checked in
State::parse: over-deep input now fails to parse (the argument is
rejected) instead of recursing until the stack overflows. 128 is far
below the measured ~300-level overflow on the smallest parsing thread and
far above any real command or data value. Adds tests that deep compound
and deep extra-style list input are rejected while shallow values still
parse.

Closes #9236
Fixes #9261
2026-11-04 18:13:01 +03:00
Ludovic MarieandClaude Opus 5.5 6535ef4545 transfer: the frozen-chat test sends an ack that moves the window before a chat that counts on it
The test sent a chat, then an ack of offset 0, which validates in either
order. It now sends an ack of 1 and then a chat acknowledging the message
the ack brought into the window, which fails validation (and kicks) when
the ack is applied after the chat.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:12:47 +01:00
MSedgeMC 6e215078fe test: a guest's bundle keeps its contents in a refusing slot
The bundle sits in a chest slot whose rules refuse the pickup, with the
prepared catalog behind it so the stack really has bundle contents. A
left click with stone in hand adds nothing, a right click with an empty
hand takes nothing, and the empty bundle in the next slot still fills.
2026-11-04 15:12:42 +00:00
MSedgeMC 9cc1db4268 fix: a bundle in a slot that withholds it keeps its contents
`BundleItem.overrideOtherStackedOnMe` gates both of its branches on
`Slot.allowModification`, which is `mayPickup` and `mayPlace` of the
stack the slot holds - not the placement test alone. `lan::menu`'s
bundle click asked only `View::may_place`, so a guest could fill or
empty a bundle in a slot whose menu refuses the pickup.
2026-11-04 15:12:41 +00:00
Ludovic Marie 3c3d54137b Merge pull request #8679 from HrafnaValkyrja/fix/entity-save-rest
🤖 Agent: I pushed 1fb68131: merged main and added `fire_ticks: 0` to the new trident test, which no longer compiled once main gave `TridentSave` that field. Note that `LeftOwner: 1b` is now always written for tridents; vanilla omits it on a freshly thrown trident still inside its owner's box. Rarely visible.
2026-11-04 16:12:06 +01:00
Ludovic Marie 527152f6f4 Merge pull request #8704 from HrafnaValkyrja/fix/portal-parity
🤖 Agent: `as_console_in_overworld` enters the overworld only when another level is in place, and puts that level back after the line, so the next server tick sees the level it left. The unit test covers only `console_level`; the portal scan is the real proof. Nothing else found.
2026-11-04 16:12:01 +01:00
Ludovic Marie 56a2de7d42 Merge pull request #8722 from HrafnaValkyrja/fix/minecart-momentum
🤖 Agent: `AbstractMinecartContainer.applyNaturalSlowdown` port is exact, f32 arithmetic included, and it returns before the ridden/behaviour factor as vanilla does. Nothing found.
2026-11-04 16:11:57 +01:00
Ludovic Marie a61f360fa5 Merge pull request #8727 from HrafnaValkyrja/fix/carpet-dupe
🤖 Agent: Only a non-source moving block at progress 1.0 now counts as its moved state. One small gap: vanilla reads the moving piston's *collision* shape, so a moved block whose support shape differs from its collision shape (leaves) supports there in vanilla but not here. The carpet machine doesn't need it.
2026-11-04 16:11:52 +01:00
Ludovic Marie 8788891c88 Merge pull request #8859 from HrafnaValkyrja/feat/panel-bans-crashes
🤖 Agent: Crash reports are read only by a name the listing returns, so `../` and absolute paths can't reach outside `crash-reports/`. The test covers both, plus a non-`.txt` file. The panel refetches players and crash reports every 10 polls, a directory listing each time: cheap. Nothing else found.
2026-11-04 16:11:47 +01:00
Ludovic Marie d9901d9fb4 Merge pull request #8869 from HrafnaValkyrja/fix/shadow-non-full-tops
🤖 Agent: `isCollisionShapeFullBlock` is the right gate (`EntityRenderer.extractShadowPiece`), and the test now covers ten non-full blocks, including the hopper whose collision bounds reach y=1. Nothing found.
2026-11-04 16:11:42 +01:00
degepieandClaude Opus 5.5 189b8b2c95 Ender dragon: post GameEvent.ENTITY_DIE when it is removed (#8740)
EnderDragon.tickDeath (at 200 death ticks) and EnderDragon.kill (/kill)
both end with gameEvent(ENTITY_DIE) at the dragon; Horizon posted none.
Sculk sensors now hear the death, a catalyst within reach blooms (no
charge: the dragon's xpReward is 0), and on /kill a warden can hear it
too (after the death flight Warden.canTargetEntity refuses a dragon with
no health left).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 20:11:18 +05:00
rdbtCVSandOpenAI Codex gpt-6.1-sol 908d81bd4c Keep Java heap memory controls when removing Horizon RAM limits
Restore the Home and glass instance editor controls only for Java instances. Keep memory_mb persistence and Java's default heap behavior.

Co-Authored-By: OpenAI Codex gpt-6.1-sol <[email protected]>
2026-11-04 15:10:34 +00:00
abit-foggyandCodex 0b9a5a478c feat(studio): add mcpr recording archive container
Co-Authored-By: Codex <[email protected]>
2026-11-04 05:10:24 -10:00
Ludovic MarieandClaude Sonnet 5.5 bb1f287b39 Launcher: the shop lives on the website; the Wardrobe keeps owned items and purchases
Ludovic decided the cosmetics shop is only on https://playhorizon.dev/shop
(browsing, prices, the founder claim, purchases through Paddle). The launcher's
Wardrobe keeps what the account owns: equip / take off, "Show my cosmetics", and
the purchase history.

- Removed the Shop tab (wardrobe_page/shop.rs), the founder claim flow and its
  checkout polling, Service::claim, Checkout / CHECKOUT_PAGES, the Shop's
  window/countdown rules and the tests of all of it.
- Added an "Open the shop" button (opens the website's shop) beside the Wardrobe
  title, and in the empty state ("You don't own any cosmetics yet.").
- The purchase history ("Your purchases") moved into the Wardrobe page.
- Lang tables: two new strings in all 86, the stale ones removed.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 16:08:49 +01:00
Ludovic MarieandClaude Opus 5.5 9e642fbe77 Merge main into #9179
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:07:50 +01:00
CocofireHDandDeepSeek V4.1 Flash 5839363b51 Merge origin/main into fix/turtle-dig-level-event
Resolve game/allays.rs: keep #8652's empty-main-hand gate and MAIN_HAND swing, and take main's Option<ItemStack> allay inventory for the returned stack.

Co-Authored-By: DeepSeek V4.1 Flash (OpenCode) <[email protected]>
2026-11-04 16:07:47 +01:00
Ludovic Marie 16acafe526 Merge pull request #8798 from horizon-rust/agent/rdbtcvs/server-shared-item-defaults
🤖 Agent: one items.json snapshot per load, keyed on the root path; overlapping loads of one root share the native hooks' answers, fine under the same-answer contract. Proof: union with #8751+#8763+#8798 on main a7df9ec: fmt, clippy -D warnings clean; item_defaults/loot/items/font/hud/item_render/cit tests pass (playhorizon).
2026-11-04 16:07:32 +01:00
Ludovic MarieandClaude Opus 5.5 6db44f17af Merge main into #8954
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:07:30 +01:00
Ludovic Marie cf667d7e21 Merge pull request #8763 from horizon-rust/agent/rdbtcvs/server-font-metrics
🤖 Agent: server fonts measure without an atlas; galactic_widths is now zero on a server but only the enchanting table's draw reads it. Proof: union with #8751+#8763+#8798 on main a7df9ec: fmt, clippy -D warnings clean; item_defaults/loot/items/font/hud/item_render/cit tests pass (playhorizon).
2026-11-04 16:07:27 +01:00
Ludovic Marie 7cf2b63e87 Merge pull request #8751 from horizon-rust/agent/rdbtcvs/server-skip-cit
🤖 Agent: a headless load now skips OptiFine CIT; every reader of item_renderer.cit is a draw path (armour stands, elytra, glint upload), so nothing server-side changes. Proof: union with #8751+#8763+#8798 on main a7df9ec: fmt, clippy -D warnings clean; item_defaults/loot/items/font/hud/item_render/cit tests pass (playhorizon).
2026-11-04 16:07:23 +01:00
Ludovic MarieandClaude Opus 5.5 a65ee808dc Merge main into #8836
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:07:07 +01:00
Ludovic MarieandClaude Opus 5.5 3b8896b3b9 Merge origin/main into feat/launcher-data-location (picks up #9265)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:07:02 +01:00
Ludovic MarieandClaude Opus 5.5 c0703a62bc Merge main into #8557
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:06:47 +01:00
AkirabaneandClaude Opus 5.5 51cd08cd3b transfer: the export empties the 2x2 grid, chat acks in order, a failed login gives its expectation back
martipops's second review of #8700.

The export closes the inventory's own 2x2 crafting grid too
(InventoryMenu.removed): guest_close_all empties it only on a real
ContainerClose(0), and a frozen client's is dropped, so its items were
in no record and lost with `player-release moved`.

A frozen player's chat acknowledgements are handled in the same pass as
the last-seen updates of its chat packets, in the order they came, so an
ack followed by a message in one tick still validates.

A login that used up an expectation and fails before it reaches play
(its connection drops in configuration, or the client refuses a
required pack) gives it back with its own deadline, so the proxy's next
attempt within that time is still expected.

The export's reply gives `freeze_ticks_left` beside the record while the
player is frozen, so a proxy does not commit on the target too late for
its release to come in time.

Refs #7484

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:05:53 +01:00
TheMrZZandCodex gpt-6.1-sol c7f0b8d666 Supply dry weather in the native criterion test context
Adapt the test world introduced on main to the added predicate read context.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 16:01:14 +01:00
voidsipandCodex d0a1bf746b Sync mob air and frozen counters to clients
Co-Authored-By: Codex <[email protected]>
2026-11-04 09:00:13 -06:00
Delete228andCodex 28b9f949ff Use world landing damage and preserve vanilla sync preloads
Route fall and stalagmite hurt through the world catalog. Retain the identical no-pack preload, atomically replace it for world packs, and protect the frozen snapshot from late vanilla readers. Select all prepared-resource hurt, landing, registry and preload regressions from the damage catalog parity harness.

Co-Authored-By: Codex <[email protected]>
2026-11-04 17:59:23 +03:00
Ludovic Marie 137956911f Merge pull request #9232 from TheMrZZ/fix/inline-execute-predicates
🤖 Agent: Review by Ludovic's AI agent (Claude), not by Ludovic himself. check_predicate now decodes an argument starting with { as inline SNBT and sends it through the same evaluator, source entity and origin as a named predicate, as ExecuteCommand.checkCustomPredicate does; the codec now requires terms for all_of/any_of and term for inverted, so a malformed inline predicate fails at parse with no callback, as CompositeLootItemCondition.createCodec does. Merges cleanly into today's main; the merger waits for verify and parity on this head.
2026-11-04 15:59:07 +01:00
jaszkus 7740127129 Merge upstream/main into optimization-launcher
launcher/src/backend.rs keeps both Settings::theme (upstream) and
Settings::performance (this branch). Each launcher/lang/*.json takes upstream's
table and gets the two Performance mode strings added again.
2026-11-04 15:58:10 +01:00
Ludovic Marie 6c5cd897fc Merge pull request #9265 from horizon-rust/fix/stale-menu-navigation-keys
🤖 Agent: main fix: removes only the three keys #8873 dropped from en_us (menu_navigation, menu_focus, menu_pointer) from the 51 tables added after its base; afterwards no table in assets/horizon/lang has a key en_us lacks, which is what lang::tests checks.
2026-11-04 15:56:07 +01:00
Ludovic MarieandClaude Opus 5.5 c787285cfb Merge main into #9179
main's wardrobe_claim now calls shop_claim; its save error is out of this PR.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:55:21 +01:00
Ludovic MarieandClaude Opus 5.5 3b2da02d11 Lang: drop the removed Menu Navigation keys from 51 more tables
#8873 removed horizon.options.menu_navigation, menu_focus and menu_pointer from
en_us.json and the 35 tables its branch had; the 51 tables main gained since
kept them, so lang::tests::bundled_files_are_served_and_have_no_stale_or_repeated_keys
fails on main (cv_cu first).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:55:11 +01:00
Ludovic MarieandClaude Opus 5.5 4a0122986d Send the brand before the client information, and no ClientInformation at login
Vanilla's ClientHandshakePacketListenerImpl.handleLoginFinished sends the
brand payload, then ServerboundClientInformationPacket; Horizon sent them the
other way round. ClientPacketListener.handleLogin sends no client
information either: the configuration phase already did, and
broadcastClientInformation runs only when an option changes. Horizon sent a
second, identical ClientInformation in play right after Login.

tools/vanilla_wire_e2e.py gains the rules brand-before-information and
no-play-information.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:53:51 +01:00
Ludovic MarieandClaude Opus 5.5 1385a4353e rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:53:33 +01:00
Florian ERNSTandCodex gpt-6.1-sol b1d0b765d4 Merge main and preserve both corpus scenario additions for #9232
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 15:51:22 +01:00
TheMrZZandCodex gpt-6.1-sol 79ba2548e4 Merge current main into named weather predicates
Preserve upstream corpus additions and destroy-progress observations alongside the weather regression.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 15:50:24 +01:00
TheMrZZandCodex gpt-6.1-sol ccea38a806 Evaluate named weather predicates using effective weather
Read the existing dimension-aware weather levels in the predicate context and honor both optional weather fields. Add a guarded Game/Java command regression and a resource-free evaluator test without changing weather simulation.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 15:48:49 +01:00
Ludovic Marie 0805df6e08 Merge pull request #9219 from horizon-rust/fix/native-base-revision
🤖 Agent: launcher-only fix, CI green on this head (launcher, verify); main merged in (lang tables only); ThinkPad: fmt, clippy -D warnings, 480+89 launcher tests pass, incl. the new pin/on_base tests and the 86-language i18n check.
2026-11-04 15:48:33 +01:00
degepieandClaude Opus 5.5 86f9a3f780 A swimming dolphin leaves its trail of dolphin particles
Dolphin.tick on a client: a dolphin in water whose squared speed is
over 0.03 (and that has AI) puts four dolphin particles a tick behind it,
0.3 to either side, set back along its view vector by 0.5 to 1.2 drawn
from its own random. client_ai_step had no dolphin, so the window never
made them. It runs over the window's copies of the mobs, so single player,
LAN guests and servers all get the trail.

Fixes #8566

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 19:48:13 +05:00
Ludovic Marie ff63338824 Merge pull request #9173 from jaszkus/feat/launcher-update-installer
🤖 Agent: the setup now draws the launcher's scene and glass plate with the player's accent; I made the main actions Kind::Primary and slowed the settings poll from 16 ms to 500 ms so the setup does not redraw at 60 fps for it.
2026-11-04 15:47:19 +01:00
voidsipandCodex 4b8643a18f Let Breezes deflect fishing hooks
Co-Authored-By: Codex <[email protected]>
2026-11-04 08:44:55 -06:00
Ludovic Marie 411ace47fe Merge pull request #9197 from TheMrZZ/fix/decorated-pot-data
🤖 Agent: this head carries all 40 of TheMrZZ's open PRs as one fast-forward chain (age order, main merged), because 38 of them append to tools/command_exec_corpus.py and conflicted pairwise; each one's current head is an ancestor, so GitHub marks all 40 merged with this one. Two hand merges to check: #8847, #8986 and #9101 now share one canBeABaby == false branch in mob_to_nbt that drops Age, ForcedAge and AgeLocked (AgeableMob.addAdditionalSaveData writes none of them), and data_of tries #9174's comparator view, then this PR's pot view. Proof: verify passed on 94ccd9c8e (this head = that + main); clippy -D warnings (also --no-default-features) and horizon-client-core/horizon-world tests pass on the union on playhorizon; the merger waits for verify and parity / parity here.
2026-11-04 15:44:44 +01:00
Ludovic Marie 1a3e0f94e7 Merge pull request #8204 from Akirabane/feat/live-heartbeat-interval
🤖 Agent: Requeued after merging #8200's main merge (12e979554) into it, no conflict. Live status heartbeat interval; lands after #8200. Fixes in this PR: the log reads the interval after setting it, as vanilla does, and a set after a cancelled heartbeat is refused, as ManagementServer does (b6204bf860). management_protocol --fresh PASS on the union of this wave plus the fix. On this head: horizon-server fmt, clippy -D warnings and 140 tests clean. The rest of the selection runs in CI run-parity.
2026-11-04 15:44:40 +01:00
degepieandClaude Opus 5.5 0a9aa16802 Name a server's data-pack enchantments by their synced description
Tooltips read the enchantment table built from the game's own files, so an
enchantment a server's data pack adds fell back to its raw id
("enchex:drill II"). The server sends that entry's definition in the
registry sync; join the entries the game lacks to the table the tooltip and
the enchanting clue read (Enchantment.getFullname), as client_biomes does
for biomes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 19:44:12 +05:00
Ludovic MarieandClaude Opus 5.5 d52e1187b4 Merge main into #8954
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:43:00 +01:00
martipopsandClaude Opus 5.5 4682b7838c Merge upstream main into fix/shop-window-clock
Keeps the Paddle checkout from main and the clock and unknown-state fix. Both sides added tests at the end of wardrobe/tests.rs and an item to the same import in wardrobe_page/shop.rs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 09:42:37 -05:00
Ludovic MarieandClaude Opus 5.5 22a7070cdb Merge main into #8836
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:42:35 +01:00
Ludovic MarieandClaude Opus 5.5 5d06f30d10 Merge main into #8557
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:42:09 +01:00
Ludovic Marie 84b3bf3027 Merge pull request #8200 from Akirabane/feat/live-accept-transfers
🤖 Agent: Requeued at rdbt's main merge 12e979554 (clean, no conflict hunks; it carries #8128, now merged). Live accept-transfers, log line as setAcceptsTransfers. management_protocol --fresh on the union of this wave (with #8128 and #8204): every accept-transfers case matches vanilla. The union passed fmt, clippy -D warnings, 3388 tests and the Python suite; rdbt reran the management_methods and accepting_transfers tests on this head. The rest of the selection runs in CI run-parity.
2026-11-04 15:40:58 +01:00
Ludovic MarieandClaude Opus 5.5 168a30e617 Merge main into the TheMrZZ chain (cg-30)
tools/command_exec_corpus.py merged per function (main's new groups kept);
the harness keeps main's destroy_progress records and the chain's record-count check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:39:50 +01:00
jaszkus ab6d67da82 Launcher: a Performance mode, and an accent that changes at once
The launcher feels slow on older computers (i5-7500, Intel HD Graphics 630):
every frame rebuilds the page, the scene redraws its moving parts, and the
glass plates blur the scene again whenever the accent or the blur changes.

Settings > Launcher > Performance mode now turns on the cheaper path: the
scene does not restart on every mouse move, the sky is baked at a quarter of
the window's resolution, the plates' blur is three box passes instead of a
Gaussian, a window resize is baked in the background, and the plates are a
solid colour (no glass, no blur, no backdrop to sample). In that mode a new
accent reaches the background in the same frame as the buttons.

Outside the mode, the accent no longer re-blurs the plates: the backdrop is
blurred once and tinted as it is read, and a tint change is baked in the
background, one bake at a time, the last tint always landing.
2026-11-04 15:38:41 +01:00
Ludovic Marie 4fdf97368b Merge pull request #9203 from NikitaMGrimm/sprint-dust-own-random
🤖 Agent: matches Entity.spawnSprintParticle drawing from this.random; the window's own player has no client-side random, so it keeps the particle engine's, and the golem's nextInt(5) and offsets now come from the same random in vanilla's order.
2026-11-04 15:38:14 +01:00
Ludovic Marie 5d5de221a7 Merge pull request #8697 from itarqos5/perf/name-the-resource-loader-threads
🤖 Agent: the Windows thread_name reads Rust's own handle, and the BUSY guard already answers a nested allocation from thread::current(); I shortened the loader names to load-<step> so Linux's 15-byte limit keeps core and collision apart.
2026-11-04 15:35:36 +01:00
Ludovic Marie b815d63191 Merge pull request #8845 from Lyvt-Dev/fix/8832-warden-pushing
🤖 Agent: matches Warden.isPushable (!isDiggingOrEmerging && super), applied both in pushable() and on the receiving side of push_entities, while the warden still pushes others; I removed the two "digging" parity cases that actually spawned a standing warden.
2026-11-04 15:35:32 +01:00
Ludovic Marie 1e7cf8b5c8 Merge pull request #8216 from janibert1/player-address
🤖 Agent: address is set in all three places NativeServerPlayer is built (guest list, the acting player via lan_acting_uuid, and the command path), as IP only with no port; I added the missing API fragment (server_player_address.json) so it gets a number at stamping.
2026-11-04 15:35:28 +01:00
Ludovic MarieandClaude Opus 5.5 b156e0570c Import: keep the shortened error arm within rustfmt's width
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:33:56 +01:00
yosifdheef 30bce4e15a Read an open shulker box's real support shape
Vanilla asks the box's block entity for its support shape because a shulker box is a dynamic shape: an open box supports only through the one-pixel plate opposite FACING, while the exported state facts always see a closed full cube. Route sturdy, canSupportRigidBlock and canSupportCenter through the level's lid state when the block is a shulker box, keeping the cached path for every other block.
2026-11-04 17:32:49 +03:00
Ludovic MarieandClaude Opus 5.5 6d07525616 Data location: path lookups fall back instead of panicking
data_dir(), build_dir() and game_home() still expect()ed on a location error.
The quiet installer runs before data_location::startup(), and retry() clears
FORCE_DEFAULT before re-reading, so a background thread could hit the panic
the review asked to remove. A location that does not open now stands in as the
default folders there, as startup() makes it for the window. Also joins the
startup problems with "\n" instead of a literal line break.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:31:38 +01:00
Ludovic MarieandClaude Opus 5.5 c34cd71603 Merge origin/main into feat/launcher-data-location
Language tables: main's keys plus the PR's data-location keys.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:31:38 +01:00
Ludovic Marie b9dc4c1f96 Merge pull request #8873 from martipops/controller/phase1-input
🤖 Agent: release_keyboard_mouse now releases only what Source::Keyboard holds, so a splitscreen handoff no longer drops the pad's held jump or sneak, and pad_button replays the actions stored at press time on release, so a binding changed mid-hold can't leave a key stuck. Players who chose Pointer in the removed Menu Navigation option get focus in menus after this.
2026-11-04 15:30:51 +01:00
masa-masa-0314andZCode 67a19214c3 CI: bump GitHub Actions to Node 24 runtimes (checkout v7, artifacts v7/v8, setup-python v7, setup-java v6)
Every actions/* pin now runs on Node.js 24 instead of the deprecated Node.js 20 runtime the runners currently force:

- actions/checkout v4 -> v7: v5.1+ blocks fork-PR checkouts under pull_request_target/workflow_run; this repo only checks out ref: main in those workflows (pr-labels, overlap-note) and the workflow_run release checkout resolves to the default branch, so nothing changes. The PR-head checkout in ci.yml runs under plain pull_request.
- actions/upload-artifact v4 -> v7: v7's direct uploads are opt-in (archive: false) and the ESM migration is transparent to callers.
- actions/download-artifact v4 -> v8: v5's path change only affects artifact-ids downloads, which no workflow uses; v8's enforced digest checks and skip-decompress only surface for direct uploads.
- actions/setup-python v5 -> v7: the removed pip-install input is unused (only python-version: '3.12' is set).
- actions/setup-java v4 -> v6: v4 itself now emits a deprecation warning (v4.9.1); temurin/25 usage is unchanged in v6.

GitHub-hosted runners ship 2.327.1+, so the node24 minimum is met.

Co-Authored-By: ZCode <[email protected]>
2026-11-04 22:27:36 +08:00
Ludovic MarieandClaude Opus 5.5 17ce0331f8 Native cache: a build goes ahead when only the cleanup is refused; ca and 50 new locales
A filesystem without file locks or a native folder linked to another drive made
Cache::acquire fail, and logged() then refused the build itself, which worked
before the cache existed (martipops' review). Only Busy still refuses; any other
lease error is a build.log warning and skips the trim.

The launcher check failed on lang/ca.json: main added 51 locales since the last
main merge, so each gets the five cache messages.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:27:35 +01:00
Ludovic Marie 08a514dc26 Merge pull request #9100 from repositorykreml1n/codex/fix-issue-9021
🤖 Agent: merging. Mining speed now reads the player's computed `minecraft:mining_efficiency` (Player.getDestroySpeed), and held Efficiency joins the equipment modifiers with its slot group, so an offhand Efficiency pickaxe no longer speeds mining. Proof: CI parity passed on this head, including the 9 new `mining_efficiency_9021` destroy-progress cases compared bit for bit with vanilla's getDestroyProgress.
2026-11-04 15:27:35 +01:00
Ludovic Marie c915f2a878 Merge pull request #9193 from Domanffe/chore/roadmap-end-city-links
🤖 Agent: nothing found; #1928, #1911 and #2037 are merged and are the fixes the new text cites.
2026-11-04 15:27:30 +01:00
Ludovic Marie e013d898ff Merge pull request #8399 from itarqos5/perf/lod-reduced-material-depth
🤖 Agent: only approximate tiles with width >= 16 stop at depth 2, and the test proves the Full tile still paints below depth 2, so the Reduced assertion cannot pass vacuously.
2026-11-04 15:27:27 +01:00
Ludovic Marie 99eb6630b7 Merge pull request #9107 from janibert1/merchant-offer-hook
🤖 Agent: the hook gets session.offers[index], the live offer, while trade_with_villager keeps the pre-hook copy; a mod raising max_uses on a shift-click only takes effect after that click, as the docs say.
2026-11-04 15:27:22 +01:00
Ludovic Marie c036a7473e Merge pull request #8935 from janibert1/death-drops
🤖 Agent: the hook runs after destroyVanishingCursedItems and before the random throws, and the retain only drops stacks a mod emptied, so with no mod the drops and their RNG draws are unchanged.
2026-11-04 15:27:17 +01:00
Ludovic Marie bdb45ed319 Merge pull request #8925 from minantiii/fix/armadillo-entity-action
🤖 Agent: matches Armadillo.rollUp/rollOut, gameEvent(ENTITY_ACTION) before makeSound and independent of isSilent; the new creatures.rs arm comes after the flap arm, so chicken/parrot flaps are unaffected.
2026-11-04 15:27:13 +01:00
Ludovic Marie 0492f8db4e Merge pull request #9142 from NerfPhaselock/fix/gpu-culling-dx12-packed
🤖 Agent: the guard keys on flat_min_sections.is_some(), so DX12 without flat page groups (no pulled pipelines) keeps GPU culling, and the test pins both DX12 cases.
2026-11-04 15:27:08 +01:00
Ludovic Marie 20ae91bbde Merge remote-tracking branch 'origin/main' into fix/native-base-revision
# Conflicts:
#	launcher/lang/af.json
#	launcher/lang/ar.json
#	launcher/lang/az.json
#	launcher/lang/ba.json
#	launcher/lang/be.json
#	launcher/lang/bg.json
#	launcher/lang/br.json
#	launcher/lang/bs.json
#	launcher/lang/ca.json
#	launcher/lang/cs.json
#	launcher/lang/cv.json
#	launcher/lang/cy.json
#	launcher/lang/da.json
#	launcher/lang/de.json
#	launcher/lang/el.json
#	launcher/lang/eo.json
#	launcher/lang/es.json
#	launcher/lang/et.json
#	launcher/lang/eu.json
#	launcher/lang/fa.json
#	launcher/lang/fi.json
#	launcher/lang/fil.json
#	launcher/lang/fo.json
#	launcher/lang/fr.json
#	launcher/lang/fur.json
#	launcher/lang/fy.json
#	launcher/lang/ga.json
#	launcher/lang/gd.json
#	launcher/lang/gl.json
#	launcher/lang/haw.json
#	launcher/lang/he.json
#	launcher/lang/hi.json
#	launcher/lang/hr.json
#	launcher/lang/hu.json
#	launcher/lang/hy.json
#	launcher/lang/id.json
#	launcher/lang/ig.json
#	launcher/lang/is.json
#	launcher/lang/it.json
#	launcher/lang/ja.json
#	launcher/lang/ka.json
#	launcher/lang/kk.json
#	launcher/lang/kn.json
#	launcher/lang/ko.json
#	launcher/lang/ky.json
#	launcher/lang/la.json
#	launcher/lang/lb.json
#	launcher/lang/li.json
#	launcher/lang/lmo.json
#	launcher/lang/lo.json
#	launcher/lang/lt.json
#	launcher/lang/lv.json
#	launcher/lang/mk.json
#	launcher/lang/mn.json
#	launcher/lang/ms.json
#	launcher/lang/mt.json
#	launcher/lang/nl.json
#	launcher/lang/no.json
#	launcher/lang/oc.json
#	launcher/lang/pl.json
#	launcher/lang/pt-br.json
#	launcher/lang/pt.json
#	launcher/lang/ro.json
#	launcher/lang/ru.json
#	launcher/lang/sah.json
#	launcher/lang/se.json
#	launcher/lang/sk.json
#	launcher/lang/sl.json
#	launcher/lang/so.json
#	launcher/lang/sq.json
#	launcher/lang/sr.json
#	launcher/lang/sv.json
#	launcher/lang/szl.json
#	launcher/lang/ta.json
#	launcher/lang/th.json
#	launcher/lang/tr.json
#	launcher/lang/tt.json
#	launcher/lang/uk.json
#	launcher/lang/ur.json
#	launcher/lang/uz.json
#	launcher/lang/vec.json
#	launcher/lang/vi.json
#	launcher/lang/yi.json
#	launcher/lang/yo.json
#	launcher/lang/zh.json
#	launcher/lang/zu.json
2026-11-04 15:26:25 +01:00
Ludovic MarieandClaude Opus 5.5 153ef53e42 Merge #8200's main merge into the heartbeat interval (no conflict)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:25:38 +01:00
rdbtCVSandCodex gpt-6.1-sol 0e58346b64 test(mobs): mark jump-world regression as requiring vanilla resources
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 14:24:16 +00:00
Ludovic Marie 6f62647a23 Merge pull request #9184 from Domanffe/fix/options-txt-save-warning
🤖 Agent: nothing found.
2026-11-04 15:23:41 +01:00
Ludovic Marie 9fb6e4f20c Merge pull request #9235 from horizon-rust/feat/launcher-paddle-claim
🤖 Agent: launcher-only. The claim opens a checkout URL only on Horizon's own /shop/pay page (prefix allowlist, tested), and the 3 s poll stops after 20 min or once the set is claimed; without a checkout in the answer the one-click claim is unchanged.
2026-11-04 15:23:36 +01:00
Ludovic Marie 1aea26b3e0 Merge remote-tracking branch 'origin/main' into HEAD 2026-11-04 15:23:33 +01:00
Ludovic Marie 7dfbd05ac3 Merge pull request #9110 from DenisHumen/fix/offhand-tempt
🤖 Agent: TemptGoal.shouldFollow and TemptingSensor test both hands; player_view now fills off_hand, and lan_guest_views swaps each guest's off hand in through the same function, so single player on the server thread gets it too.
2026-11-04 15:23:31 +01:00
Ludovic Marie 4ece2302d4 Merge pull request #9103 from DenisHumen/fix/unicode-font-sign-stretch
🤖 Agent: default ascii.png glyphs keep their 0..8 box, so sign text without Force Unicode draws exactly as before; only Unifont's half-scale glyphs change width.
2026-11-04 15:23:27 +01:00
Ludovic Marie 8453750c94 Merge pull request #8936 from Cheezer1656/upstream/native-send-chat
🤖 Agent: the message is trimmed and the Chat option (Commands Only, Hidden) applied as the chat screen does; the test needs prepared resources and is #[ignore]d, so CI does not run it.
2026-11-04 15:23:23 +01:00
Ludovic Marie 2f29699b0a Merge pull request #8938 from Cheezer1656/upstream/client-chat-received
🤖 Agent: Only Show Secure Chat now drops a line before mods hear it (ChatListener.showMessageToPlayer's order, then onClientPlayerChat), and chat_received returns at once with no mods loaded, so the chat-type registry lookup costs nothing without them.
2026-11-04 15:23:18 +01:00
Ludovic Marie 0073f3bb30 Merge pull request #8872 from Delete228/create-named-damage
🤖 Agent: controlled particles are dropped on a resource reload so a stale sprite-set index cannot pick another set, and the only cost for vanilla particles is one more match arm in tick and in the translucent test.
2026-11-04 15:23:14 +01:00
Ludovic MarieandClaude Opus 5.5 569f9644dc Pass the event sink to sync::client_tick in the wolf head-tilt test
sync::client_tick took an events vector in c0cd050fac; main's newer
remote_mob_data test still called it with two arguments.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:23:12 +01:00
Ludovic Marie 31fac68df4 Merge pull request #8822 from AfrobamaYT/camera-pose-hook
🤖 Agent: one dispatch per frame, each non-finite value falls back to vanilla's on its own, and posed_rotation is only set when the pose differs, so billboards keep the vanilla rotation path when no mod moves the camera.
2026-11-04 15:23:09 +01:00
Ludovic Marie 07a1a4af76 Merge pull request #8698 from ErikKalikas/mod-menu-extra
🤖 Agent: the 32 KiB cap is enforced on both encode and decode, a vanilla client ignores the unknown horizon:menu_extra channel, and the lan_bot test pins that the payload arrives right before its own window's OpenScreen.
2026-11-04 15:23:04 +01:00
Ludovic Marie 1077a75e5f Merge pull request #8636 from GabrielNichols/codex/port-pack-harness-verdict
🤖 Agent: an explicit passed:false or a result with zero executed checks now stays Partial, and a legacy count-only result with passes still reads harness green, as carried_harness_keeps_current_and_legacy_successes pins.
2026-11-04 15:22:59 +01:00
Ludovic Marie c77c40b021 Merge pull request #8512 from hildlabs/shader-option-sliders
🤖 Agent: a drag saves the option and rebuilds the pack once, on release, not on every pointer move; the manifest already refuses min >= max and step <= 0, so slider_position and slider_value cannot divide by zero.
2026-11-04 15:22:54 +01:00
Ludovic Marie 12336db449 Merge pull request #8438 from ErikKalikas/mod-reload-listeners
🤖 Agent: the pack stack is rebuilt only when a mod registered a listener, so a /reload without native mods pays nothing, and folder_ok refuses '..' and absolute paths, so a listener cannot read outside data/<namespace>/.
2026-11-04 15:22:49 +01:00
Ludovic Marie f010091162 Merge pull request #7226 from Corbonat/fix/gui-glint-atlas-5851
🤖 Agent: GUI glint now takes vanilla's ITEMS-atlas rectangles (Stitcher's order, split and filtering padding reproduced) while CIT glint keeps sprite-local UVs; the cost is one more decode of the item textures per pack load in prepare_glint_atlas, nothing per frame.
2026-11-04 15:22:45 +01:00
Ludovic Marie c42b5b17f0 Merge pull request #9187 from Domanffe/chore/stamp-native-hooks-399
🤖 Agent: re-running tools/stamp_native_hooks.py on this PR's base (38df83828) reproduces the tree byte for byte, and main has not touched a fragment since; the native-hook-stamp workflow that should have opened this PR has failed its last five runs, which the orchestrator will look at.
2026-11-04 15:22:41 +01:00
Delete228andCodex 25dd488069 Merge current main while preserving world damage snapshots
Keep the successful falling-block hurt advancement, passenger frostbite tick and general world-pack tag synchronization.

Co-Authored-By: Codex <[email protected]>
2026-11-04 17:20:36 +03:00
Ludovic MarieandClaude Opus 5.5 ab3eceeeb9 Launcher Shop: the founder claim opens Paddle's free checkout; purchase history
- When the cosmetics service answers a claim with a checkout (the 0 CHF Paddle checkout
  Ludovic chose for founder claims), the launcher opens it in the browser, but only
  Horizon's own checkout page (https://playhorizon.dev/shop/pay?…). It then asks the
  service every 3 s, for 20 minutes at most, until Paddle's webhook has granted the set.
  The button says "Waiting for the checkout…" and a link opens the checkout again.
- The Wardrobe tab's claim button takes the same path.
- "Your purchases" under the founder panel: date, item, price (Free at 0), state (Paid,
  Partly refunded, Refunded, Charged back) and the order number from Paddle's receipt,
  for Mojang's purchase-history rule.
- The claim note no longer promises "no card asked" (unconfirmed at 0 CHF).
- 11 new strings in all 86 tables; the dropped one removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:20:33 +01:00
Ludovic Marie 14868bea32 Merge pull request #8575 from MSedgeMC/fix/xp-orb-light
🤖 Agent: matches ExperienceOrbRenderer: getBlockLightLevel is block+7 clamped with sky untouched (was a floor of 10 on both), the quad spans (-0.5,-0.25)..(0.5,0.75) at 0.3, and light is read at the eye (0.425).
2026-11-04 15:19:56 +01:00
Florian ERNST 75b6327008 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/inline-execute-predicates 2026-11-04 15:19:41 +01:00
Florian ERNSTandCodex cd8d1c5fd7 Evaluate inline execute predicate values
Co-Authored-By: Codex <[email protected]>
2026-11-04 15:19:40 +01:00
AxxioandAntigravity 5013115571 launcher: fix more button click, list gaps, input height, blur, and scroll propagation
- Prevent Play more button click from bubbling or launching the game
- Fix missing gaps between items in virtualized lists (cull::column) for versions and patch notes
- Match small field height (36px) and tokens for Motd, Port, Token, AddPlayer, and DeleteServer
- Do not apply 24px baseline blur when blur is set to 0
- Stop scroll event propagation on dropdowns, version lists, and nav tabs

Fixes #9148
Fixes #9169
Managed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 15:18:37 +01:00
Ludovic Marie 7950c14bba Merge pull request #8947 from NikitaMGrimm/offscreen-hdr
🤖 Agent: without HORIZON_HDR_OUTPUT, offscreen_output() is None and every new branch is skipped; the crash marker is only written with a surface, so an offscreen HDR run cannot turn HDR off for the next windowed start.
2026-11-04 15:18:06 +01:00
Ludovic MarieandClaude Opus 5.5 db733482fd Drop the unused block_jump_factor wrapper and re-export
Mob::block_jump_factor is the one callers use; the free function and rabbit's
re-export had no callers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:16:35 +01:00
Ludovic Marie 2c90c298c1 Merge pull request #8543 from NerfPhaselock/perf/gpu-culling-g1b
🤖 Agent: inert until gpu-culling is switched on (only control.rs and scene_replay do), and the fade-end heap means a frame with no fade due does no extra work; martipops' read found every fade start sets the flag before prepare.
2026-11-04 15:16:22 +01:00
rdbtCVSandCodex gpt-6.1-sol 6b33c32f7b fix(fishing): share synchronized random helper with remote hooks
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 14:15:53 +00:00
Ludovic MarieandClaude Opus 5.5 bf939d4926 Give translucent_batchable its doc comment back
The gizmo helpers went in between translucent_batchable's doc comment and the
function, so rustdoc showed the batching text on debug_line_opaque.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:15:44 +01:00
Ludovic Marie 5dc270d3f9 Merge pull request #8817 from itarqos5/test/lod-bench-span-histogram
🤖 Agent: example-only; the last histogram bucket (32) is printed as 32+ so a capped count is not misread as exact.
2026-11-04 15:15:08 +01:00
martipopsandClaude Opus 5.5 d36aaf9c0c Launcher: the Shop reads its window on the service's time, and an unknown claim state is safe
A fast clock closed a window the service still called open, hiding the Claim button on a set that's retired for good. Each answer's Date header now shows how far the clock is ahead, and the Shop reads the window's dates with that taken off. A clock on time or behind reads as before.

A claim state the launcher doesn't know now parses instead of failing the catalogue and the account's answer. It offers no claim and doesn't say the set is over.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 09:15:05 -05:00
Loki 613b1f9574 Merge remote-tracking branch 'origin/main' into fix/8703-breeze-invulnerable-to-breeze 2026-11-04 23:15:04 +09:00
Ludovic MarieandClaude Opus 5.5 14e59bf1ad Keep dead and spectating guests in projectile ownership
has_guests counts only guests a projectile can hit (alive, loaded, here, not
spectating), so gating dead_guests and guest_entities on it dropped a lone
dead player's pearl from ender_pearls_vanish_on_death (pearl_death harness)
and a dead shooter's arrow from its owner. Both lists are built every tick
again; only the guest target copy stays gated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:15:04 +01:00
Ludovic Marie 482d31394c Merge pull request #8766 from NikitaMGrimm/translucent-batching-checkbox
🤖 Agent: switch() now ANDs translucent_batching_offered() exactly as hdr_smooth_gradients already does, and the saved preference is untouched, so a device that can batch gets it back.
2026-11-04 15:15:03 +01:00
Ludovic Marie 3a2bc05ae2 Merge pull request #8738 from NikitaMGrimm/lan-close-test-windows
🤖 Agent: the loop now asserts the outbox really holds bytes (pending > 0 three times 50 ms apart) before sending Disconnect, which the old 64 MiB burst only assumed; the 256-MiB cap fails loudly instead of hanging.
2026-11-04 15:14:58 +01:00
Ludovic Marie 81d1cf35d1 Merge pull request #8671 from Qwerner88880/pr-ready-beside
🤖 Agent: only Public tree and Python tests move beside cargo, and every Python test that names cargo mocks it, so they never fight cargo for the target dir; Parity stays after cargo since it builds.
2026-11-04 15:14:54 +01:00
Ludovic Marie e1597349dd Merge pull request #8711 from NikitaMGrimm/install-sh-test-skip-windows
🤖 Agent: skipIf(os.name == "nt") sits above the bash/openssl skip, so Windows never reaches System32's WSL bash (#5383), and CI's Linux job still runs all signature cases.
2026-11-04 15:14:49 +01:00
martipopsandClaude Opus 5.5 58ffef7e6b Launcher: a cosmetic item's id and hash can't leave the cache folder
Service::item_cached built its path from catalogue values unchecked. It now refuses an entry
whose id or hash isn't 1 to 64 of a-z, A-Z, 0-9 and _, the rule the game already applies
(horizon_cosmetics::fetch), before the disk or the service is touched.

The hash isn't checked against the bytes: nothing in this repository says what it is a digest of.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp7yEJwPTJ2kw29LWdk1
2026-11-04 09:14:35 -05:00
Ludovic Marie 77f9739ab0 Merge remote-tracking branch 'origin/main' into cg31fix-9069
# Conflicts:
#	crates/horizon-client/src/game/lan.rs
2026-11-04 15:14:09 +01:00
voidsipandCodex b27922de61 Synchronize raider celebration metadata
Send the celebration flag and its false default for all raiders, then apply it on clients. Exercise real tracking and packet decoding against native dirty metadata without loading shared resource state.

Fixes #8848

Co-Authored-By: Codex <[email protected]>
2026-11-04 08:14:02 -06:00
degepieandClaude Opus 5.5 22148d740e Minecarts: sync DisplayState and DisplayOffset to clients
The tracker sends AbstractMinecart's DATA_ID_CUSTOM_DISPLAY_BLOCK (entry
11, optional block state) and DATA_ID_DISPLAY_OFFSET (entry 12, the kind's
default offset), and the client reads them back into the cart, so a cart's
custom block shows over the network and in single player.

Fixes #8050.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 19:11:27 +05:00
Ludovic MarieandClaude Opus 5.5 405186aa36 Squid ink: send it as server particles, not every mob particle
be258bc235 sent every MobEvent::ParticleAt as a particle packet with a speed
of 0.1. The squid's ink velocity was already multiplied by 0.1, so clients got
a hundredth of it. The other ParticleAt events (hearts, splashes, wither smoke,
glow, bubbles) are client-side addParticle calls in vanilla that the clients
draw themselves, so they were drawn twice.

Squid.spawnInk is ServerLevel.sendParticles(count 0, speed 0.1): it is now a
MobEvent::LevelParticles, which draws it here and sends it to the players
within 32 blocks, and ParticleAt stays local.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:10:53 +01:00
rdbtCVSandCodex gpt-6.1-sol 1ca0b67be3 Merge main while retaining lifecycle and observability API goldens
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 14:10:41 +00:00
LokiandAntigravity 89e8dd9c16 mobs: breeze damage invulnerability from other breezes (fixes #8703)
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 23:10:24 +09:00
Ludovic MarieandClaude Opus 5.5 e30618104b Setup: Primary main actions, and look for settings twice a second
Install/Update, Uninstall and Launch Horizon are each screen's one main action:
Kind::Primary, as the launcher marks them. The live-settings wake drew the
window every 16 ms and read settings.json's metadata each frame; 500 ms still
picks a change up while the setup is open.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:10:14 +01:00
TiggerTakkyTTTandCodex d32a0cfbf8 Merge upstream main into auth API goldens
Resolve the api.rs test-module overlap while retaining the auth and command goldens alongside upstream lifecycle coverage.

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 21:10:04 +07:00
Ludovic MarieandClaude Opus 5.5 dfd4d41e63 Import: drop the line-wrap spaces inside two error messages
Both strings had a run of spaces where an editor wrapped them, so the note
read "a Horizon                  game build."

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:56 +01:00
Ludovic Marie 85d380ff3c Merge remote-tracking branch 'origin/main' into cg31/8691 2026-11-04 15:09:56 +01:00
Ludovic MarieandClaude Opus 5.5 640ff80c83 Merge the stack's own main merges (#8652, #8657, #8658, #8660)
rdbtCVS's agent merged main into four of the stacked branches. Merging those
heads here lands every PR of the stack with #8691.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:55 +01:00
Ludovic MarieandClaude Opus 5.5 695feba0bc Far terrain zoom detail: off by default
Up to about 45% more far tiles while a zoom is held, and a far tile takes
seconds: a quick spyglass look queues work that lands after it. The extra stays
in horizon-extras.txt (lodZoomDetail:true) until a measurement shows it pays.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:42 +01:00
Ludovic MarieandClaude Opus 5.5 d0da65d108 Resource loaders: name threads load-<step>
Linux keeps 15 bytes of a thread name, so "horizon-load-core" and
"horizon-load-collision" both read "horizon-load-co" in the heap census and the
startup trace there. "load-<step>" keeps every loader apart.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:34 +01:00
Ludovic MarieandClaude Opus 5.5 6d9c504d06 Shulker: a riding shulker's setPos still closes the lid
Shulker.setPos resets DATA_PEEK on any block change after the first tick,
passenger or not; only the teleport slide is skipped for a passenger. The
slide is cleared by startRiding (on_start_riding), not by every move.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:33 +01:00
Ludovic Marie 6d43a3593b Merge pull request #9212 from horizon-rust/fix/8669-useitemon-freeze
🤖 Agent: The freeze came from map searches opened from chests, which built full jigsaw starts and scanned every chunk of each saved region for each candidate. Same answers on main and this branch (24 targets × 5 points × 4 runs); on a pregenerated world the bamboo-camp map search drops from 2.1–3.6 s to 0.04–0.07 s of server-thread CPU, and main hit the 60 s watchdog in 4 of 9 runs. fmt, both clippies and the touched crates' tests pass, plus 10 harnesses incl. server_world_gen (/locate against vanilla); dedicated_server's vanilla oracle crashed on the loaded desktop, so CI runs it.
2026-11-04 15:09:32 +01:00
Ludovic MarieandClaude Opus 5.5 98e73cd84b rustfmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:22 +01:00
Ludovic MarieandClaude Opus 5.5 d580df1589 mob_ai parity: drop the 'digging' warden push cases
They spawned a warden with no flag, which stands: nothing in the fixture starts
a dig, so they compared a standing warden under a digging name. The emerging
cases stay; the unit test covers digging through set_pose.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:18 +01:00
Ludovic MarieandClaude Opus 5.5 180e7b2631 Native builds: say which mod pins the commit, and rebuild on the picked Horizon
A player's native builds kept compiling 9d979ca (Nightly 2026-10-31)
whatever Nightly or build profile they picked: Right-Click Crops 1.0.0
pins that commit and names no hook points' version, so the pin chose the
build's commit while the Mods page said "built on Horizon <picked Nightly>".

The build record now keeps the picked version's own commit and the mods
whose pin chose another; the Mods page names them, and "Rebuild on
Horizon" builds every mod on the picked version's commit (on_base,
native-build --ignore-pins), which later rebuilds keep. Older records
whose commit differs from their Nightly's read the pins from the
instance's mods. Also translates "Paste a friend's invite code".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:12 +01:00
Ludovic MarieandClaude Opus 5.5 a4d2184bdf Clear the old level's orbs with the other remote objects
orbs_after_client_respawn was only ever called with true, so the helper
and its test only restated Vec::new(). clear_remote_objects now clears the
orbs itself, which also makes the connect path's own orbs.clear() redundant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:09:09 +01:00
Ludovic MarieandClaude Opus 5.5 5752c7bb5b Native mods: give NativeServerPlayer.address its API fragment
The field said "API next" without a fragment, so stamping would never give it
a number. tools/native-hook-fragments/server_player_address.json requests one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:08:52 +01:00
Ludovic MarieandClaude Opus 5.5 38c31cac5d Armadillo: a baby's brush is not taken on the client
Armadillo.brushOffScute returns false for a baby, so the brush falls through
to FAIL (rolled up) or Mob.mobInteract's PASS instead of ending the click.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:08:41 +01:00
Ludovic MarieandClaude Opus 5.5 2c7b49062e Vehicle teleport: review fixes (rotation packet, cooldown per tick, rider's vehicle)
- /rotate on a server's player sends ClientboundPlayerRotationPacket
  (ServerPlayer.forceSetRotation) when its turn ends, riding or not,
  instead of a zero-motion teleport (or nothing for a rider).
- The guest's boarding cooldown counts down once per tick in
  guest_server_tick, not once per enter_guest; a vehicle dropped in
  guest_vehicle_tick also starts it.
- A teleported vehicle's riding guests hear the vehicle's teleport
  (ClientboundTeleportEntityPacket), so a driven boat is not put back
  by the client's next MoveVehicle.

Found by martipops's review.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:06:22 +01:00
Ludovic MarieandClaude Opus 5.5 94ccd9c8ee Merge #9140 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:06:18 +01:00
Ludovic MarieandClaude Opus 5.5 15f1090ed1 Merge #9130 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:06:18 +01:00
Ludovic MarieandClaude Opus 5.5 c063a488ce Multiplayer: CLIENT_TICK_END only while the client has a level
Minecraft.tick sends ServerboundClientTickEndPacket only while it has a
level, which ClientPacketListener.handleLogin makes and
handleConfigurationStart clears. Horizon sent one every tick from the end
of configuration, so a client waiting for a busy server's Login (Paper
with plugins: seconds) sent dozens of play packets vanilla never sends.

tools/vanilla_wire_e2e.py joins a vanilla 26.3 server through
net_capture.py with the server's Login held back 1 s, and checks the
capture against rules of vanilla's client; no-play-before-login is the
first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:06:14 +01:00
Ludovic MarieandClaude Opus 5.5 4d7b9f28b7 Merge main into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:05:36 +01:00
rdbtCVSandCodex gpt-6.1-sol 12e9795542 Merge main into live transfer management branch
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 14:05:27 +00:00
Ludovic Marie 7bacc86a5e Merge remote-tracking branch 'origin/main' into cg32/fix-8733 2026-11-04 15:03:51 +01:00
Ludovic MarieandClaude Opus 5.5 e9109d87f4 Remote mob takes: keep food and shears as the 26.3 client answers them
The 26.3 client's Animal.mobInteract answers CONSUME for an adult's food
(SUCCESS for a baby that can age up), and Sheep.mobInteract answers CONSUME
for shears whether or not the sheep is sheared: only the server checks
canFallInLove and readyForShearing. So both keep taking the click. Keep the
dye change, which matches DyeItem.interactLivingEntity, and add its
isSheared check: a sheared sheep passes any dye.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:03:22 +01:00
Ludovic MarieandClaude Opus 5.5 39a8fc809b Merge #9197 into the TheMrZZ chain (cg-30)
Conflicts resolved per function: every top-level def matches one side (the
harness main() keeps both sides' options), every groups() entry is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:03:16 +01:00
Ludovic MarieandClaude Opus 5.5 7486e7ba45 Merge #9174 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:03:03 +01:00
Ludovic MarieandClaude Opus 5.5 7a1256e51d Merge #9157 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:03:01 +01:00
Ludovic MarieandClaude Opus 5.5 8cfa002c12 Merge #9145 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:03:00 +01:00
Ludovic MarieandClaude Opus 5.5 7704a05c0b Merge #9117 into the TheMrZZ chain (cg-30)
Conflicts resolved per function: every top-level def matches one side (the
harness main() keeps both sides' options), every groups() entry is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:03:00 +01:00
Ludovic MarieandClaude Opus 5.5 ae2d4b2442 Remote boats: swing when boarding, pass only as AbstractBoat.interact does
The 26.3 client's AbstractBoat.interact answers SUCCESS (a swing) whenever
the player isn't sneaking and the boat isn't out of control; only the
server's startRiding checks for room, so a full boat or a riding player
doesn't pass on the client. AbstractChestBoat passes only out of control,
not sneaked at, with room; otherwise it opens the chest. Minecart.interact
is the one that answers CONSUME and doesn't swing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:57 +01:00
razzo715andClaude Opus 5.5 c5b45b6bec Idle brewing stands and campfires sleep like idle furnaces
A brewing stand that isn't brewing, has no ingredient, has nothing to
refuel from and whose bottles already match its block state, a lit
campfire with nothing on it, and an unlit one with no cooking progress do
nothing in their server tick; they now sleep on the same terms as idle
furnaces. Lighting or putting out a campfire changes its block state, which
wakes it.

16,384 idle brewing stands cost 2.01 ms a tick instead of 8.14, and as
many unlit campfires 2.05 instead of 7.52.

Part of #8558

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:53 +01:00
razzo715andClaude Opus 5.5 386d37503d Idle furnaces sleep until their block entity or block state changes
An unlit furnace, blast furnace or smoker with no cooking progress and no
fuel and input pair to light does nothing in AbstractFurnaceBlockEntity's
server tick. Such a furnace now leaves the tick loop: BlockEntities keeps a
set of sleeping positions, and any change to one (get_mut, insert, take and
put, remove, a block state change, a chunk load, a bulk edit) wakes it at
once, so it ticks on the same game tick as before.

16,384 idle furnaces cost 2.07 ms a tick instead of 8.76 (2.11 for as many
chests, which have no ticker).

Part of #8558

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:53 +01:00
Ludovic MarieandClaude Opus 5.5 bc2adf84dc Merge #9101 into the TheMrZZ chain (cg-30)
Conflicts resolved per function: every top-level def matches one side (the
harness main() keeps both sides' options), every groups() entry is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:50 +01:00
Ludovic MarieandClaude Opus 5.5 19b0fd00ca Test: fill the fields main added since this branch
Merging main left a test initializer without the field main added
(TridentSave.fire_ticks / Interface.initial_values and
Resources.shadow_linear), so the test target stopped compiling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:16 +01:00
Ludovic MarieandClaude Opus 5.5 1fb681318d Test: fill the fields main added since this branch
Merging main left a test initializer without the field main added
(TridentSave.fire_ticks / Interface.initial_values and
Resources.shadow_linear), so the test target stopped compiling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:15 +01:00
Ludovic MarieandClaude Opus 5.5 48c2142692 Fishing: draw the bite's plunge from the hook's synchronized random
FishingHook takes the plunge from syncronizedRandom, reseeded each tick
from the hook's UUID and the game time, not from the level's random: a
draw there shifts every later level draw out of vanilla's order. The
plunge is also computed in float as Mth.nextFloat does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:02:13 +01:00
Ludovic Marie fed2d76e02 Merge remote-tracking branch 'origin/main' into cg32/fix-9084 2026-11-04 15:01:57 +01:00
Ludovic Marie bc9d7d3807 Merge remote-tracking branch 'origin/main' into cg32/fix-8679 2026-11-04 15:01:55 +01:00
Ludovic MarieandClaude Opus 5.5 bd72a6a4eb Merge #9078 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:51 +01:00
Ludovic MarieandClaude Opus 5.5 6c593adc80 Merge #9053 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:50 +01:00
Ludovic MarieandClaude Opus 5.5 2f904b7019 Merge #9032 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:50 +01:00
Ludovic MarieandClaude Opus 5.5 8719c32206 Merge #9029 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:49 +01:00
Ludovic MarieandClaude Opus 5.5 3d2e7d93d4 Merge #9024 into the TheMrZZ chain (cg-30)
Conflicts resolved per function: every top-level def matches one side (the
harness main() keeps both sides' options), every groups() entry is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:49 +01:00
Evrim CicekandClaude Opus 5.5 aa9002e445 A saved dragon or crystal keeps the fields kept with it once it moved
The dragon and the crystals were saved over the compound they were loaded
with only while they stood where they were loaded (anon_raw looks it up
by position), and never over the fields remember_tags keeps for them.
A dragon always flies off, so its Silent, Tags or name were lost at the
next save. Both now save over persist.raw, as /data get already reads
them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:01:42 +03:00
Ludovic Marie 6475f62f3c Merge pull request #8844 from TiggerTakkyTTT/test/server-api-backup-goldens
🤖 Agent: Server API backup goldens: test-only, the download case reads the real streamed bytes. The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. management_protocol (--fresh, on that union): every case of this PR matches vanilla; the only 3 differences were #8204's heartbeat cases, fixed there. Other harnesses PASS on the union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. dedicated_server could not run locally (the vanilla oracle's world generation hit its watchdog under the desktop's memory pressure), so it and the rest of the selection run in CI run-parity.
2026-11-04 15:01:34 +01:00
Ludovic MarieandClaude Opus 5.5 4fc40ee8e0 Merge #9017 into the TheMrZZ chain (cg-30)
Conflicts resolved per function: every top-level def matches one side (the
harness main() keeps both sides' options), every groups() entry is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:33 +01:00
Ludovic MarieandClaude Opus 5.5 815b10e907 HDR: put set_hdr's doc comment back on set_hdr
NikitaMGrimm's test report noted set_hdr_lighting landed between set_hdr
and its doc comment.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:30 +01:00
Ludovic Marie 48684a9b1b Merge pull request #8834 from TiggerTakkyTTT/test/server-api-lifecycle-goldens
🤖 Agent: Server API lifecycle goldens: test-only, type-checked replacement of the volatile fields. The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. management_protocol (--fresh, on that union): every case of this PR matches vanilla; the only 3 differences were #8204's heartbeat cases, fixed there. Other harnesses PASS on the union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. dedicated_server could not run locally (the vanilla oracle's world generation hit its watchdog under the desktop's memory pressure), so it and the rest of the selection run in CI run-parity.
2026-11-04 15:01:28 +01:00
Ludovic MarieandClaude Opus 5.5 9c11a961b4 Merge #9001 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:19 +01:00
Ludovic MarieandClaude Opus 5.5 72352fd257 Merge #9000 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:18 +01:00
Ludovic MarieandClaude Opus 5.5 076ed2dd4a Merge #8997 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:17 +01:00
Ludovic MarieandClaude Opus 5.5 fda782feeb Merge #8991 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:16 +01:00
Ludovic MarieandClaude Opus 5.5 04542d1eff Merge #8986 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches
one side, every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:15 +01:00
Ludovic MarieandClaude Opus 5.5 fc381670ce Merge #8984 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches one side,
every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:14 +01:00
Ludovic MarieandClaude Opus 5.5 bb9d8c7d5b Server packs: keep vanilla's hash-only cache check
Drop the ZIP structure validation from the download path. Vanilla's
DownloadedPackSource checks only the SHA-1 (checkExistingFile), and the
strict zip-crate validator rejected the Java-compatible streamed archive
that started this PR, which Horizon's own pack reader already reads; any
other archive Java opens but the zip crate doesn't would have been refused
too. It also decompressed the whole pack on every cache hit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:01:10 +01:00
Ludovic MarieandClaude Opus 5.5 b8eaa4cb3c Merge #8981 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:00:47 +01:00
Ludovic MarieandClaude Opus 5.5 6e4d10fcd1 Merge #8979 into the TheMrZZ chain (cg-30)
Corpus conflict resolved per function: every top-level def matches one side,
every groups() entry of both sides is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:00:46 +01:00
Ludovic Marie e1fca55972 Merge pull request #8128 from TiggerTakkyTTT/fix/server-management-entity-range
🤖 Agent: Entity broadcast range: vanilla logs the requested percentage, then clamps 10..1000 and saves the clamped value. The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. management_protocol (--fresh, on that union): every case of this PR matches vanilla; the only 3 differences were #8204's heartbeat cases, fixed there. Other harnesses PASS on the union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. dedicated_server could not run locally (the vanilla oracle's world generation hit its watchdog under the desktop's memory pressure), so it and the rest of the selection run in CI run-parity.
2026-11-04 15:00:17 +01:00
Ludovic MarieandClaude Opus 5.5 aabda3e079 Merge #8970 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:43 +01:00
Ludovic MarieandClaude Opus 5.5 94b8b2fb47 Merge #8968 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:42 +01:00
Ludovic MarieandClaude Opus 5.5 492887393e Merge #8965 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:42 +01:00
Ludovic MarieandClaude Opus 5.5 f8fe006043 Merge #8959 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:42 +01:00
Ludovic MarieandClaude Opus 5.5 badc87b99e Merge #8956 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:42 +01:00
Ludovic MarieandClaude Opus 5.5 de08a70aa5 Merge #8953 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:41 +01:00
Ludovic MarieandClaude Opus 5.5 2f4de5382f Merge #8949 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:40 +01:00
Ludovic MarieandClaude Opus 5.5 81977ac47e Merge #8943 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:40 +01:00
Ludovic MarieandClaude Opus 5.5 b5efda5f7d Merge #8940 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:39 +01:00
Ludovic MarieandClaude Opus 5.5 e61dd55a1e Merge #8922 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:39 +01:00
Ludovic MarieandClaude Opus 5.5 d610f6ed98 Merge #8909 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:38 +01:00
Ludovic MarieandClaude Opus 5.5 4bcc83ce33 Merge #8888 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:37 +01:00
Ludovic MarieandClaude Opus 5.5 74e2e70389 Merge #8878 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:37 +01:00
Ludovic MarieandClaude Opus 5.5 7f39f7025c Merge #8860 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:36 +01:00
Ludovic MarieandClaude Opus 5.5 807b7a62f5 Merge #8847 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:36 +01:00
Ludovic MarieandClaude Opus 5.5 c74d24ac9d Merge #8841 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:35 +01:00
Ludovic MarieandClaude Opus 5.5 54ef85585d Merge #8821 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:35 +01:00
Ludovic MarieandClaude Opus 5.5 0d67cfd734 Merge #8799 into the TheMrZZ chain (cg-30)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:35 +01:00
GabrielNichols ec037e9e7f Merge upstream/main into codex/native-cache-cleanup-8028 2026-11-04 10:58:02 -03:00
Ludovic MarieandClaude Opus 5.5 b6204bf860 Management: a set after the heartbeat was cancelled is refused, as vanilla's
ManagementServer.scheduleHeartbeat cancels the running heartbeat and
schedules a new one only above 0. A set to 0 or less that stopped a
running heartbeat leaves the cancelled future in place, and cancel(true)
on it returns false, so vanilla refuses every later set until the server
restarts: it logs that the interval was not updated, keeps nothing and
answers with the interval in force. The management_protocol harness
showed it (vanilla answered 0 to the last set to 30, saved 0 and read 0
after the restart; Horizon took 30).

Horizon now keeps that state beside the interval. The harness cancels
with -5 instead of 0, so the value saved and read back after the restart
differs from the default, and expects vanilla's -5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:57:38 +01:00
Ludovic MarieandClaude Opus 5.5 c2aa51c15f Merge origin/main into feat/admin-player-position
Resolve the generated docs/api/server-reference.md row: main's text with
the new dimension field.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:55:35 +01:00
rdbtCVSandCodex gpt-6.1-sol bbedf3b9ec fix(render): collapse skinned entity culling condition
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 13:55:34 +00:00
Alexander KrylovandHermes Agent bac0d3ca47 Data packs: a dropped pack that cannot be copied is reported
`copy_pack` walks a dropped folder with `let _ = fs::copy(...)` in every
branch and answers `true` for anything that holds a `pack.mcmeta`, so a folder
that half-copied (a held file, a full disk, a path too long) was announced as
installed and the pack appeared with files missing.

`copy_dir` now returns whether every copy landed and logs each file it could
not write; `copy_pack` passes that answer up, and the drop handler says the
pack is one it could not copy. A pack that is no pack still answers false with
no log line.

The test copies a real pack into a path that cannot be made and asserts false,
then copies it into a real folder and asserts the file arrives.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 16:55:06 +03:00
Mayonesu0andClaude Opus 5.5 2f6d9c7dc4 mobs: a powered wither refuses a player's wind charge
WitherBoss.hurtServer refuses a direct AbstractArrow or WindCharge once
isPowered. A wind charge hurts mobs as DamageKind::Thrown, which
wither::accepts let through, so every charge took a point off the armor.

The check is on the projectile, not the damage kind. A breeze's
BreezeWindCharge deals the same damage but is not a WindCharge (both only
extend AbstractWindCharge), so it still hurts, unlike what the issue asks.
/damage with the wind_charge type has no direct charge and goes through too.

tools/test_wither_wind_charge_parity.py compares the three cases against a
vanilla dedicated server.

Fixes #8779

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:54:22 +01:00
voidsipandCodex 850c43dedb Synchronize zombie infection and cure sounds
Use the vanilla infection/conversion level events and the client cure-start entity event. Respect silent mobs, draw cure volume before pitch on each client and avoid duplicate captured sound packets.

Fixes #8892

Co-Authored-By: Codex <[email protected]>
2026-11-04 07:52:45 -06:00
Ludovic Marie 4d6f1253d2 Merge pull request #8620 from horizon-rust/feat/launcher-shop-ui
🤖 Agent: Ludovic approved the shop and the dark founder set (11-04). verify, launcher, parity-scope green on this exact head (rdbtCVS's options-test fix included); desktop fmt/clippy/tests passed on the shop changes. The claim window stays closed on the service until the Paddle 0 CHF checkout follow-up lands, so merging only shows the shop and worn cosmetics.
2026-11-04 14:48:32 +01:00
Ludovic Marie f521bd9eb1 Merge pull request #8194 from Akirabane/feat/prometheus-entities-chunks
🤖 Agent: Prometheus entity/chunk counts read only when the supervisor samples server-status; main merged (rdbt's d938f6a1dd). The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. Harnesses PASS on that union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. boat_submerged, boat_paddle_reset and dedicated_server did not run locally: the vanilla oracle's world generation hit its watchdog 3 times under the desktop's memory pressure, before any comparison. They and the rest of the selection run in CI run-parity.
2026-11-04 14:48:28 +01:00
Ludovic Marie 971ded7bc7 Merge pull request #8171 from Akirabane/fix/8156-boat-powder-snow
🤖 Agent: Boat powder snow matches PowderSnowBlock.entityInside (stuck 0.9/1.5/0.9, nextBoolean then two randomBetween draws). The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. Harnesses PASS on that union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. boat_submerged, boat_paddle_reset and dedicated_server did not run locally: the vanilla oracle's world generation hit its watchdog 3 times under the desktop's memory pressure, before any comparison. They and the rest of the selection run in CI run-parity.
2026-11-04 14:48:24 +01:00
Ludovic Marie c6734d6acb Merge pull request #8167 from Akirabane/fix/8156-rider-frost
🤖 Agent: Rider frost walker / block effects for passengers; old_position reset keeps the sweep to the vehicle's cell. The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. Harnesses PASS on that union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. boat_submerged, boat_paddle_reset and dedicated_server did not run locally: the vanilla oracle's world generation hit its watchdog 3 times under the desktop's memory pressure, before any comparison. They and the rest of the selection run in CI run-parity.
2026-11-04 14:48:20 +01:00
Florian ERNSTandCodex 7474284c81 Expose decorated pot state to block data commands
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:48:09 +01:00
razzo715andClaude Opus 5.5 603bd9e4ad /item and /loot reach every container block entity's slots
/item ... block, execute if items block and /loot insert|replace block
only found slots in a chest, so a furnace, brewing stand, crafter,
chiseled bookshelf, shelf or jukebox answered "does not have slot". They
now take every block entity that is a Container in vanilla
(BlockItemAccessor.getContainer), and each one sets its slot through its
own Container.setItem: a furnace restarts cooking only for a new input, a
crafter enables the slot again, a bookshelf takes only books and a jukebox
starts or stops its song. Any other block answers "is not a container".
/loot checks canPlaceItem first, as LootCommand does.

Fixes #8685

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:46:56 +01:00
Alexander KrylovandHermes Agent c6093b3821 Roadmap: the two End city items link the pull requests that fixed them
Both items were filed help-wanted and their issues are closed, so
`python tools/roadmap.py check` answers "every linked issue and pull request is
closed: ship or drop it" for each: the work landed and the roadmap never
learned which pull request did it, so `sync` cannot move either item.

Links added: #1928 for end-city-banners, #1911 and #2037 for end-city-loot,
each merged on 2026-10-26, plus the sentence in `dev` that says what the fix
did. ROADMAP.md and roadmap/roadmap.json are what `roadmap.py build` writes
from the toml.

Status stays help-wanted: `now` needs an owner and a tracking issue and the Now
list is already at its 8-item cap, while `shipped` needs a release tag and
these went into Nightly builds after v0.2.x froze.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 16:45:04 +03:00
2a410bab80 Format the max-stack wire test the way cargo fmt checks it.
Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 14:42:29 +01:00
Alexander KrylovandHermes Agent f9e59fa328 Port map: seven rows whose methods the tree already ports
Each row below was marked `missing`, and the Rust function it should point at is
in the tree, with a doc comment naming the vanilla method it mirrors. Verified
by reading each function, not by the note the row carried.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 16:41:28 +03:00
Evrim CicekandClaude Opus 5.5 a8e50ef283 A silent ender dragon dies without the global death roar
EnderDragon.tickDeath sends globalLevelEvent(1028) at the death's first
tick only when !isSilent(). Horizon roared for every dragon. The dragon's
Silent is kept with the saved fields Horizon models nowhere
(remember_tags), so the death reads it from there.

tools/test_dragon_death_roar_parity.py compares the 1028 events a client
is sent for a dying loud and a dying silent dragon with vanilla's
dedicated server (VanillaProtocolClient's new level-events mode,
LevelEventTranscript).

Fixes #8739

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:41:20 +03:00
AxxioandAntigravity 671e8750e9 Remote mob takes: guard breeding cooldown, sheared sheep and matching dyes (fixes #9121)
In Vanilla Minecraft 26.3 (Animal.mobInteract and Sheep.mobInteract):
- Right-clicking an adult animal with breeding food when unable to breed
  returns PASS on the server; the client does not swing or spawn feed particles.
- Right-clicking a sheared sheep with shears returns PASS and does not swing.
- Right-clicking a sheep with matching dye returns PASS and does not swing.

Guard these interactions in client's remote_mob_takes so the client does not
falsely predict swing arm and feed particles.

Managed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 14:40:23 +01:00
degepieandClaude Opus 5.5 45bb5766eb Advancements: every trigger casts the looking_at ray only when asked
PlayerPredicate.matches looks for the entity under the player's view
only when the predicate has looking_at. #8687 skipped the ray for tick
and location only; every other trigger that builds the player's view
still cast it, against every entity, on each event.

Each of those call sites now passes its trigger id (player_entity_for),
so the ray runs only when one of that trigger's criteria may ask. A view
that serves two triggers (the thrown-item pickup, the KilledTrigger
damage source) checks both. Commands keep player_entity(), which always
looks.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 18:39:51 +05:00
dbdfed59aa A bee sting poisons mobs, not only the player.
Vanilla poisons any living thing the sting hurts, so a villager or a fox was taking the hit and walking away clean.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 14:39:37 +01:00
voidsipandCodex f1566f80d3 Give chest loot tables precedence over saved items
Leave randomizable-container inventories empty when their NBT supplies a valid loot table, allowing the existing command and chunk loading paths to retain the table. Add a red/green regression and a real vanilla inventory-load comparison.

Fixes #8920

Co-Authored-By: Codex <[email protected]>
2026-11-04 07:38:04 -06:00
Alexander KrylovandHermes Agent b85e07a5de Stamp the pending native hook fragments through API 399
main carries 40 fragments whose markers have not been consumed, so
`python tools/stamp_native_hooks.py --check` exits 1 there. This is one run of
that tool on current main: it numbers the fragments in first-parent merge
order, writes the markers into the hand-written API types, and inserts each
`doc_summary` as an `API N adds ...` paragraph in docs/native-mods.md.

No hand-written line: the stamp pass owns native-hooks.json, NATIVE_HOOKS_API
and docs/native-mods.md, and editing any of them by hand is what the fragments
replaced.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 16:38:00 +03:00
67cfb2dc40 Keep a stack's own max stack size when it is sent to a client.
A cobblestone stack limited to 16 was merging to 64 after the component was dropped on the way to the window.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 14:37:17 +01:00
Arslan ArmanulyandClaude Opus 5.5 d75f5a4280 Shader packs: turn off a pack whose integer colortex0 can't reach the screen
A pack with an integer colortex0 and no `final` left to draw it (none, or
one that is skipped) has no colours the screen can show: #9007 made the
screen only clear, so Arc v0.15.2 drew a black world. `PackPost::new` now
refuses such a pack, `Renderer::set_shader_pack` returns the reason, and
the game turns the pack off with the usual log line and chat message, as
a pack that fails to load, so the world draws without it. An integer
colortex0 with a working `final` still loads and draws.

Fixes #9165

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:37:09 +05:00
rdbt 2c87445fff Merge branch 'main' into fix/golem-interactions 2026-11-04 13:31:54 +00:00
Alexander KrylovandHermes Agent 218c8db228 Client settings: a failed options.txt write is logged
Settings::save writes options.txt with `let _ = fs::write(...)`, so a write that
fails (a held file, a read-only folder, a full disk) leaves the player's
options, key bindings and volumes at whatever the last successful save put
there, and the game says nothing. Every option change goes through this call.

This logs the error where the rest of the crate warns (`log::warn!` in
`audio.rs`) and leaves the other files alone: `horizon.save`,
`controller.save`, `extras.save` and `mods.save` return no Result, so widening
their signatures is a separate change.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 16:31:10 +03:00
rdbt be4ee9131a Merge branch 'main' into fix/allay-take-back-main-hand 2026-11-04 13:29:41 +00:00
Ludovic Marie f95da0d5e7 Merge pull request #8619 from Akirabane/fix/8488-execute-on-owner
🤖 Agent: execute on owner resolves the owner guest first. the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:49 +01:00
Ludovic Marie 837e0498c2 Merge pull request #8520 from Akirabane/perf/pool-idle-per-worker
🤖 Agent: Per-worker idle release; cross-worker idle is mutexed, per-job cost a lock + Instant::now. the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:45 +01:00
CocofireHDandOpenCode 8fdb2ffce7 Build limit: say it when a placement above the world fails
BlockItem.place's FAIL ends the click but does not consume the action, so
the server must still send build.tooHigh/build.tooLow. The guest path
counted the failed hand use as consuming and skipped the message.

Co-Authored-By: OpenCode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 14:28:41 +01:00
Ludovic Marie 89d08be7cf Merge pull request #8218 from Akirabane/fix/7843-server-pack-tags
🤖 Agent: World pack tags merged only for registries with tag files. the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:41 +01:00
Ludovic Marie 398aa7aa09 Merge pull request #8188 from Akirabane/fix/8180-bucket-hollow-blocks
🤖 Agent: Bucket ray uses ClipContext.Block.OUTLINE like BucketItem.use; main merged. the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:37 +01:00
Ludovic Marie 5c1f65bb9c Merge pull request #8182 from Akirabane/fix/8175-bool-std430
🤖 Agent: Shader bool/bvec buffer rewrite; rdbt's main merge 0e010734d9 (one mod-line hunk) re-tested: horizon-shaders 249 tests OK. the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:32 +01:00
Ludovic Marie 7bc9b358dc Merge pull request #8170 from Akirabane/fix/2912-routing
🤖 Agent: Routing of flat worlds through the pipeline; lands after #8163 (carries its commits). the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:27 +01:00
Ludovic Marie abfb7aee9e Merge pull request #8163 from Akirabane/fix/2912-flat-features
🤖 Agent: Superflat features match FlatLevelGeneratorSettings.adjustGenerationSettings. the union of all 15 of this wave's PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop; harness subset on that union PASS: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container (only a subset ran locally; the full selection runs in CI run-parity).
2026-11-04 14:28:22 +01:00
Ludovic MarieandClaude Opus 5.5 a13845ca22 Map searches: skip chunks no wanted start fits, and read one chunk to tell an old one
Opening a generated chest with an exploration map nobody searched yet (a camp chest's camp
or secret map, a shipwreck's treasure map) runs the map's structure search on the server
thread. Two things made one such UseItemOn cost seconds on the official server (#8669):

- Every candidate chunk got its full starts built (all of its sets, jigsaw layouts
  included). Vanilla's StructureCheck.canCreateStructure only places the wanted
  structure's start piece and checks its biome. `may_start` now does that for jigsaws
  (`place_start` / `lay_out_children` split out of `assemble_drawing`), and `locate`
  builds a chunk's starts only where a wanted structure may start.
- Each candidate asked whether its chunk was old through the blender's region scan,
  which decodes every chunk record of each region file it touches. Vanilla's
  StructureCheck reads the one chunk. `OldWorld::is_old_chunk` now answers from a
  scanned region's bits, else from that chunk's record alone.

The slow-event line also names the item, block, position and face of a use, and the use
is split into use_mods / use_block / use_item / open_loot / open_maps /
use_advancements spans (`spans::guard` ends a span where its scope does).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:28:06 +01:00
Alexander KrylovandHermes Agent b13109ea12 Launcher: a failed wardrobe or cosmetics save is printed
The Wardrobe page drops the result of every write it makes: the outfit it keeps
on disk (`wardrobe::Saved::save`), the game's cosmetics switches
(`horizon_cosmetics::prefs::Prefs::save`) and the line recording what the
service confirmed (`prefs::set_confirmed`). All three return an `io::Result`,
so a failed write left the page showing the new outfit while the file on disk
still held the old one, and nothing said so.

This prints the error where the launcher already speaks with `eprintln!`
(`instances.json`, `settings.json`, accounts) and changes nothing else: the
in-memory state still carries the change.

Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
2026-11-04 16:27:22 +03:00
Nikita Maximilian GrimmandClaude Opus 5.5 210234f2ad Sprint dust of a mob comes from the mob's own random
Entity.spawnSprintParticle draws the crumb's x and z from this.random.
Horizon drew them from the particles' random for iron golems and other
players (the rabbit hop already uses the rabbit's own), so a golem walking
out of sight changed every particle drawn after it, such as a campfire's
smoke. The window's own player keeps the particles' random, which stands
in for its own as elsewhere.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:26:06 +01:00
rdbt 8237939970 Merge branch 'main' into fix/nautilus-interactions 2026-11-04 13:25:49 +00:00
Florian ERNSTandCodex e91ba5d21a Expose live comparator output to block data commands
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:25:22 +01:00
Arslan ArmanulyandClaude Opus 5.5 a50d9bbef4 Shader packs: draw solid and cutout terrain with gbuffers_terrain_solid and gbuffers_terrain_cutout
A pack with either program now draws terrain in two parts over the same
draws: the quads over opaque texels (and lava) through
gbuffers_terrain_solid, the cutout ones through gbuffers_terrain_cutout,
each falling back to gbuffers_terrain's chain. Solid terrain has no
default alpha test; cutout terrain keeps the 0.1 one. Packs with neither
program keep the single draw.

This is what Iris 1.11.7 drew with our probe packs (both programs, only
one of them, alpha 0.05 outputs, renderStage).

Fixes #9137

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:25:10 +05:00
rdbt 3f9ccee0fd Merge branch 'main' into fix/squid-ink-particles-broadcast 2026-11-04 13:25:02 +00:00
voidsipandCodex 87b5918538 Show native mod installation consent in both browser views
Keep the trust notice above the project and results, and scroll it into view when installation pauses for consent.

Fixes #8978

Co-Authored-By: Codex <[email protected]>
2026-11-04 07:23:59 -06:00
Arslan ArmanulyandClaude Opus 5.5 2358c282ca Shader packs: a fragment output's component qualifier picks its channels
Horizon read only `location` from a fragment output's layout, so
`layout(location = 1, component = 1) out uvec3 x;` was written from
channel 0, and two outputs splitting one location by component became two
declarations at that location, which naga rejects. Each such output is now
a private global copied into its components of one vector per location
after the pack's `main`; overlapping, mismatched or overflowing components
are errors, as glslangValidator reports them.

Fixes #9135

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:23:17 +05:00
AxxioandAntigravity e8c7ebb11f fix(remote): do not predict swing on non-interactive objects (fixes #9120)
In Vanilla Minecraft Java Edition 26.3, interacting with non-interactive
objects (paintings, fixed or empty item frames with empty hands, sneaking
or occupied boats without chests, and non-interactive minecarts) returns
InteractionResult.PASS, which leaves the click to the held item without
animating a hand swing. Furthermore, boarding a boat or rideable minecart
answers InteractionResult.CONSUME on the client, which ends the use
without predicting an arm swing.

Update remote_use_entity to check object interact passes and prevent
predicting hand swing animations on non-interactive objects or when
boarding vehicles.

Managed by Axxio.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 14:18:59 +01:00
AxxioandAntigravity f2a95400e3 Fishing bobber applies downward plunge impulse on bite (fixes #9131)
In vanilla 26.3 (FishingHook), when a bite occurs, DATA_BITING is set to true, triggering onSyncedDataUpdated on both server and client to apply an initial downward plunge impulse to velocity.y of -0.4F * Mth.nextFloat(syncronizedRandom, 0.6F, 1.0F) (-0.24 to -0.40).

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 14:17:35 +01:00
rdbt 99f0a14b40 Merge branch 'main' into fix/xp-orb-light 2026-11-04 13:17:01 +00:00
jaszkusandOpenCode f0d96d8648 Launcher: the setup wears the launcher's look
The Windows setup window drew the Night sky and its violet pixel buttons, so
it looked nothing like the launcher it installs.

It now draws the redesign's scene behind the dark window (the dawn sky behind
the light one), tints the scene with the player's accent exactly as the
launcher does, and reads Glass, Blur, Accent and Appearance from settings.json
every frame, so a change made in the launcher applies on the next frame. The
card is a glass plate, its buttons are the launcher's rounded translucent ones
and its switch takes the accent.

Co-Authored-By: OpenCode <[email protected]>
2026-11-04 14:16:16 +01:00
3572bf84fd Every player near a raid wave hears the horn, not only the first.
Vanilla sends each player their own horn, thirteen blocks toward the wave, so people further along the raid were hearing nothing.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 14:12:38 +01:00
Arslan ArmanulyandClaude Opus 5.5 9255ab808d Shader packs: normalMatrix in a core-profile gbuffers stage is the geometry's
`uniform mat3 normalMatrix` in a gbuffers program stayed a frame uniform
nothing fills, so it read zero. In a stage whose #version is 150 or later
and doesn't name the compatibility profile it is now the normal matrix the
compatibility profile's gl_NormalMatrix already uses; in the compatibility
profile it still reads zero. Our probe pack's Iris 1.11.7 pictures fit
this at 330 core, 330, 150 and 330 compatibility.

The GPU test's draw code moves into a helper both matrix tests share.

Fixes #9136

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:11:59 +05:00
d3a1908ac1 A breeze turns a thrown trident away instead of taking the hit.
Vanilla deflects every projectile but a wind charge, so the trident was dealing its full damage and could call down lightning.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 14:08:59 +01:00
TiggerTakkyTTTandCodex 6e1ce1b2a4 Server API: pin auth and command replies
Add HTTP goldens for bearer authentication, Host validation, command input errors, stopped-server rejection, and route misses.

Fixes #9156

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 20:07:37 +07:00
0c8a5431ab A fishing hook can catch a minecart and reel it in.
Vanilla treats a minecart as pickable, so the hook was flying through carts and a reel never pulled them.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 14:06:29 +01:00
voidsipandCodex f5a937cb4c Keep moderation replies translatable for each client
Formatting mute and temporary-ban replies as English discarded their translation structure before clients received them. Preserve nested components and duration units, with readable fallbacks for vanilla clients that do not have Horizon's language bundles.

Fixes #8255

Co-Authored-By: Codex <[email protected]>
2026-11-04 07:04:32 -06:00
Florian ERNSTandCodex c388810601 Clamp stacks inserted into empty hopper slots
Co-Authored-By: Codex <[email protected]>
2026-11-04 14:04:17 +01:00
Ludovic MarieandClaude Opus 5.5 648630f0d3 dedicated: the [transfer] start-up error reads as one sentence
The message had a run of 18 spaces in the middle of the string (a line
break that was not a string continuation); a backslash continuation keeps
the source wrapped and the message on one line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:00:30 +01:00
Arslan ArmanulyandClaude Opus 5.5 f557d78c34 Shader packs: a custom uniform without its declared int, float or bool type reads 0
In Iris 1.11.7 a custom uniform or variable whose expression has another
scalar type than the declared one reads 0 as a whole: an int given 2.0
or 7 / 2, a float or int given a comparison, a bool given a number. So
does one naming `true` or `false`, which Iris's custom uniforms don't
know, or calling `round`, `ifb`, `lerp`, `print` or any other function
they don't have. Horizon evaluated all of these.

custom.rs: #9119's `Shape` becomes `Ty`, which tells int, float and bool
apart, and int vectors from vectors. Numbers keep whether they were
written as floats, the built-ins get the types our probes found in Iris
(INT_NAMES, BOOL_NAMES; the rest are floats), and the operators and
functions follow rules fitted to our probe packs' values and warnings in
Iris. `centerDepthSmooth` joins HIDDEN: Iris warns it is unknown.

Based on #9119 (shaderpacks/custom-uniform-type-mismatch). In main's
`expressions_follow_c_precedence_and_file_order`, `c` (`|| false`) and
`d` (`int = -7 / 2`) now read 0, and `i` is declared `bool`, which keeps
it covering the BIOME_* notes.

Fixes #9126

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 17:58:15 +05:00
ElyTheCatandCodex gpt-6.1-sol 8f5ba77ab3 Fix entity culling review safety cases
Bind hidden proof reuse to the exact camera and use render-complete bounds for non-mob entities. Keep the async worker active independently of the mob precheck and preserve shader-pack shadow spans.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 16:22:39 +03:30
AkirabaneandClaude Opus 5.5 020dc51cfe transfer: an opt-in [transfer] module, a hand-over id, and the review's fixes
Kacper's review of #8700 and martipops's on #8700 and #8730.

The module is off by default. `server.toml` gets a `[transfer]` section
(enabled = false, expect_seconds = 10, max_expect_seconds = 60,
max_expected = 64, max_freeze_seconds = 30) in place of the constants in
lan/arrivals.rs. horizon-server passes it to the game as `--transfers`,
beside `--control-socket`, not as a server.properties key. While it is off
every player-* request is answered "transfers are off on this server",
the hub has no table and a tick does nothing for transfers. With it on,
the game refuses to start without Velocity forwarding, so a player can
only come in through the proxy. The section is kept apart from vanilla's
accepts-transfers.

Record version 1 gets a `handover` id. An export with `freeze` starts a
hand-over: a random id, and the record kept with the player. A repeat
export while it lasts answers with the same id and the same bytes and
does not restart the freeze, so a proxy that lost the reply can ask
again. An export without `freeze` is a read, with a null id. The record
also carries the player's statistics (`stats`), as the stats file has
them.

The export now closes the player's menu in the player's own level, as
the close of every guest does on leaving, and refuses when that level
cannot be opened. It copies the live mod attachments into the player
first, so the compound is what a save would write. A player who is
leaving cannot be exported.

An expectation is used up when the login starts, on the network thread,
once the bans, the whitelist and the player limit let it in. A login
still in configuration when the time runs out keeps it.

A frozen player is not kicked for floating, and its chat and signed
commands are dropped after the messages they say the client has seen are
applied, so its chat still validates after the freeze; chat
acknowledgements are kept.

docs/server.md gives the section, who can ask (the endpoint's limits,
the token being all or nothing) and how a server reads a record: another
version or data version is refused, the NBT in `player` wins over the
plain fields, unknown fields are ignored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:49:51 +01:00
Florian ERNST 1ab1d98c1c Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/end-clock-commands 2026-11-04 13:45:21 +01:00
Ludovic Marie 38df83828d Merge pull request #9087 from arslanarm/shaderpacks/ignored-format-declarations
🤖 Agent: clean-room check passed (black-box probes of their own, no source). Merged main and fixed #8999's test call to the removed Config::finish. Verified main + this head on the desktop: fmt, clippy -D warnings (shaders, render, all targets), 1270 tests, 0 failures; GPU fixtures not run.
2026-11-04 13:43:13 +01:00
Florian ERNSTandCodex 5dc8279a5d Support built-in End clock command state and defaults
Co-Authored-By: Codex <[email protected]>
2026-11-04 13:42:41 +01:00
rdbtCVSandCodex gpt-6.1-sol 663ce80174 Adapt scene replay to compressed sprite pixel storage
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:40:54 +00:00
rdbtCVSandCodex gpt-6.1-sol 7b300cc61e Initialize atlas sprite tables in renderer test fixtures
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:39:54 +00:00
rdbtCVSandCodex gpt-6.1-sol e60a96790d Initialize orb rotation in the upstream NBT fixture
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:39:54 +00:00
rdbtCVSandCodex gpt-6.1-sol 18f07a0304 Initialize atlas sprite tables in renderer test fixtures
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:39:53 +00:00
rdbtCVSandCodex gpt-6.1-sol 631ededd91 Initialize atlas sprite tables in renderer test fixtures
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:39:53 +00:00
rdbtCVSandCodex gpt-6.1-sol 6e602c4c2a Initialize material and item identity fields in shadow test fixtures
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:39:17 +00:00
TheLion102009andClaude Opus 5.5 1a4e2d208c mod API: scenes of blocks drawn in a screen
What Create's Ponder shows its machines in: native_draw_scene(batch,
rect, blocks, view) draws a few block states, at positions of the scene's
own (NativeSceneBlock), into a box of a screen, seen as NativeSceneView
says (centre, yaw, pitch, pixels per block). Each block's model is drawn
shaded by face, its faces against solid neighbours left out; a mod block
entity given with it is drawn through native_block_entity_submit, its
spin parts turning. The quads go in with the GUI's isolated depth and
scissor (HudBatch::depth_since), as the smithing screen's armour stand.

The view's matrix is a point reflection, so the unturned scene is seen
from the north as a player at yaw 0 sees it, unmirrored; the tests pin
which way yaw and pitch turn it.

Builds on the block entity drawing change (spin and place_turning).
Pending fragment: scenes_in_screens.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:39:12 +01:00
TheLion102009andClaude Opus 5.5 ccac8f85ff mod API: text, turning parts and copycats in block entity drawing
- native_draw_text(draw, text, at, color, glowing, outline): a Text under
  a block entity's pose as a sign draws its lines (Font.drawInBatch in a
  BlockEntityRenderer), each span in its colour, glowing text full bright,
  an optional outline; it returns the width. The sign's glyph quads are
  shared (block_entity_render::text_vertices).
- BlockEntityDraw::spin(axis, pivot, degrees_per_tick, phase) and still():
  parts that turn by themselves. The game turns them each frame from the
  game time and partial tick, so with keep() a shaft at a steady speed is
  drawn again with no call of native_block_entity_submit until its block
  entity changes (Flywheel's rotating instances, on the CPU).
- native_block_face_sprite(state, side): the texture a block state shows
  on a side, by the name native_draw_cube takes, for copycat blocks.

Connected textures need no code: a content mod ships OptiFine CTM rules
under assets/<id>/optifine/ctm/, as the docs now say.

Pending fragments: draw_text.json, spinning_geometry.json,
block_face_sprite.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:38:34 +01:00
TheLion102009andClaude Opus 5.5 9cffbd192d mod API: fluid currents, and a mod's own fluids in tanks
- native_fluid_flow(pos): FluidState.getFlow(level, pos) in the level in
  place, the normalised current an item in the fluid is pushed by
  (entities::fluid_flow, through the same flow_at items and boats use).
  A water wheel reads its speed from it.
- Tanks take a mod's own fluids (create:chocolate): native_fluid_insert
  now accepts any id in a namespace other than minecraft, besides water
  and lava; extract and move already carried names. Cauldrons still take
  only water and lava. NativeFluidTank becomes public and
  native_fluid_tanks joins Host.

The world itself still has only vanilla's fluids: no source or flowing
blocks or buckets of a mod's fluid.

Pending fragments: fluid_flow.json, mod_fluids.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:38:25 +01:00
TheLion102009andClaude Opus 5.5 05cb3702ad mod API: chunk tickets for native mods
NeoForge's TicketController.forceChunk: native_force_chunk(owner, chunk,
hold) holds a chunk of the level in place for a namespaced owner
(create:train/<uuid>), and native_forced_chunks(owner) lists what it holds.
A held chunk is kept as a /forceload'ed one is: Streaming has
mod_forced beside forced, Ranges reads both, the ticket levels give them
the FORCED level, and a level with any stays open. /forceload neither
lists nor releases them and they aren't saved with forced chunks: a mod
holds them again in native_level_load.

Pending fragment: chunk_tickets.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:37:25 +01:00
TheLion102009andClaude Opus 5.5 298e68aa52 mod API: container_accepts, a mod container's say on what goes in
NeoForge's IItemHandler.isItemValid for a mod's ModContainer: hoppers,
droppers and native_insert_item ask native_container_accepts(pos, entity,
slot, stack, face, cancel) before an item goes into a slot the container's
inputs and faces allow. `cancel` refuses it and the mover tries the next
slot, so a millstone takes only what it has a recipe for and a wrong item
can't clog it. tryMoveInItem now carries the face it goes in by.

Pending fragment: container_accepts.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:37:25 +01:00
rdbtCVSandCodex gpt-6.1-sol caf9530c20 Carry material identities into the double-sided shadow geometry
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:36:02 +00:00
rdbtCVSandCodex gpt-6.1-sol 24b89f986d Compare Forge installer argument paths consistently across Windows and Unix
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:34:05 +00:00
rdbtCVSandCodex gpt-6.1-sol e11b83fc8e Compare Forge installer argument paths consistently across Windows and Unix
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:33:02 +00:00
rdbtCVSandCodex gpt-6.1-sol ee746cf569 Supply the device when copying animated material atlas sprites
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:32:52 +00:00
LokiandAntigravity 6a627e54b1 mobs: calculate LivingEntity jump power from attributes, block factor and jump boost (fixes #9058)
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 21:32:44 +09:00
rdbtCVSandCodex gpt-6.1-sol 0a6527a5f3 Merge main and preserve both atlas and material-map rendering changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:32:34 +00:00
rdbtCVSandCodex gpt-6.1-sol f8a85f1bf9 Merge main and preserve both atlas and material-map rendering changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:32:34 +00:00
rdbtCVSandCodex gpt-6.1-sol 007df3944e Merge main and preserve both atlas and material-map rendering changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:32:33 +00:00
rdbtCVSandCodex gpt-6.1-sol ee85900b87 Merge main and preserve both atlas and material-map rendering changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:32:31 +00:00
NerfPhaselockandClaude Opus 5.5 64afd9f58a Renderer: GPU culling is not offered on DX12 while the solid pipelines read the page
Since f2c8e2ab1 the vanilla solid pipelines read their vertices and
records from the arena page by @builtin(vertex_index), per-section draws
too. DX12's count draws (multi_draw_indexed_indirect_count, which GPU
culling draws with) leave each draw's base vertex out of vertex_index:
wgpu-hal's draw_indexed_indirect_count sets its first-vertex constant to
0 and uses the command signature without it. GPU frames there read every
section's quads from the start of its page: most solid terrain missing,
stray quads in its place. Vulkan's vertex_index has the base vertex.

GPU culling is now not offered on DX12 with flat page groups (where the
pulled pipelines draw), so the word `gpu-culling on` is refused there,
until GPU culling's draws carry their base vertex some other way.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:31:10 -05:00
Ludovic MarieandClaude Opus 5.5 705645a7ab Merge rdbtCVS's main merge of this branch (same content, kept the stacked resolution)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:30:12 +01:00
Ludovic MarieandClaude Opus 5.5 5ce693ae48 Merge the #8200 stack's latest head (no change to the tree)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:30:12 +01:00
voidsipandCodex 83380b3b30 Synchronize optical-flow scene-change reduction
Divergence workgroups must all finish reading the histograms before either is replaced. Finalize in a separate dispatch so the reduction also observes every partial sum, without relying on a relaxed atomic completion counter.

Fixes #8627

Co-Authored-By: Codex <[email protected]>
2026-11-04 06:29:02 -06:00
TheMrZZandCodex gpt-6.1-sol 33ba27d9f5 Omit default fields from saved map data
Follow the optional defaults in MapItemSavedData.CODEC while retaining
required fields, nondefault flags and nonempty banner/frame lists.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 13:28:54 +01:00
Ludovic MarieandClaude Opus 5.5 4439459e71 Merge #8200's stack (with #8128 and main) into the heartbeat interval
Resolves the management handlers, the port map and the harness side by side.
Also: management_int's Codec.INT doc comment back on management_int, and the
interval's log line as vanilla writes it (MinecraftServerSettingsServiceImpl
reads the interval after setting it, so both numbers are the new one).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:28:38 +01:00
JulesB40andClaude Opus 5.5 e128384ac8 lan_e2e piston-slide: check the client moves the head itself
horizon-ctl block reports the client's own moving piston at a position
(moving: progress, extending). A head that only arrives as the server's
moving_piston block, with no move of the client's, doesn't count.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:28:32 +01:00
JulesB40andClaude Opus 5.5 de3604215d lan_e2e piston-slide: power the repeating command block with redstone
A repeating command block placed by /setblock with auto:1b never got its
first tick in Horizon, so the second extension didn't happen.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:28:31 +01:00
JulesB40andClaude Opus 5.5 4c7bdc63a6 Pistons: send the changed blocks at ServerChunkCache.tick's point
The early broadcast in tick_lan sent the blocks as they were after the
tick's block events: a block placed by a scheduled tick and then pushed
went out as the moving block, and the client couldn't run the push. The
blocks now go out in tick_blocks, after the random ticks and before the
block events, as ServerLevel.tick runs ServerChunkCache.tick. piston-slide's
second extension pushes a stone a command block placed that tick.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:28:31 +01:00
JulesB40andClaude Opus 5.5 58b0da9a87 Pistons: the held block changes go out before the next block events
ServerChunkCache.tick broadcasts the changed blocks before runBlockEvents,
so what the last tick's block entities changed (a retracted piston's base
put back) reaches the clients ahead of a block event that builds on it.
tick_lan now sends the changes before the block events, and holds the
tick's own late ones as before. piston-slide also powers the piston again
the tick after its base is back, with a repeating command block.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:28:30 +01:00
JulesB40andClaude Opus 5.5 7dfcbcaa0b Pistons: a moved block reaches the clients a tick after it lands
ServerLevel.tick broadcasts the changed blocks (ServerChunkCache.tick)
after the scheduled and random ticks, before the block events, the
entities and the block entities, so what those change goes out with the
next tick's. Horizon sent it the same tick: the block a moving piston
placed reached the clients while their own copy was still a tick from the
end of its move, and at /tick rate 1 it jumped the rest. The writes after
that point are held out of the log while it is sent; the guest move check
still sees them as unsent. lan_e2e's piston-slide check covers it.

Fixes #8430

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:28:29 +01:00
rdbtCVSandCodex gpt-6.1-sol 5bfe7af0c1 Collapse the nested invite refresh condition for Rust 2024 Clippy
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:27:25 +00:00
rdbtCVSandCodex gpt-6.1-sol 65b76b1a17 Merge main while retaining both frame culling and shader entity types
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:26:32 +00:00
rdbtCVSandCodex gpt-6.1-sol 12541fdeeb Merge main and retain sprite rectangles alongside shader material maps
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:26:20 +00:00
rdbtCVSandCodex gpt-6.1-sol c700853616 Retain the latest Shop updates alongside the options test correction
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:23:31 +00:00
rdbtCVSandCodex gpt-6.1-sol 84eb7390db Initialize item rotation in the new area-effects fixture
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:22:46 +00:00
rdbtCVSandCodex gpt-6.1-sol 5f7b5aa3f6 Adapt Realms connections and screen metadata to the current title API
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:22:16 +00:00
TheLion102009andClaude Opus 5.5 0f0a09f377 mod API: machines break blocks with a tool and use items as a fake player
What Create's drill, saw, harvester and deployer need, as Host helpers:

- destroy_block(pos, tool, collect): Level.destroyBlock(pos, true) with the
  loot rolled for `tool` and no player (Block.dropResources(state, level,
  pos, blockEntity, null, tool)). Particles, the block entity's contents
  in the drops, ore experience and the fluid left behind are vanilla's
  own path: GameLevel carries the tool and, with `collect`, keeps the
  drops instead of popping them. The mods' say on drops applies.
- set_break_progress(breaker, pos, progress): ServerLevel.destroyBlockProgress,
  the same broadcast a mob breaking a door uses (now shared,
  server_destroy_block_progress).
- use_as_player(stack, eye, yaw, pitch, target, sneaking): NeoForge's
  FakePlayer. useItemOn on a block, useItem in the air or interactOn with
  an entity, through the guest use path (forced_hit + guest_use), in
  survival with only `stack` in hand. The server's empty player shell is
  filled for the call and everything it touched is put back; it answers
  None while a player acts. NativeUseTarget and NativeFakeUse are new in
  mod_api.

Pending fragments: machine_breaking.json, fake_player.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:21:43 +01:00
rdbtCVSandCodex gpt-6.1-sol 9eac9f79b1 Match the world light type and parenthesize the remaining packed light expectation
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:21:41 +00:00
Ludovic MarieandClaude Opus 5.5 af6e2b1295 Read #8999's shadow test config without finish()
#9087 removes Config::finish (its only work was the gdepth rule); the test reads the settings as they are.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:20:57 +01:00
Evrim CicekandClaude Opus 5.5 18498a7e2d A silent elder guardian's curse shows the apparition without the sound
ElderGuardian.customServerAiStep sends GUARDIAN_ELDER_EFFECT with
isSilent() ? 0.0F : 1.0F, and ClientPacketListener.handleGameEvent plays
the curse sound only for 1. Horizon sent 1 to guests and played the sound
for the host whatever the elder's Silent flag.

The curse event now carries the flag. tools/test_elder_curse_parity.py
compares the parameter a client is sent by a loud and a silent elder with
vanilla's dedicated server (VanillaProtocolClient's new elder-curse mode;
the goat-voices mode shares its phased-scene plumbing).

Fixes #8932

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:20:52 +03:00
Ludovic Marie 67992e9fed Merge remote-tracking branch 'origin/main' into cg28/9087 2026-11-04 13:20:51 +01:00
TheLion102009andClaude Opus 5.5 92397d9caf mod API: Host forms of the generic and private Game helpers
Trait mods couldn't reach several documented helpers through `dyn Host`:
their signatures were generic or used gameplay-private types. Host now has:

- draw_model (standalone models in block_entity_submit)
- mod_block_entity(pos) and change_mod_block_entity(pos, &mut change)
  (native_with_mod_block_entity's form; on a client mod_block_entity
  returns what the server sent through block_entity_update_tag)
- item_entities, level_random(dimension)
- play_sound and send_particles for the level in place, from any server
  hook (until now only on LevelWriteHost)
- block_name_in_tag and server_item_in_tag
- component, set_component, remove_component
- trigger_criterion with a &dyn Fn test
- equipment(slot): the player's own on a client, the acting player's on
  a server
- sneaking(): whether the acting player holds sneak
  (UseOnContext.isSecondaryUseActive in a use hook)

NativeItemEntity, NativeBlockReadError, NativeComponentError and
NativeCriterion become public and are re-exported from mod_api.
Pending fragment: tools/native-hook-fragments/host_bridge.json.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:19:30 +01:00
rdbtCVSandCodex gpt-6.1-sol bc6ac0ab30 Retain upstream game systems removed by the earlier XP orb branch update
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:19:11 +00:00
rdbtCVSandCodex gpt-6.1-sol e3806f3017 Format launcher changes using its Rust 2021 edition
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:17:49 +00:00
rdbtCVSandCodex gpt-6.1-sol 0e010734d9 Merge main and preserve both shader routing and tag changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:15:32 +00:00
Florian ERNSTandCodex 8b83cd8285 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/pig-saddle-shears
Co-Authored-By: Codex <[email protected]>
2026-11-04 13:15:11 +01:00
Florian ERNSTandCodex 553483c421 Shear an unoccupied pig saddle before mounting
Co-Authored-By: Codex <[email protected]>
2026-11-04 13:13:51 +01:00
Ludovic Marie c2b104827c Merge pull request #9097 from horizon-rust/dezer/logos
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this merge head; the merge keeps #9091's clickable edition tabs with the new grass block and brand icon.
2026-11-04 13:13:42 +01:00
Ludovic Marie 7481e652fc Merge pull request #9093 from horizon-rust/dezer/settings-spy
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this merge head; the merge keeps both new Launcher fields (settings_marked beside #9099's mods_sticky).
2026-11-04 13:13:38 +01:00
Ludovic Marie 2d4f8380f2 Merge pull request #9092 from horizon-rust/dezer/lists
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this merge head; the main merge only re-applied the 14 px inset to Home's new head layout from #9091.
2026-11-04 13:13:34 +01:00
rdbtCVSandCodex gpt-6.1-sol 04bed4f9d8 Merge main and preserve finite rotation guards in shared entity teleports
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:12:57 +00:00
rdbtCVSandCodex gpt-6.1-sol 380a2ec59c Merge main and move Realms into the reusable client library
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:12:14 +00:00
rdbtCVSandCodex gpt-6.1-sol 9a223ef014 Scroll the cosmetics cycle button into view in the options behavior test
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:10:38 +00:00
rdbtCVSandCodex gpt-6.1-sol bf460e86cf Merge main and retain live transfer and status settings with both parity cases
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:09:55 +00:00
Ludovic Marie a55b9a2e52 Merge pull request #9007 from arslanarm/shaderpacks/uint-colortex0-blit
🤖 Agent: updated onto main after #8042, #8510 and #9002 landed (merge of main; #8999 fixture gets item_id_declared/material_maps; #9007 is stacked on #9005 so the trailing test modules resolve once). Verified on the desktop, main + #8999 + #9007 (+ #9005): fmt, clippy -D warnings (shaders, render, all targets), 1268 tests, 0 failures; GPU fixtures not run (no adapter over ssh).
2026-11-04 13:09:03 +01:00
Ludovic Marie 8350e76e2a Merge pull request #9005 from arslanarm/shaderpacks/const-local-sample-offset
🤖 Agent: updated onto main after #8042, #8510 and #9002 landed (merge of main; #8999 fixture gets item_id_declared/material_maps; #9007 is stacked on #9005 so the trailing test modules resolve once). Verified on the desktop, main + #8999 + #9007 (+ #9005): fmt, clippy -D warnings (shaders, render, all targets), 1268 tests, 0 failures; GPU fixtures not run (no adapter over ssh).
2026-11-04 13:08:58 +01:00
Ludovic Marie e4fe4e8122 Merge pull request #8999 from arslanarm/shaderpacks/shadow-map-coverage
🤖 Agent: updated onto main after #8042, #8510 and #9002 landed (merge of main; #8999 fixture gets item_id_declared/material_maps; #9007 is stacked on #9005 so the trailing test modules resolve once). Verified on the desktop, main + #8999 + #9007 (+ #9005): fmt, clippy -D warnings (shaders, render, all targets), 1268 tests, 0 failures; GPU fixtures not run (no adapter over ssh).
2026-11-04 13:08:55 +01:00
Ludovic MarieandClaude Opus 5.5 f28f5b6d50 Launcher: the Shop's item wells lit for the dark founder set
The founder set is now navy and charcoal (Ludovic, 11-04). On the dark
appearance's near-black well they all but vanished (well 17,11,31 against a
tee front of 33,32,41), so the dark well is a violet grey, lighter at the
top like a showcase. The light well stays.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:08:37 +01:00
Florian ERNSTandCodex gpt-6.1-sol de877a581d Merge main and retain cloud duration scaling with owner attribution
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 13:07:11 +01:00
rdbtCVSandCodex gpt-6.1-sol 7ae5d35547 Merge main and reuse localized loader version labels in every language
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:06:42 +00:00
rdbtCVSandCodex gpt-6.1-sol b3678682e4 Merge main and reuse translated build import status messages
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:05:34 +00:00
rdbtCVSandCodex gpt-6.1-sol 217d39afd8 Merge main and pass block movement properties through new entity ticks
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:04:36 +00:00
rdbtCVSandCodex gpt-6.1-sol 80c02acdf1 Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:03:54 +00:00
rdbtCVSandCodex gpt-6.1-sol afe23fbba6 Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:03:53 +00:00
rdbtCVSandCodex gpt-6.1-sol 01bf400ff7 Merge main while preserving random tick caching and fixing light test lints
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:03:51 +00:00
rdbtCVSandCodex gpt-6.1-sol 04322a6f52 Adapt rocket behavior fixture to shared view direction helper
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:03:23 +00:00
Ludovic Marie ea06a53506 Merge pull request #9098 from horizon-rust/dezer/look
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this head (473+85); menus open at once because an animation inside a deferred menu kept its invisible first frame under GPUI Fast (seen on the desktop run), pages and toasts keep the 200 ms pop.
2026-11-04 13:03:21 +01:00
rdbtCVSandCodex gpt-6.1-sol 25574a2d21 Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:02:49 +00:00
rdbtCVSandCodex gpt-6.1-sol 661e038c08 Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:02:48 +00:00
rdbtCVSandCodex gpt-6.1-sol d938f6a1dd Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:02:46 +00:00
rdbtCVSandCodex gpt-6.1-sol c37a6c6543 Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:02:44 +00:00
rdbtCVSandCodex gpt-6.1-sol 241155c490 Merge main and reconcile parallel changes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:02:36 +00:00
Ludovic Marie 26a79956fd Merge pull request #9049 from senjinthedragon/portal-shadow-casters
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 13:02:11 +01:00
Ludovic Marie e2cf10bbd0 Merge pull request #9048 from senjinthedragon/glow-frame-entity-id
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 13:02:07 +01:00
Ludovic Marie c0df7b1959 Merge pull request #9047 from senjinthedragon/pack-format-decimal
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 13:02:02 +01:00
Ludovic MarieandClaude Opus 5.5 dab0067925 Merge #9005's branch: both trailing test modules stay
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:01:47 +01:00
Ludovic Marie 40c4441550 Merge remote-tracking branch 'origin/main' into cg28/9007
# Conflicts:
#	crates/horizon-client-render/src/gfx/pack_post.rs
2026-11-04 13:01:32 +01:00
Ludovic MarieandClaude Opus 5.5 09022c09b9 Give #8999's shadow filtering fixture the fields main added
#8042 added GbuffersInputs.item_id_declared and #8510 World/Geometry.material_maps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:01:26 +01:00
Ludovic Marie e221e9d786 Merge remote-tracking branch 'origin/main' into cg28/9005
# Conflicts:
#	crates/horizon-client-render/src/gfx/pack_post.rs
2026-11-04 13:00:50 +01:00
Ludovic Marie f5243832bb Merge remote-tracking branch 'origin/main' into cg28/8999 2026-11-04 13:00:50 +01:00
rdbtCVSandCodex gpt-6.1-sol 5d6b0be54b Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:41 +00:00
rdbtCVSandCodex gpt-6.1-sol 9b6841e04e Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:39 +00:00
rdbtCVSandCodex gpt-6.1-sol 1161302d27 Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:38 +00:00
rdbtCVSandCodex gpt-6.1-sol ad47d9b3fd Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:37 +00:00
rdbtCVSandCodex gpt-6.1-sol 55fa82033f Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:35 +00:00
rdbtCVSandCodex gpt-6.1-sol 81d02ec5d4 Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:33 +00:00
rdbtCVSandCodex gpt-6.1-sol ea92f9ea77 Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:32 +00:00
rdbtCVSandCodex gpt-6.1-sol 7ec86a7aec Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:32 +00:00
rdbtCVSandCodex gpt-6.1-sol 62e8a49947 Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:31 +00:00
rdbtCVSandCodex gpt-6.1-sol 1ef6bdd171 Merge main to reconcile upstream changes and CI fixes
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:00:30 +00:00
Ludovic Marie b3ec6f2d35 Merge pull request #9023 from arslanarm/shaderpacks/clear-false-ping-pong
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:59:25 +01:00
Ludovic Marie 73d3ee3d20 Merge pull request #9022 from arslanarm/shaderpacks/forced-flip-written
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:59:20 +01:00
Ludovic Marie 04b8b27a3b Merge pull request #9020 from arslanarm/shaderpacks/nether-pack-shade
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:59:16 +01:00
Ludovic Marie 638c0044f0 Merge pull request #9019 from arslanarm/shaderpacks/clouds-textured-fallback
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:59:12 +01:00
Ludovic Marie 797ca1fab9 Merge pull request #9015 from arslanarm/shaderpacks/custom-uniform-unknown-names
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:59:08 +01:00
Ludovic Marie 4b1157a697 Merge pull request #9014 from arslanarm/shaderpacks/feature-flag-case
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:59:03 +01:00
Ludovic Marie c60bb5a341 Merge pull request #9004 from arslanarm/shaderpacks/length-array-size
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:58 +01:00
Ludovic Marie e927a62b23 Merge pull request #9002 from arslanarm/shaderpacks/trinary-minmax
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:54 +01:00
Ludovic Marie 440d486f39 Merge pull request #8994 from arslanarm/shaderpacks/ssbo-vector-atomics
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:50 +01:00
Ludovic Marie 3c9dd1fb50 Merge pull request #8993 from arslanarm/shaderpacks/custom-uniform-option-macros
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:47 +01:00
Ludovic Marie ee5ef77630 Merge pull request #8975 from arslanarm/shaderpacks/properties-feature-macros
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:42 +01:00
AkirabaneandClaude Opus 5.5 aa4869c86c dedicated: player-expect and player-export on the control endpoint
The read side of a seamless transfer (#7484, step 2). A proxy moving a
player between two servers tells the target who is coming and reads the
player out of the source.

`player-expect <uuid> [seconds]` records that the player will log in
within that time (10 s by default, 60 at most, 64 players at once). It
refuses a bad id, a time that is not a whole number above zero, a player
already connected and a full table. The login uses the expectation up;
one no login used is reported when it runs out. The player still logs in
as anyone else: holding them is a later step.

`player-export <uuid> [freeze]` closes the player's menu as vanilla does
(AbstractContainerMenu.removed puts the carried stack back into a
connected player's inventory) and answers with record version 1: the
player compound as a save writes it (base64 NBT), the advancements JSON
and a few plain fields. It refuses a player who is not connected, has not
finished joining, is riding, asleep or dead. With `freeze`, the player's
own packets are dropped and its acknowledgements still handled, until the
freeze is let go after 600 ticks with a warning.

docs/server.md gives both requests and the record's shape.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:58:41 +01:00
Ludovic Marie 9dc66afd4d Merge pull request #8974 from arslanarm/shaderpacks/custom-smooth-fade-time
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:39 +01:00
Ludovic Marie 37aba3abe5 Merge pull request #8510 from gigamans442/fix/independent-terrain-materials-20261103
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:34 +01:00
Ludovic Marie 7d8f45fedd Merge pull request #8474 from gigamans442/fix/independent-uniform-defaults-20261103
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:29 +01:00
Ludovic Marie f897927efa Merge pull request #8042 from arslanarm/shaderpacks/rendered-item-id
🤖 Agent: verified in a union of the 21 open shader-pack PRs on main 3fb4c3138 (desktop): fmt, clippy -D warnings on all targets, 3766 tests, 0 failures. GPU fixtures not run (no adapter over ssh); harnesses not run (shader-pack-only code).
2026-11-04 12:58:24 +01:00
rdbtCVSandCodex gpt-6.1-sol 606a58c04c Fix formatting of warden pushing tests
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 11:58:09 +00:00
voidsipandCodex b011ec1e66 Find keytool independently of the parity Java shim
Cached oracle runs replace java on PATH, so looking beside that executable points into the cache shim directory instead of the JDK. Resolve keytool directly and support JAVA_HOME when it is not on PATH.

Fixes #9079

Co-Authored-By: Codex <[email protected]>
2026-11-04 05:54:54 -06:00
Ludovic MarieandClaude Opus 5.5 0efb67fc12 launcher: the Language list keeps a solid backing under its glass
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:51:41 +01:00
JoansitohandGrok <grok-4.7> d230a5dee8 Drop the unused return at the end of the raider test.
The same needless return would fail clippy on the celebration test.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:50:24 +01:00
JoansitohandGrok <grok-4.7> 7d5f66a915 Drop the unused return at the end of the trader test.
Clippy rejects a return that is the last statement and does nothing.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:50:03 +01:00
270cd628bb Drop the extra borrow on the witch throw sound.
Clippy rejects &mut on a events field that is already a mutable reference.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 12:47:56 +01:00
Ludovic Marie c92fcbb17e Merge remote-tracking branch 'origin/main' into w8200 2026-11-04 12:47:18 +01:00
Ludovic Marie 9443236b20 Merge branch 'refs/heads/w8128' into w8200 2026-11-04 12:47:12 +01:00
Ludovic Marie 2d3d6a0c6e Merge commit 'cg29/8128' into cg29/8200
# Conflicts:
#	crates/horizon-client/src/game/dedicated/management.rs
#	crates/horizon-client/src/lan/mod.rs
#	crates/horizon-server/src/management_methods.rs
#	tools/port_map/rest-017.tsv
#	tools/test_management_protocol_parity.py
2026-11-04 12:47:12 +01:00
Ludovic MarieandClaude Opus 5.5 435b2837cd Keep the input ledger in the PR, not in the repo
The repo holds no per-PR research records; the ledger stays linked from the PR at its commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:46:44 +01:00
Ludovic MarieandClaude Opus 5.5 b12f0ad465 Keep the input ledger in the PR, not in the repo
The repo holds no per-PR research records; the ledger stays linked from the PR at its commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:46:44 +01:00
Ludovic MarieandClaude Opus 5.5 c38354c637 Give the captured frame no hand items
Main's capture.rs builds a Frame, which now has hand_items.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:46:38 +01:00
Ludovic Marie 6ea3787844 Merge remote-tracking branch 'origin/main' into cg28/8042 2026-11-04 12:46:26 +01:00
Ludovic MarieandClaude Opus 5.5 9d5e59e762 Management: log the entity broadcast range asked for, as vanilla does
MinecraftServerSettingsServiceImpl.setEntityBroadcastRangePercentage logs the
requested percentage before DedicatedServerProperties clamps it to 10..1000.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:46:19 +01:00
Ludovic MarieandClaude Opus 5.5 247d9941f2 launcher: fmt the Language list's anchor
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:43:55 +01:00
Ludovic Marie acd184c1d9 Merge pull request #9094 from horizon-rust/dezer/modrinth
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this head; Modrinth's Java mods stay listed for Horizon instances with the existing Works/Partly/Doesn't run mark, and installs still check SHA-512 before writing.
2026-11-04 12:43:20 +01:00
Arslan ArmanulyandClaude Opus 5.5 f0214c19b5 Shader packs: a custom uniform without its declared shape reads 0
In Iris 1.11.7 a custom uniform or variable whose expression doesn't give
its declared shape (a float given skyColor, a vec2 given a vec3), or that
joins a vector and a scalar with an operator (vec3 * 1.0), reads 0 as a
whole. Horizon evaluated it anyway.

custom.rs now checks each item's shape once, on the first eval, after the
unknown-name check of #9015: Shape and shape() follow rules fitted to our
probe packs' values in Iris (operators, vecN constructors, which functions
take vectors, matrices, the declared type). A failing item reads 0 with a
note, and #9015's pass marks everything using it.

The load-time dry run (pack_post.rs) gave every built-in as a scalar, so
skyColor looked like a float there; it now gives each one at its size
(pack_uniforms::zero).

Based on #9015 (shaderpacks/custom-uniform-unknown-names); its test's
`w = vec3(...) * 0.0 + 2.0` and main's `e = vec3(...) * 2` now read 0,
as Iris reads them.

Fixes #9086

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:42:50 +05:00
Ludovic MarieandClaude Opus 5.5 4ceaf4a8c2 Merge origin/main into dezer/logos
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:42:25 +01:00
Ludovic MarieandClaude Opus 5.5 7d41c97f80 Merge origin/main into dezer/settings-spy
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:42:01 +01:00
Ludovic MarieandClaude Opus 5.5 78f8d86785 Merge origin/main into dezer/lists
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:41:46 +01:00
Ludovic Marie a04888e69e Merge pull request #9096 from horizon-rust/dezer/avatar
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this head (474+83); one session-server lookup per account per 24 h, the empty answer (default skin) cached too, so an offline launcher doesn't retry each frame.
2026-11-04 12:41:18 +01:00
Florian ERNSTandCodex 57d465a5a9 Evaluate inline slot sources in item commands
Co-Authored-By: Codex <[email protected]>
2026-11-04 12:40:05 +01:00
JoansitohandGrok <grok-4.7> 929aa267e0 Villager conversion keeps Silent, NoAI and the other shared flags.
Infection and the cure were dropping the flags convertCommon copies onto the new mob.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:39:25 +01:00
JoansitohandGrok <grok-4.7> b2ebcee304 A rolled-up armadillo no longer accepts food on the client.
The client predicted Animal.mobInteract before the shell's FAIL, so the click did not stop.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:39:25 +01:00
JoansitohandGrok <grok-4.7> 25a0cf0396 A celebrating raider no longer jumps while it is riding.
The celebration tick rolled the jump even when the raider was a passenger, which vanilla skips.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:39:25 +01:00
JoansitohandGrok <grok-4.7> ab167cadb9 A wandering trader no longer drinks in the End.
The End has a fixed time, so it is neither dark nor bright outside and neither potion is used.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:39:25 +01:00
JoansitohandGrok <grok-4.7> 88bd95bb32 A silent witch no longer plays its drink and throw sounds.
Vanilla skips both playSound calls, including the pitch draw, when the witch is silent.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
2026-11-04 12:39:25 +01:00
Ludovic MarieandClaude Opus 5.5 88ee593627 launcher: menus open at once; the Language list stays in the window
The pop animation inside a deferred menu kept its first frame under GPUI Fast: the
version menu never showed on the desktop run, the Language list lost its plate. Menus
open at once again (pages and toasts keep the pop); the Language list is anchored and
opens upwards when there is no room below.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:38:44 +01:00
Ludovic Marie 056d097a9a Merge pull request #8687 from degepie/fix/looking-at-vehicles
🤖 Agent: vanilla: PlayerPredicate lookingAt over every non-spectator entity; tick/location gated by may_look. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:30 +01:00
Ludovic Marie d234d6daaa Merge pull request #8683 from degepie/fix/damage-sources
🤖 Agent: vanilla: indirectMagic(potion|cloud, owner) instead of main's Damage::player; overlaps #9053 (TheMrZZ), second to land resolves. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:25 +01:00
Ludovic Marie 1294fca5e5 Merge pull request #8662 from degepie/fix/bare-ids
🤖 Agent: vanilla: 26.3 EntityPredicate is a dispatchedMap by sub-predicate id; TagPredicate.id is a HolderSet. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:21 +01:00
Ludovic Marie 23261d97b3 Merge pull request #8654 from degepie/fix/throw-bucket-triggers
🤖 Agent: vanilla: hurtServer fires PLAYER_HURT_ENTITY at 0 damage outside i-frames; interact trigger after filled_bucket. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:17 +01:00
Ludovic Marie 422d9b6bbb Merge pull request #9051 from glitchsnake/fix/8785-wither-block-slowdown
🤖 Agent: vanilla: WitherBoss.makeStuckInBlock is empty; berry damage kept. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:13 +01:00
Ludovic Marie 6e9a91f185 Merge pull request #8964 from glitchsnake/fix/8957-spectator-container-vibrations
🤖 Agent: vanilla: ContainerOpenersCounter edges for CONTAINER_OPEN/CLOSE; tautological test removed. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:09 +01:00
Ludovic Marie 8737bbfe7e Merge pull request #8952 from glitchsnake/fix/8934-elder-guardian-home
🤖 Agent: vanilla: ElderGuardian.customServerAiStep setHomeTo(blockPosition, 16); tests trimmed; carries #8966's commits (guardian.rs conflict). Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:05 +01:00
Ludovic Marie 7332139da4 Merge pull request #8966 from glitchsnake/fix/8933-silent-guardian-flop
🤖 Agent: vanilla: Guardian.aiStep flop under !isSilent; tests folded into one. Lands before #8952 (stacked on it). Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:34:00 +01:00
Ludovic Marie 7e7336db35 Merge pull request #8501 from glitchsnake/fix/8419-summon-autocomplete
🤖 Agent: vanilla: SUMMONABLE_ENTITIES = canSummon types; only player and fishing_bobber are noSummon in 26.3. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:33:56 +01:00
Ludovic Marie 641cba9b30 Merge pull request #8887 from HrafnaValkyrja/fix/warden-vanilla-rules
🤖 Agent: vanilla: AbstractBoat.tick startRiding branch, a refused rider is neither seated nor pushed (#8916 closed as its duplicate). Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:33:52 +01:00
Ludovic Marie 26ae1d2991 Merge pull request #8864 from contactjayclatty/fix/8724-glow-squid-environmental-damage
🤖 Agent: vanilla: GlowSquid.hurtServer dims only if Squid.hurtServer is true, which needs getLastHurtByMob. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:33:48 +01:00
Ludovic Marie be63efabcc Merge pull request #8857 from contactjayclatty/fix/8776-wither-takes-no-effect
🤖 Agent: fixed on the branch: the refusal is WitherBoss.addEffect's (Mob::add_effect); canBeAffected stays vanilla so AreaEffectCloud keeps the wither as a victim. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:33:44 +01:00
Ludovic Marie 1b3e7c0f7f Merge pull request #8853 from contactjayclatty/fix/8721-zero-duration-effects
🤖 Agent: vanilla: MobEffectInstance.tickServer returns false at 0 (hasRemainingDuration), tickEffects removes it. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:33:40 +01:00
Ludovic Marie 9dcad939f7 Merge pull request #8843 from contactjayclatty/fix/8790-wither-cannot-ride-a-minecart
🤖 Agent: vanilla: OldMinecartBehavior/NewMinecartBehavior seat through startRiding, WitherBoss.canRide false. Proof: union of main b1f5b238 + this batch's 14 heads on the desktop: fmt, clippy -D warnings, tests of horizon-client/-lib/-core/horizon-world green (GPU gfx/post_chain/menu_blur skipped). Harness subset only (not the full parity.py selection, which CI runs on this head): 22 passed (active_effect_tick active_effects effect_notifications status_effect consume_effect sculk block_entity_anim chest_pair command_parse chat_screen command_exec peaceful_summon living_hurt living_damage damage_source player_hurt damage_reduction server_player_damage boat_authority vehicle_container powder_snow_collision squid_client); dedicated_server and server_entities failed on the vanilla oracle's watchdog under desktop load, mob_ai not finished (oracle 1 h timeout under load): left to CI.
2026-11-04 12:33:36 +01:00
Arslan ArmanulyandClaude Opus 5.5 f106b832ef Shader packs: atomic functions on a shared vector in a declaration list or a shared struct
An atomic function on a component of a shared integer vector (`atomicMin(mn.x, i)`) was
rewritten only when the vector had a declaration of its own. Two other forms reached naga
unsplit and failed with "Atomic operation is invalid: Pointer [..] to atomic is invalid":

- a declaration naming several variables, `shared ivec3 mn, mx;`: each name is now declared
  on its own, and the ones atomic functions use by component are split as before;
- a member of a shared struct whose type is defined in place,
  `shared struct { ivec3 mn; uint other; } sh;`: the member becomes `int mn_hz[3];` in the
  struct, with `hz_get_sh_mn()` / `hz_set_sh_mn(v)` for whole-vector reads and writes, through
  the member paths #8994 added for buffer blocks. A struct type whose name is used anywhere
  else stays as written.

Based on #8994 (shaderpacks/ssbo-vector-atomics), whose member-path rewrite this reuses.

Fixes #9085

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 16:31:59 +05:00
JanandClaude Sonnet 5.5 5198fae465 Native mods: a trade's live offer can be changed by mods (merchant_offer_traded)
After each trade made with a villager or a wandering trader, the mods are
asked with the live MerchantOffer in the trading session (uses counted):
uses, max_uses, demand, special_price_diff, price_multiplier and xp may be
changed and the change stands for the next click and the offers sent to the
player. Bukkit's PlayerTradeEvent#getTrade is mutable the same way. The
existing trade_with_villager stays listen-only and hears the offer as the
trade left it. A collect-contract hook with an API fragment.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 12:31:11 +01:00
Ludovic Marie f74b20d999 Merge the branch's fmt fix and main
Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/src/mobs/guardian.rs
2026-11-04 12:28:20 +01:00
Ludovic Marie 725a5dcfa4 Merge #8966 into #8952: both append to guardian.rs
The two branches each added a test module at the end of guardian.rs, so
whichever lands second conflicts. Carry #8966 here; land #8966 first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

# Conflicts:
#	crates/horizon-client/src/mobs/guardian.rs
2026-11-04 12:27:13 +01:00
DenisHumenandClaude Opus 5.5 269718fac1 Draw sign text glyphs over their own box, not a fixed 8x8 cell
Sign text drew every glyph as an 8x8 quad at the pen. Vanilla's
BakedSheetGlyph draws x+left..x+right and y+up..y+down, which the HUD
already does. With Force Unicode Font a Latin letter is an 8x16 Unifont
glyph at half scale (4 units wide, advance 5), so stretching it to 8
units overlapped its neighbours.

Fixes #8699

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:26:18 +02:00
Ludovic Marie 23ae9e7f82 Merge pull request #9091 from horizon-rust/dezer/home
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this head (473+84 passed); below 1280 px the mods button folds into ⋯ so the invite field keeps a 150 px minimum to the right of Play (screenshots at 1000×700).
2026-11-04 12:24:28 +01:00
TheMrZZandCodex gpt-6.1-sol 4baa918620 Omit ignored age fields from zombie nautilus saves
Follow AgeableMob save gating for zombie nautiluses while retaining modeled
age values for normal nautiluses. Cover stale raw data and the command path.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 12:22:21 +01:00
Ludovic Marie 3319108619 Merge pull request #9095 from horizon-rust/dezer/partner
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this head; the GitHub mark is our own SVG in horizon-theme's glass icons, the Sculk card keeps its 2 px hover lift without the border.
2026-11-04 12:15:47 +01:00
Ludovic Marie 7a969a6122 Merge pull request #9099 from horizon-rust/dezer/mods
🤖 Agent: desktop fmt/build/clippy -D warnings/tests 0 at this head; the card re-renders only when its offset changes (on_scroll + next frame), so a long list costs nothing extra while scrolling; a scrollbar-thumb drag catches up on the next event (noted in the body).
2026-11-04 12:11:24 +01:00
Ludovic MarieandClaude Opus 5.5 9dfb3149fb Launcher: the Shop's countdown rounds whole days to the nearest
From three days up the span showed whole days rounded down, so a seven-day
window read "6 days left" a minute after it opened.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:08:53 +01:00
DenisHumenandClaude Opus 5.5 c6efd15f0c Mobs see the player's off-hand item (tempting food in the off hand)
Game::player_view left PlayerView.off_hand as None, so TemptGoal, the
animal brains' TemptingSensor, piglins' loved items, goats, happy
ghasts and taming never saw what the player holds in the off hand.
Vanilla's TemptGoal.shouldFollow and LivingEntity.isHolding check both
hands. Fill it from the off-hand slot, as main_hand is filled.

Fixes #8858

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:04:58 +02:00
Ludovic MarieandClaude Opus 5.5 99725aa079 launcher: the scroll spy takes the scroll off the sections' layout tops
A section's bounds from the ScrollHandle are its layout position before the scroll, so
the list stayed on the first section until the pane reached its end (seen on the desktop
run: Account still marked over the Launcher card).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:03:43 +01:00
DenisHumenandClaude Opus 5.5 344a829699 Opaque debug lines write depth, so water and clouds stay behind them
DrawableGizmoPrimitives.getGroup puts a line whose alpha is 255 in the
opaque group, which GizmoFeatureRenderer.buildLines draws with
RenderTypes.lines (pipeline/lines: DepthStencilState.DEFAULT, depth
written); only lines with alpha below 255 use
linesTranslucentNoDepthWrite. Horizon drew every depth-tested gizmo line
without writing depth, so translucent terrain and clouds drawn later
covered F3+B hitboxes and F3+G chunk borders in front of them.

The opaque lines now upload first and draw with a depth-writing
pipeline; the translucent ones (the chunk border's outer columns) keep
the old one.

Fixes #8465

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:02:25 +02:00
repositorykreml1nandCodex f0634ed6f2 fix: honor the player's mining efficiency attribute
Read the computed attribute when calculating destroy speed, including active
hand enchantments in the map without activating them in the wrong slot.
Add nine differential real-game probes against vanilla Player.getDestroySpeed.

Fixes #9021

Co-Authored-By: Codex <[email protected]>
2026-11-04 14:00:06 +03:00
masa-masa-0314andZCode 73715cdab4 Launcher: add data-location strings to the new language tables
Upstream added 52 launcher language tables on main; they predate this branch's
data-location strings, and the launcher's i18n completeness test requires every
table to carry every key. Add the missing entries with the English text, and
the Chinese translation for the new startup message.

Verified with fmt --check, clippy --all-targets -D warnings and the launcher
lib tests on the merged tree (482 here, 486 on Linux in WSL).

Co-Authored-By: ZCode <[email protected]>
2026-11-04 18:56:59 +08:00
LokiandAntigravity 0b831b1dca mobs: cargo fmt guardian.rs
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 19:53:23 +09:00
masa-masa-0314 f83a9c1ae9 Merge remote-tracking branch 'upstream/main' into feat/launcher-data-location 2026-11-04 18:52:36 +08:00
Loki 81e9973c62 Merge remote-tracking branch 'origin/main' into fix/8934-elder-guardian-home 2026-11-04 19:51:29 +09:00
Arslan ArmanulyandClaude Opus 5.5 2e4a5a13a6 Shader packs: textureMatrix is the texture matrix in gbuffers vertex stages
A gbuffers vertex stage that declared the core-profile `uniform mat4
textureMatrix` got a frame uniform the game has no value for, so it read
zeros, and packs computing their UVs as `textureMatrix * vaUV0` sampled
one texel per face. In the vertex stage it is now gl_TextureMatrix[0], as
projectionMatrix and modelViewMatrix already are their compatibility
matrices. Our probes read the identity there in Iris 1.11.7, and zero in
the fragment stage, which keeps its own frame slot.

Tests: the vertex stage's textureMatrix takes no frame slot while the
fragment stage's keeps one; on a host GPU, the vertex stage reads the
identity, a UV through it is the UV, and the fragment stage reads zero.

Fixes #9080

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:46:37 +05:00
Arslan ArmanulyandClaude Opus 5.5 b873ea2dd6 Shader packs: read struct and buffer-block members that atomic functions use
naga's GLSL frontend read `s.member` on the right-hand side out of a loaded
copy of the whole struct. Once an atomic function marks that member atomic,
the copy's member is an atomic value no operator, conversion, call or store
takes ("Incompatible operands: Atomic(...) as Uint"). The member is now
loaded through a pointer to it, as array elements already were; constant
expressions keep the value form.

Tests: every read the #9081 probe makes (assignment, operators,
constructors, min, subgroupBroadcastFirst) on a shared struct member, a
buffer-block member, a member of a shared struct array and a nested member;
constants' members in constant expressions; a member beside a runtime-sized
array; and, on a host GPU, the values those reads return.

Fixes #9081

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:44:19 +05:00
masa-masa-0314andZCode f1f7847050 CI: rerun launcher checks
The launcher job stopped with exit code 101 while verify succeeded and the same
fmt, clippy and lib tests pass on Linux locally (486 passed).

Co-Authored-By: ZCode <[email protected]>
2026-11-04 18:42:43 +08:00
Ludovic MarieandClaude Opus 5.5 e584b02ce2 launcher: the knob's slide owns its ends; fmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:42:02 +01:00
Ludovic MarieandClaude Opus 5.5 9cab156727 launcher: fmt the instance dialog's logos
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:40:27 +01:00
Ludovic MarieandClaude Opus 5.5 a61d9f473d launcher: menus in the cards' glass, the website's motion, a whiter light look
Menus and popovers take the cards' glass with a shadow instead of the darker float, and
arrive as the website's hzl-pop (fade and 6 px rise, 200 ms); Settings' Language list is
one of them now (it was the old square box and ran off the window). Hovers ease on the
design's quick curve, pages and toasts pop, the glass switch's knob slides; reduced
motion draws each at its end. In light, the Play group's icons take the accent's ink and
the dawn sky is washed with white.

Addresses dezer-x's launcher feedback (points 7, 8 and 11).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:40:21 +01:00
Ludovic MarieandClaude Opus 5.5 a6ab89c89b Launcher: the Shop in the glass look, the new founder set, First Light in Settings
The Shop and Wardrobe tabs on glass plates in both appearances (Geist type, the
glass ink, light wells a soft violet grey so cream and violet items keep their
edges); the founder panel stacks under a 1280 px window. The Wardrobe gets a Shirt
row; its launcher theme row is gone: launcher themes are free for everyone in
Settings > Appearance (Launcher theme: Horizon or First Light), never claimed.
The top bar's Wardrobe follows the cosmetics service only. Shop previews use the
7-day window. The Shop strings in the 61 languages main added since, and the new
ones everywhere.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:39:59 +01:00
Ludovic MarieandClaude Opus 5.5 47222e152f Cosmetics: a torso slot, top-level parts on their own bone, on by default
A shirt's sleeves ride the arms: a top-level part may name its own bone (Item::limbs,
one root per bone under the item's attachment, Item::roots). The game draws each root
on its bone, the launcher's preview too. Slot::Torso, hidden under a chestplate.

Cosmetics run by default (Ludovic, 2026-11-04: on when the shop opens); other
players' and your own already default to shown. HORIZON_COSMETICS=0 turns them off;
horizon_cosmetics::forced() is HORIZON_COSMETICS=1 for the launcher's Wardrobe.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:39:59 +01:00
Ludovic Marie 3fb4c3138e Merge pull request #8815 from itarqos5/perf/reduce-a-far-chunk-when-read
🤖 Agent: reviewed by literal-review (agent-state/literal-review.md): a far chunk is reduced only to the cell width that reads it (405 → 103 µs per chunk in its bench), same output; verify + launcher green at this head.
2026-11-04 11:37:02 +01:00
TiggerTakkyTTTandCodex ce098ebe9d Server API: pin observability replies
Add JSON and Prometheus golden cases for status, logs, sampled metrics, cursor handling, and stopped or running health output.

Fixes #9083

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 17:31:18 +07:00
Arslan ArmanulyandClaude Opus 5.5 c94eb80064 Shader packs: read colortex formats only where Iris 1.11.7 does
Probe packs of ours, run in Iris 1.11.7, set colortexNFormat in many places. Horizon now
follows what Iris did with them:
- a format in a block comment counts only where `const` starts its line (our probes' Iris
  values fit this; it isn't a rule Iris states): not on the `/*` line, not after ` * `, not
  after another declaration on the line;
- a format in a vertex shader doesn't count;
- the colortex formats of the shadow programs' fragment shaders are read when the shadow pass
  doesn't run (observed in our probes for shadow.fsh and shadowcomp.fsh); nothing else of
  those files is read then (Horizon's choice: nothing else was probed);
- declaring `gdepth` no longer makes colortex1 RGBA32F.

Fixes #9063

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 15:30:59 +05:00
rdbtCVSandOpenAI Codex 5bdbffb5a5 Merge latest main after RAM limit removal
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-04 10:28:35 +00:00
masa-masa-0314andZCode f46a9c83a0 Launcher: open Settings when the data folder cannot be opened
A player whose drive is unplugged, whose location.json is damaged, or whose
migration lock is held used to get a silent exit: eprintln goes nowhere in the
Windows release build or from a desktop launcher. Startup now falls back to the
default folders and records the error, so the window opens on Settings with the
message, Retry and Use the default folder. Retry restarts into the configured
folders when they are readable again; Use the default folder keeps the old
pointer as location.json.old and restarts. Nothing in the moved folders is
deleted, and no launcher path reaches its expect() before the window exists.

Merge upstream/main (017c84f1f) while doing so: keep the launcher render env
strip and account-session removal alongside HORIZON_HOME, keep both the server
location_busy and stopped_line methods, keep one WorldLock, and keep the
explicit unlock in the world-lock test. The data-location strings the newer
language tables did not have yet are added (English fallback), with a Chinese
translation for the new message.

Tested: launcher lib 482 passed, bin 84 passed, integration suites passed;
cargo fmt --check and clippy --all-targets -D warnings pass.

Co-Authored-By: ZCode <[email protected]>
2026-11-04 18:23:55 +08:00
Ludovic Marie 17d4944fa9 Merge branch 'dezer/mods' into dezer/modrinth 2026-11-04 11:23:48 +01:00
Ludovic MarieandClaude Opus 5.5 e4b7c5723f launcher: fmt the Mods page's sticky card and sort
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:23:47 +01:00
Ludovic MarieandClaude Opus 5.5 57dba96d66 launcher: our own grass block for Vanilla, the real Horizon icon for Horizon
The Vanilla mark was baked from Mojang's grass textures in the prepared files, so the
Java Edition tab and the New instance card showed a letter until the game was prepared.
It is now an original drawing (tools/launcher_grass_block.py), always there. Horizon's
mark was a redrawn tile with the bar in the accent colour; it is now the brand icon.

Addresses dezer-x's launcher feedback (points 5 and 6).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:23:40 +01:00
rdbtCVSandOpenAI Codex f198e226ee Keep field focus helper used by the theme editor
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-04 10:21:29 +00:00
Ludovic MarieandClaude Opus 5.5 83d38f5735 launcher: fmt the scroll spy's tests
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:21:14 +01:00
rdbtCVSandOpenAI Codex c25f708490 Merge main and remove remaining RAM limit features
Remove the hard allocation ceiling and glass launcher slider added since the original revert. Keep read-only memory diagnostics and unrelated upstream changes.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-04 10:18:52 +00:00
Ludovic Marie 201026e91e Merge pull request #9077 from horizon-rust/fix/mgmt-stop-exits
🤖 Agent: management server/stop now ends horizon-server as vanilla's does. Desktop: management_protocol FAIL on main 017c84f1fe (supervisor TimeoutExpired), PASS on e132eeba6c (--fresh, 167 s); dedicated_server PASS both; horizon-server fmt/clippy -D warnings/134 tests pass; the new supervisor test fails with the fix reverted.
2026-11-04 11:17:59 +01:00
Ludovic MarieandClaude Opus 5.5 ad7e2b3594 launcher: the scroll spy's test reads the section at 1000 px right
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:14:09 +01:00
Ludovic MarieandClaude Opus 5.5 6c597549a9 launcher: the Mods page's Modrinth placeholder follows its tab
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:13:59 +01:00
Ludovic MarieandClaude Opus 5.5 0b15d4a0d5 launcher: the top bar wears the signed-in player's face even when the account kept no skin
dezer-x's launcher feedback, point 14. The chip draws the account's head from its saved skin URL;
an account saved without one (older sign-ins, or a profile answered without its skin) fell back to
the default skin from a prepared version's files, and with none prepared to the user icon.
Such an account now asks Mojang's public session server (no token) for its skin, keeps the URL a
day beside the cached skins, and the face comes from the skin as before (textures.minecraft.net
only). Signed out or offline with nothing cached, the user icon stays.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:10:11 +01:00
Ludovic MarieandClaude Opus 5.5 b7b3c2cedf launcher: GitHub's mark on Sponsor on GitHub, Sculk's card on every page, no border
dezer-x's launcher feedback, points 12 and 13:
- Sponsor on GitHub carries GitHub's mark (github.svg in the glass icons, drawn for Horizon:
  filled, its cat cut out by a mask).
- Sculk's card stays at the sidebar's foot on every page, Servers too; its border is gone (it
  still lifts under the pointer).
- On Servers, Sculk's banner sits right under the server, where it shows without scrolling past
  the server's tabs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:09:00 +01:00
Ludovic MarieandClaude Opus 5.5 e2639c66d6 launcher: Home drops the Resources rows, keeps the invite code beside Play, edition tabs switch
dezer-x's launcher feedback, points 1, 2 and 15:
- Overview (glass look) ends with the screenshots and the patch log: the Resources and
  Minecraft folder rows (and their Sign in with Microsoft) are gone; Prepare stays in Play's
  menu, the folder in the folder menu, signing in in the top bar.
- The invite code stays on the actions' row at every width; under 1280 px Play and the version
  are narrower, the puzzle gives way (the Mods tab does the same) and the field takes the rest,
  with the shorter "Paste an invite code".
- The Java Edition / Rust Edition tabs switch: to that edition's instance played last, or to the
  New instance dialog on Vanilla or Horizon when there is none.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:06:47 +01:00
Ludovic MarieandClaude Opus 5.5 aba52d49b6 launcher: Modrinth is back on the Mods page, every kind has its tab
Ludovic's point 16 on dezer-x's launcher feedback: mod translators are coming and the launcher
plays Java instances, so the Mods page searches Modrinth again (undoes what 0416fe600c hid, in
the 0.2.38 design).
- Tabs: Mods, Modpacks, Resource packs, Shader packs, Data packs.
- Mods, Modpacks and Resource packs pick their source under the search: Horizon's registry or
  Modrinth, CurseForge beside them as coming soon, inert. Shader packs stay on the registry
  (Horizon's .hshader), data packs on Modrinth, into the world picked there (its worlds read again
  when the tab opens; source_for gave data packs Horizon's registry, which has none).
- A Horizon instance's Modrinth mods are listed, each marked Works, Partly or Doesn't run in
  Horizon once its jar is checked; a Java instance's tabs still search Modrinth for its version and
  loader. Installs keep the SHA-512 check before writing.
- docs/mod-browser.md, launcher/README.md and the Mods page's module comments say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:06:29 +01:00
TheMrZZandCodex gpt-6.1-sol 17bc3161d5 Preserve saved Sitting flags for both nautilus species
Restore the modeled flag independently of Owner and serialize its current value. Cover defaults, owner removal, raw-None writes and model mutation against stale raw NBT, with a frozen Game command regression.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 11:04:48 +01:00
Ludovic MarieandClaude Opus 5.5 02f481fe07 launcher: the Mods page's card stays in view, Sort by sits beside the search
dezer-x's launcher feedback, points 3 and 10:
- The project card beside the results stays in view as the page scrolls (the mockup's
  lg:sticky lg:top-0): sticky.rs measures the row while it paints and moves the card down by what
  the page scrolled past it, never past the row's end. A scrolled page is drawn from its layer,
  so the page's scroll asks on the next frame whether the card must follow, and redraws only then.
- Sort by is on the search's right, as tall and as round as the search, its choices on a glass
  popover with the chosen one ticked; Modrinth's filter bar no longer repeats it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:02:39 +01:00
Arslan ArmanulyandClaude Opus 5.5 4cc3fc51d9 Shader packs: raw custom textures default to linear + clamp, and a PNG under a name of the pack's own isn't bound
A raw (`.dat`) custom texture is sampled linear and clamped unless its
`.mcmeta` says otherwise, each key overriding its own default only; PNGs
stay nearest and repeating. A PNG (the pack's or a game resource) under
`texture.<stage>.<name>` whose name is no sampler of the game's is not
bound. Iris 1.11.7 read these values with a probe pack of ours; the
binding rule is a fit to its cases.

Fixes #9064

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:59:00 +05:00
Ludovic MarieandClaude Opus 5.5 9f7e16a7b6 launcher: Settings' list follows the scroll, each last section in turn
dezer-x's launcher feedback, point 9. GPUI Fast draws a scrolled pane from its cached layer
without rendering the view, so the list only moved when something else redrew the page; at
the end the mockup's rule then jumped straight to Data collection.
- The pane's scroll asks, on the next frame, which section is read, and redraws only when it
  changed.
- The reading line sits at 30 % of the pane, then slides to its bottom over the last 70 % of a
  pane of scroll, so About and Data collection, too short to reach 30 %, each come in turn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:58:53 +01:00
Ludovic MarieandClaude Opus 5.5 ac67a24940 fmt
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:57:47 +01:00
Ludovic MarieandClaude Opus 5.5 57c08c432b launcher: versions and patch notes get their gap, the pages stop short of the top bar
dezer-x's launcher feedback, point 4:
- Each Versions and Patch notes card carries the 10 px bottom margin cull::column already
  counted as their gap (the mockup's gap-2.5): the cards were fused.
- The glass look's scroll area starts 14 px under the top bar, so cards scrolled up stop short of
  it; the pages' heads hold 14 px less above their titles, so nothing moves at rest.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:57:43 +01:00
Ludovic MarieandClaude Opus 5.5 fd381c7ad7 Merge origin/main into feat/launcher-shop-ui
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:57:12 +01:00
Arslan ArmanulyandClaude Opus 5.5 e05ce228d6 Shader packs: RGB5_A1, RGB565 and R3_G3_B2 buffers keep their bits
These formats live in 8-bit wgpu textures, so a pack read back 8-bit values where Iris 1.11.7
reads fewer bits. What a pass writes into such a buffer, and its clear colour, now keep the bits
Iris kept in our probe pack p8106c: the 8-bit byte's top 5, 5, 5 and 1 bits for RGB5_A1, 5, 6
and 5 for RGB565 and R3_G3_B2, repeated to fill 8. This is a fit to the probe's Iris values on
one driver (all 600 values match), not a rule Iris states.

Fixes #8106

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:54:55 +05:00
Ludovic MarieandClaude Opus 5.5 a53ae76d3c Merge main
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:53:34 +01:00
Ludovic MarieandClaude Opus 5.5 3faaa3f304 Merge main
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:53:34 +01:00
Ludovic MarieandClaude Opus 5.5 e81cf7e35c Merge main
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:53:34 +01:00
Ludovic MarieandClaude Opus 5.5 09d7b3625e Merge main
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:53:33 +01:00
Ludovic MarieandClaude Opus 5.5 f6e559c186 mobs: one flop test over both guardians, loud and silent
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:51:41 +01:00
Ludovic MarieandClaude Opus 5.5 5bea3820c1 game: drop the opener edge test that computed its own answer
It derived the open and close edges in the test from recheck_opener_count's
return, which the existing opener tests already pin; it never reached the
game events this PR gates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:51:32 +01:00
Ludovic MarieandClaude Opus 5.5 efb950a10d Merge main into fix/8933-silent-guardian-flop
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:51:32 +01:00
Ludovic MarieandClaude Opus 5.5 8bd408af30 Merge main into fix/8957-spectator-container-vibrations
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:51:24 +01:00
Ludovic MarieandClaude Opus 5.5 425145eb50 mobs: one elder guardian home test, beside the code it pins
Keep the home and keep-existing-home cases and the restriction they give
in one test right after elder_server_ai_step (so it does not collide with
the flop tests #8966 appends to guardian.rs), drop the test that pinned
extra.home staying empty, and keep Mob::toward private.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:51:17 +01:00
Ludovic MarieandClaude Opus 5.5 1fea8858dd Merge main into fix/8934-elder-guardian-home
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:50:53 +01:00
Ludovic MarieandClaude Opus 5.5 b500705d23 mobs: the wither's addEffect refuses, its canBeAffected does not
WitherBoss overrides addEffect to return false and canBeAffected to refuse
the wither effect only. AreaEffectCloud.serverTick picks its victims with
canBeAffected, so a cloud of strength or healing still takes the boss as a
victim: its instant effects land and the cloud shrinks. Keep
mob_can_be_affected and Mob::can_be_affected vanilla's canBeAffected and
put the blanket refusal in Mob::add_effect.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:50:46 +01:00
TiggerTakkyTTTandCodex 1ddca546f0 Server API: pin moderation replies
Add JSON golden cases for stopped-server operator and whitelist edits, no-op outcomes, unavailable running-only actions, and representative validation errors.

Fixes #9073

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 16:50:43 +07:00
Ludovic MarieandClaude Opus 5.5 6581318519 Merge main into fix/8776-wither-takes-no-effect
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:50:14 +01:00
1013b2dc5e Put the cleared experience orbs back into their join list.
The respawn rule returns a Vec, and the client stores orbs in a JoinList, so verify could not assign one to the other.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:43:11 +01:00
6c4a43178f Keep the closed-shulker arrow test in the library that can build a mob.
The game crate's tests cannot see the library's cfg(test) mob fixture, so verify failed while compiling them.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:36:55 +01:00
Ludovic MarieandClaude Opus 5.5 e132eeba6c Server: name vanilla's stop path in the comment
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:36:38 +01:00
TiggerTakkyTTTandCodex 4d260ccdac Server API: pin event replies
Add JSON golden cases for event polling, cursors, type filters, Last-Event-ID resumption, invalid selectors, and exact server-sent event framing.

Fixes #9066

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 16:33:56 +07:00
9515965bd5 Broadcast entity event 46 so a random teleport's portal particles reach the window.
Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:28:58 +01:00
785fd33992 The player who fills a composter was left out of level event 1500, so the fill particles and sound never reached them.
Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:27:56 +01:00
0ddc08b053 Bounce arrows off a closed shulker instead of removing them.
Shulker.hurtServer returns false for an AbstractArrow while the shell is closed, and AbstractArrow.onHitEntity then deflects REVERSE at power 0.2. The hit path was dropping that refusal.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:26:10 +01:00
80c9d8d5a7 Drop experience orbs when the client changes dimension.
A new ClientLevel starts empty, but the old orbs stayed and followed the player after the end fountain, so they were never picked up.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:25:59 +01:00
ca31bc0c02 Keep a reloaded potion amplifier of 255 instead of clamping it to the lowest level.
MobEffectInstance stores amplifier as an unsigned NBT byte. Sign-extending that byte turned 255 into -1, and the effect constructor then clamped it to 0.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:20:35 +01:00
TiggerTakkyTTTandCodex a225724ed4 Server API: pin config replies
Add JSON golden cases for redacted config reads, stopped and running config patches, protected settings, malformed bodies, invalid ranges, invalid value shapes, and unknown settings.

Fixes #9056

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 16:20:04 +07:00
Arslan ArmanulyandClaude Opus 5.5 2f24d605ce Shader packs: fill a stitched block atlas's padding with each sprite's edge
The stitched atlas now draws every sprite into each level with a small render
pass instead of copies. The padding around a sprite repeats its nearest edge
pixel, as vanilla's TextureAtlas.uploadInitialContents does: it blits with a
clamp-to-edge sampler, and animate_sprite.vsh stretches the UVs over the
padding. Packs that filter or sample just past a sprite's edge read its own
colour instead of an empty pixel.

Part of #9037

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:13:33 +05:00
Arslan ArmanulyandClaude Opus 5.5 51ea650d28 Shader packs: stitch the block atlas as the game does, each sprite at its own size
With a pack, flat terrain draws now read a block atlas laid out by vanilla's
Stitcher, with SpriteLoader's mip level and padding. Every sprite keeps its own
size, copied from the array level where the arrays hold it unscaled, and its
place goes to the sprite table. atlasSize and the texel distances packs derive
from it now match Iris 1.11.7: 2048 x 2048 on 26.3's sprites, where the grid
of 64 px cells gave 2304. Draws without the flat path keep the grid. The
padding stays empty for now.

Part of #9037

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 14:13:18 +05:00
d46ebbdaa5 Start the shulker teleport slide from the move packet.
Shulker.createInterpolationHandler is NO_OP, so setPos runs when the packet arrives, from the block the shulker occupied before it. client_tick only counts the slide down.

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 10:05:56 +01:00
Florian ERNST 48b17308b2 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/special-recipe-command 2026-11-04 09:53:28 +01:00
Arslan ArmanulyandClaude Opus 5.5 e2e4df377c Shader packs: the shadow pass draws terrain faces turned away from the light
An Iris-format pack's shadow map held no faces turned away from the light,
for two reasons: the shadow and shadow_water pipelines got the game's
terrain geometry with back-face culling on, and the shadow lists kept only
the quad buckets that can face the light. Under a hill at low sun such
faces (cave ceilings) are often all that shadows a cave, so packs drew
sunlight and shafts inside it. Iris 1.11.7 keeps both sides in its shadow
map (probe in #9033).

The pack shadow pipelines now draw both sides, and pack shadow views ask
for every face of their sections; Horizon's own packs keep the facing
selection.

Fixes #9033

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:52:38 +05:00
TheMrZZandCodex gpt-6.1-sol 6cd68ac305 Load summoned potion clouds into the runtime model
Retain loaded cloud timing, radius and potion settings, and keep live cloud identity during memory pruning. Add a loader contract and preserve a strict diagnostic for separate dose-phase and damage-source gaps.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 09:47:37 +01:00
Florian ERNST fcb7d020cb Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/axolotl-memory-ttl 2026-11-04 09:46:17 +01:00
Florian ERNST 96afb04b19 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/mob-effect-command-result 2026-11-04 09:40:33 +01:00
Florian ERNST acd5d78e64 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/parrot-saved-age 2026-11-04 09:38:01 +01:00
Arslan ArmanulyandClaude Opus 5.5 0d1c7706fc Shader packs: flat terrain reads each sprite's place in the block atlas from a table
Flat terrain draws now look a sprite layer's corner and side up in a table
(set 3, binding 2: x | y << 16, side) instead of working out a grid cell. The
atlas is still the grid, so packs see the same coordinates; the table lets the
atlas place sprites of different sizes next.

Part of #9037

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 13:34:26 +05:00
Florian ERNST d4d03d6219 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/parrot-saved-age 2026-11-04 09:33:06 +01:00
Florian ERNST e50732827a Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/turtle-summon-home 2026-11-04 09:31:03 +01:00
Florian ERNST 621f9496db Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/turtle-summon-home 2026-11-04 09:28:11 +01:00
TiggerTakkyTTTandCodex 9c92abadf9 Merge resolved simulation distance branch into entity range management
Carry the resolved management stack into the entity broadcast range PR while preserving all routes, port-ledger entries, and parity cases from current main.

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 15:25:24 +07:00
Ludovic MarieandClaude Opus 5.5 eb03a89c7c Server: the management protocol's server/stop ends the supervisor
Since #8245 a stop that doesn't come from the supervisor's stdin or the
control API leaves the supervisor in its stopped phase. The management
`server/stop` only set `stop`, so horizon-server kept running after it,
where vanilla's process exits (the management_protocol harness timed out).
It now shuts down as `POST /api/v1/stop` does, through one helper.

Fixes #9027

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 09:23:49 +01:00
Florian ERNST 3382af51d8 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/command-cart-loaded-success 2026-11-04 09:23:15 +01:00
Florian ERNSTandCodex 35af5c3a6c Merge remote-tracking branch 'fork/fix/command-cart-loaded-success' into fix/command-cart-loaded-success
Co-Authored-By: Codex <[email protected]>
2026-11-04 09:23:09 +01:00
LokiandAntigravity d237664ed1 mobs: exclude wither from sweet berry bush and powder snow slowdown (fixes #8785)
In Vanilla Java Edition 26.3, WitherBoss.makeStuckInBlock overrides Entity.makeStuckInBlock as an empty NO-OP.
While Cobweb (WebBlock) already ignored Wither alongside Spiders, Wither was still getting stuck in and having its fall distance reset by Sweet Berry Bushes and Powder Snow.

This aligns effects_from_blocks with vanilla Java bytecode:
- Wither is immune to slowdown and fall distance resets from sweet berry bushes and powder snow.
- Wither still takes sweet berry bush damage when moving in mature bushes.
- Powder snow snowflake particles continue to spawn when moving.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 17:19:46 +09:00
Florian ERNST 632746dbb7 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/noai-animal-love 2026-11-04 09:17:21 +01:00
Florian ERNSTandCodex 6588538fe8 Merge remote-tracking branch 'fork/fix/noai-animal-love' into fix/noai-animal-love
Co-Authored-By: Codex <[email protected]>
2026-11-04 09:17:15 +01:00
Florian ERNST 2354aeaf29 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/noai-sulfur-cube-fuse
Co-Authored-By: Codex <[email protected]>

# Conflicts:
#	tools/command_exec_corpus.py
2026-11-04 09:09:22 +01:00
SenjinandClaude Opus 5.5 c0d5806f45 Shader packs: End portal faces reach the shadow pass with their block entity
The shadow pass drew a block entity's plain faces but left out its End portal or gateway faces
(the portal run of #8361). A pack's shadow pass sees block entities whole, so the portal faces
cast too, with end_portal.png bound as for the gbuffers draw; beams still do not cast.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 09:08:33 +01:00
SenjinandClaude Opus 5.5 04104943fb Shader packs: a glow item frame and what it holds draw as glow_item_frame
Every frame's span was `item_frame`, so a pack's entity.properties could not tell glow frames
(and the item or map in them) from plain ones. Each frame, and the item or map it holds, now
takes its own frame's type.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 09:06:11 +01:00
SenjinandClaude Opus 5.5 b711d2336d Packs: a decimal pack format reads as its whole part
`Codec.INT` reads a JSON number through `Number.intValue`, so a pack.mcmeta with
`"min_format": [97.1]` declares format 97 and is compatible. Horizon rejected the decimal,
so such packs (Patrix 26.3) were listed as incompatible.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 09:05:55 +01:00
Florian ERNST 018098927c Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/allay-pocket-inventory 2026-11-04 09:03:18 +01:00
Florian ERNST 25bf0ae04d Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/allay-invalid-brain-retention 2026-11-04 08:58:58 +01:00
Florian ERNST ba06ac2671 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/hurting-projectile-float-inertia 2026-11-04 08:56:33 +01:00
Florian ERNST 22488873a6 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/hurting-projectile-float-inertia 2026-11-04 08:53:35 +01:00
Florian ERNST e52df2a4e7 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/tnt-block-state-codec 2026-11-04 08:47:30 +01:00
Florian ERNST a9e26418ad Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/marker-data-mutation 2026-11-04 08:42:50 +01:00
Florian ERNST f7f0e04f7a Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/axolotl-hunting-memory 2026-11-04 08:38:25 +01:00
Florian ERNSTandCodex c152d0c7dd Keep the remaining sulfur contact scope visible in the ledger
Co-Authored-By: Codex <[email protected]>
2026-11-04 08:36:16 +01:00
Florian ERNST 4d79106840 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/sulfur-cube-mob-contact 2026-11-04 08:32:58 +01:00
Florian ERNSTandCodex 3ab97d81fb Make sulfur contact fixtures independent of test order
Co-Authored-By: Codex <[email protected]>
2026-11-04 08:30:05 +01:00
Florian ERNST a631a2c4fc Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/sulfur-cube-mob-contact
# Conflicts:
#	tools/command_exec_corpus.py
2026-11-04 08:20:28 +01:00
Florian ERNST 71efcb3bd7 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/sulfur-cube-mob-contact 2026-11-04 08:17:51 +01:00
Florian ERNSTandCodex a43750547f Apply hot sulfur cube contact damage to mobs
Co-Authored-By: Codex <[email protected]>
2026-11-04 08:17:50 +01:00
TheMrZZandCodex gpt-6.1-sol d5ca800e8f Accept fresh mob hits with full Resistance
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 08:08:12 +01:00
Florian ERNSTandCodex 70bfeb0b45 Tick sulfur cube fuses independently of NoAI
Co-Authored-By: Codex <[email protected]>
2026-11-04 07:59:50 +01:00
Arslan ArmanulyandClaude Opus 5.5 0625049299 Shader packs: a Clear = false buffer starts each frame from its last write
Every frame now starts with texture A as each colortex buffer's "main". A
buffer that isn't cleared and ended the last frame on B has B copied to A
first; B keeps what was last written into it. So where a program leaves
pixels alone (discard) or blends, the texture it writes holds what Iris
1.11.7 shows there, measured with probe packs of our own: the previous
frame's last write in #9009's case 1, one accumulation per frame for a
buffer only blended onto, and 0 where a buffer flipped twice a frame was
never written.

Fixes #9009

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:58:15 +05:00
Arslan ArmanulyandClaude Opus 5.5 67191f028a Shader packs: light Nether faces at the Nether's up-face 0.9, as Iris does
With a shader pack lighting the faces (`oldLighting` off), the mesher gave every block and fluid
face 1.0. Iris 1.11.7 gives our probe pack 0.9 (229 of 255) on every Nether face, lava sides
included, and 1.0 in the overworld. These values fit the dimension's up-face value of vanilla's
CardinalLighting (NETHER up 0.9, DEFAULT up 1.0); that is a fit of our probe's Iris values, not an
established Iris rule. Fluid sides under a pack drop vanilla's side factor.

Fixes #9012

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:53:46 +05:00
Ludovic MarieandClaude Opus 5.5 bea9bad765 Drop blank lines left in the command corpus group list
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:57 +01:00
Ludovic MarieandClaude Opus 5.5 b26016c7d0 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:43 +01:00
Ludovic MarieandClaude Opus 5.5 35122d81fb Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:43 +01:00
Ludovic MarieandClaude Opus 5.5 02f5c497a9 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:42 +01:00
Ludovic MarieandClaude Opus 5.5 6bab8124e1 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:42 +01:00
Ludovic MarieandClaude Opus 5.5 b566311709 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:42 +01:00
Ludovic MarieandClaude Opus 5.5 0ab6729535 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:42 +01:00
Ludovic MarieandClaude Opus 5.5 dd1fc46290 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:42 +01:00
Ludovic MarieandClaude Opus 5.5 1ade779174 Merge #8940's marker pig fix
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:42 +01:00
Ludovic MarieandClaude Opus 5.5 765697c19b Clear leftover pigs before the marker group's pig count
The entities group leaves pigs loaded, so the single-pig check saw 3 when it
runs after it, as it does in the full corpus.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 07:51:35 +01:00
Arslan ArmanulyandClaude Opus 5.5 b9fdf62763 Shader packs: a custom uniform naming what Iris can't see reads 0 whole
In Iris 1.11.7 a `uniform.*`/`variable.*` expression that names
something its custom uniforms don't know fails as a whole and reads 0,
even when the name is in an `if` branch that isn't taken, and so does
every uniform or variable that uses it (probe packs of our own). Horizon
read only the name as 0 and kept the rest, and saw fogStart, fogColor
and eight more built-in names that Iris's custom uniforms don't.

Custom now works out, on its first evaluation, which items name an
unknown or hidden name, directly or through another item, and gives
them 0. Two names Iris sees that Horizon doesn't give yet
(currentRenderedItemId, lightningBoltPosition) still read 0 alone.

Fixes #8995

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:34:10 +05:00
Florian ERNSTandCodex 9f5d8f7992 Respect numeric array conversions and edit acceptance
Co-Authored-By: Codex <[email protected]>
2026-11-04 07:22:44 +01:00
Arslan ArmanulyandClaude Opus 5.5 5b58cd736e Shader packs: without gbuffers_clouds, draw the clouds with gbuffers_textured, else gbuffers_basic
A pack without `gbuffers_clouds` left the clouds to the game, so they never reached the pack's
buffers. Iris 1.11.7 draws them with `gbuffers_textured`, else `gbuffers_basic` (not
`gbuffers_textured_lit`), blended, as its ShaderDoc's gbuffers table lists and as our probe packs
measure.

Fixes #9011

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:22:28 +05:00
Arslan ArmanulyandClaude Opus 5.5 47b400625f Shader packs: a forced flip of a buffer the program writes cancels its flip
`flip.<program>.<buffer> = true` on a buffer the program also writes was
ORed with the write's own flip, so the buffer flipped once. Iris 1.11.7
leaves it unflipped (probe packs of our own): a later program samples the
side the program didn't write (0 for a cleared buffer), and a blended
write lands on what the program wrote (0.25 + 0.5 reads 0.749). The
forced flips are now XORed with the written ones.

Fixes #9010

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 11:21:01 +05:00
Arslan ArmanulyandClaude Opus 5.5 e317145573 Shader packs: match iris.features names in any case, as Iris does
Iris 1.11.7 accepts the names of `iris.features.optional` and
`iris.features.required` in any case, and defines `IRIS_FEATURE_<name>`
with the name as the pack wrote it (probe packs of our own). Horizon
compared them with its uppercase list exactly, so `ssbo` defined nothing
and `compute_shaders` as required was noted as unsupported. The
HIGHER_SHADOWCOLOR check now matches in any case too.

Fixes #8996

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:58:28 +05:00
Arslan ArmanulyandClaude Opus 5.5 c4e0dd1600 Shader packs: a pack with an integer colortex0 loads
The world blit wrote its float colour into colortex0 whatever its
format, so a pack giving colortex0 an integer format (RGBA32UI, Arc
v0.15.2) failed wgpu's pipeline validation and was turned off. An
integer colortex0 now gets only what the pack writes: the blit leaves it
out (still copying depth and clearing the other buffers), it is cleared
in a pass of its own, and a pack without `final` gets a cleared screen
instead of a float copy of it.

Fixes #8459

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:58:00 +05:00
Arslan ArmanulyandClaude Opus 5.5 ab7164bba1 Shader packs: a const local may be a texel offset
A `const` local whose initializer is a constant expression is a
constant expression itself (GLSL 4.60, 4.3.3, as #8462 quotes it).
naga's GLSL frontend kept it as a local variable, so a texel offset
given by one failed with "Sample offset must be a const-expression". It
now becomes a module constant, as a global `const` does, also inside a
loop.

Fixes #8462

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:57:52 +05:00
TheMrZZandCodex gpt-6.1-sol 8a05c5c899 Default loaded sulfur cube health after applying size
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 06:55:25 +01:00
Florian ERNSTandCodex 0cff71db71 Use registered projectile UUIDs in command NBT views
Co-Authored-By: Codex <[email protected]>
2026-11-04 06:53:50 +01:00
Arslan ArmanulyandClaude Opus 5.5 72ca9db476 Shader packs: .length() of a sized buffer member as an array size
`.length()` of an explicitly sized array is a constant expression
whether or not the array is (GLSL 4.60, 4.3.3, as #8463 quotes it). In
a constant context (an array size) naga's GLSL frontend lowered the
array itself there and failed with "Unexpected runtime-expression"; the
array is now lowered outside it, for its type only.

Fixes #8463

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:52:03 +05:00
Arslan ArmanulyandClaude Opus 5.5 6d40db2be5 Shader packs: read shadowtex0/1 linearly through a plain sampler, as Iris does
Iris 1.11.7 filters shadowtex0 and shadowtex1 linearly when a program reads
them through a plain sampler2D (probe packs of our own: a quarter texel past a
texel's centre moves a quarter of the way to its neighbour, in gbuffers and in
composite), unless the pack sets shadowtex0Nearest, shadowtexNearest or
shadow0MinMagNearest (shadowtex0), or shadowtex1Nearest or
shadow1MinMagNearest (shadowtex1). Horizon read the nearest texel, so a lookup
next to a block's edge took the higher block's depth and shadowed faces Iris
lights, with stripes on far terrain.

wgpu filters a depth format only in a comparison, so a shadowtex read that way
now reads an R32Float copy of the final depth (copied through a buffer after
the shadow pass's terrain), with the linear sampler, on devices with
FLOAT32_FILTERABLE. Others keep the nearest texel.

Part of #8464

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:46:14 +05:00
HrafnaValkyrjaandClaude Sonnet 5.5 b723f5e5c5 Merge origin/main into fix/portal-parity
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 00:40:21 -05:00
Florian ERNSTandCodex 824af41138 Clear stale sleeping position from awake villager saves
Co-Authored-By: Codex <[email protected]>
2026-11-04 06:35:11 +01:00
Arslan ArmanulyandClaude Opus 5.5 c218bbe9a0 Shader packs: atomic functions on a component of a buffer block's vector
GLSL 4.60 8.11 lets an atomic memory function take a single component
of a vector in a buffer, as RenderPearl's hand light does with
atomicAdd(hand_light.right.x, rg); naga has no atomic vectors, so the
stage failed with "Pointer to atomic is invalid" and was skipped.

The rewrite that turns such shared vectors into arrays of components
now also covers members of std430 buffer blocks (named or not), in
graphics stages as in compute ones. The block keeps its std430 layout:
a padding member where the vector's alignment moved it, and four
elements per 3-component vector of an array. Other layouts, runtime-
sized arrays and blocks whose layout isn't known up to the vector stay
as written.

Fixes #8977

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:31:55 +05:00
Florian ERNSTandCodex 8cac78d7fa Turn waking villagers toward their bed and reset pitch
Co-Authored-By: Codex <[email protected]>
2026-11-04 06:23:24 +01:00
NerfPhaselockandClaude Opus 5.5 1a3125fcfd Renderer: GPU culling draws only the sides that face the eye
A GPU frame drew every solid quad of a section it kept and left the back
faces to the rasterizer, where a CPU frame draws only the runs of buckets
that can face the eye (Sides::visible_from, its MARGIN included). Each
record now carries where its buckets end and what visible_from compares
the eye with, and the tables the eye less each origin, both as u32 keys
that order as those floats do, so cull compares integers and writes a draw
per run, at most four per record.

A mesh sorted for two passes, which GPU culling never meets, keeps one run
of all its quads, and a frame whose eye less an origin is NaN is the CPU's.
Regions hold four draws per record, and cull counts the kept sections
itself.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 00:20:10 -05:00
Arslan ArmanulyandClaude Opus 5.5 a64ff0e803 Shader packs: min3, max3 and mid3 of GL_AMD_shader_trinary_minmax
A stage that enables GL_AMD_shader_trinary_minmax gets the extension's
min3, max3 and mid3 (genType, genIType and genUType overloads: the
per-component minimum, maximum and median of x, y and z) in its prelude,
for each one it calls and doesn't define itself. naga has none of them,
so such programs were skipped with "Unknown function 'max3'".

Fixes #8460

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 10:07:59 +05:00
Florian ERNSTandCodex e3e7502659 Omit supplied Age from slime and magma cube saves
Co-Authored-By: Codex <[email protected]>
2026-11-04 06:02:32 +01:00
martipopsandClaude Opus 5.5 84e3266fcc Controllers: one input path for pad and keyboard; focus menus, crosshair inventories
Input parity (pad and keyboard reach the game the same way):
- Held keys record which device holds them; splitscreen's keyboard
  handoff releases only the keyboard's, so the other player's controller
  keeps its holds.
- The pad sends Shift and Ctrl through the keyboard's modifier state, so
  the stack throw in inventories throws the whole stack again.
- Every bindable controller action has an explicit route, checked by a
  test; screenshot and smooth camera work from a controller binding, and
  pad look counts for the tutorial and zoom like the mouse.
- tools/pad_parity_e2e.py and headless Game tests compare pad and
  keyboard outcomes.

Menus and inventories:
- Menus use focus; inventories, containers and the screens whose
  clickable parts have no focus targets use a crosshair pointer centred on
  the point it clicks. The Menu Navigation option and the right stick
  click's swap are removed.
- A snapping engine settles the pointer: a glide onto the nearest slot,
  flicks commit to the neighbour they head for, the D-pad hops a slot.
- Pointer speed, scroll speed and navigation repeat settings per player.
- Focus starts in vanilla's tab order, returns to the widget a screen was
  left on, and only one widget is ever highlighted.
- Only a new press or real stick movement takes input from the mouse; a
  screen, focus or keyboard change releases held buttons until let go.



Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp81zX4YZ9N1mLLAwzKH
2026-11-03 23:56:48 -05:00
Florian ERNSTandCodex c54af83a72 Fix mob attribute reads for transient baby speed
Co-Authored-By: Codex <[email protected]>
2026-11-04 05:53:29 +01:00
TheMrZZandCodex gpt-6.1-sol 6351cb3e1d Report function results for return fail
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 05:45:23 +01:00
Ludovic MarieandClaude Opus 5.5 933ed06f02 Merge the corpus stack below #8970 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:06 +01:00
Ludovic MarieandClaude Opus 5.5 1ef25329f9 Merge the corpus stack below #8968 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:05 +01:00
Ludovic MarieandClaude Opus 5.5 2f6f693c7b Merge the corpus stack below #8965 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:03 +01:00
Ludovic MarieandClaude Opus 5.5 815cada227 Merge the corpus stack below #8959 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:02 +01:00
Ludovic MarieandClaude Opus 5.5 ebcd2d2ba0 Merge the corpus stack below #8956 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:01 +01:00
Ludovic MarieandClaude Opus 5.5 81a99d60f0 Merge the corpus stack below #8953 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:01 +01:00
Ludovic MarieandClaude Opus 5.5 21dba43d9f Merge the corpus stack below #8949 (command_exec_corpus.py groups kept from both sides; axolotl memories: #8949's boolean hunting memory and #8860's integer TTLs both kept)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:43:00 +01:00
Ludovic MarieandClaude Opus 5.5 31167b1290 Merge the corpus stack below #8943 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:52 +01:00
Ludovic MarieandClaude Opus 5.5 306fdba861 Merge the corpus stack below #8940 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:52 +01:00
Ludovic MarieandClaude Opus 5.5 5ebc5279dc Merge the corpus stack below #8922 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:51 +01:00
Ludovic MarieandClaude Opus 5.5 db38db7622 Merge the corpus stack below #8909 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:51 +01:00
Ludovic MarieandClaude Opus 5.5 1a1887f52e Merge the corpus stack below #8888 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:50 +01:00
Ludovic MarieandClaude Opus 5.5 5101d519d2 Merge the corpus stack below #8860 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:50 +01:00
Ludovic MarieandClaude Opus 5.5 c1a8542360 Merge the corpus stack below #8878 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:50 +01:00
Ludovic MarieandClaude Opus 5.5 858c0a8de1 Merge the corpus stack below #8847 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:49 +01:00
Ludovic MarieandClaude Opus 5.5 9f666f514a Merge the corpus stack below #8841 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:49 +01:00
Ludovic MarieandClaude Opus 5.5 1301409191 Merge the corpus stack below #8821 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:48 +01:00
Ludovic MarieandClaude Opus 5.5 c785e69eda Merge the corpus stack below #8799 (command_exec_corpus.py groups kept from both sides)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:41:48 +01:00
Florian ERNSTandCodex 1d4869bf83 Use native suffocation predicates and shapes in mob base ticks
Bind the existing registry to shared collision tables and resource/world tags, then test the thin eye box against the actual suffocating shape. Keep probe worlds and partial-block integration controls on the runtime binding.

Co-Authored-By: Codex <[email protected]>
2026-11-04 05:40:18 +01:00
Arslan ArmanulyandClaude Opus 5.5 0127a87ccf Shader packs: expand macros in custom uniform and variable lines
Iris 1.11.7 reads a custom uniform's or variable's line in
shaders.properties with the macros expanded: the pack's options, the
standard macros and the file's own #defines (function-like ones too),
in the name as well as the expression (probe pack of our own). Horizon
read them raw, so an option such as RenderPearl's FOG read 0.

#define in .properties files now goes through the preprocessor's own
parsing (Macros::define_line), so function-like macros work there too.

Fixes #8976

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 09:39:10 +05:00
TheMrZZandCodex gpt-6.1-sol e6fa96799f Enable large-fireball explosion fire with mob griefing
Pass the rule's fire flag from the impact arm while preserving the shared
non-fire explosion default. Cover bounded enabled/disabled and TNT scenes,
with explicit fire-presence gates and unmodified raw count diagnostics.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 05:14:41 +01:00
LokiandAntigravity 08187d1377 mobs: suppress Guardian and Elder Guardian flop sounds when silent (fixes #8933)
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 13:14:18 +09:00
Florian ERNSTandCodex f65b7642e7 Pin remote fireball power test to widened float inertia
Use the exact Java value instead of the stale double-literal expectation; preserve the original motion behavior and strengthen the assertion.

Co-Authored-By: Codex <[email protected]>
2026-11-04 05:13:21 +01:00
Florian ERNST cfb2db51a7 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/hurting-projectile-float-inertia 2026-11-04 05:10:47 +01:00
Florian ERNSTandCodex caf3d88be6 Retain Allay liked-player memory after failed Brain loads
Preserve the live modeled liked-player fields when a whole-Brain data edit omits or invalidates the required memories map. Keep valid empty-map and individual-key clears working.

Co-Authored-By: Codex <[email protected]>
2026-11-04 05:09:20 +01:00
ElyTheCat c7d577e4c8 Optimize creature and projectile tick hot paths
Reuse the mob world snapshot without cloning per mob, skip unloaded work before nearest-player and difficulty queries, cache difficulty by chunk, and avoid guest-target allocations when no LAN guests exist. Empty projectile ticks now flush queued effects and return without scanning mobs or constructing projectile state.\n\nCo-Authored-By: OpenAI Codex <[email protected]>
2026-11-04 07:35:05 +03:30
Florian ERNSTandCodex 0ec74c25b5 Widen hurting-projectile float inertia before scaling motion
Use Java float inertia values in the shared helper, retain its existing branch order, and pin exact motion with focused dry, submerged and custom-acceleration controls.

Co-Authored-By: Codex <[email protected]>
2026-11-04 04:54:51 +01:00
LokiandAntigravity f32999331f game: guard container open and close game events by opener count edge (fixes #8957)
In vanilla Java 26.3, `GameEvent.CONTAINER_OPEN` and `CONTAINER_CLOSE`
are fired exclusively when `ContainerOpenersCounter.openerCountChanged`
detects an opener count transition across zero (0 -> 1 on open, 1 -> 0
on close). Spectators never join `ContainerOpenersCounter`, and
subsequent openers on an already open container do not re-trigger edges.

Guarding `post_block_game_event` with `if first` and `if last` prevents
spectators from emitting vibrations that alert sculk sensors and wardens.

Co-Authored-By: Antigravity <[email protected]>
2026-11-04 12:48:30 +09:00
Florian ERNSTandCodex b97c098c23 Decode primed TNT block state before saving
Keep the decoded state on the TNT model and encode it in command, chunk and portal save views. Invalid inputs fall back to the default TNT state.

Co-Authored-By: Codex <[email protected]>
2026-11-04 04:41:22 +01:00
TheMrZZandCodex gpt-6.1-sol 9c63f70f62 Load validated mannequin pose for live eye anchoring
Set the mannequin's live avatar pose from its saved lowercase value,
using the five accepted poses and standing for absent or invalid inputs.
Keep existing dimension math and raw saved-field writing unchanged.
Cover exact native eye anchors, UUIDs, body positions and invalid controls.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 04:33:23 +01:00
Florian ERNSTandCodex a96240dcfb Keep Allay pocket stacks separate from held filters
Load and save the independent one-slot inventory, preserving stack components and item limits. Route existing inventory callers through that state.

Co-Authored-By: Codex <[email protected]>
2026-11-04 04:26:12 +01:00
Cheezer1656andClaude Opus 5.5 6ebcff17a2 Native mods: native_send_packet sends a play packet to the server
A client mod can now send any serverbound play packet, a plugin channel's
custom payload included, to any server, a vanilla one too. mod_api
re-exports the packet type as ServerboundPlayPacket. An error before the
player is in a server's play state, and for a custom payload past
ServerboundCustomPayloadPacket.MAX_PAYLOAD_SIZE.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011R7zRSugxibySUu8PosW5U
2026-11-03 20:17:07 -07:00
Loki e8794c917c Merge remote-tracking branch 'origin/main' into fix/8934-elder-guardian-home 2026-11-04 12:14:42 +09:00
LokiandAntigravity f77fd4fbed mobs: set Elder Guardian wander restriction on ai.home (fixes #8934)
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 12:14:12 +09:00
ElyTheCat abb852a057 Merge branch 'main' into perf/entity-occlusion-culling-pipeline 2026-11-04 06:37:50 +03:30
ElyTheCat df94a9512f Change OpenGL version in README
Updated the OpenGL version in the README from 4.6 to 4.2.
2026-11-04 06:34:36 +03:30
Florian ERNSTandCodex eacb3e16a8 Preserve axolotl hunting memory values and lifetimes
Co-Authored-By: Codex <[email protected]>
2026-11-04 03:56:54 +01:00
Nikita Maximilian Grimm 0d4e0a1b1d Merge remote-tracking branch 'origin/main' into offscreen-hdr 2026-11-04 03:48:24 +01:00
Delete228andCodex 8c943fb1ac Refresh damage definitions when opening another world
Keep removed definitions frozen in the current session and rebuild the next catalog from current resources. Cover resource removal, reload and fresh-world behavior.

Co-Authored-By: Codex <[email protected]>
2026-11-04 05:48:23 +03:00
Nikita Maximilian Grimm e96694c5e8 Merge remote-tracking branch 'origin/main' into translucent-batching-checkbox
# Conflicts:
#	crates/horizon-client-render/src/graphics_ui.rs
2026-11-04 03:48:10 +01:00
Delete228andCodex 4ad8604280 Load world damage definitions and reload their frozen registry tags
Preserve ordinary player damage, enchantment predicates and synchronized registry data for enabled resource/data packs. Validate definitions before publication and keep world definitions stable through reload.

Co-Authored-By: Codex <[email protected]>
2026-11-04 05:40:37 +03:00
TheMrZZandCodex gpt-6.1-sol 16b49d2810 Dispatch damage commands to dropped items
Apply item Health damage with stack resistance and source eligibility,
retain its display name for lethal feedback, and reuse destroyed contents.
Cover real command results, Health, resistance, and selective removal.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 03:30:53 +01:00
GabrielNichols f11f2f3736 Merge commit '367b20268dacb3c6fc2b73af18f5e5439ffd9045' into codex/port-pack-harness-verdict 2026-11-03 23:25:40 -03:00
GabrielNichols 699fde5c01 Merge upstream/main into codex/native-cache-cleanup-8028
Preserve canonical launcher translations and add cache messages to the newly supported locales.
2026-11-03 23:25:06 -03:00
ElyTheCatandOpenAI Codex 70411290d6 Optimize entity occlusion before model generation
Add conservative AABB prechecks, a non-blocking two-stage async ray and plane pipeline, and reusable voxel/proof caches for dense scenes. Keep synchronous culling on the exact mesh path, expose an async-only threshold, and add focused tests plus release/OpenGL benchmark evidence for issue #7549.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-04 05:54:46 +03:30
Florian ERNSTandCodex 9f8dc4e9f2 Apply marker data mutations to existing entities
Co-Authored-By: Codex <[email protected]>
2026-11-04 03:23:15 +01:00
Cheezer1656andClaude Opus 5.5 9df8b23636 Native hooks: ClientChatReceivedEvent (client_chat_received)
A client mod is told of each chat line from the server just before it
shows, and may replace or cancel it: system messages (chat and action
bar), player chat and disguised chat. Chat Delay's queue now keeps each
message's sender and chat type, so the hook fires when the message
leaves the queue, as vanilla's showMessageToPlayer does, and a
cancelled message lets the next one through.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 19:20:30 -07:00
Cheezer1656andClaude Opus 5.5 332f192412 Native mods: native_send_chat sends chat or a command to the server
A client mod sends what the player could type, through send_chat
(ClientPacketListener.sendChat / sendCommand): a leading / is a command,
anything else signed chat. No client chat event and no chat history. An
error when not in a server's play state, for an empty message, and when the
Chat option blocks the message.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 19:15:51 -07:00
deleteandCodex 77bb0af3b1 Exercise generated world canvas dispatch with shared draw state
Add the world canvas fixture type to workspace dispatch tests and verify ordered mutation through the generated event.

Co-authored-by: Codex <[email protected]>
2026-11-04 05:01:36 +03:00
TheMrZZandCodex gpt-6.1-sol 993d04b098 Resolve loaded projectile origin owners by UUID
Fall back to the retained Owner UUID for execute on origin when there is
no mob/player binding. Preserve existing player, guest and mob bindings.
Cover loaded stand/pig owners and unknown/absent owner controls.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 02:53:22 +01:00
JanandClaude Sonnet 5.5 275856c8bb Native mods: a dying player's inventory goes through native_loot_drops (#8919)
Player.dropEquipment: after vanishing-curse items are gone and before the
rest is thrown, the mods may add to, change or take out the stacks. The
context names the player (entity id, minecraft:player), no table. Not
heard with keep_inventory, as vanilla keeps the items then. No new hook:
an API fragment extends native_loot_drops.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 02:48:23 +01:00
Nikita Maximilian GrimmandClaude Opus 5.5 19942359aa HDR: screenshots from an --offscreen run, without an HDR display
`HORIZON_HDR_OUTPUT=pq` (or `scrgb`) now offers that output to an
`--offscreen` renderer, which has no display to ask. HDR is drawn as with
a window (the 16-bit frame included) and a screenshot writes its `-hdr.png`
beside the SDR one; nothing is presented, and Auto takes 203 and 1000 nits.
A 16-bit frame with HDR off reaches the offscreen target as it reaches a
swapchain. Nothing changes without the variable or with a window.

Fixes #8879

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 02:44:01 +01:00
deleteandCodex c0591d4215 Support tickable sounds owned by compiled native code
Apply owner-updated gain, pitch and position through ordinary sound-engine channels without attaching vanilla entity or music behavior. Verify paused updates, stop and release with the channel backend.

Co-authored-by: Codex <[email protected]>
2026-11-04 04:33:04 +03:00
deleteandCodex 4df6818e3a Add native world text and atlas quad canvas
Submit camera-relative world overlays independently of block entities, using normal depth-tested atlas and font rendering. Preserve RGBA quads and draw eight-offset outlined text with the existing polygon-offset front pass.

Co-Authored-By: Codex <[email protected]>
2026-11-04 04:32:59 +03:00
MegaBytesMe bc97e77d55 Merge remote-tracking branch 'upstream/main' into feat/windows-arm64-support
# Conflicts:
#	launcher/Cargo.toml
2026-11-04 01:29:10 +00:00
Florian ERNSTandCodex 8786783616 Reject special recipes in recipe commands
Co-Authored-By: Codex <[email protected]>
2026-11-04 02:26:17 +01:00
a9db1d3291 Shulker: ease a teleport with vanilla's 6-tick render offset
A client shulker that changed block only slid with ordinary position
interpolation. Shulker.getRenderPosition pulls the model back toward
the block it left for 6 ticks.

Fixes #8901

Co-Authored-By: Grok <grok-4.7> <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-11-04 02:25:24 +01:00
TheMrZZandCodex gpt-6.1-sol e46fdc8aba Emit landing game events for items and armor stands
Track item falls until ground contact and dispatch HIT_GROUND from both
item movement and the armor stand landing path. Cover real Game dispatch
with fresh sculk listeners and matched NoGravity controls.

Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-04 02:18:02 +01:00
ElyTheCat 946bfb559c Merge origin/main into entity occlusion culling pipeline
Keep the latest main particle culling and graphics settings alongside the shared synchronous and asynchronous entity occlusion path.\n\nCo-Authored-By: OpenAI Codex <[email protected]>
2026-11-04 04:41:47 +03:30
janibert1andClaude Sonnet 5.5 98f679bd84 Native mods: NativeServerPlayer carries the player's IP address
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-04 02:11:31 +01:00
Florian ERNSTandCodex e7db06bfca Count only changed mob effects in effect give
Co-Authored-By: Codex <[email protected]>
2026-11-04 02:09:25 +01:00
contactjayclattyandOpenCode 5470844f14 mobs: environmental damage does not dim a glow squid
Squid.hurtServer reports a hit only when a living entity caused it, and GlowSquid.hurtServer dims the squid only when that returns true. Horizon dimmed on every hit, so a glow squid on a campfire, suffocating or drying out lost its glow.

Fixes #8724

Co-Authored-By: OpenCode (Jay's Unintelligence) <[email protected]>
2026-11-04 08:43:44 +08:00
Florian ERNSTandCodex 414e7a5c07 Preserve TTL on axolotl integer brain memories
Co-Authored-By: Codex <[email protected]>
2026-11-04 01:42:54 +01:00
deleteandCodex c57982648b Merge current Horizon main for native mod API review
Resolve adjacent, independent mob test modules while preserving both test suites.

Co-authored-by: Codex <[email protected]>
2026-11-04 03:37:34 +03:00
HrafnaValkyrjaandClaude Sonnet 5.5 9ad64d72c3 Boats never take a Warden or Wither aboard, like Entity.canRide
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 19:31:05 -05:00
contactjayclattyandOpenCode 726cd34fb2 mobs: the wither takes no potion effect
WitherBoss.addEffect returns false, so nothing lands on the boss. Horizon refused only the wither effect itself (the WitherSkeleton rule), so splash potions, area clouds, /effect and tipped arrows all worked on it.

Both paths that add an effect now refuse every effect for the wither; the wither skeleton keeps its own rule.

Fixes #8776

Co-Authored-By: OpenCode (Jay's Unintelligence) <[email protected]>
2026-11-04 08:29:31 +08:00
Cheezer1656andClaude Opus 5.5 437f078e47 Dispatch tests: stub ClientboundPlayPacket for client_packet
The dispatch fixture compiles every generated hook against its own stubs;
client_packet names ClientboundPlayPacket, which it lacked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011R7zRWRvULWvkt2DSEw3VN
2026-11-03 17:27:37 -07:00
deleteandCodex a5f384f89c Discard native controlled particles on resource reload
Keep owned sprite-set indices from referring to a different declared particle after the resource sets are rebuilt.

Co-authored-by: Codex <[email protected]>
2026-11-04 03:25:14 +03:00
Florian ERNSTandCodex ea85770a6f Omit supplied parrot Age from saved NBT
Co-Authored-By: Codex <[email protected]>
2026-11-04 01:19:27 +01:00
Delete228andCodex 66c3284171 Expose controlled particle visibility without removing its state
Keep hidden controller ticks and population accounting while emitting no geometry, matching visibility-gated source particles.

Co-Authored-By: Codex <[email protected]>
2026-11-04 03:16:14 +03:00
contactjayclattyandOpenCode 4a4ec9a5ff mobs: a zero-duration effect is over, not infinite
LivingEntity.tickEffects drops an effect whose duration is 0, because MobEffectInstance.hasRemainingDuration is false there. Horizon counted it down from 0 to -1, the infinite-duration sentinel, so an effect loaded with duration 0 stayed forever and even took its first tick.

Fixes #8721

Co-Authored-By: OpenCode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 08:14:47 +08:00
Lyvt-DevandCodex 5db48acf25 Fix Warden collision pushability while emerging or digging
Fixes #8832

Co-Authored-By: Codex <[email protected]>
2026-11-04 01:11:15 +01:00
TiggerTakkyTTTandCodex 5657fabce7 Server API: pin backup replies
Add JSON golden cases for backup listing, creation, download, deletion, whole-world restore, dimension restore, and representative errors. Exercise streamed download bytes and normalize only the generated backup name.

Fixes #8839

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 07:07:49 +07:00
contactjayclattyandOpenCode 46387fb392 game: a wither never rides a minecart
WitherBoss.canRide returns false, so OldMinecartBehavior.pushAndPickupEntities cannot seat the boss in a moving cart. can_ride_cart excluded only the warden.

Fixes #8790

Co-Authored-By: OpenCode (DeepSeek V4.1 Flash) <[email protected]>
2026-11-04 08:07:20 +08:00
HrafnaValkyrjaandClaude Sonnet 5.5 1c57d808d8 Shadows: only blocks with a full collision shape take a mob shadow
A hopper, cauldron or composter reaches y=1 with its rim, so its collision
bounds looked like a full block and a mob shadow was drawn across the top.
EntityRenderer.extractShadowPiece draws a shadow piece only when the block
below isCollisionShapeFullBlock; the same gate now applies here.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 19:07:17 -05:00
HrafnaValkyrja ae59c5dde7 Merge remote-tracking branch 'origin/main' into fix/portal-parity 2026-11-03 19:06:51 -05:00
HrafnaValkyrja f4f901f2b1 Merge remote-tracking branch 'origin/main' into fix/carpet-dupe 2026-11-03 19:06:26 -05:00
Florian ERNST 7b47de35c3 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/noai-animal-love 2026-11-04 01:04:38 +01:00
Delete228andCodex d6700da628 Allow owned native particle tick controllers in the ordinary engine
Retain sprite sets, particle setting and distance gates, interpolation, lifetime helpers, blending and normal population limits. Verify owned clone state, movement, expiry, sprite frames and rendered alpha/layer.

Co-Authored-By: Codex <[email protected]>
2026-11-04 03:04:19 +03:00
HrafnaValkyrja c9ecd5d900 Merge remote-tracking branch 'origin/main' into fix/portal-parity
# Conflicts:
#	crates/horizon-client/src/game/dedicated.rs
2026-11-03 19:03:30 -05:00
HrafnaValkyrja 47b64a3288 Merge remote-tracking branch 'origin/main' into fix/carpet-dupe
# Conflicts:
#	crates/horizon-world/src/level/tests.rs
2026-11-03 19:03:30 -05:00
HrafnaValkyrjaandClaude Sonnet 5.5 6fabf6d69a Server API: list and read crash reports, and a panel section for them
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 19:02:42 -05:00
Florian ERNSTandCodex c600fea060 Tick animal love counters when AI is disabled
Co-Authored-By: Codex <[email protected]>
2026-11-04 01:02:24 +01:00
HrafnaValkyrjaandClaude Sonnet 5.5 ff02139e7f Panel: Ban and Pardon for players, and a list of banned players
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 19:01:29 -05:00
Cheezer1656andClaude Opus 5.5 2e669e614d Native mods: a client hook point that reads the server's play packets
native_client_packet hears every play packet the window's game handles,
decoded and in order, just before it is handled: handle_packet, plus the
chunk and ForgetLevelChunk paths in poll_remote that bypass it. It only
reads. mod_api re-exports the packet enum as ClientboundPlayPacket so
trait mods can name it.

Closes #8820

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 16:55:03 -07:00
TiggerTakkyTTTandCodex 50888d15bc Server API: pin lifecycle replies
Add reusable JSON golden cases for the stable status, metrics, logs, start, stop, and restart response contracts. Normalize only release, process, and elapsed-time values while checking their types.

Fixes #8816

Co-Authored-By: Codex (GPT-5.6) <[email protected]>
2026-11-04 06:51:24 +07:00
Delete228andCodex 31ce9b1787 Support named native mob damage types and combat classifications
Preserve custom source IDs through ordinary mob damage, protection, immunity, knockback and loot paths. Skip enchantment protection for bypasses_effects damage.

Co-Authored-By: Codex <[email protected]>
2026-11-04 02:48:49 +03:00
Florian ERNSTandCodex 154d703413 Resolve turtle summon home before applying command position
Co-Authored-By: Codex <[email protected]>
2026-11-04 00:44:43 +01:00
AfrobamaYTandClaude Opus 5.5 4f6a3fc20c Native mods: a camera to draw the world from (native_camera_pose)
A client mod can move and turn the camera a frame is drawn from: after
vanilla placed it, native_camera_pose gets its position, yaw and pitch and
may set them (a cinematic, replay or film camera). The picture, culling, the
sound's listener and what faces the camera follow it; the player, their aim,
the hand and chunk loading stay the player's. Values that aren't finite keep
vanilla's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 00:38:35 +01:00
Florian ERNST 312885da94 Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/command-cart-loaded-success 2026-11-04 00:34:47 +01:00
Florian ERNSTandCodex 7ff407670a Merge branch 'main' of https://github.com/horizon-rust/horizon into fix/command-cart-loaded-success
Co-Authored-By: Codex <[email protected]>
2026-11-04 00:32:57 +01:00
rdbtCVSandCodex gpt-6.1-sol b5635af3b1 perf(server): share item defaults while resource tables load
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-03 23:32:50 +00:00
Florian ERNSTandCodex 478bdb0d27 Reset loaded command minecart successes through command sync
Co-Authored-By: Codex <[email protected]>
2026-11-04 00:30:37 +01:00
rdbtCVSandCodex gpt-6.1-sol 6fd33b70f7 perf(server): measure fonts without building a texture atlas
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-03 23:13:50 +00:00
Nikita Maximilian GrimmandClaude Opus 5.5 c726e412a1 Translucent Batching: the box reads Off where the GPU can't batch, as its label does
Fixes #8759.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 00:13:16 +01:00
rdbtCVSandCodex gpt-6.1-sol 09dd6322b4 perf(server): skip custom item textures during item loading
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
2026-11-03 23:06:14 +00:00
itarqos5andClaude Opus 5 d934726850 test(lod bench): report how many spans a far column holds, not just the mean
`lod_grid_bench` printed a mean of 5.3 to 5.9 spans a column. A mean
alone says nothing about the shape of the distribution, which is what
decides whether a column's spans fit a fixed inline capacity or take a
heap block; sizing `Spans` off the mean would be a guess.

Print the distribution beside it, with a running total so the share that
fits any given capacity can be read straight off.

Measured at seed 0, 40 tiles a level, Full far detail: four spans or
fewer covers 38% of columns at level 0, 28% at level 2, 26% at level 4
and 32% at level 6, with a tail reaching 14 to 16.

These are the spans worldgen returns, before the client's `shade_sky`
splits fluid and leaf runs a light level at a time.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
2026-11-03 23:01:49 +00:00
itarqos5andClaude Opus 5 ec5b8b4c31 perf(far terrain): reduce a far chunk only to the cell width that reads it
A chunk's detail arrived as `ReducedColumns`, which reduced the 16x16
columns to cells 2, 4, 8 and 16 blocks wide up front: 85 columns beside
the chunk's 256, and four passes of `Column::reduced` over every span of
every footprint.

One chunk is covered by one far tile, so one of the four widths is what
gets read, and a chunk whose detail arrives while full-detail terrain
still draws it reads none. Building all four cost 405 us of a far
terrain thread for every chunk the world sent (measured over 200 chunks
of 5.7 spans a column, the mean `lod_grid_bench` reports); reducing on
the first read of a width costs 103 us, and nothing at all for a chunk
no far tile draws reduced.

`OnceLock` a level each, so a second mesh job reading the same width
takes the reduction already built rather than racing to repeat it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
2026-11-03 22:59:03 +00:00
Nikita Maximilian GrimmandClaude Opus 5.5 c1ab6def52 LAN test: fill the socket until the outbox waits, so the disconnect waits on Windows too
Windows takes a write of any size whole while its buffer has room, so the 64 MiB the test sent all went
through and the disconnect with it. Fixes #8734.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 23:37:05 +01:00
HrafnaValkyrja 2082bd559c Merge remote-tracking branch 'origin/main' into fix/entity-save-rest 2026-11-03 17:32:05 -05:00
Torin SappandClaude Opus 5.5 0954041bd5 Content: read every declaration through one reader
Content::check read each declared block, item, mob effect, creative tab,
entity type and menu with Content::file, which opens the mod's archive
again and parses its whole central directory, unbuffered, every time.
Loading a packaged mod was quadratic in its size. Checking chipped's
.hmod (6,981 blocks, 58,563 entries) took over 20 minutes; with one
reader it takes 2 seconds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 15:27:22 -07:00
degepie f23ab6c91e Merge remote-tracking branch 'origin/main' into fix/bare-ids 2026-11-04 03:16:59 +05:00
Stink-O 458199f845 Merge branch 'main' of https://github.com/ludovic111/horizon into native/far-terrain-edits 2026-11-03 22:13:16 +00:00
degepie 0daeda2021 Merge remote-tracking branch 'origin/main' into fix/throw-bucket-triggers 2026-11-04 03:12:26 +05:00
HrafnaValkyrjaandClaude Sonnet 5.5 4a30280e22 Let a finished moving piston support what is attached to it
A moving block at full progress now supports like the block it carries, as vanilla's
moving block entity does (getExtendedProgress is 0 at progress 1.0). Wall coral fans
and the carpets on them survived in vanilla when a slime column landed; here the fan
popped because the slime block behind it was still a moving piston, so the carpet
duplication machine had nothing to duplicate.

Fixes #8490

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 17:00:54 -05:00
Nikita Maximilian GrimmandClaude Opus 5.5 ee8c5b7b13 install.sh signature test: skip on Windows, where install.sh doesn't install
Fixes #8708.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 22:59:16 +01:00
CocofireHDandHermes 0a5b6efe10 Commands: /attribute resolves its target in the target's level
`cmd_attribute` looked the entity up in the command source's level only, while
a selector with no spatial constraint searches every loaded level
(`EntitySelector.findEntities`). A target in another dimension therefore came
back as an id the level did not hold: the generic path answered "Entity
entity. has no attribute <name>" and max_health, movement_speed and
attack_damage silently did nothing.

Run the attribute work with the target's level in place through the existing
`in_target_level` helper, the one /data, /kill and /tp already use.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 22:57:04 +01:00
HrafnaValkyrjaandClaude Sonnet 5.5 31ee745f7e Minecarts: chest and hopper carts use the container slowdown
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 16:47:47 -05:00
NerfPhaselock 6ea9e9608f Merge remote-tracking branch 'upstream/main' into perf/gpu-culling-g1b
# Conflicts:
#	crates/horizon-client-render/src/gfx.rs
2026-11-03 16:39:17 -05:00
Teiwazik 1c88130124 Merge remote-tracking branch 'origin/main' into fix/leave-server-sounds 2026-11-03 22:39:08 +01:00
ErikKalikasandClaude Opus 5.5 9841d6bffd Native mods: a mod menu type opened with extra data (openMenu with an extra-data writer)
A mod block opening its content mod's menu type asks native_menu_extra_data for bytes
of the mod's own; a horizon:menu_extra payload carries them with the window and the
block's position to Horizon clients right before OpenScreen. The client takes the
block from it instead of the last clicked one, and native_screen_pos /
native_screen_extra_data read both while the mod's screen is open.

Fixes #8675

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 23:28:50 +02:00
itarqos5andClaude Opus 5 e602ed84ac fix(heap census): read thread names off Windows, and name the loaders
`horizon-ctl memory` falls back to a thread's name for allocations made
outside any step, span or job. On Windows `thread_name` returned nothing
at all -- the stub for every platform without `pthread_getname_np` -- so
every untagged allocation on every thread fell into one `unnamed thread`
bucket. That bucket was 580 MiB of a playing game's 1219 MiB resident
set: the largest of all of them, and useless, because it named no thread.

Rust keeps the name `Builder::name` set in its own thread handle, so the
fallback reads it from there. No new dependency, and nothing new in a
release binary: `GetThreadDescription` would have wanted Windows 10 1607
and an import in every build, where this is the name Rust already holds.

The resource loaders had no name to read either: they are spawned through
`Step::spawn` and bare `scope.spawn`, which leave a thread unnamed. They
go through a new `named_scope`, which covers the eight core steps, the
five world-rest loaders and the panorama. The job pool already named its
workers, and so did far terrain.

What the same run reports now, where it reported one 580 MiB bucket:

| MiB | tag |
|---:|---|
| 328.2 | Far sampling |
| 76.4 | unnamed thread |
| 52.0 | Far decoration |
| 35.4 | Far terrain |
| 13.9 | Far meshing |
| 12.4 | World data preload |
| 8.2 | Worldgen loader |
| 8.0 | main |

Far terrain holds ~430 MiB of the heap across its four threads, beside
the 661 MiB of far-tile vertices already measured in video memory. None
of that was visible before.

The startup trace gains the same names: 13 of its 17 lanes read
`unnamed`, which a trace viewer cannot lay out usefully.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
2026-11-03 21:28:16 +00:00
HrafnaValkyrjaandClaude Sonnet 5.5 ba9dcb41e4 Move riders with a teleported vehicle, rotate without teleporting, dismount at source, add the player boarding cooldown
Entity.teleportPassengers: teleporting a boat, minecart or mob takes its mob,
player and guest riders along at once. /rotate is Entity.forceSetRotation:
rotation fields only, pitch clamped before the setter. Ender pearl and bed
entry stop riding at their source instead of at guest-turn teardown. Players
and guests get Entity.boardingCooldown (60 ticks) checked before boarding.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 16:27:07 -05:00
CocofireHDandHermes a1c4fa382e Mobs: a digging turtle sends the level event it is supposed to
`Turtle.aiStep` broadcasts LevelEvent 2001 while it digs; the handler drew the
particles into the local buffer instead, which a serving client never renders
and no client hears. Route it through `level_event_at(2001, ...)` like every
broken block does, so the sand crumbs and the break sound both follow.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 22:23:53 +01:00
CocofireHDandHermes c0cd050fac Mobs: a remote warden's digging particles reach the client
`Warden.clientDiggingParticles` is client work, but `client_tick` discarded the
`DigParticles` events it had already computed (`animate(m, &mut Vec::new())`),
and `sync::client_tick` had no sink to hand them to. Thread the game's event
vector through; it lands in `warden_dig_particles`, which draws the 30 block
particles from the block under the warden.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 22:21:42 +01:00
TeiwazikandClaude Opus 5.5 ca17074fd9 Older servers: nested items and the song flag in item components
- jukebox_playable: EitherHolder.streamCodec is either(holder, key), so
  its flag is set for a holder; it was read the other way round, and an
  item with a song by key broke the rest of its packet (on a 1.21.11
  server a whole container_set_content at login).
- container and use_remainder (and sulfur_cube_content) hold item
  stacks: their bytes were kept as the server sent them, in its item and
  component numbering, and before 26.1 as ItemStacks (a count first). Read
  from an older server, they are now read whole and written again as
  pinned templates.
- pot_decorations writes its items' pinned ids, and a value rewritten
  this way is read back in the pinned numbering.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
2026-11-03 22:20:51 +01:00
degepieandClaude Opus 5.5 dd3b316fc9 Advancements: looking_at sees every entity, in every trigger
PlayerPredicate.matches looks for the entity the player looks at
(ProjectileUtil.getEntityHitResult over every non-spectator entity but
the player, then hasLineOfSight) whatever the trigger. Horizon searched
only the mobs, and only for using_item, so a looking_at boat, minecart,
armor stand, frame, painting or crystal never matched, and nothing
matched in tick, location or any other trigger.

looking_at_entity now walks every entity list plus frames, paintings
and crystals, and player_entity sets looking_at for every trigger. The
per-tick tick and location triggers cast the ray only when one of their
criteria may ask (names looking_at or a reference predicate), worked
out once per tree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 02:10:03 +05:00
OpenAI Codex 11121fc59c Merge public main with visibility audit and span test isolation fixes
Co-Authored-By: Codex <[email protected]>
2026-11-03 16:08:24 -05:00
HrafnaValkyrja 28c50a1f0b Merge remote-tracking branch 'origin/main' into fix/entity-save-rest 2026-11-03 15:42:21 -05:00
huiandClaude Opus 5.5 d9692d81b1 pr_ready: run the Python checks beside cargo
The Python tests and the public-tree check only read files, yet cargo
waited for them: about two minutes of every run. They now run in a
thread beside rustfmt, clippy and the tests; the harnesses, which build
with cargo, still come after it, and --verbose keeps everything in
order so the output doesn't interleave.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 21:31:36 +01:00
OpenAI Codex e4b9dfa9a4 Format the split material census adapter
Co-Authored-By: Codex <[email protected]>
2026-11-03 15:23:32 -05:00
TeiwazikandClaude Opus 5.5 f725a92d3d Older servers: read item components whose layout changed
Three components kept their names but changed their stream codecs, so an
older server's item holding one failed to decode (with a vanilla 1.21.11
server: "1 extra bytes" and "NBT truncated" on container_set_slot):

- pot_decorations (26.3): a list of up to four item ids, a brick for a
  plain side, became four optional item stack templates;
- jukebox_playable (26.1): an EitherHolder, a flag then the song's key or
  holder, became a holder; a key has no pinned holder and is dropped;
- damage_resistant (26.1): a bare damage type tag became a HolderSet.

Two quirks say which a version has, and one reader (read_added) now
serves the slot, template and wire patch readers alike: it drops the
components the pinned version lacks, rewrites these three into the
pinned layout and reads the rest as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
2026-11-03 21:20:06 +01:00
degepieandClaude Opus 5.5 5d6752f7bd Potion and cloud kills and falling-block hits fire their advancement triggers
A harming splash potion or lingering cloud hurt mobs through Mob::hurt, a
player_attack from the player whoever threw it, and never recorded the thrower
in last_hurt_by_player, so on the server thread the kill reached nobody's
player_killed_entity. They now deal indirectMagic(potion or cloud, owner)
(HealOrHarmMobEffect.applyInstantaneousEffect), the thrower's kill when a
player threw it, and the mob keeps the potion or cloud as the killing blow's
direct entity (Mob::last_direct).

A falling block hitting the player now fires entity_hurt_player with the
falling block as causing and direct entity (FallingBlockEntity.causeFallDamage,
LivingEntity.hurtServer).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 01:18:33 +05:00
CocofireHDandHermes be258bc235 Mobs: a mob's particles reach the clients, not a headless buffer
`MobEvent::ParticleAt` was drawn with `self.particles.add` only, and a serving
client has no window: the squid's 30 ink particles never left the server. Send
them as packets like every other server-thread particle site; with count 0 the
packet's deltas are the particle's motion.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 21:12:25 +01:00
OpenAI Codex 797d6d0ae9 Count all split material textures in the new GPU census
Co-Authored-By: Codex <[email protected]>
2026-11-03 15:11:37 -05:00
TeiwazikandClaude Opus 5.5 7140f3cbd2 Older servers: read past the item components 26.3 dropped
26.3 removed the map_color and swing_animation data components. A 1.21.x,
26.1 or 26.2 server still sends them in item patches (an explorer map's
color, a 1.21.11 weapon's swing), and the item readers, which renumber
the component id to -1 there, could not read the value: the whole packet
was skipped, so a container holding such an item showed empty (seen on a
Folia 1.21.11 server: container_set_content, set_entity_data and
update_advancements undecodable).

The registry id generator now lists the components an older version has
and the pinned one lacks, and the item readers read their values past in
that version's layout (an int; two varints) and drop them, from added and
removed components alike. tooltip_display's hidden component ids are
renumbered as well, both ways.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
2026-11-03 21:10:39 +01:00
CocofireHDandHermes 61791d3e20 Mobs: a nautilus answers its own food, and a tame one is mounted
Neither nautilus had an arm in `remote_mob_takes`, so a tame adult's mount or
screen click fell through, and the generic `Animal.mobInteract` arm claimed any
`kind.food` item for an untamed one although `AbstractNautilus.mobInteract`
only takes `#minecraft:nautilus_taming_items` until it is tamed.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 21:10:34 +01:00
GabrielNichols 06f5846948 Merge remote-tracking branch 'upstream/main' into codex/port-pack-harness-verdict 2026-11-03 17:08:33 -03:00
CocofireHDandHermes 7d59356e42 Mobs: the golems answer a right click
`IronGolem.mobInteract` takes an iron ingot on a damaged golem and
`CopperGolem.mobInteract` takes an empty hand while it holds an item or shears
on its antenna; both were missing from the client's interaction check, so the
click fell through to the item and to the off hand.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 21:07:56 +01:00
GabrielNichols 012eaf84f0 Merge upstream/main into codex/native-cache-cleanup-8028
Keep upstream palette and locale fixes, preserving the native cache messages and safety behavior.
2026-11-03 17:00:43 -03:00
CocofireHDandHermes 13698219ce Mobs: an allay answers an empty main hand only
`Allay.mobInteract` returns SUCCESS for a holding allay only when the click
came from an empty main hand; everything else is PASS so the held item is
still used. The client answered a flat true whenever the allay held anything,
which swallowed the click, and the server's take-back path ignored the hand.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 20:57:02 +01:00
degepieandClaude Opus 5.5 02205a31e0 Advancement predicates read a bare id as a minecraft: one
A data pack that writes an id, tag or registry key without a namespace
("panda", "#planks", "the_nether", "inventory_changed") never matched:
the predicates compared the JSON string literally. Vanilla reads them
through Identifier codecs, where a missing namespace is minecraft.

`full` qualifies the id wherever a predicate compares one: id_matches
(plain id and #tag), the location dimension, entity sub-predicate keys,
effect keys, the loot condition type, the trigger name, the inventory and
recipe indexes, and recipe_unlocked / changed_dimension. damage_source's
tags[].id is a holder set (TagPredicate) and goes through id_matches.
The entity predicate's short key aliases follow from the qualification;
the non-vanilla "type" alias is gone (vanilla 26.3 refuses that key).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 00:56:01 +05:00
OpenAI Codex 6833688d8a Merge public main preserving material and RGB test modules
Co-Authored-By: Codex <[email protected]>
2026-11-03 14:53:12 -05:00
CocofireHDandHermes 710a2ad3a9 Mobs: an age-locked baby draws no happy villager particle
`AbstractHorse.handleEating` (and the llama and camel ones) gates both the age
up and the particle on `isBaby() && !isAgeLocked()`. The client's copy only
asked for `alive() && is_baby()`, so feeding an age-locked baby drew a particle
the server never agreed to.

The decision moves into `ages_on_food`, where the lock belongs.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 20:52:26 +01:00
valkdzxandClaude Opus 4.8 9ae15a3eac Elytra rocket boost: take the look vector from the float sine table
The firework glide boost in follow_own_player read the look direction from
super::view_vector (Rust f64 sin/cos), while the server derives the player's
rocket boost from getLookAngle()'s float sine table. travelFallFlying already
uses the table; this boost was the one path left on f64 trig, so boosting
under a rocket while gliding drifted the client off the server's position and
tripped the movement check. Use horizon_world::view_direction::view_vector
(the float table), matching the server and the rest of the glide. Covers the
remote path too, which now routes through follow_own_player.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-11-03 21:49:28 +02:00
TeiwazikandClaude Opus 5.5 faa8667704 Stop every sound when leaving a server
Leaving a multiplayer server goes from close_world straight to
leave_server, which never stopped the sounds: the biome's ambient loop
played on over the title screen until the next world. leave_server now
does what finish_close_world does for single player, as vanilla's
updateLevelInEngines(null) on Minecraft.disconnect: it clears the
ambience, lets the music manager go of its track and stops every sound.
A kick takes the same path.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
2026-11-03 20:49:25 +01:00
CocofireHDandHermes 2ef3c9d5f2 Mobs: a zombie horse eats a red mushroom, other horses do not
`AbstractHorse.isFood` is the `#minecraft:horse_food` tag and `ZombieHorse`
overrides it with `#minecraft:zombie_horse_food`; the food table was shared by
both, so a zombie horse refused its mushroom and a red mushroom in hand fell
through to a horse's `handleEating` and healed it.

The table moves into `eating(species, item)`, which applies the per-subclass
tag rule and the eat sound gains ZombieHorse's own. A tamed skeleton horse now
eats horse food too, as it inherits that tag (an untamed one still passes).

Co-Authored-By: Hermes <[email protected]>
2026-11-03 20:46:30 +01:00
HrafnaValkyrjaandClaude Sonnet 5.5 2a2d59e505 Keep /tp rotation on every entity kind (rotation on the Entity trait)
/tp <entity> x y z <yaw> <pitch> set the rotation only on mobs and players.
Items, orbs, TNT, falling blocks, rockets, armor stands, boats, minecarts,
projectiles and tridents dropped the pitch (and several the yaw), so
/data get Rotation read [0, 0] where vanilla reads [90, 10].

Entity gets rotation() and set_rotation() (Entity.teleportSetPosition:
setYRot, setXRot clamped to -90..90, setOldRot). The kinds with no rotation
field gain one (pitch for armor stands and boats), /tp calls the trait
method for every kind, and the entity saves write the field.

Part of #8024

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 14:42:19 -05:00
hildlabs 85e7b3bfb5 Merge remote-tracking branch 'origin/main' into shader-option-sliders
# Conflicts:
#	docs/api/shaders.md
2026-11-03 20:40:47 +01:00
CocofireHDandHermes 425c842a51 Mobs: a turtle carrying an egg refuses its food
`Turtle.canFallInLove` is `super.canFallInLove() && !this.hasEgg()`, and
`Animal.mobInteract` only eats when `getAge() == 0 && canFallInLove()`. The
food gate only looked at age and the love timer, so an egg-carrying turtle ate
seagrass, entered love mode and played the heart particles.

Co-Authored-By: Hermes <[email protected]>
2026-11-03 20:38:21 +01:00
GabrielNichols 60bbc4f278 launcher: complete missing palette and browser translations 2026-11-03 16:37:26 -03:00
GabrielNichols a283fd939b launcher: use wardrobe visibility when listing palette pages 2026-11-03 16:37:25 -03:00
HrafnaValkyrjaandClaude Sonnet 5.5 2282df048d Console commands run in the overworld, not the level the last tick left in place
A dedicated server's console source is bound to the overworld. Horizon ran a
console line in whichever level was in place, which after a tick with forced
chunks in the nether is the nether: summons, fills and selectors landed there.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 14:34:46 -05:00
GabrielNichols 2fb4d8f7a3 fix(port-pack): reject empty and failed harness verdicts 2026-11-03 16:34:24 -03:00
itarqos5andClaude Opus 5 703ab70218 feat(import): a game build dropped on the window is imported
The launcher could already install a `horizon-client` of your own beside
the releases (`build_import`), to try a merged change before the next
Nightly, but only through the Versions page's file picker or the
`import-build` command. Dropping the program on the window answered
"horizon-client.exe can't be imported: drop a .mrpack, a .zip or a
.hshader", which is the one file the window would not take.

It now takes it. `Dropped::Build` covers the program itself, a release
archive and a GitHub Actions artifact -- a zip around the release's own
archive, which `read_program` already looks into. An `.exe`, `.gz` or
`.tgz` is taken on its extension unread, so `build_import` is the one to
say why a file by that name is not a game, which is a better answer than
"nothing in it that Horizon reads". A file with no extension (a Linux or
macOS build) and a zip nothing else claims are read and checked through
the new `build_import::holds_game`, last, because that reads the whole
program.

The drop starts the same job the picker does: `start_build_import` is
split out of `import_build` so there is one path, one `Msg::Imported`
and one guard against a second import or a removal running at the same
time.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-03 19:34:08 +00:00
GabrielNichols c217b00548 launcher: cover cache messages in all current launcher locales 2026-11-03 16:28:22 -03:00
GabrielNichols 78dd8d2032 launcher: translate cache cleanup messages for added Hebrew locale 2026-11-03 16:26:48 -03:00
HrafnaValkyrjaandClaude Sonnet 5.5 55d3014458 Add the player's dimension to GET /api/v1/players
The status of each online player (server-status, and /api/v1/players built on it) already
carries the position; it now also carries the dimension the player is in, for example
minecraft:the_nether. The ROUTES text and the generated reference name it, and
tools/server_admin_e2e.py checks the dimension and the three coordinates on a real server.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 14:22:41 -05:00
GabrielNichols 325f338203 Merge upstream/main into codex/native-cache-cleanup-8028
Preserve current launcher changes and translations alongside safe native cache cleanup.
2026-11-03 16:19:41 -03:00
Ludovic MarieandClaude Opus 5.5 0e98e286f7 Launcher: the Shop tab, the founder set to claim for free while its window is open
The Wardrobe page gets Wardrobe | Shop tabs when the cosmetics service shows the
Shop. The Shop has the founder set: when it can be claimed until, the time left, one
Claim button, the four items on cards (Free, Owned, Equipped, Soon), and every state
the mockups list: signed out, loading, offline, service down, claiming, claimed,
owned, not open yet, a preview for one account, closed (the items are gone; owned
ones stay), the claim failing. Equip stays in the Wardrobe. No price anywhere.

HORIZON_SHOP_STATE=<state> previews each one in development builds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 20:14:58 +01:00
itarqos5andClaude Opus 5 49886f7154 feat(lod): far terrain a zoom magnifies is planned finer
`Plan::visit`'s falloff keeps a far tile's cells roughly the same size on
screen, which silently assumes the field of view never changes. A
spyglass takes a tenth of it, and the zoom mod as much again: the same
tile then covers many times more pixels, so its cells look as coarse as
a tile ten times nearer would. Looking through a spyglass at the horizon
showed exactly the blocky cells it was given for an unzoomed screen.

`ZoomAim` carries what the plan needs to answer that: the view
direction, the cone the narrowed view covers, and how much finer to go.
Tiles inside the cone split further; every other tile keeps the plan it
would have had, tile for tile.

The cone is the point. Lifting the whole circle to a tenth of the cell
size asks for about a hundred times the tiles, which at the 4078 tiles
and ~1500 quads a tile measured at `lodDistance` 256 is gigabytes of
vertices. `MAX_ZOOM_BOOST` caps it at three times finer as well, so the
worst case is roughly 45% more far tiles while the zoom is held, given
back when it is released.

Reads the field of view actually in use rather than any one zoom, so it
covers all three: the spyglass (`getFieldOfViewModifier` while
`isScoping`), the zoom mod (`native_zoom`) and any mod that narrows the
view through `native_compute_fov`. The yaw is rounded to five degrees
before it reaches the plan, and the cone widened by as much, so turning
the mouse does not rebuild the quadtree every frame; the aim is part of
`Lod::update`'s area, so a zoom starting or ending replans exactly once.

The `lodZoomDetail` extra switches it off. It does nothing unless far
terrain is on and the view is actually zoomed, so an unzoomed frame
plans as it always did.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-03 19:13:40 +00:00
CorbonatandCodex b87aee2af6 Adapt glint test quad to the merged model fields
Use upstream's neutral light, color, AO, render type and shade defaults in the generated glint fixture. Preserve its geometry, UVs and assertions.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-03 22:13:07 +03:00
AkirabaneandClaude Sonnet 5.5 dd762d48ff Commands: /execute on owner no longer finds the guest in place when the owner is not in the world
`Game::target_with_uuid` answered `Target::Player` for the level's own player
UUID. During a guest's turn the player in place is that guest, so a wolf
whose owner is the world's player (and not among the players online, for
example a single-player world moved to a dedicated server) made
`execute as <wolf> on owner run give @s ...` act on whoever ran the command.

Vanilla's `on owner` is `OwnableEntity.getOwner`, which resolves the stored
UUID through the level (`EntityReference.getLivingEntity`, then the level's
UUID lookup): an owner who is not in the level gives no entity. The player in
place is now the acting guest's UUID during a guest's turn and the level's
player otherwise. An owner who is online as another guest was already found
first by `target_by_uuid`.

Fixes #8488

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 20:11:23 +01:00
degepieandClaude Opus 5.5 36b6b3e935 Thrown snowballs, eggs and pearls and bucketed mobs fire their advancement triggers
A player's snowball, egg or ender pearl that hurts a mob (0 damage
included, as LivingEntity.hurtServer reports it) now credits the hit to
the thrower and fires player_hurt_entity with the projectile as the
direct entity, as the content-mod throwable arm already did. Scooping a
fish, axolotl or tadpole into a water bucket now fires
player_interacted_with_entity with the water bucket and the mob, as
ServerGamePacketListenerImpl.handleInteract does after
Bucketable.bucketMobPickup succeeds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UE3PMKbnTcxfBSeT5uQWCK
2026-11-04 00:06:36 +05:00
MegaBytesMe 4f1704263f feat(windows): add native ARM64 build support (#8247) 2026-11-03 19:02:08 +00:00
MSedgeMC e4db5de23c port_map: ExperienceOrbRenderer.getBlockLightLevel names the orb's rule
The submit rows said the billboard was inline; the light the billboard is
drawn with had no row at all.
2026-11-03 18:33:34 +00:00
MSedgeMC 8e935b200d test: pin the orb's light, its probe and its quad
A dark cell packs block light 7 where the old code packed 10 in both nibbles,
torchlight 9 clamps to 15, and daylight keeps the sky byte; the quad's four
corners resolve to a quarter of its height below the orb.
2026-11-03 18:33:34 +00:00
MSedgeMC 26261d9643 fix: an experience orb is lit seven levels above its block, and its sprite hangs right
ExperienceOrbRenderer.getBlockLightLevel is the level's block light plus
seven, clamped, with the sky light left alone; the pass raised both nibbles
to a floor of 10 instead. submit's quad runs from (-0.5, -0.25) to
(0.5, 0.75) under a 0.3 scale after translate(0, 0.1, 0), so the sprite is a
whole block tall about the orb and hangs a quarter of it below; Horizon drew
a symmetric 0.3 square 0.075 too low. The probe is the orb's eye, half its
box above its feet.
2026-11-03 18:33:34 +00:00
SenjinandClaude Opus 5.5 68f626d9f6 HDR: an HDR Lighting switch, on by default; off, HDR shows SDR's picture
HDR does two jobs: an HDR canvas, and Horizon's look on it. Off, nothing in the world is
lifted, light sources, the sun and sunlight included, and the lighting passes don't run, so
HDR output matches SDR: the vanilla look on an HDR desktop, and a neutral base for packs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 19:24:21 +01:00
Federico TaglianiandCopilot App 85556f651b Accept Java-compatible streamed server packs
Co-authored-by: Copilot App <[email protected]>
2026-11-03 19:14:16 +01:00
hildlabsandClaude Opus 5.5 b5d039487b Shader API: a pack's options turn its [shadow] map off and pick its size
`[shadow] option` names the option the map is drawn while it is not 0: off,
there is no map, no shadow pass and `has_shadow` reads 0, as `[reflection]
option` does for the water mirror. `resolution_option` and `distance_option`
name an int option, or a choice whose values are numbers, whose value replaces
`resolution` or `distance`, checked against the same ranges; changing one
rebuilds the map, as any option change reloads the pack. The options sizing
the map need no `override`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 19:12:24 +01:00
Federico TaglianiandCopilot App fa62beefdf Reject corrupt server resource packs
Co-authored-by: Copilot App <[email protected]>
2026-11-03 19:02:42 +01:00
AkirabaneandClaude Opus 5.5 c4bc3a29d1 Worker pool: release idle generators while the client keeps meshing
After a world loads, the client keeps queueing a few meshes a second for a
minute or more (vanilla's leaf distance pass reshapes the trees). Two things
in the shared pool then kept the server's generator buffers and caches,
which nothing reads until the player moves into new ground:

- Every wake restarted every sleeping worker's idle wait, even a worker
  that got no job, so with jobs queued every few seconds no worker ever
  reached IDLE_RELEASE. A worker now counts from its own last job.
- `all_idle` (#7519 empties the generator's shared caches there) needs
  every worker idle at once, which never happens while one source stays
  busy. A source with nothing queued or running for IDLE_RELEASE is now
  told on its own: `idle` for each worker's state and `all_idle`, once
  until its next job. The release frees nothing a later job needs: the
  generator's caches are emptied whole, as at their limit (#7519).

Each freed release hands the pages back to the OS (`release_free_memory`).

Measured with the alloc-census build, seed 1234, RD 12, offscreen, 150 s
after opening a new world (two runs): stream.generate 171.8 -> 45.3 MB,
the biome cache 1366 chunks -> 0, the live heap 645 -> 527 MB, the resident
set 1063 -> 1001-1014 MB.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 19:02:00 +01:00
Federico TaglianiandCopilot App b8740c5d9d Support minified server pack shaders
Co-authored-by: Copilot App <[email protected]>
2026-11-03 18:50:49 +01:00
LokiandAntigravity 9cda105a19 chat: include entity_types and wind charges in summon autocomplete (fixes #8419)
Co-Authored-By: Antigravity <[email protected]>
2026-11-04 02:50:13 +09:00
ErikKalikas 9a99293888 Merge remote-tracking branch 'upstream/main' into mod-reload-listeners
# Conflicts:
#	crates/horizon-client/src/mod_api.rs
#	crates/horizon-mod-api-gen/dispatch-tests/src/lib.rs
2026-11-03 19:49:27 +02:00
OpenAI Codex 3a0132d80e Make encoded material GPU controls detect filtering and mip regressions
Strengthen only the original synthetic oracle: fractional contrasting data, hostile unused mip contents and deliberately invalid filtering sampler negative control. Production/API code is byte-identical to the already completed full-check head ec6054c.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:48:18 -05:00
AkirabaneandClaude Sonnet 5.5 ce0d76008a fix(boats): a boat in powder snow is slowed and throws snowflakes
`PowderSnowBlock.entityInside` makes any entity that is not a living one stuck
(`makeStuckInBlock` 0.9, 1.5, 0.9) and, on the server, sends a snowflake when
the entity moved horizontally. Boats did neither: no particle showed when a
boat entered powder snow, and it was not slowed (#8156).

A boat whose box, or the box it had before its move, reaches into powder snow
now keeps a stuck multiplier at the end of its tick, as `AbstractBoat.tick`'s
`applyEffectsFromBlocks` does, and its next move goes its velocity times the
multiplier and spends the velocity (`Entity.move`). The server also sends the
snowflakes, twice a tick as `AbstractBoat.tick` applies the block effects twice,
for boats a player drives as for the others. A boat that is not in powder snow
moves exactly as before. Only the boat's own box is scanned.

Refs #8156

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 18:46:07 +01:00
AkirabaneandClaude Sonnet 5.5 d056f6d157 horizon-server: Prometheus metrics by dimension (loaded chunks and entities)
The game's status now lists each level's loaded chunks and entities
(`levels`), the level in place and the parked ones counted the same way
(players and block entities apart). prometheus() turns them into
horizon_loaded_chunks{dimension} and horizon_entities{dimension}; a level
with no count has no line rather than a 0. The docs drop the item from
"Not there yet".

Fixes #8191

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 18:43:31 +01:00
Arslan ArmanulyandClaude Opus 5.5 db726a1c49 Shader packs: read shaders.properties with the feature macros, as Iris does
Iris 1.11.7 reads shaders.properties with IRIS_FEATURE_<name> defined for
the features it has, whether the pack lists them as optional, as required
or not at all (probe pack); its programs still see only the optional ones.
Horizon defined them only after reading the file, so a custom uniform or
a screen behind `#ifdef IRIS_FEATURE_*` was left out. The Shader Options
page reads the file with the same macros.

Fixes #8493

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 22:37:01 +05:00
Arslan ArmanulyandClaude Opus 5.5 29d8afbf25 Shader packs: smooth() fade times count tenths of a second, as in Iris
Iris 1.11.7's values in our probe fit a half-life of a tenth of the fade
time's seconds (probe pack across the scene suite's weather changes):
smooth(rainStrength, 20, 20) reads 0.87 four seconds after the rain is
full, where Horizon's fade of 20 seconds gave 0.2. Horizon's smooth()
lagged ten times behind those values.

Fixes #8492

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 22:37:01 +05:00
Ludovic MarieandClaude Opus 5.5 d7bf2ad63c Merge main (main's rustfmt fix for client_lib.rs, #8040)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 18:34:43 +01:00
utophii fefde42033 feat: add musl linux release build 2026-11-03 20:32:47 +03:00
Stink-OandClaude Opus 5.5 bb55cab336 Far terrain: columns a single-player server changes in bulk out of the window's view are summarized again
A column changed by World::edit_runs (a mod's native_edit_blocks) goes whole only to the players it was sent to.
The single-player window's far terrain kept drawing an unsent column as the window last saw it (Lod::saved),
so a strike edited while the player was away left the old land in far terrain until the player came within
view distance again. The hub now hands such columns to its memory-connected window (handoff::changed), which
gives them to far terrain to summarize (Lod::observe), as it does columns it unloads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 17:26:51 +00:00
hildlabsandClaude Opus 5.5 01d6c3d88b Shader packs: a Horizon pack's float and int options are sliders
On a Horizon pack's options page a float or int option is a slider, as FOV
is, where it was a button stepping through every value. A float takes the
nearest step from its minimum (the last slot its maximum), an int whole
numbers, rounded to the f32 the pack's override gets so a slider back in the
middle is the default exactly. The value is saved, and the pack rebuilt, once
when the slider is let go, and only if it changed. Shift-click puts it back
to its default; Tab, Enter and the arrows work it as they do vanilla's
sliders, a step at a time and stopping at the ends. Bool and choice options
stay buttons.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 18:25:16 +01:00
Federico TaglianiandCopilot App a2d36b2f31 Fix Metal sprite texture array splitting
Co-authored-by: Copilot App <[email protected]>
2026-11-03 18:24:43 +01:00
AkirabaneandClaude Sonnet 5.5 e3b0f06133 feat(server): live status-heartbeat-interval in the management protocol
`serversettings/status_heartbeat_interval` and `/set` were not answered, and the
supervisor started its heartbeat only when the interval was above 0 at start,
with that first value for good.

The heartbeat thread is now always there and counts its seconds itself. It
reads the interval each second and starts counting again at every set, as
vanilla cancels the heartbeat and schedules a new one
(`ManagementServer.scheduleHeartbeat`: the first after as many seconds), so a
server that began with none can have one and a server that had one can stop it,
with no restart. 0 or less means none, and the interval is kept as it was set,
as vanilla keeps any int.

The set writes `status-heartbeat-interval` to server.properties and to
server.toml's `[properties]`, logs vanilla's "Updated status heartbeat
interval from {} to {}" and answers with the interval now in force. A value
that is not an integer is refused as the other settings' are.

The port map moves `DedicatedServer.setStatusHeartbeatInterval` out of
`skip:server` and the management protocol parity harness compares the get, the
set, an invalid value, the saved property and the value after a restart with
vanilla's. That harness runs a real vanilla server and was not run here; the
unit tests cover the live heartbeat and the persistence.

Fixes #8190

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 18:18:29 +01:00
AkirabaneandClaude Sonnet 5.5 52e29c1ac7 test(lan): accepting transfers changes live
Log in by transfer on a server whose listener is not bound (a local connection):
turned away with `multiplayer.disconnect.transfers_disabled` while transfers are
off, taken once `Shared::set_accepts_transfers(true)` has run, and turned away
again after it is set back. With the handshake reading the fixed
`config.accepts_transfers` again, the middle login fails.

Refs #8190

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 18:18:03 +01:00
AkirabaneandClaude Sonnet 5.5 59140732d6 feat(server): live accepts-transfers in the management protocol
`serversettings/accept_transfers` and `/set` (`ServerSettingsService`,
`DedicatedServer.setAcceptsTransfers`) were not answered. The listener read
`accepts-transfers` once at start, from a fixed field of its configuration.

The listener's shared state now holds it as an atomic, as it holds the live
login limit, and the transfer handshake of `lan/conn.rs` reads that. The
management method changes it at once, writes `accepts-transfers` to
server.properties and, through the supervisor, to server.toml's `[properties]`
so a restart keeps it, and logs vanilla's "Update accepts transfers from {} to
{}". A value that is not a boolean is refused as the other settings' are.

The port map moves `DedicatedServer.setAcceptsTransfers` out of `skip:server`
and the management protocol parity harness compares the get, the set, an
invalid value, the saved property and the value after a restart with vanilla's.
That harness runs a real vanilla server and was not run here.

Refs #8190

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 18:18:03 +01:00
CorbonatandCodex ab9032a188 Merge pinned main 49a6e02 into GUI glint atlas fix
Preserve upstream line shader setup alongside GUI glint atlas preparation.
Keep local Windows parity helper repairs outside this contribution.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-03 20:16:12 +03:00
OpenAI Codex ec6054c5c0 Record baseline native material API sanity-check input
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:14:28 -05:00
OpenAI Codex 881a8b8be6 Record pinned public-main API integration inputs
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:13:25 -05:00
OpenAI Codex b227d63fe8 Record pinned public-main material API integration inputs
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:13:25 -05:00
OpenAI Codex a900d9ad88 Initialize authored-content API in a new public-main test literal
Actual final-main Clippy/test compile identified the newly added Interface construction; retain its previous neutral48-byte contents. Independent production behavior unchanged.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:11:38 -05:00
OpenAI Codex 49d6e26bc2 Initialize material API fields in new public-main test literals
Preserve existing fixture behavior with absent companion maps and no block-surface scope. Consulted only the necessary current-main struct construction sites for this integration.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:11:13 -05:00
OpenAI Codex 5ca8699133 Initialize material scope for the new main line geometry API
The independent candidate reserves block companions for terrain; the newly added main line draw has no colour surface identity and explicitly uses defaults.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:10:46 -05:00
OpenAI Codex 2d0f159464 Record nonbehavioral material integration comment updates
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:08:06 -05:00
OpenAI Codex ef9d8b67d5 Transport terrain resource-pack material maps to shader samplers
Proposed independently developed replacement for closed #7800, addressing #7747. Preserve colour-layer identity across split arrays, nearest encoded data transport, neutral defaults, resource ownership and colour-selected companion frames. Add original CPU and actual-GPU controls with a pinned input/exposure ledger. Frozen candidate production hunks preserved; integration adjusts a test-module append and clarifies comments.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:07:54 -05:00
ElyTheCatandOpenAI Codex f28e838da8 Invalidate worker voxel caches on snapshot refresh
Key retained ray and plane traversal grids by the snapshot lifetime, not only world revisions, so refreshed chunk visibility cannot reuse stale opaque cells. Add a regression test for a wall becoming transparent.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 20:34:24 +03:30
OpenAI Codex 9315b5a43f Preserve authored ordinary uniform initial values
Proposed independently developed replacement for closed #7842, addressing #7811. Preserve compiler-evaluated constants in actual UBO layout, retain runtime-write precedence, and add original typed and real-GPU controls. Public research input ledger records pinned permissive shader-pack inputs, frozen candidate lineage and exposure limits.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 11:57:59 -05:00
HrafnaValkyrjaandClaude Sonnet 5.5 ab212945d7 Entity saves: ghast ExplosionPower, trident LeftOwner, ticked fireballs burn
Ghast.addAdditionalSaveData writes ExplosionPower (default 1). An ownerless
projectile has left its owner after its first tick, so a trident saves
LeftOwner. AbstractHurtingProjectile.tick ignites a fireball for a second
every tick (wind charges do not burn).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 11:46:06 -05:00
HrafnaValkyrja 765cec5be7 Merge remote-tracking branch 'origin/main' into fix/item-tnt-block-speed 2026-11-03 11:45:42 -05:00
Ludovic MarieandClaude Opus 5.5 76ae2acc14 Merge #8122, #8118 and #8108 (management series) into entity broadcast range management
Stacks the management series so the queue lands them in order; every
handler, setter, port-map row and harness check of the four is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 17:40:00 +01:00
ElyTheCatandOpenAI Codex 2716f761c4 Remove obsolete occlusion test filename
The tests now live in occlusion_culling_tests.rs, matching the feature test naming convention.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 20:06:14 +03:30
ElyTheCatandOpenAI Codex d3ae3e9f46 Reuse async occlusion traversal buffers
Keep DDA worker scratch and result vectors across jobs, reset voxel state on snapshot changes, and move culling tests into standard feature test files.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 20:05:10 +03:30
ErikKalikasandClaude Opus 5.5 420ef24de8 Native mods: a mod's own data folders, read on each data load
native_add_server_reload_listeners lists a mod's listeners (NeoForge's
AddServerReloadListenersEvent): an id and a folder under
data/<namespace>/. On each data load of a server (a world opening,
/reload), before native_server_data_loaded,
native_server_reload_listener_apply hands over the folder's .json files
as vanilla's SimpleJsonResourceReloadListener reads them: <ns>:<path>
ids, the top pack's file for each id over the resource root and the
world's enabled data packs, a file that isn't JSON logged and left out.
Fixes #8400.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 18:30:59 +02:00
itarqos5andClaude Opus 5 9267d457a0 perf(worldgen): Reduced far detail paints only the top of a wide column
Vanilla's surface rules reach eight blocks down, layering dirt under
grass and sand under a beach. A far tile 256 or 1024 blocks across draws
at most a block or two of each column's vertical face, so the six below
that are work no view resolves.

Reduced far detail now stops at depth 2 for tiles 16 blocks a column or
wider. Depth 1 is still painted, so a tile's surface -- its height, its
top state, its water and its biome -- is exactly what it was: dumping
every column of 25 tiles at levels 4 and 6 gives a file identical to the
one before this change, all 12800 columns. Only the interior of a span,
seen on a cliff face, loses its vanilla material. Full far detail, the
default, is untouched, and so are narrow tiles, whose faces are tall
enough in view to show the layering.

Measured with `lod_grid_bench` over 100 tiles a run, five runs, taking
the least-interfered run of each (this machine's spread is wide):

| level | stride | before | after |
|---|---|---|---|
| 2 | 4 | 10.6 ms | 10.8 ms (gated off) |
| 4 | 16 | 11.1 ms | 10.3 ms (-7.2%) |
| 6 | 64 | 8.7 ms | 8.2 ms (-5.7%) |

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-03 16:24:17 +00:00
HrafnaValkyrjaandClaude Sonnet 5.5 284fba6c68 Slow dropped items and primed TNT on soul sand and honey
Entity.move ends with getBlockSpeedFactor for every entity; items and
primed TNT now call the shared helper from #8096.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 10:49:25 -05:00
NerfPhaselockandClaude Opus 5.5 ce776dff98 Renderer: GPU culling leaves fading sections to the CPU
Since the pass culls the near solid sections itself, a section it draws
appears whole where a CPU frame fades it in or out (vanilla's chunk
fade-in, far terrain's fades). A recorded section now gets FADING in its
record, which cull skips, and its walk mark, so the CPU draws it with its
fade, exactly while the instance loop's own expression for it is not a
whole section's (section_visibility and the far-terrain fade, bit for bit).
The flag is decided at upload, when a section leaves or is held for far
terrain, and anew for every record when the fade-in time changes.

A heap of fade ends in GpuCulling says when to look again, so a frame with
no end due does no fade work; Records counts the flagged records, and
switching GPU culling off forgets the flags with the records. The debug
check also compares each flag and mark with what the fades say.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 09:16:16 -05:00
voidsipandCodex 0f4313d19c fix(input): read unmapped Linux trigger axes
Read conventional Linux trigger axes when no trigger button is mapped.
Preserve mapped button input and exclude the 8BitDo stick-axis layout.
Cover pull values, absent input and mapping precedence.

Co-Authored-By: Codex <[email protected]>
2026-11-03 07:38:31 -06:00
AkirabaneandClaude Opus 5.5 acc6d017c4 A server sends its world packs' tags for every registry, not only dialogs
TagNetworkSerialization.serializeToNetwork sends each synchronized registry's
tags as TagLoader bound them from the whole pack stack. Horizon's server read
the resource root's tag files only and merged the world's data packs into
the dialog tags alone, so no client (vanilla or Horizon) was told that a
pack put dirt in #minecraft:mineable/pickaxe or a block in #climbable. After
/reload, keep_registries read the root's tags again without any pack, the
packs' dialog tags included.

merge_pack_tags merges, for every synchronized registry the world's packs
have tag files for (tags/<path>, a mod's registry under tags/<ns>/<path> as
Registries.tagsDirPath has it), the root's files and then each pack's in
order ("replace": true, nested tags expanded), at load and on /reload.
Registries no pack touches keep load_tags' tags.

Refs #7843

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 14:03:41 +01:00
masa-masa-0314andCodex 34543f259f Merge upstream main and fix launcher relocation CI
Complete relocation strings in new language tables, explicitly release completed world-operation locks, and retain unsaved settings when a move cannot save them.

Co-Authored-By: Codex <[email protected]>
2026-11-03 20:55:24 +08:00
TiggerTakkyTTTandCodex 63554a33a7 Merge upstream main into entity range management
Preserve entity broadcast range parity alongside the merged allow-flight management changes and log clamped values.

Co-Authored-By: Codex (GPT-6.1) <[email protected]>
2026-11-03 19:24:37 +07:00
AkirabaneandClaude Sonnet 5.5 685d0b1e8c Buckets: the fluid ray passes through the hollow of a torch, fence or flower
pick_fluid stopped at any non-air cell without a fluid instead of clipping
the block's outline, so a water bucket could not fill through the empty part
of a torch's cell. ClipContext.Block.OUTLINE lets the ray through; the ray
now stops only where it meets an outline box. This covers the buckets
(empty, fish), glass bottle, lily pad / frogspawn and spawn eggs, which
all use this ray.

Fixes #8180

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 13:15:02 +01:00
AkirabaneandClaude Sonnet 5.5 ee480fe577 fix(shaders): a bool member of a std430 buffer translates
GLSL gives a `bool` in a `layout(std430)` buffer a size and an alignment of
4. naga does not let a bool into a storage buffer at all, so the program
failed with "Global variable [6] '' is invalid: ... The type is not
host-shareable": no name, no member, and the line of the include site (#8175).

Such a member now becomes a `uint` of the same size and every use of it is
rewritten before the stage is scanned: a read is `(flag != 0u)` (also through
an instance, `state.flag`, and an array, `flags[i]`), a plain assignment is
`flag = uint(...)`. Nothing is added or removed from a line, so the pack's
line numbers stay right. It applies to render and compute programs.

What the rewrite cannot make sure of is refused with the member's name and its
line, instead of naga's anonymous error: a compound assignment (`flag |= x`),
an assignment used as a value, a local or parameter of the same name, a `bool`
vector member and an instance array of such a buffer.

Tests: the five shapes of the issue translate; the refusals name the member and
the line; the rewritten text is checked exactly; a compute program reads and
writes a bool member. Before the change the first test fails with the exact
message of the issue for every shape.

Fixes #8175

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 13:05:45 +01:00
AkirabaneandClaude Opus 5.5 54a8a4e47c Superflat worlds without structures place their features and lakes too
A superflat world that decorates (`features` or `lakes`) but generates no
structure set, because `generate_structures` is off or its
`structure_overrides` is `[]`, still went through FlatTerrain, which lays the
layers and the Void platform only. FlatLevelSource decorates with
adjustGenerationSettings whatever its structures do.

FlatTerrain::structure_settings becomes pipeline_settings: the settings of a
superflat world that lets a structure set generate or that decorates. World
loading streams such a world on the flat pipeline when its structures generate
or it decorates; the pipeline then has no structure starts when
generate_structures is off, and no set when the overrides are empty.
FlatTerrain keeps the worlds with neither, and the Void.

Refs #2912

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 12:29:30 +01:00
AkirabaneandClaude Sonnet 5.5 209f2fd34d fix(motion): a passenger freezes in powder snow, and the frostbite overlay shows it
A player carried by a boat only ran `passenger_pose_tick`, never the part of
`LivingEntity.aiStep` that applies the effects of the blocks it is in. Powder
snow's FREEZE never added a tick of frost to the window's own player, and
nothing thawed it, so `ticks_frozen`, which the frostbite overlay reads, stayed
where it was: the server froze and hurt the rider, who saw no frost (#8156).

`passenger_block_effects_tick` samples the blocks the passenger is in, as the
client does on foot, and thaws two ticks a tick out of powder snow as the
server does; the riding branch of the client tick calls it after the pose.
The server's copy of a riding guest is untouched: it already freezes in
`server_block_damage`.

Refs #8156

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 12:10:24 +01:00
AkirabaneandClaude Opus 5.5 b40b532a67 Parity harness: superflat chunks against vanilla's FlatLevelSource
tools/test_flat_generation_parity.py runs VanillaChunkGenerationOracle with
HORIZON_FLAT=<flat_level_generator_preset>, which builds a real FlatLevelSource
from the preset registry (with ChunkMap's random state for a generator without
noise, and createState's structure state), and chunk_dump with the same
variable on OverworldGenerator::flat. Every block, quart biome and block entity
of the target chunks is compared after decoration, for the three presets that
decorate (Overworld, Desert, Tunnelers' Dream) and three that only place layers
and structures. A decorating preset must hold blocks beyond its layers in
vanilla, so the decoration is known to be covered.

Refs #2912

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 12:03:18 +01:00
AkirabaneandClaude Opus 5.5 679b9f53be Superflat worlds with structures place their features and lakes
A superflat world on the structure pipeline (Generator::flat, #5509) built its
decoration with no placed feature at all. FlatLevelSource decorates with
FlatLevelGeneratorSettings.adjustGenerationSettings of its biome (26.3
bytecode): the lava lakes of createLakesList in the lakes step when `lakes` is
on; the biome's own features when `features` is on and the layers are not all
air (or the biome is the_void), except those of the underground and surface
structure steps, and of the lakes step when `lakes` is on.

FlatSettings now carries `features` and `lakes`, and Decoration::new_with takes
that adjustment. The FillLayerFeatures stay last in TOP_LAYER_MODIFICATION, as
the builder adds them after the biome's features.

Refs #2912

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 12:03:17 +01:00
TiggerTakkyTTTandCodex f90f7aac52 Add live entity broadcast range management
Co-Authored-By: Codex (GPT-6.1) <[email protected]>
2026-11-03 17:00:44 +07:00
ElyTheCatandOpenAI Codex 3ea5d68c2a Add configurable entity precheck slider
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 13:13:13 +03:30
ElyTheCatandOpenAI Codex d387eb8c3c Apply entity precheck threshold to both culling modes
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 13:03:52 +03:30
ElyTheCatandOpenAI Codex a38ef04585 Make entity precheck threshold configurable
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 13:02:30 +03:30
ElyTheCatandOpenAI Codex 7e22c998dd Split async occlusion into ray and plane workers
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:49:33 +03:30
ElyTheCatandOpenAI Codex d5abaf67c8 Fix async entity occlusion cache reuse
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-03 12:40:36 +03:30
masa-masa-0314andCodex 45b1e501de Launcher: use octal permissions in relocation regression test
Fix Linux clippy::non_octal_unix_permissions without changing the unreadable-source fixture or its assertions.

Co-Authored-By: Codex <[email protected]>
2026-11-03 15:27:26 +08:00
masa-masa-0314andCodex ed7f1f4428 Launcher: retain data relocation safety regression tests
Cover source-preserving migration, real unreadable-file failures, unsafe destinations and links, stale workers, leases, atomic pointer failure and structured path remapping. Fix canonical Windows drive prefix validation exposed by the permanent tests.

Refs #7218

Co-Authored-By: Codex <[email protected]>
2026-11-03 15:08:13 +08:00
GabrielNichols 5bd3afa1ad launcher: bound native build caches and support safe cleanup (#8028)
Limit fast/release compiler outputs after builds and expose cache inspection and cleanup. Coordinate builds and cleanup with OS/Cargo locks; preserve installed versions and player data.
2026-11-03 04:00:03 -03:00
Arslan ArmanulyandClaude Opus 5.5 514bada684 Shader packs: currentRenderedItemId per draw, and the hand per held item
`currentRenderedItemId` was a frame uniform reading 0 in every program, and
a hand program that read it without declaring it failed to translate, so
the hand fell back to gbuffers_textured_lit.

- `HzDraw`'s fourth int, a pad until now, is `currentRenderedItemId`: -1 for
  terrain, water and the sky, 0 for entity runs (items drawn in the level
  are not done).
- The hand draws one run per arm and held item, each with its own `HzDraw`
  slot. A block item reads its block's block.properties id, or 0 when that
  file lacks it, whatever item.properties says; any other item reads its
  item.properties id, 65535 when the file lacks it, and 0 without the file.
- The hand's programs may read `currentRenderedItemId` undeclared; terrain's
  and entities' may not.

This is what Iris 1.11.7 does, observed with probe packs of our own.

Part of #6812

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 09:23:27 +05:00
Ludovic MarieandClaude Opus 5.5 f545530d3e launcher: pick the loader's version in New instance
Modded now lists the chosen loader's versions for the chosen Minecraft
version, asked of its service once per loader and version: Newest stable
first, then every version newest first, betas marked. The choice goes to
loaders::choose when the instance is made, and goes back to the newest
stable one when the loader or the game version changes. The loader card
shows the version chosen. Its four strings are in every language.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 03:44:18 +01:00
Minantiii 71566a3268 Armadillos: rolling up and out posts GameEvent.ENTITY_ACTION
Armadillo.rollUp and Armadillo.rollOut call gameEvent(ENTITY_ACTION)
before their sound, silent or not; sculk sensors, shriekers and wardens
now hear it.

Fixes #8714
2026-11-02 23:38:29 -03:00
Ludovic MarieandClaude Opus 5.5 0b47accde1 launcher: import a modded .mrpack as a Java instance
instance import <file> --java makes a Minecraft: Java Edition instance from a
Modrinth pack, as Prism Launcher imports one: the Minecraft version and the
mod loader its index names (minecraft, fabric-loader, quilt-loader, forge,
neoforge), every override (overrides/, client-overrides/) and file it
carries, files checked against their hash; a server-only file and
server-overrides/ are left out, and a path that leaves the folder is
refused. A pack that fails half way leaves no instance behind. Without
--java a modded pack still goes to the Modpacks import.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 02:58:28 +01:00
Ludovic MarieandClaude Opus 5.5 349deacbb3 launcher: test Forge's and NeoForge's real install profiles and a dry-run launch
The processors are planned (steps) before they run, so a test checks the
client steps three real install profiles ask for (NeoForge 26.3.0.101,
Forge 26.3-66.1.0 and 1.20.1-47.4.0, recorded without icon and library
lists) against values read off the profiles by hand. The ignored real
install now also builds Play's command and runs it with the JVM's
--dry-run: module path, classpath and main class resolve, nothing of the
game runs, no account is involved.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 02:43:41 +01:00
Ludovic Marie d0bdbc9eb0 Merge branch 'launcher-forge-versions' into launcher-forge-install 2026-11-03 02:37:45 +01:00
reservedbytes 03f2b226ff fix(items): player drops use vanilla's exact throw motion 2026-11-03 00:01:39 -01:00
CorbonatandCodex 8a529538f2 Merge pinned upstream main for GUI glint validation
Integrate e2385d0ddb while retaining logical item-atlas glint coordinates, metadata rejection and GUI/CIT separation.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-03 01:51:00 +03:00
deloskiyt eb0a4cf524 feat(realms): implement in-game Configure Realm and Add Realm screens, widen join button 2026-11-02 22:50:34 +01:00
deloskiyt fae8efb8fd fix(realms): restore button icons, wire up all click actions, and add join code & pending invites screens 2026-11-02 22:35:21 +01:00
deloskiyt 2b427b6ccc feat(realms): exact 1:1 port of vanilla RealmsMainScreen and RealmsHeader layout 2026-11-02 22:07:23 +01:00
deloskiyt bef44dd626 fix(client): match authentic vanilla Realms UI layout and correct cookie authentication format 2026-11-02 21:47:53 +01:00
deloskiyt a5391f2e9c feat(client): implement Minecraft Realms support and title screen browser
- Enable Minecraft Realms button on main menu and handle navigation
- Implement dedicated Realms browser screen supporting world list, status badges, selection and refreshing
- Implement Realms HTTPS client querying pc.realms.minecraft.net using AuthSession access token
- Add join endpoint resolution and direct server connection via Action::Connect
- Update agent mode serialization and port map ledger
2026-11-02 21:02:53 +01:00
Ludovic MarieandClaude Opus 5.5 d9e8401558 launcher: install and launch Forge and NeoForge instances
forge_install downloads the installer (checked against the .sha1 its
repository publishes), takes the libraries from its maven/ folder or the
repository (each sha1 checked), runs the client's processors on the
instance's Java as java -cp <jar+classpath> <Main-Class> <args> with the
install profile's data, skips a processor whose outputs already match and
refuses one that leaves another sha1. The profile is kept as
versions/<id>/<id>.json with a copy of the game's jar named after it, as
Forge's ignoreList (${version_name}.jar) expects; java_launch::resolve now
lets a child name its own jar.

java_game picks the Java before installing the loader, so the installer
runs on the Java the game runs on. The new instance's loader picker shows
every loaders::Kind with its logo.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 20:57:45 +01:00
Ludovic MarieandClaude Opus 5.5 2c9dac6bbc Block sprites: keep the CPU copy compressed once uploaded
The block texture array's pixels (21 MB with the default resources) stayed in
RenderAssets for the whole session, though the GPU holds them and the copy
only serves a renderer made again. Once a renderer exists they are
lz4-compressed on a thread of their own, as the GUI atlas is (#6843); a
renderer built again uncompresses them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 20:26:53 +01:00
ElyTheCat 6dca822e6a Merge remote-tracking branch 'origin/main' into perf/async-entity-occlusion-tuned 2026-11-02 22:37:03 +03:30
ElyTheCatandOpenAI Codex 37380065b7 Optimize async entity occlusion hot paths
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 22:34:15 +03:30
ElyTheCatandOpenAI Codex 4e069b1e21 Merge origin/main before occlusion performance follow-up
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 21:04:45 +03:30
ElyTheCatandOpenAI Codex c13e224a44 Merge origin/main into entity occlusion branch
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 20:10:58 +03:30
ElyTheCatandOpenAI Codex 03ef18163d Format entity occlusion culling module
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 19:45:19 +03:30
masa-masa-0314andCodex 7df27f1cf9 Launcher: move managed data to another drive
Add a two-step data-folder move that copies and verifies launcher, game and build data offline, preserves source recovery copies, and atomically commits an independent location pointer before reopening. Protect migration with process leases, reject links and overlapping destinations, preserve Windows access permissions and remap managed structured paths.

Fixes #7218

Co-Authored-By: Codex <[email protected]>
2026-11-03 00:03:28 +08:00
ElyTheCatandOpenAI Codex 1068905d3d Add entity occlusion benchmark evidence
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 18:49:10 +03:30
ElyTheCat 7bee9ec3cf Merge origin/main into entity occlusion branch 2026-11-02 18:47:38 +03:30
ElyTheCatandOpenAI Codex a9a08ef1a6 Separate async entity occlusion rewrite
Isolate the entity occlusion cache and worker from particle visibility. Keep entity modes in the existing settings, submit inflated entity AABBs before model generation, and refine visible entities with their emitted bounds. Preserve fail-open handling and add async culling telemetry.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 18:47:26 +03:30
CorbonatandCodex c5adff2477 Merge main and reject invalid GUI glint atlas texture metadata
Preserve main's face-normal and sprite-rectangle caches while keeping GUI
vanilla atlas coordinates separate from CIT sprite-local glint UVs.
Reject malformed texture sections before stitching and read metadata from
the same prepared snapshot as rendered textures. Add rejection, control,
prepared-resource and real-oracle emitted-UV regression coverage.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-02 14:55:28 +03:00
ElyTheCatandOpenAI Codex a71e1d0665 Translate particle culling graphics settings
Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 13:57:47 +03:30
CorbonatandCodex 709fa73ee3 Fix GUI item glint UV scale using the vanilla item atlas
Reconstruct the pinned item atlas layout from the active sprite sources so
standard GUI glint receives the same atlas UVs as vanilla. Use the device
limit and selected filtering when rebuilding the renderer; keep the HUD
texture coordinates and CIT glint coordinates separate.

Add independent vanilla UV and packing regressions, including animation,
generated sources, padding, invalid metadata and atlas-limit boundaries.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
2026-11-02 13:16:52 +03:00
ElyTheCatandOpenAI Codex e6ce596144 Update mouse input fixture for trace logging
Stub the new input trace types and no-op recorder so the focused device-event fixture continues to compile when it extracts Game::device_event.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 13:09:26 +03:30
ElyTheCatandOpenAI Codex 8d603dc51f Refresh async culling benchmark evidence
Record both mode orders on the merged head and make the single-scene and cache-counter comparison limits explicit.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 11:55:38 +03:30
ElyTheCat 627309b80a Merge remote-tracking branch 'origin/main' into perf/async-occlusion-culling 2026-11-02 11:40:05 +03:30
ElyTheCatandOpenAI Codex 9c10104446 Optimize asynchronous entity and particle culling
Replace the collision-prone four-way visibility cache with a bounded sorted cache and validate repeated occlusion proofs through frame-local ordered lookup. Keep entity visibility conservative and off block entities, and add batched particle culling modes sharing the occlusion worker.

Add a reproducible benchmark note and screenshots for Sync and Async modes.

Fixes #6684

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 11:40:02 +03:30
ElyTheCatandOpenAI Codex 778dad1e4a Keep entity occlusion culling off block entities
Keep block entity frustum checks and section cache rendering independent of the ordinary entity occlusion culler. Show mode-specific culling help in the graphics UI.

Co-Authored-By: OpenAI Codex <[email protected]>
2026-11-02 08:04:05 +03:30
rdbtCVSandClaude Sonnet 5.5 e7bdd16474 Remove the RAM limit
A soft limit that lowers render distance and then crashes the game is not
a cap, and only Windows could hold the game under a number at all. Takes out
the launcher's Maximum memory row, HORIZON_MEMORY_LIMIT_MB, /ramlimit, the
governor and the watcher thread (reverts #5800 and #5801). A Java instance's
-Xmx stays settable from the launcher CLI.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-02 00:48:32 +00:00
2081 changed files with 265111 additions and 24256 deletions

No files matched your search

+2 -1
View File
@@ -1,3 +1,4 @@
self-hosted-runner:
# The Mac mini's runner (ludovics-mac-mini), which holds the maintainer's own Minecraft files.
labels: [horizon-game, ludovics-mac-mini]
# windows-11-vs2026-arm is GitHub's Windows ARM64 image (2026-08), newer than actionlint's label list.
labels: [horizon-game, ludovics-mac-mini, windows-11-vs2026-arm]
+15 -9
View File
@@ -1,10 +1,16 @@
# Trusted reviewers: one list for two things (docs/release-flow.md "Trusted reviewers").
# Trusted reviewers: one list for three things (docs/release-flow.md "Trusted reviewers").
# - `/nightly 2640 2642` picks the pull requests the Nightly holds (tools/ci/nightly_set.py);
# - `/close`, `/reopen`, `/label`, `/unlabel` triage issues and pull requests (tools/ci/triage_command.py).
# Ludovic is always allowed and isn't listed. One GitHub login per line, with who added it and when. Removing a
# line revokes that login at once. A line with only `paused` stops both for every reviewer (Ludovic's still work).
kacpherek # added by Ludovic, 2026-10-27
rdbtCVS # added by Ludovic, 2026-10-27
Online5277 # added by Ludovic, 2026-10-27 (his top three by merged pull requests)
levzzz5154 # added by Ludovic, 2026-10-28 (Horizon Team; author of horizon-mixins)
Qwerner88880 # added by Ludovic, 2026-10-31 (Discord vladislav_hagen; native-mod hooks)
# - `/close`, `/reopen`, `/label`, `/unlabel` triage issues and pull requests (tools/ci/triage_command.py);
# - `/unclaim @login` releases someone else's claim, and listed reviewers have no claim limit (tools/ci/claim_command.py).
# One reviewer per line: `<numeric GitHub ID> <login>`, with who added it and when. The ID decides; the login is only
# for people reading this file and for messages. A login is a name anyone can claim once its owner renames or deletes
# the account; an ID is never reused. Get it with `gh api users/<login> --jq .id`, or run
# `python3 tools/ci/resolve_reviewer_ids.py` to fill in the IDs and `--check` to catch renames.
# A line in any other form (a bare login included) grants nothing. Ludovic is always allowed (OWNER_ID in
# tools/ci/nightly_set.py) and isn't listed. Removing a line revokes that account at once. A line with only `paused`
# stops all three for every reviewer (Ludovic's still work).
54669067 kacpherek # added by Ludovic, 2026-10-27
87152661 rdbtCVS # added by Ludovic, 2026-10-27
84875247 Online5277 # added by Ludovic, 2026-10-27 (his top three by merged pull requests)
86072227 levzzz5154 # added by Ludovic, 2026-10-28 (Horizon Team; author of horizon-mixins)
87478350 Qwerner88880 # added by Ludovic, 2026-10-31 (Discord vladislav_hagen; native-mod hooks)
+5 -5
View File
@@ -22,7 +22,7 @@ jobs:
outputs:
platforms: ${{ steps.platforms.outputs.platforms }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- id: platforms
run: echo "platforms=$(python3 -c 'import json, tools.horizon_mod_binstall as m; print(json.dumps(m.PLATFORMS))')" >> "$GITHUB_OUTPUT"
build:
@@ -35,7 +35,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
@@ -67,7 +67,7 @@ jobs:
[ -f "$binary.exe" ] && binary="$binary.exe"
mkdir -p dist
tar -czf "dist/$(basename "$binary" | sed 's/\.exe$//')-${{ matrix.target }}.tar.gz" -C "$(dirname "$binary")" "$(basename "$binary")"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: cargo-horizon-mod-${{ matrix.target }}
path: dist/*.tar.gz
@@ -81,8 +81,8 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
pattern: cargo-horizon-mod-*
path: dist
+109 -12
View File
@@ -41,10 +41,10 @@ jobs:
fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
if: runner.environment == 'github-hosted'
with:
python-version: '3.12'
@@ -126,7 +126,7 @@ jobs:
fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Does this pull request change the launcher or a crate it builds?
@@ -158,7 +158,103 @@ jobs:
run: |
cargo +stable fmt --check
cargo +stable clippy --all-targets --locked -- -D warnings
cargo +stable test --locked --lib
cargo +stable test --locked --lib --bins
# Compile and test Windows ARM64 on an actual ARM64 host, and inspect the resulting PE headers. By hand only
# (`gh workflow run checks --ref <branch>`): GitHub's runner queue is the bottleneck, and main's runs gate the
# releases (tools/release_train.py waits for them), so neither pull requests nor main's pushes start this
# 90-minute job. The Nightly and launcher releases build and check these binaries on every run.
windows-arm64:
name: Windows ARM64 game and launcher
if: github.event_name == 'workflow_dispatch'
runs-on: windows-11-vs2026-arm
timeout-minutes: 90
env:
RUSTFLAGS: -C target-feature=+crt-static
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Use every runner core for Cargo
shell: pwsh
run: |
$cores = (Get-CimInstance Win32_Processor | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum
"CARGO_BUILD_JOBS=$cores" >> $env:GITHUB_ENV
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-pc-windows-msvc
- uses: Swatinem/rust-cache@v2
with:
shared-key: windows-arm64-game
- uses: Swatinem/rust-cache@v2
with:
workspaces: launcher
shared-key: windows-arm64-launcher
- name: Check LLVM assembler used by ring
shell: pwsh
run: |
$llvmBin = Join-Path $env:ProgramFiles 'LLVM\bin'
if (-not (Get-Command clang -ErrorAction SilentlyContinue) -and (Test-Path (Join-Path $llvmBin 'clang.exe'))) {
$env:PATH = "$llvmBin;$env:PATH"
"PATH=$env:PATH" >> $env:GITHUB_ENV
}
$clang = Get-Command clang -ErrorAction Stop
Write-Output "Using $($clang.Source)"
& $clang.Source --version
- name: Build the game for native Windows ARM64
run: cargo build --release --locked --target aarch64-pc-windows-msvc -p horizon-client -p horizon-client-lib --bin horizon-client --bin horizon-ctl -p horizon-prepare --bin horizon-prepare -p horizon-server --bin horizon-server
- name: Check the game's PE machine
run: python tools/check_pe_arch.py arm64 target/aarch64-pc-windows-msvc/release/horizon-client.exe target/aarch64-pc-windows-msvc/release/horizon-ctl.exe target/aarch64-pc-windows-msvc/release/horizon-prepare.exe target/aarch64-pc-windows-msvc/release/horizon-server.exe
- name: Check game DLL imports
run: python tools/check_imports.py target/aarch64-pc-windows-msvc/release/horizon-client.exe target/aarch64-pc-windows-msvc/release/horizon-ctl.exe target/aarch64-pc-windows-msvc/release/horizon-prepare.exe target/aarch64-pc-windows-msvc/release/horizon-server.exe
- name: Start every game executable
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$client = 'target/aarch64-pc-windows-msvc/release/horizon-client.exe'
$output = & $client --help 2>&1
if ($LASTEXITCODE -ne 0 -or ($output -join "`n") -notmatch '--resources') { throw 'horizon-client --help failed' }
foreach ($binary in @('horizon-ctl.exe', 'horizon-prepare.exe', 'horizon-server.exe')) {
$path = "target/aarch64-pc-windows-msvc/release/$binary"
$output = & $path --help 2>&1
if ($LASTEXITCODE -ne 0 -or -not $output) { throw "$binary --help failed" }
}
foreach ($binary in @('horizon-prepare.exe', 'horizon-server.exe')) {
$path = "target/aarch64-pc-windows-msvc/release/$binary"
$output = & $path --version 2>&1
if ($LASTEXITCODE -ne 0 -or -not $output) { throw "$binary --version failed" }
Write-Output ($output -join "`n")
}
- name: Test and build the native Windows ARM64 launcher
working-directory: launcher
run: |
cargo test --release --locked --target aarch64-pc-windows-msvc --lib
cargo build --release --locked --target aarch64-pc-windows-msvc
- name: Check the launcher's PE machine
working-directory: launcher
run: python ../tools/check_pe_arch.py arm64 target/aarch64-pc-windows-msvc/release/horizon-launcher.exe
- name: Check launcher DLL imports and command-line startup
working-directory: launcher
# bash, as launcher.yml's check on the same runner: the launcher is a GUI-subsystem program, and a pwsh
# `$output = & launcher --version` read no output from it (run 42217014588).
shell: bash
run: |
launcher=target/aarch64-pc-windows-msvc/release/horizon-launcher.exe
python ../tools/check_imports.py "$launcher"
out=$("$launcher" --version)
echo "$out"
[[ "$out" == "horizon-launcher "* ]] || { echo "::error::horizon-launcher --version printed '$out'"; exit 1; }
- name: Build a native-mod game with the launcher's managed toolchain
working-directory: launcher
shell: pwsh
env:
HORIZON_LAUNCHER_HOME: ${{ runner.temp }}\horizon-native-arm64-toolchain
HORIZON_NATIVE_TARGET_DIR: ${{ runner.temp }}\horizon-native-arm64-toolchain\target
run: |
$ErrorActionPreference = 'Stop'
cargo test --release --locked --target aarch64-pc-windows-msvc --lib rust_toolchain::tests::managed_arm64_toolchain_builds_the_game -- --ignored --exact --nocapture
$client = Join-Path $env:HORIZON_NATIVE_TARGET_DIR 'fast/horizon-client.exe'
python ../tools/check_pe_arch.py arm64 $client
python ../tools/check_imports.py $client
# Most pull requests leave the harnesses to parity-main, which runs them on main a batch of merges at a time
# (.github/workflows/parity-main.yml; releases wait for it). A pull request still runs them itself when it changes
# a harness, an oracle, a probe or the runner, or carries the run-parity label (run-parity.yml, when it is added).
@@ -169,7 +265,7 @@ jobs:
outputs:
run: ${{ steps.scope.outputs.run }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
@@ -181,7 +277,7 @@ jobs:
run: |
changed=$(git diff --name-only "$(git merge-base "$BASE_SHA" HEAD)" HEAD)
harness=$(grep -E '^(tools/test_[a-z0-9_]+_parity\.py|tools/(parity[a-z_]*|lab)\.py|tools/parity_hosts\.json|mods/horizon/src/test/|crates/[^/]+/examples/|crates/horizon-client/src/game/[a-z_]*(fixture|harness)\.rs|\.github/workflows/parity[a-z-]*\.yml)' <<< "$changed" || true)
if [ -n "$harness" ] || [[ " $LABELS " == *" run-parity "* ]]; then
if [ -n "$harness" ] || [[ " $LABELS " == *" run-parity "* ]] || [[ " $LABELS " == *" frame-identity "* ]]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "The pull request runs its harnesses: ${harness:-label run-parity}"
else
@@ -196,6 +292,7 @@ jobs:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
full: ${{ contains(github.event.pull_request.labels.*.name, 'full-parity') }}
frame_identity: ${{ contains(github.event.pull_request.labels.*.name, 'frame-identity') }}
# Horizon never downloads the game. These jobs need a self-hosted Linux runner
# (label horizon-game) holding the maintainer's own copy: the official launcher's
# game directory in HORIZON_MINECRAFT_DIR and the pinned server jar in
@@ -204,13 +301,13 @@ jobs:
if: github.event_name == 'workflow_dispatch'
runs-on: [self-hosted, linux, horizon-game]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.12'
- name: Install Rust toolchain
run: rustup toolchain install stable --profile minimal
- uses: actions/setup-java@v4
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '25'
@@ -552,11 +649,11 @@ jobs:
if: github.event_name == 'workflow_dispatch'
runs-on: [self-hosted, linux, horizon-game]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.12'
- uses: actions/setup-java@v4
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '25'
+4 -2
View File
@@ -36,7 +36,7 @@ jobs:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
persist-credentials: false
@@ -50,6 +50,8 @@ jobs:
GH_TOKEN: ${{ github.token }}
BODY: ${{ github.event.comment.body }}
AUTHOR: ${{ github.event.comment.user.login }}
# Who may act is decided by this numeric ID: a login can be claimed by someone else after a rename.
AUTHOR_ID: ${{ github.event.comment.user.id }}
ISSUE: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}
REPO: ${{ github.repository }}
@@ -63,7 +65,7 @@ jobs:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
persist-credentials: false
+49 -12
View File
@@ -1,6 +1,6 @@
name: game build
# Builds the game for every platform from `ref`: the Java exporters, then Linux and the Linux server and Windows on
# GitHub, macOS on the Mac mini's runner with a 1500-frame offscreen run of a new world (smoke-test.json): Apple
# Builds the game for every platform from `ref`: the Java exporters, then Linux and the Linux server, Windows x64
# and ARM64 on GitHub, and macOS on the Mac mini's runner with a 1500-frame offscreen run of a new world (smoke-test.json): Apple
# Silicon, and Intel cross-built there for x86_64-apple-darwin and checked under Rosetta 2 (#1161). Called by
# release.yml (the releases) and nightly-build.yml (the opt-in Nightly), always from main's copy of this file: the
# caller's commit, never the one being built. It holds no secrets: signing the checksums is the callers' own job,
@@ -45,7 +45,7 @@ jobs:
# Three full preparations (two isolated, one batched) besides the compile.
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false
@@ -73,7 +73,7 @@ jobs:
! \( -name '*.class' \( -path 'exporters/server/dev/horizonlab/parity/*' -o -path 'exporters/client/dev/horizonlab/parity/*' \) \))
[ -z "$stray" ] || { echo "::error::not one of our classes: $stray"; exit 1; }
python3 tools/check_prepare_exporters.py --binary target/release/horizon-prepare
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: original-exporters
path: exporters/
@@ -83,6 +83,10 @@ jobs:
build:
needs: exporters
name: build ${{ matrix.name }}
# Windows ARM64 and musl are new: a failure never holds the other platforms' release (the signing job
# needs this job, and a failed entry of it would fail it). The release steps leave the missing
# optional archives out with a warning.
continue-on-error: ${{ matrix.name == 'windows-arm64' || matrix.name == 'linux-musl' }}
strategy:
fail-fast: false
matrix:
@@ -94,13 +98,28 @@ jobs:
# The server build too: packaging/server/install.sh installs it on Linux.
server_dir: horizon-server-linux-x86_64
server_asset: horizon-server-linux-x86_64.tar.gz
- name: linux-musl
os: ubuntu-22.04
musl: true
dir: horizon-linux-musl-x86_64
asset: horizon-linux-musl-x86_64.tar.gz
- name: windows
os: windows-latest
dir: horizon-windows-x64
asset: horizon-windows-x64.zip
exe: .exe
pe_arch: x64
# The C runtime linked in, so the game runs without the Visual C++ redistributable.
rustflags: -C target-feature=+crt-static
- name: windows-arm64
os: windows-11-vs2026-arm
target: aarch64-pc-windows-msvc
dir: horizon-windows-arm64
asset: horizon-windows-arm64.zip
exe: .exe
pe_arch: arm64
# The ARM64 runner builds and runs native ARM64 executables; keep the C runtime in the archive.
rustflags: -C target-feature=+crt-static
- name: macos
mac: true
dir: horizon-macos-arm64
@@ -128,7 +147,7 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false
@@ -141,21 +160,29 @@ jobs:
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
if: runner.environment == 'github-hosted'
if: runner.environment == 'github-hosted' && !matrix.musl
with:
# A release restores only what a release saved: an untrusted build has its own key and saves nothing.
shared-key: ${{ inputs.trusted && 'release' || 'nightly' }}-${{ matrix.name }}
save-if: ${{ inputs.trusted }}
- name: ALSA and udev headers
if: runner.os == 'Linux'
if: runner.os == 'Linux' && !matrix.musl
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libasound2-dev libudev-dev
- name: Build the client, horizon-ctl, horizon-prepare and horizon-server
if: ${{ !matrix.musl }}
run: cargo build --release --locked ${{ matrix.target && format('--target {0}', matrix.target) || '' }} -p horizon-client -p horizon-client-lib --bin horizon-client --bin horizon-ctl -p horizon-prepare --bin horizon-prepare -p horizon-server --bin horizon-server
- name: Every program the release folder takes was built
if: ${{ !matrix.musl }}
run: |
for binary in horizon-client horizon-ctl horizon-prepare horizon-server; do
test -f "$BIN/$binary${{ matrix.exe }}" || { echo "::error::$BIN/$binary${{ matrix.exe }} is missing"; exit 1; }
done
- name: Verify Windows executable architecture
if: runner.os == 'Windows'
run: |
python tools/check_pe_arch.py "${{ matrix.pe_arch }}" \
"$BIN/horizon-client${{ matrix.exe }}" "$BIN/horizon-ctl${{ matrix.exe }}" \
"$BIN/horizon-prepare${{ matrix.exe }}" "$BIN/horizon-server${{ matrix.exe }}"
- name: The signing keychain, unlocked
if: runner.os == 'macOS' && vars.MACOS_SIGN_IDENTITY != '' && inputs.developer_id
env:
@@ -173,6 +200,7 @@ jobs:
codesign --verify --strict --verbose=2 "$BIN/$binary"
done
- name: Only system libraries
if: ${{ !matrix.musl }}
# Windows: no non-system DLL (a "DLL not found" dialog on some players' machines). Linux: the standard
# loader and libraries every desktop distribution has. macOS: only the system's own libraries.
run: |
@@ -188,13 +216,14 @@ jobs:
- name: Baseline x86-64 CPUs only
# No POPCNT or LZCNT at all (the script says why only those): a C library's count intrinsic killed the
# game before main on a Core 2 (#7264). Not the Mac builds: every Intel Mac that runs macOS 11 has both.
if: runner.os != 'macOS'
if: runner.os != 'macOS' && matrix.pe_arch != 'arm64' && !matrix.musl
run: |
set -euo pipefail
rustup component add llvm-tools
${{ runner.os == 'Windows' && 'python' || 'python3' }} tools/check_cpu_baseline.py \
$(for binary in horizon-client horizon-ctl horizon-prepare horizon-server; do echo "$BIN/$binary${{ matrix.exe }}"; done)
- name: Every program starts
if: ${{ !matrix.musl }}
run: |
set -euo pipefail
bin=$BIN
@@ -239,18 +268,26 @@ jobs:
# Published with the release; the publish job checks it again before it publishes anything.
cp "$home/smoke.json" "$RUNNER_TEMP/smoke-test.json"
# One smoke-test.json per build, the Apple Silicon one's; the Intel build must pass its own run too.
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: runner.environment == 'self-hosted' && !matrix.target
with:
name: smoke-test.json
path: ${{ runner.temp }}/smoke-test.json
if-no-files-found: error
retention-days: 3
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: original-exporters
path: exporters
- name: Build, check and package for musl in Alpine
if: matrix.musl
# Run Docker on the Ubuntu host: JavaScript actions themselves require glibc.
run: |
docker run --rm -v "$PWD:/work" -w /work \
-e HORIZON_VERSION -e HORIZON_NIGHTLY \
rust:alpine3.22 sh tools/ci/build-musl.sh
- name: Package
if: ${{ !matrix.musl }}
run: |
set -euo pipefail
cargo run --locked -p xtask -- package "$BIN/horizon-client${{ matrix.exe }}" "dist/${{ matrix.dir }}"
@@ -277,7 +314,7 @@ jobs:
codesign --verify --strict "dist/${{ matrix.dir }}/$binary"
done
packaging/macos/notarize.sh "dist/${{ matrix.asset }}"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset }}
path: dist/${{ matrix.asset }}
@@ -299,7 +336,7 @@ jobs:
rm -f "${{ matrix.server_asset }}"
tar czf "${{ matrix.server_asset }}" "${{ matrix.server_dir }}"
ls -l "${{ matrix.server_asset }}"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: matrix.server_asset
with:
name: ${{ matrix.server_asset }}
+47 -15
View File
@@ -1,5 +1,5 @@
name: launcher
# Builds the launcher on Linux, macOS and Windows, and releases it by itself (docs/release-flow.md): after a
# Builds the launcher on Linux, macOS, Windows x64 and ARM64, and releases it by itself (docs/release-flow.md): after a
# push to main that changes the launcher or a crate it builds in (the paths below),
# every 10 minutes (a release waiting for merges to settle goes out) and by hand, tools/release_train.py decides as
# for the game.
@@ -15,8 +15,9 @@ name: launcher
# under Rosetta 2 (#1161). With the repository variable MACOS_SIGN_IDENTITY set, Horizon.app and the .dmg are signed with
# Developer ID, notarized and stapled, from the runner's signing keychain (packaging/macos/README.md);
# unset (forks), they are signed ad hoc.
# Windows ships the launcher's own binary twice: in the ZIP (what a launcher updates itself from) and as
# horizon-launcher-setup.exe, which opens the per-user installer instead of Home (launcher/src/installer.rs).
# Windows ships each architecture's launcher in its ZIP (what a launcher updates itself from) and as
# horizon-launcher-setup.exe or horizon-launcher-setup-arm64.exe, which opens the per-user installer instead of Home
# (launcher/src/installer.rs).
# gh workflow run launcher --ref <branch> # dry run: builds everything, publishes nothing
# gh workflow run launcher --ref main -f dry_run=false -f force=true
@@ -73,7 +74,7 @@ jobs:
version: ${{ steps.decide.outputs.version }}
dry_run: ${{ steps.mode.outputs.dry_run }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- id: mode
@@ -102,6 +103,10 @@ jobs:
needs: decide
if: needs.decide.outputs.release == 'true'
name: build ${{ matrix.name }}
# Windows ARM64 is new: its failure never holds the other platforms' release (the signing job
# needs this job, and a failed entry of it would fail it). The release steps leave the missing
# ARM64 files out with a warning.
continue-on-error: ${{ matrix.name == 'windows-arm64' }}
strategy:
fail-fast: false
matrix:
@@ -125,9 +130,20 @@ jobs:
os: windows-latest
asset: horizon-launcher-windows-x64.zip
binary: horizon-launcher.exe
pe_arch: x64
setup_asset: horizon-launcher-setup.exe
# The C runtime linked in: without it the launcher asks for VCRUNTIME140.dll,
# which only the Visual C++ redistributable installs.
rustflags: -C target-feature=+crt-static
- name: windows-arm64
os: windows-11-vs2026-arm
target: aarch64-pc-windows-msvc
asset: horizon-launcher-windows-arm64.zip
binary: horizon-launcher.exe
pe_arch: arm64
setup_asset: horizon-launcher-setup-arm64.exe
# The C runtime linked in, so the standalone ARM64 launcher needs no redistributable.
rustflags: -C target-feature=+crt-static
runs-on: ${{ matrix.mac && (vars.MAC_RUNNER || 'ludovics-mac-mini') || matrix.os }}
# Notarizing the app and then the .dmg can take a few minutes each.
timeout-minutes: ${{ matrix.mac && 60 || 40 }}
@@ -150,7 +166,7 @@ jobs:
working-directory: launcher
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: ${{ needs.decide.outputs.head }}
persist-credentials: false
@@ -182,6 +198,9 @@ jobs:
# Every test target (the library, the binary, tests/), on each system the launcher ships for.
- run: cargo test --release --locked ${{ matrix.target && format('--target {0}', matrix.target) || '' }} ${LAUNCHER_FEATURES:+--features "$LAUNCHER_FEATURES"}
- run: cargo build --release --locked ${{ matrix.target && format('--target {0}', matrix.target) || '' }} ${LAUNCHER_FEATURES:+--features "$LAUNCHER_FEATURES"}
- name: Verify Windows executable architecture
if: runner.os == 'Windows'
run: python ../tools/check_pe_arch.py "${{ matrix.pe_arch }}" "$BIN/${{ matrix.binary }}"
- name: The version is the release's
run: |
out=$("$BIN/${{ matrix.binary }}" --version)
@@ -212,7 +231,8 @@ jobs:
"$launcher" links --json
# The Intel Mac launcher can come out before any game build has its asset (#1161): then there is nothing
# to install yet. Only that entry skips, and only on a version list that parsed and had no build here
# (exit 3); a launcher or a script that fails still fails the step.
# (exit 3); a launcher or a script that fails still fails the step. The Windows ARM64 launcher always has
# one: it installs the x64 build where a release has no ARM64 one (backend.rs, GAME_FALLBACK).
if [[ "${{ matrix.name }}" == macos-x64 ]]; then
versions=$(retry "$launcher" --json versions)
no_build='import json, sys; sys.exit(3 if not any(r["available_here"] for r in json.load(sys.stdin)) else 0)'
@@ -236,6 +256,11 @@ jobs:
# release linked to a Nix store is a warning here, not a broken launcher.
soft() { if [[ "$RUNNER_OS" == "Linux" ]]; then "$@" || echo "::warning::$* failed"; else "$@"; fi; }
if [[ "$RUNNER_OS" == "Windows" ]]; then
if ! python ../tools/check_pe_arch.py "${{ matrix.pe_arch }}" "$client"; then
# A Nightly without an ARM64 build: the ARM64 launcher installs the x64 one (backend.rs, GAME_FALLBACK).
[[ "${{ matrix.pe_arch }}" == arm64 ]] && python ../tools/check_pe_arch.py x64 "$client" || exit 1
echo "::warning::the latest game has no ARM64 build: the launcher installed the x64 one"
fi
python ../tools/check_imports.py "$client"
elif [[ "$RUNNER_OS" == "Linux" ]]; then
soft python3 ../tools/check_imports.py "$client"
@@ -295,8 +320,8 @@ jobs:
# installer instead of Home (launcher/src/installer.rs). Its name is what makes it the setup,
# so the ZIP's copy stays the launcher. Both go into the release's signed SHA256SUMS.
if: runner.os == 'Windows'
run: cp "$BIN/${{ matrix.binary }}" out/horizon-launcher-setup.exe
- uses: actions/upload-artifact@v4
run: cp "$BIN/${{ matrix.binary }}" "out/${{ matrix.setup_asset }}"
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset }}
path: launcher/out/*
@@ -314,12 +339,12 @@ jobs:
# tools/ci/sign_release.sh, which calls only the runner's sha256sum and OpenSSL (docs/release-flow.md).
environment: ${{ needs.decide.outputs.dry_run != 'true' && 'release' || '' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
sparse-checkout: tools/ci/sign_release.sh
sparse-checkout-cone-mode: false
persist-credentials: false
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
path: dist
pattern: horizon-launcher-*
@@ -331,13 +356,13 @@ jobs:
EPHEMERAL: ${{ needs.decide.outputs.dry_run == 'true' && 'yes' || '' }}
TAG: ${{ needs.decide.outputs.tag }}
run: bash tools/ci/sign_release.sh "$TAG" dist/horizon-launcher-*
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: signature
path: dist/SHA256SUMS*
if-no-files-found: error
retention-days: 3
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: needs.decide.outputs.dry_run == 'true'
with:
name: ephemeral.pub
@@ -351,7 +376,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Notes
@@ -361,7 +386,7 @@ jobs:
python3 tools/release_train.py notes --kind launcher --tag "${{ needs.decide.outputs.tag }}" \
--head "${{ needs.decide.outputs.head }}" --out notes > /dev/null
{ echo "## $(cat notes/title.txt)"; echo; cat notes/body.md; } >> "$GITHUB_STEP_SUMMARY"
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
@@ -373,6 +398,13 @@ jobs:
key=(); [[ -n "$EPHEMERAL" ]] && key=(--key "$(cat dist/ephemeral.pub)")
cargo run --locked --quiet -p horizon-release-sign -- verify --tag "$TAG" --publishing "${key[@]}" dist
# Never marked latest: Horizon's own releases stay the repository's latest.
- name: The Windows ARM64 launcher, if it built
# Its build may fail without holding the release (continue-on-error): the release then has no ARM64
# files, and ARM64 launchers stay on their version until one has (backend::launcher_update_from).
run: |
for file in horizon-launcher-windows-arm64.zip horizon-launcher-setup-arm64.exe; do
[[ -f "dist/$file" ]] || echo "::warning::$file is missing: this release goes out without it"
done
- name: Publish ${{ needs.decide.outputs.tag }}
if: needs.decide.outputs.dry_run != 'true'
env:
@@ -387,7 +419,7 @@ jobs:
run: |
echo "Would publish ${{ needs.decide.outputs.tag }} at ${{ needs.decide.outputs.head }}: $(cat notes/title.txt)"
ls -l dist
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: launcher-release-notes
path: notes/
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
run:
working-directory: crates/horizon-mod-registry
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy
# Ours keep their build folder between jobs.
- uses: Swatinem/rust-cache@v2
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && fromJSON('["self-hosted", "linux", "horizon-light"]') || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Check the manifest
env:
GH_TOKEN: ${{ github.token }}
+6 -2
View File
@@ -24,7 +24,7 @@ jobs:
pull-requests: write
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
fetch-depth: 0
@@ -52,4 +52,8 @@ jobs:
git fetch origin "+refs/heads/$branch:refs/remotes/origin/$branch"
fi
git push --force-with-lease origin "$branch"
gh pr create --base main --head "$branch" --title 'Stamp native hook API fragments' --body 'Assign API numbers to the pending native hook fragments. Close and reopen this PR to run CI.'
# The repo doesn't let GitHub Actions open pull requests (that setting would also let it approve them):
# the branch is pushed, and a maintainer opens the PR from it.
if ! gh pr create --base main --head "$branch" --title 'Stamp native hook API fragments' --body 'Assign API numbers to the pending native hook fragments. Close and reopen this PR to run CI.'; then
echo "::warning::Stamped fragments are on $branch; open a pull request from it (GitHub Actions may not open one here)."
fi
+45 -11
View File
@@ -92,7 +92,7 @@ jobs:
source_tag: ${{ steps.pick.outputs.source_tag }}
dry_run: ${{ steps.mode.outputs.dry_run }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
@@ -124,14 +124,14 @@ jobs:
env:
SHA: ${{ steps.pick.outputs.sha }}
run: git archive --format=tar.gz --prefix="horizon-${SHA:0:7}/" -o horizon-source.tar.gz "$SHA"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: steps.pick.outputs.build == 'true'
with:
name: horizon-source
path: horizon-source.tar.gz
if-no-files-found: error
retention-days: 3
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: nightly-notes
path: nightly-notes.md
@@ -155,32 +155,42 @@ jobs:
# The key's only job: main's sign_release.sh hashes the archives and signs with the runner's OpenSSL.
environment: ${{ needs.pick.outputs.dry_run != 'true' && 'release' || '' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
sparse-checkout: tools/ci/sign_release.sh
sparse-checkout-cone-mode: false
persist-credentials: false
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
path: dist
pattern: horizon-*
merge-multiple: true
- name: Sign the checksums (SHA256SUMS, SHA256SUMS.sig) for the nightly channel
- name: Sign the checksums for the nightly channel (SHA256SUMS.sig) and for this build (SHA256SUMS.v2.sig)
# SHA256SUMS.sig is for launchers from before v2 (signed for "nightly"); SHA256SUMS.v2.sig is signed for
# nightly-<date>-<commit>, the version the release title names, which current launchers and servers
# require (docs/security.md, "Nightly signatures"). The publish step titles the release from the same values.
env:
RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }}
RELEASE_SIGNING_KEY_NEXT: ${{ secrets.RELEASE_SIGNING_KEY_NEXT }}
EPHEMERAL: ${{ needs.pick.outputs.dry_run == 'true' && 'yes' || '' }}
ALSO_FOR: nightly-${{ needs.pick.outputs.date }}-${{ needs.pick.outputs.short }}
run: |
# Windows ARM64's build may fail without holding the Nightly (game-build.yml): then it is left out.
arm64=(); if [[ -f dist/horizon-windows-arm64.zip ]]; then arm64=(dist/horizon-windows-arm64.zip)
else echo "::warning::horizon-windows-arm64.zip is missing: SHA256SUMS goes out without it"; fi
musl=(); if [[ -f dist/horizon-linux-musl-x86_64.tar.gz ]]; then musl=(dist/horizon-linux-musl-x86_64.tar.gz)
else echo "::warning::horizon-linux-musl-x86_64.tar.gz is missing: SHA256SUMS goes out without it"; fi
bash tools/ci/sign_release.sh nightly dist/horizon-linux-x86_64.tar.gz dist/horizon-windows-x64.zip \
"${arm64[@]}" "${musl[@]}" \
dist/horizon-macos-arm64.zip dist/horizon-macos-x64.zip dist/horizon-server-linux-x86_64.tar.gz \
dist/horizon-source.tar.gz
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: signature
path: dist/SHA256SUMS*
if-no-files-found: error
retention-days: 3
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: needs.pick.outputs.dry_run == 'true'
with:
name: ephemeral.pub
@@ -195,19 +205,24 @@ jobs:
contents: write
issues: read # the open release-blocker issues (gate)
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
- name: The signature, checked as launchers check it
- name: The signatures, checked as launchers check them
env:
EPHEMERAL: ${{ needs.pick.outputs.dry_run == 'true' && 'yes' || '' }}
DATE: ${{ needs.pick.outputs.date }}
SHORT: ${{ needs.pick.outputs.short }}
run: |
key=(); [[ -n "$EPHEMERAL" ]] && key=(--key "$(cat dist/ephemeral.pub)")
cargo run --locked --quiet -p horizon-release-sign -- verify --tag nightly --publishing "${key[@]}" dist
# The version's own signature, which current launchers require: the title below names the same version.
cargo run --locked --quiet -p horizon-release-sign -- verify --tag "nightly-${DATE}-${SHORT}" \
--signature SHA256SUMS.v2.sig --publishing "${key[@]}" dist
- name: Ready to publish (the smoke test drew a world, no release-blocker is open)
# Right before publishing, as for a release: an issue labelled while the builds ran still holds it. A dry run
# only reports what a real run would do (that is how a blocker's fix is tried).
@@ -224,18 +239,37 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ needs.pick.outputs.sha }}
SHORT: ${{ needs.pick.outputs.short }}
DATE: ${{ needs.pick.outputs.date }}
SOURCE_TAG: ${{ needs.pick.outputs.source_tag }}
run: |
set -euo pipefail
python3 tools/ci/nightly.py rotate
signature=(); [[ -f dist/SHA256SUMS.sig ]] && signature=(dist/SHA256SUMS.sig)
[[ -f dist/SHA256SUMS.v2.sig ]] && signature+=(dist/SHA256SUMS.v2.sig)
# The title is what launchers parse (strictly) into the version SHA256SUMS.v2.sig is signed for.
[[ "${SHA:0:7}" == "$SHORT" ]] || { echo "::error::the build's commit isn't the one signed"; exit 1; }
# The ARM64 archive only when its build succeeded (the sign job left it out of SHA256SUMS otherwise).
arm64=()
if [[ -f dist/horizon-windows-arm64.zip ]]; then
arm64=("dist/horizon-windows-arm64.zip#Game files the launcher installs (Nightly): Windows ARM64 (horizon-windows-arm64.zip)")
else
echo "::warning::horizon-windows-arm64.zip is missing: the Nightly goes out without it (ARM64 launchers install the x64 build)"
fi
musl=()
if [[ -f dist/horizon-linux-musl-x86_64.tar.gz ]]; then
musl=("dist/horizon-linux-musl-x86_64.tar.gz#Game files for musl Linux (horizon-linux-musl-x86_64.tar.gz)")
else
echo "::warning::horizon-linux-musl-x86_64.tar.gz is missing: the Nightly goes out without it"
fi
# A pre-release, never GitHub's latest: the latest stays the last vX.Y.Z, which launchers and servers from
# before the Nightly-only switch keep (they don't read Nightlies).
gh release create nightly --prerelease --latest=false --target "$SHA" \
--title "Nightly $DATE · ${SHA:0:7}" --notes-file dist/nightly-notes.md \
"dist/horizon-linux-x86_64.tar.gz#Game files the launcher installs (Nightly): Linux (horizon-linux-x86_64.tar.gz)" \
"${musl[@]}" \
"dist/horizon-windows-x64.zip#Game files the launcher installs (Nightly): Windows (horizon-windows-x64.zip)" \
"${arm64[@]}" \
"dist/horizon-macos-arm64.zip#Game files the launcher installs (Nightly): macOS on Apple Silicon (horizon-macos-arm64.zip)" \
"dist/horizon-macos-x64.zip#Game files the launcher installs (Nightly): macOS on Intel (horizon-macos-x64.zip)" \
"dist/horizon-server-linux-x86_64.tar.gz#Nightly dedicated server for Linux (horizon-server-linux-x86_64.tar.gz)" \
+3 -1
View File
@@ -27,7 +27,7 @@ jobs:
issues: write # answers in the thread
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
persist-credentials: false
@@ -41,6 +41,8 @@ jobs:
GH_TOKEN: ${{ github.token }}
BODY: ${{ github.event.comment.body }}
AUTHOR: ${{ github.event.comment.user.login }}
# Who may act is decided by this numeric ID: a login can be claimed by someone else after a rename.
AUTHOR_ID: ${{ github.event.comment.user.id }}
ISSUE: ${{ github.event.issue.number }}
COMMENT_URL: ${{ github.event.comment.html_url }}
run: python3 tools/ci/nightly_set.py command
+2 -2
View File
@@ -48,7 +48,7 @@ jobs:
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
- uses: actions/checkout@v7
if: steps.when.outputs.run == 'true'
with:
fetch-depth: 0
@@ -82,7 +82,7 @@ jobs:
run: python3 tools/ci/parity_issues.py --since "${{ steps.audit.outputs.since }}" ${{ inputs.dry_run && '--dry-run' || '' }} | tee -a "$GITHUB_STEP_SUMMARY"
- name: Logs of the failed harnesses
if: steps.when.outputs.run == 'true' && steps.audit.outputs.exit != '0'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: nightly-parity-logs
path: .local/parity-runner/logs/
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
fetch-depth: 0
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
runs-on: ${{ vars.SELF_HOSTED_CI != 'off' && vars.SELF_HOSTED_CI != 'light' && fromJSON('["self-hosted", "linux", "horizon-checks"]') || 'ubuntu-latest' }}
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install Rust and the ALSA and udev headers
+2 -2
View File
@@ -30,7 +30,7 @@ jobs:
runs-on: [self-hosted, macOS, horizon-parity-main]
timeout-minutes: 240 # a core-crate batch reruns ~300 harnesses (~60-90 min), a bisect a few rounds of the failures
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: ${{ github.sha }}
fetch-depth: 0
@@ -69,7 +69,7 @@ jobs:
python3 tools/parity_main.py bisect "$BASE_SHA" --comment
- name: Logs of the failed harnesses
if: steps.harnesses.outcome == 'failure'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: parity-main-logs
path: .local/parity-main-logs/
+14 -2
View File
@@ -20,6 +20,12 @@ on:
busy the job waits for it.
type: boolean
default: false
frame_identity:
description: >-
The pull request carries the frame-identity label: it must leave the picture alone (HDR, shader packs), so
the frame_identity harness, which no automatic selection picks, runs too.
type: boolean
default: false
permissions:
contents: read
env:
@@ -33,7 +39,7 @@ jobs:
# harnesses at a time) up to a few hours, and any pull request's job may land there.
timeout-minutes: ${{ inputs.full && 360 || 180 }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
# The commit the event names: a fork's later pushes need another run-parity label. The next step merges
# main into it.
@@ -87,9 +93,15 @@ jobs:
PARITY_HOSTS: ''
BASE_SHA: ${{ steps.merge.outputs.base }}
run: python3 tools/parity.py --changed "$BASE_SHA" --timeout 2400 # world_spawn takes ~1480 s on a busy runner
- name: The pictures this change must leave alone
if: ${{ !cancelled() && inputs.frame_identity }}
env:
PARITY_HOSTS: ''
BASE_SHA: ${{ steps.merge.outputs.base }}
run: python3 tools/parity.py frame_identity --timeout 2400
- name: Logs of the failed harnesses
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: parity-logs
path: .local/parity-runner/logs/
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
persist-credentials: false
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
urgent: ${{ steps.decide.outputs.urgent }}
dry_run: ${{ steps.mode.outputs.dry_run }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- id: mode
+1
View File
@@ -27,3 +27,4 @@ jobs:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.event.pull_request.head.sha }}
full: ${{ contains(github.event.pull_request.labels.*.name, 'full-parity') }}
frame_identity: ${{ contains(github.event.pull_request.labels.*.name, 'frame-identity') }}
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
run:
working-directory: crates/horizon-telemetry-server
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy
# Ours keep their build folder between jobs.
- uses: Swatinem/rust-cache@v2
+3 -1
View File
@@ -29,7 +29,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
ref: main
persist-credentials: false
@@ -43,6 +43,8 @@ jobs:
GH_TOKEN: ${{ github.token }}
BODY: ${{ github.event.comment.body }}
AUTHOR: ${{ github.event.comment.user.login }}
# Who may act is decided by this numeric ID: a login can be claimed by someone else after a rename.
AUTHOR_ID: ${{ github.event.comment.user.id }}
ISSUE: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
COMMENT_URL: ${{ github.event.comment.html_url }}
+8 -3
View File
@@ -9,8 +9,10 @@ Feature → files → harness for the most-changed areas: [docs/architecture.md]
- **Never commit Mojang's files**: game jars, assets, decompiled source, prepared resources (`.local/`).
`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`, and, for the
folders in `HORIZON_HOME` (logs, crash reports, telemetry consent, recordings; not saves), `HORIZON_LAUNCHER_HOME`, `XDG_DATA_HOME` and
`XDG_CONFIG_HOME`, plus `HORIZON_LAUNCHER_NO_SELF_UPDATE=1` and `HORIZON_NO_AUDIO=1`. Saves, `options.txt` and
caches go in the game folder, by default the working directory: start the game from a scratch folder with
`--game-dir` pointing at it, or they land in the repo root, hidden by `.gitignore`. 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 the person whose agent opened it and
@@ -32,7 +34,10 @@ Feature → files → harness for the most-changed areas: [docs/architecture.md]
- Contributors first: `gh pr list --state open --search <area>` before you start; never duplicate theirs.
- **No hardcoded user-facing strings.** Keep UI translation-ready from the start. In the launcher (`launcher/`), wrap visible text with `tr("...")` or `trf("... {arg} ...", &[("arg", &val)])` from `crate::i18n` instead of raw string literals or `format!`. In the game, use translation keys (`Text::tr`, `lang::get`), never hardcoded English strings.
- **Claim before you start**: comment `/claim` on the issue (no issue yet: open one, then claim it). An assigned
issue is taken: pick another. `/unclaim` when you stop; a claim with no open PR linking it is released after 5 days.
issue is taken, and so is one labelled `in progress` (claimed on Discord): pick another. `/unclaim` when you stop;
a claim with no open PR linking it is released after 5 days. The issues free to claim, without reading threads:
`gh issue list --search 'is:open no:assignee -label:"in progress" -label:tracker -label:proposal -label:"not claimable" -linked:pr'`
- **Controllers reach what registers.** Draw controls with a registering helper (`ui::button`, `settings_shell::foot_button`); `graphics_style::button` and `screen_draw::button` only paint, and registering one part of a screen ends its crosshair, so register all of it.
## Crates
Generated
+447 -3
View File
@@ -147,6 +147,18 @@ dependencies = [
"x11rb",
]
[[package]]
name = "arcstr"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d"
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "arrayref"
version = "0.3.9"
@@ -303,6 +315,21 @@ dependencies = [
"objc2 0.6.5",
]
[[package]]
name = "btoi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b5ab9db53bcda568284df0fd39f6eac24ad6f7ba7ff1168b9e76eba6576b976"
dependencies = [
"num-traits",
]
[[package]]
name = "bufstream"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -329,6 +356,12 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
@@ -458,6 +491,7 @@ checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"num-traits",
"serde",
"windows-link",
]
@@ -513,6 +547,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "combine"
version = "4.6.8"
@@ -651,6 +691,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.23"
@@ -768,6 +817,17 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
[[package]]
name = "derive_utils"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc05a5d33db20c784f873e84934ad94bb209a090987ac5f62fede2c178234f23"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
]
[[package]]
name = "des"
version = "0.8.1"
@@ -899,6 +959,18 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fdeflate"
version = "0.3.7"
@@ -1058,8 +1130,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasm-bindgen",
]
[[package]]
@@ -1088,6 +1162,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gif"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "gilrs"
version = "0.11.2"
@@ -1217,6 +1301,15 @@ dependencies = [
"serde_core",
]
[[package]]
name = "hashlink"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb"
dependencies = [
"hashbrown 0.17.1",
]
[[package]]
name = "heck"
version = "0.5.0"
@@ -1318,6 +1411,7 @@ dependencies = [
"ab_glyph",
"bytemuck",
"chrono",
"dunce",
"flate2",
"fs4",
"gilrs",
@@ -1363,6 +1457,7 @@ dependencies = [
"serde_json",
"sha1",
"sha2",
"toml",
"unicode-bidi",
"ureq",
"wgpu",
@@ -1400,6 +1495,7 @@ dependencies = [
"chrono",
"flate2",
"fs4",
"getrandom 0.3.4",
"gilrs",
"glam",
"horizon-anticheat",
@@ -1411,6 +1507,7 @@ dependencies = [
"horizon-client-render",
"horizon-command",
"horizon-core",
"horizon-cosmetics",
"horizon-dualsense",
"horizon-friends",
"horizon-hdr",
@@ -1605,6 +1702,21 @@ dependencies = [
"serde_json",
]
[[package]]
name = "horizon-mod-deps"
version = "0.1.0"
dependencies = [
"gif",
"mysql",
"quinn",
"redis",
"rusqlite",
"rusty-opus",
"snap",
"tungstenite",
"yaml-rust2",
]
[[package]]
name = "horizon-mods"
version = "0.1.0"
@@ -1715,6 +1827,7 @@ dependencies = [
"horizon-mcp",
"horizon-release-sign",
"horizon-server-files",
"horizon-world",
"libc",
"p12-keystore",
"png",
@@ -1999,6 +2112,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "io-enum"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f859dc81e05e585e93cfc84dffa4fd068d0651ca229c51bca6101c056b63b93a"
dependencies = [
"derive_utils",
]
[[package]]
name = "ipconfig"
version = "0.3.4"
@@ -2197,6 +2319,17 @@ dependencies = [
"redox_syscall 0.9.4",
]
[[package]]
name = "libsqlite3-sys"
version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14a5f01b87b92705d07b433c06f96cbc764637d81a0bffb5734ce17cf0f9a2b3"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "libudev-sys"
version = "0.1.4"
@@ -2246,6 +2379,18 @@ version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "lru"
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
[[package]]
name = "lru-slab"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
[[package]]
name = "lz4_flex"
version = "0.14.0"
@@ -2335,6 +2480,60 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
[[package]]
name = "mysql"
version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090068bc0a24762663338da78414148333d1ee246a7293c10a517ddf1548b291"
dependencies = [
"bufstream",
"bytes",
"crossbeam-queue",
"crossbeam-utils",
"flate2",
"io-enum",
"libc",
"lru",
"mysql_common",
"named_pipe",
"pem",
"percent-encoding",
"rustls",
"rustls-pemfile",
"socket2 0.6.5",
"twox-hash",
"url",
"webpki",
"webpki-roots 1.0.10",
]
[[package]]
name = "mysql_common"
version = "0.36.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20e7f6a15265801679ddf1ddb38fd74b4555a677a78378d4561fbe5f0b1d4d72"
dependencies = [
"base64",
"bitflags 2.13.2",
"btoi",
"byteorder",
"bytes",
"chrono",
"crc32fast",
"flate2",
"getrandom 0.3.4",
"num-bigint",
"num-traits",
"regex",
"saturating",
"serde",
"serde_json",
"sha1",
"sha2",
"thiserror 2.0.21",
"uuid",
]
[[package]]
name = "naga"
version = "30.0.1"
@@ -2354,7 +2553,7 @@ dependencies = [
"num-traits",
"once_cell",
"pp-rs",
"rustc-hash",
"rustc-hash 1.1.0",
"spirv",
"thiserror 2.0.21",
"unicode-ident",
@@ -2368,10 +2567,19 @@ checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49"
dependencies = [
"hashbrown 0.17.1",
"indexmap",
"rustc-hash",
"rustc-hash 1.1.0",
"thiserror 2.0.21",
]
[[package]]
name = "named_pipe"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b"
dependencies = [
"winapi",
]
[[package]]
name = "ndk"
version = "0.9.0"
@@ -2971,6 +3179,16 @@ dependencies = [
"hmac",
]
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64",
"serde_core",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -3205,6 +3423,62 @@ dependencies = [
"memchr",
]
[[package]]
name = "quinn"
version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.3",
"rustls",
"socket2 0.6.5",
"thiserror 2.0.21",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"
dependencies = [
"bytes",
"getrandom 0.4.3",
"lru-slab",
"rand",
"rand_pcg",
"ring",
"rustc-hash 2.1.3",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.21",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2 0.6.5",
"tracing",
"windows-sys 0.61.2",
]
[[package]]
name = "quote"
version = "1.0.47"
@@ -3252,6 +3526,15 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rand_pcg"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "range-alloc"
version = "0.1.5"
@@ -3298,6 +3581,22 @@ dependencies = [
"yasna",
]
[[package]]
name = "redis"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7f3d75b5a3301746bff39a767bf4c5c42506b08513527308b3875bfb64a031"
dependencies = [
"arcstr",
"combine",
"itoa",
"percent-encoding",
"ryu",
"socket2 0.6.5",
"url",
"xxhash-rust",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@@ -3374,12 +3673,32 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rusqlite"
version = "0.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9bba6f3506135e120a6586df05be18b48d18896600646a4bec8f993d91a594"
dependencies = [
"bitflags 2.13.2",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustc_version"
version = "0.4.1"
@@ -3439,12 +3758,22 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
dependencies = [
"web-time",
"zeroize",
]
@@ -3465,6 +3794,12 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "rusty-opus"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46604e6809c06441da1cfdde1f0cc25a4f2bf42c7d005aa387c2875c02d78762"
[[package]]
name = "ruzstd"
version = "0.9.1"
@@ -3474,6 +3809,12 @@ dependencies = [
"twox-hash",
]
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "salsa20"
version = "0.10.2"
@@ -3492,6 +3833,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "saturating"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -3691,6 +4038,12 @@ dependencies = [
"serde",
]
[[package]]
name = "snap"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886"
[[package]]
name = "socket2"
version = "0.5.10"
@@ -3711,6 +4064,17 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "socks"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
dependencies = [
"byteorder",
"libc",
"winapi",
]
[[package]]
name = "spake2"
version = "0.4.0"
@@ -3995,13 +4359,23 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
version = "1.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
dependencies = [
"libc",
"mio",
"pin-project-lite",
"socket2 0.6.5",
"windows-sys 0.61.2",
]
[[package]]
@@ -4071,6 +4445,9 @@ name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "tree_magic_mini"
@@ -4089,6 +4466,18 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "tungstenite"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22"
dependencies = [
"bytes",
"log",
"rand",
"thiserror 2.0.21",
]
[[package]]
name = "twox-hash"
version = "2.1.5"
@@ -4164,6 +4553,7 @@ dependencies = [
"once_cell",
"rustls",
"rustls-pki-types",
"socks",
"url",
"webpki-roots 0.26.11",
]
@@ -4196,6 +4586,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
@@ -4431,6 +4827,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
@@ -4449,6 +4855,12 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "wgpu"
version = "30.0.1"
@@ -4502,7 +4914,7 @@ dependencies = [
"portable-atomic",
"profiling",
"raw-window-handle",
"rustc-hash",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 2.0.21",
"wgpu-core-deps-apple",
@@ -4626,6 +5038,22 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -4635,6 +5063,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.54.0"
@@ -5223,6 +5657,16 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "550a2b930b62486a393c52d5c3b84bff264b28aa437ed64694d31e93b1757af7"
[[package]]
name = "yaml-rust2"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e5b818a27a4cd30884ea380857a5e56f7ec3ba24a3990a3cc0b95af3238e18"
dependencies = [
"arraydeque",
"hashlink",
]
[[package]]
name = "yasna"
version = "0.6.0"
+1
View File
@@ -39,6 +39,7 @@ members = [
"crates/horizon-optifine",
"crates/horizon-shader-api",
"crates/horizon-release-sign",
"crates/horizon-mod-deps",
"xtask",
]
resolver = "2"
+62 -64
View File
@@ -1,77 +1,87 @@
<p align="center">
<img src="docs/media/banner.webp" alt="The Horizon website: the game you know, rebuilt in Rust" width="100%">
</p>
<p align="center">
<a href="https://github.com/horizon-rust/horizon/actions/workflows/ci.yml"><img src="https://github.com/horizon-rust/horizon/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
<img src="https://img.shields.io/badge/Minecraft-Java%2026.3-5D9C3F" alt="Minecraft Java 26.3">
<img src="https://img.shields.io/badge/Rust-stable-B7410E?logo=rust&logoColor=white" alt="Rust">
<img src="https://img.shields.io/badge/vanilla%20parity-100%25-F0795A" alt="Vanilla parity 100%">
<a href="#project-status"><img src="https://img.shields.io/badge/player--visible%20gaps-0%20open-F0795A" alt="Player-visible gaps open"></a>
<img src="https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-2A2470" alt="Linux, macOS and Windows">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3B2F7E" alt="MIT license"></a>
<img src="docs/media/banner.webp" alt="Horizon: the game you know, rebuilt in Rust" width="100%">
</p>
<p align="center">
<a href="https://playhorizon.dev"><b>Website</b></a> ·
<a href="#download"><b>Download</b></a> ·
<a href="https://playhorizon.dev/docs/"><b>Documentation</b></a> ·
<a href="https://github.com/horizon-rust/horizon/releases"><b>Release notes</b></a> ·
<a href="https://playhorizon.dev/docs/"><b>Docs</b></a> ·
<a href="https://discord.gg/playhorizon"><b>Discord</b></a> ·
<a href="ROADMAP.md"><b>Roadmap</b></a> ·
<a href="#project-status"><b>Project status</b></a> ·
<a href="#contributing"><b>Contributing</b></a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/vanilla%20parity-100%25-F0795A" alt="Vanilla parity 100%">
<a href="#project-status"><img src="https://img.shields.io/badge/player--visible%20gaps-0%20open-F0795A" alt="Player-visible gaps open"></a>
</p>
# Horizon
Minecraft: Java Edition 26.3, rewritten in Rust. Same worlds, same rules, same terrain, checked against the Java game by more than 300 differential harnesses. Free, open source and unofficial.
**Minecraft: Java Edition 26.3, rewritten in Rust.** Your worlds, the same rules and the same terrain as the Java game, checked against it by hundreds of differential tests, in one native program with no Java. Free, open source (MIT) and unofficial: you play it with your own copy of Java Edition.
> Not an official Minecraft product, nor approved by or associated with Mojang or Microsoft. Horizon contains no Minecraft code, assets or data: you need your own copy of Java Edition 26.3.
## Download
Get **the Horizon launcher** from [playhorizon.dev](https://playhorizon.dev) or [the launcher releases](https://github.com/horizon-rust/horizon/releases?q=launcher-v&expanded=true) (Linux x86-64, macOS on Apple Silicon, macOS on Intel from the next Nightly, Windows x64). It installs and updates the game, prepares its files from your copy of Minecraft, and updates itself.
**You need** a Microsoft account that owns Java Edition ([why](docs/microsoft-account.md)) and a GPU with Vulkan or OpenGL 3.3 (Linux), Vulkan or DirectX 12 (Windows), or a Mac (Apple Silicon, or Intel from the next Nightly). An existing Minecraft install (official launcher, Prism, MultiMC...) is reused; without one, Play downloads 26.3 from Mojang for your account. Step by step: [Install and first launch](https://playhorizon.dev/docs/install).
<table>
<tr>
<td width="50%"><img src="docs/media/launcher-play.webp" alt="The launcher's Home page: the selected instance ready to play, the version picker, Join a friend and the instance's tabs"></td>
<td width="50%"><img src="docs/media/launcher-settings.webp" alt="The launcher's Settings page: Microsoft account, offline name, updates, appearance and accent colour"></td>
</tr>
</table>
<p align="center">
<img src="docs/media/readme/valley.webp" alt="A river valley with a plains village, savanna hills and a cherry grove, seen from above with a long view distance" width="100%">
</p>
## What works today
- **The game:** vanilla world generation, mobs, villagers, raids, redstone, every command, the HUD and the recipe book, on vanilla saves. Worlds from 1.8 to 26.2 are upgraded. [Status](#project-status).
- **Extras:** new menus, graphics presets, distant terrain up to 1024 chunks, controllers, zoom, minimap and more, off by default where they change the vanilla look. [Settings](https://playhorizon.dev/docs/settings).
- **Packs:** resource packs, and data packs with functions, predicates, item modifiers and world generation.
- **Multiplayer:** invite friends with a code, Open to LAN, join servers (online mode included). [Multiplayer](https://playhorizon.dev/docs/multiplayer).
- **Mods:** content mods (blocks, items, data) and native Rust mods, built by the launcher with nothing to install. Java mod jars do not run. [Mods](https://playhorizon.dev/docs/mods).
Everything below is in the current release.
In the Rust client, with vanilla assets and no mods or shaders:
- **The game.** Survival, creative, hardcore and spectator; vanilla world generation in all three dimensions, mobs, villagers, raids, redstone, every command, the HUD and the recipe book. Vanilla saves open as they are, and worlds from 1.8 to 26.2 are upgraded.
- **Multiplayer.** Join vanilla and Horizon servers (online mode included), open a world to LAN, or invite friends with a code from the launcher's Home page.
- **Graphics.** Distant terrain up to 1,024 chunks, graphics presets, HDR and controllers, all off by default where they would change the vanilla look ([settings](https://playhorizon.dev/docs/settings)).
- **Packs.** Resource packs, data packs and shader packs ([below](#mods-and-shader-packs)).
- **Mods.** Content mods and native Rust mods, installed from the launcher ([below](#mods-and-shader-packs)).
- **Servers.** Host one from the launcher, or run `horizon-server` on a VPS ([below](#host-a-server)).
- **The launcher.** It installs and updates the game, prepares its files from your copy of Minecraft, and updates itself.
<table>
<tr>
<td colspan="2"><img src="docs/media/sunset.webp" alt="Sunset over a savanna river valley and a plains village"></td>
<td width="50%"><img src="docs/media/readme/sunset.webp" alt="Sunset behind a cherry grove on a hill, with a river below"></td>
<td width="50%"><img src="docs/media/readme/nether.webp" alt="A crimson forest in the Nether, with a piglin among the fungi"></td>
</tr>
<tr>
<td width="50%"><img src="docs/media/survival.webp" alt="Survival at dusk: a zombie walks up, with the hotbar, health, hunger and experience"></td>
<td width="50%"><img src="docs/media/settings.webp" alt="Vanilla's Video Settings screen with Horizon's own buttons at the top: Horizon Graphics and Advanced Graphics"></td>
</tr>
<tr>
<td width="50%"><img src="docs/media/village.webp" alt="A plains village among savanna hills, with farms, villagers and an iron golem"></td>
<td width="50%"><img src="docs/media/nether.webp" alt="A crimson forest in the Nether with a zombified piglin"></td>
<td width="50%"><img src="docs/media/readme/survival.webp" alt="Survival in a village square: two villagers, flowers, the hotbar, health and hunger"></td>
<td width="50%"><img src="docs/media/readme/multiplayer.webp" alt="Two players in a LAN world: the other player's name tag and the player list"></td>
</tr>
</table>
<sub>Nightly 2026-11-05 on a Mac mini (Apple M4 Pro, Metal): vanilla's textures, no mods or shader packs, render distance 32 with distant terrain on.</sub>
## Download
Get **the Horizon launcher** from [playhorizon.dev](https://playhorizon.dev), or from [the launcher releases](https://github.com/horizon-rust/horizon/releases?q=launcher&expanded=true):
| Platform | File | Needs |
| --- | --- | --- |
| Windows x64 | `horizon-launcher-setup.exe` | Windows 10 or 11, a GPU with Vulkan or DirectX 12 |
| Windows on Arm | `horizon-launcher-setup-arm64.exe` | Windows 11 on Arm |
| macOS, Apple Silicon | `horizon-launcher-macos-arm64.dmg` | macOS 12 or later |
| macOS, Intel | `horizon-launcher-macos-x64.dmg` | macOS 12 or later |
| Linux x86-64 | `horizon-launcher-linux-x86_64.AppImage` or `.tar.gz` | a glibc distribution, X11 or Wayland, a GPU with Vulkan or OpenGL 3.3 |
**You also need a Microsoft account that owns Minecraft: Java Edition.** You sign in with it in the launcher, which plays only with an account that owns the game ([why](docs/microsoft-account.md)). Horizon contains no Minecraft code or assets: an existing install (official launcher, Prism, MultiMC...) is reused, and without one the launcher downloads 26.3 from Mojang for your account. Step by step: [Install and first launch](https://playhorizon.dev/docs/install).
<table>
<tr>
<td width="50%"><img src="docs/media/readme/launcher-home.webp" alt="The launcher's Home page: the Horizon instance, Play, the version picker, the friend invite field, screenshots and the patch log"></td>
<td width="50%"><img src="docs/media/readme/launcher-mods.webp" alt="The launcher's Mods page: Horizon's mod registry with Zoom selected, and tabs for modpacks, resource packs, shader packs and data packs"></td>
</tr>
</table>
## Mods and shader packs
- **Content mods** (`.hmod`): blocks, items, recipes, loot, models and textures, with no code.
- **Native mods** (`.hnmod`): Rust that the launcher compiles into the game for you, with nothing to install.
- **Java mods** (Fabric, NeoForge, Forge) don't run: Horizon has no Java. The launcher carries over the data and assets inside their jars, and imports modpacks (`.mrpack`, CurseForge zips, Prism instances) the same way ([modpacks](https://playhorizon.dev/docs/modpacks)).
- **Shader packs**: Iris/OptiFine-format packs (`shaders/` folders) and Horizon's own WGSL packs, under Video Settings → Shader Packs ([pack format](docs/shaders.md)).
Mods are off by default: a fresh install plays exactly like vanilla 26.3. The launcher's Mods page browses Horizon's registry and Modrinth. [Mods](https://playhorizon.dev/docs/mods) · [make a mod](docs/mods.md).
## Host a server
The launcher's **Servers** page creates, starts, backs up and updates a server, and invites friends over Tailscale. On a VPS, `horizon-server` installs with one command and needs no Java. [Host a server](https://playhorizon.dev/docs/hosting) ([reference](docs/hosting.md)).
## Modpacks
The launcher imports Modrinth and CurseForge packs and plays the mods Horizon can carry over: today, content mods made of data and assets, not Java code. [Modpacks](https://playhorizon.dev/docs/modpacks).
The launcher's **Servers** page creates, starts, backs up and updates a server, and invites friends. On a VPS, `horizon-server` installs with one command, needs no Java, and takes vanilla 26.3 and Horizon players alike. [Host a server](https://playhorizon.dev/docs/hosting) ([reference](docs/hosting.md)).
## Agent control
@@ -80,13 +90,11 @@ AI agents and scripts can play Horizon. `horizon-ctl`, shipped with the game, re
```sh
horizon-ctl launch --world Survival --seed 5 --wait # finds the version the launcher installed
horizon-ctl state # position, health, food, target, screen...
horizon-ctl find-block birch_log # the nearest logs
horizon-ctl goto -0.5 65 -2.5 --near 2.5 # walks there along a path
horizon-ctl break -1 65 -3 # mines it; replies with the drops
horizon-ctl open-inventory && horizon-ctl click-slot 0 # menus: slots, buttons by label, text fields
```
Commands block until done and reply with the result; `horizon-ctl help` lists them all. [Agent control](docs/native-client.md#agent-control-horizon-ctl-and-mcp).
`horizon-ctl help` lists every command. [Agent control](docs/native-client.md#agent-control-horizon-ctl-and-mcp).
## Build from source
@@ -105,13 +113,9 @@ cargo run --release -p horizon-client
Prepared files land in the gitignored `.local/`: never commit or share them. Options, controls and architecture: [the native client guide](docs/native-client.md); all developer docs: [docs/README.md](docs/README.md).
## Roadmap
What is in progress, what comes next and where to help: [ROADMAP.md](ROADMAP.md), or [playhorizon.dev/hub](https://playhorizon.dev/hub) with the work board and what shipped.
## Contributing
Issues and pull requests are welcome: read [CONTRIBUTING.md](CONTRIBUTING.md) (vanilla first, small pull requests). Questions: [Discord](https://discord.gg/playhorizon). Before a pull request, run the self-check and paste its output:
Issues and pull requests are welcome: read [CONTRIBUTING.md](CONTRIBUTING.md) (vanilla first, small pull requests), and see [ROADMAP.md](ROADMAP.md) or [playhorizon.dev/hub](https://playhorizon.dev/hub) for what's next. Questions: [Discord](https://discord.gg/playhorizon). Before a pull request, run the self-check and paste its output:
```sh
git config core.hooksPath .githooks
@@ -124,7 +128,7 @@ It runs rustfmt, clippy, the touched crates' tests, the Python tests, the public
**Player-visible gaps: 0 open** of the 833 the [port ledger](tools/port_ledger.py) found, reading every vanilla 26.3 method against Horizon's code: vanilla behaviour a player would notice that Horizon doesn't have yet. By area: Animals [0](https://github.com/horizon-rust/horizon/issues/3512); Monsters and bosses [0](https://github.com/horizon-rust/horizon/issues/3513); Entities, projectiles, vehicles and decorations [0](https://github.com/horizon-rust/horizon/issues/3514); Items, enchantments and inventories [0](https://github.com/horizon-rust/horizon/issues/3515); Blocks, redstone and fluids [0](https://github.com/horizon-rust/horizon/issues/3516); World, world generation and saves [0](https://github.com/horizon-rust/horizon/issues/3517); Screens, HUD and client packets [0](https://github.com/horizon-rust/horizon/issues/3518); Entity and block rendering, models [0](https://github.com/horizon-rust/horizon/issues/3519); Server and players on the server [0](https://github.com/horizon-rust/horizon/issues/3520); Mob AI, villagers and raids [0](https://github.com/horizon-rust/horizon/issues/3521). This number goes down as gaps close (`tools/parity_gaps.py`); the harness results below say that what is tested matches.
The table is the project's own audit: every subsystem matches vanilla in every harness that compares it. 100% means 100% of what the harnesses compare, not that nothing else differs.
The table is the project's own audit: every subsystem matches vanilla in every harness that compares it. 100% means 100% of what the harnesses compare, not that nothing else differs. What players still run into: [known issues](docs/known-issues.md).
### Java parity: 100%
@@ -147,14 +151,8 @@ Scores: 0 (absent), 0.25 (started), 0.5 (playable, major features missing), 0.75
| Audio | 1 | None: the sound engine, sound events, music, situational music, ambience and subtitles match vanilla |
| Block behaviours and ticking | 1 | None: block updates, random ticks, redstone, dispensers (6,000 cases), crafters (4,000), container spills (4,000), copper golems and the sculk family (435 scenarios) match |
### Known issues
Skins served over https show the default skin unless already cached; pack icons show the unknown-pack texture in game; item tooltips lack attribute lines.
## Licence
[MIT](LICENSE), for the project's original work; dependencies keep their own licences ([notices](THIRD_PARTY_NOTICES.md)). Minecraft is a trademark of Mojang Synergies AB; its code and assets belong to Mojang and Microsoft under the [Minecraft EULA](https://www.minecraft.net/en-us/eula).
[MIT](LICENSE), for the project's original work; dependencies keep their own licences ([notices](THIRD_PARTY_NOTICES.md)). Horizon is made by [Ludovic Marie](https://lu4ovic.com) ([@ludovic111](https://github.com/ludovic111)) and [its contributors](https://github.com/horizon-rust/horizon/graphs/contributors); support it on [GitHub Sponsors](https://github.com/sponsors/ludovic111).
## Author
Made by [Ludovic Marie](https://lu4ovic.com) ([@ludovic111](https://github.com/ludovic111)).
NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT. Minecraft is a trademark of Mojang Synergies AB; its code and assets belong to Mojang and Microsoft under the [Minecraft EULA](https://www.minecraft.net/en-us/eula).
+54 -35
View File
@@ -206,7 +206,7 @@ Manage a server from a console, ban and pardon players, and drive it from other
<details><summary>Technical</summary>
`horizon-server console` (a text interface), `ban`, `pardon`, `ban-ip` and `mcp` over stdio. Admin API additions: the player list, kick, ban, op and whitelist, an SSE stream of logs and events, a text metrics endpoint, config GET and PATCH, backup delete and download. The panel's later features (bans, worlds, server.properties import, logs and crash reports, modpacks) and vanilla's management protocol (the JSON-RPC over WebSocket that the `management-server-*` keys configure), so vanilla's tools can manage a Horizon server. [docs/server.md](docs/server.md), [docs/api/server.md](docs/api/server.md).
`horizon-server console` (a text interface), `ban`, `pardon`, `ban-ip` and `mcp` over stdio. Admin API additions: the player list, kick, ban, op and whitelist, an SSE stream of logs and events, a text metrics endpoint, config GET and PATCH, backup delete and download. The last pieces merged: bans edited while the server is stopped ([#9767](https://github.com/horizon-rust/horizon/issues/9767)) and the MCP tools' contracts pinned by a test ([#9748](https://github.com/horizon-rust/horizon/issues/9748)). The panel's later features and vanilla's management protocol are their own item (server-panel-management). [docs/server.md](docs/server.md), [docs/api/server.md](docs/api/server.md).
</details>
@@ -266,7 +266,7 @@ Shader packs were removed and are being rebuilt from scratch. They come back, of
A clean-room rewrite in `horizon-shaderpack`, from the packs' public format documentation only: reading packs, the preprocessor and `.properties` files are merged ([#1584](https://github.com/horizon-rust/horizon/issues/1584), [#1585](https://github.com/horizon-rust/horizon/issues/1585), [#1586](https://github.com/horizon-rust/horizon/issues/1586)); translating programs to WGSL through naga, buffers, uniforms and the terrain, entity, sky, weather and hand stages are in review ([#1698](https://github.com/horizon-rust/horizon/issues/1698) to [#1801](https://github.com/horizon-rust/horizon/issues/1801)).
More: [#1561](https://github.com/horizon-rust/horizon/issues/1561) · [#1771](https://github.com/horizon-rust/horizon/issues/1771) · [#1786](https://github.com/horizon-rust/horizon/issues/1786)
More: [#1561](https://github.com/horizon-rust/horizon/issues/1561) · [#1771](https://github.com/horizon-rust/horizon/issues/1771) · [#1786](https://github.com/horizon-rust/horizon/issues/1786) · [#9045](https://github.com/horizon-rust/horizon/issues/9045) · [#6233](https://github.com/horizon-rust/horizon/issues/6233) · [#6485](https://github.com/horizon-rust/horizon/issues/6485) · [#3734](https://github.com/horizon-rust/horizon/issues/3734) · [#3125](https://github.com/horizon-rust/horizon/issues/3125)
</details>
@@ -298,6 +298,8 @@ Far terrain drawn from the world you really built and explored, caves and builds
Keep a 3D mip store on disk built from real chunks, drawn by the GPU beside the seed preview for areas nobody has visited. Written from scratch and measured before it is kept. Builds on Far Detail and the far-terrain levels of detail already shipped; the open work on them is [#718](https://github.com/horizon-rust/horizon/issues/718).
More: [#4678](https://github.com/horizon-rust/horizon/issues/4678)
</details>
#### A batch of targeted optimisations
@@ -308,7 +310,7 @@ Several smaller speed-ups, each measured: idle block entities that sleep, cheape
Block entities and hoppers that skip work when idle (the hopper item scan is already skipped when no item is near); a particle frustum test first (also a parity gap), then particle quads built on the GPU; jigsaw shortcuts that skip blocked connectors and pool elements known not to fit, with layouts unchanged (trial chambers and ancient cities first); batched raw mouse input on Windows for 8000 Hz mice; a better atlas packer for huge resource packs (a 64x pack builds a 704 MiB atlas for 452 MiB of textures); sign-heavy scenes ([#1461](https://github.com/horizon-rust/horizon/issues/1461)). Profile first, keep what pays, show before and after numbers.
More: [docs/profiling.md](docs/profiling.md)
More: [docs/profiling.md](docs/profiling.md) · [#7752](https://github.com/horizon-rust/horizon/issues/7752) · [#8251](https://github.com/horizon-rust/horizon/issues/8251) · [#10229](https://github.com/horizon-rust/horizon/issues/10229)
</details>
@@ -322,6 +324,8 @@ Recognise mods built for versions before 26.1 in the mod browser, so they can be
A mapping table from intermediary and SRG names so pre-26.1 jars can be classified, and a rename for mod ids with dashes (`fabric-api`); the port step then treats them like newer jars ([docs/mod-browser.md](docs/mod-browser.md)).
More: [#10262](https://github.com/horizon-rust/horizon/issues/10262)
</details>
#### A modpack's own resource and shader packs
@@ -396,6 +400,16 @@ A Datapacks tab beside Mods and Resource packs: install into a chosen world's da
### Servers and multiplayer
#### Server panel extras and vanilla's management protocol
More of a server managed from its web panel (bans, worlds, logs and crash reports, modpacks), and vanilla's own server-management tools working with a Horizon server.
<details><summary>Technical</summary>
The panel's later features (bans, worlds, server.properties import, logs and crash reports, modpacks) and vanilla's management protocol (the JSON-RPC over WebSocket that the `management-server-*` keys configure), so vanilla's tools can manage a Horizon server. Split from server-admin. [docs/server.md](docs/server.md), [docs/api/server.md](docs/api/server.md).
</details>
#### Ely.by and other sign-in providers
Sign in with other account providers, such as Ely.by or any Yggdrasil-compatible one, beside Microsoft.
@@ -526,6 +540,8 @@ Contraptions and physics like the Create and Aeronautics mods, as a mod for Hori
Issue [#805](https://github.com/horizon-rust/horizon/issues/805) (porting QOL mods) closed with this answer: it would come as a native Horizon mod.
More: [#6302](https://github.com/horizon-rust/horizon/issues/6302) · [#5313](https://github.com/horizon-rust/horizon/issues/5313) · [#5556](https://github.com/horizon-rust/horizon/issues/5556)
</details>
#### Proximity voice chat as a native mod
@@ -548,6 +564,8 @@ Every past and future Minecraft version prepared for Horizon automatically. When
The port pipeline ([docs/version-ports.md](docs/version-ports.md), `tools/version_port.py`) detects a release, prepares it, diffs its classes against the pinned 26.3, regenerates the data-driven tables, runs every parity harness on it and writes a port report that becomes issues. The goal is as little human work as possible for any version, past or future. Detection and the first slices are merged; the scheduled agent that runs it every day isn't installed yet. Two versions in one build are out of scope.
More: [#4758](https://github.com/horizon-rust/horizon/issues/4758)
</details>
#### Controllers: tested on real pads, and in the launcher
@@ -558,6 +576,8 @@ Make controller support solid: tested with real PlayStation and Xbox pads and a
The game side is merged ([docs/controllers.md](docs/controllers.md)): pads read with gilrs, menus, rumble, glyphs, remapping and DualSense adaptive triggers, lightbar and haptics, but not yet confirmed on real pads or a Steam Deck (DualSense haptics stay behind a switch until then). Left: a test pass on real hardware, and the same focus navigation in the GPUI launcher.
More: [#10298](https://github.com/horizon-rust/horizon/issues/10298) · [#8678](https://github.com/horizon-rust/horizon/issues/8678)
</details>
#### The full debug screen
@@ -600,16 +620,6 @@ An output device list in Music & Sounds, falling back to the system default. [#1
</details>
#### Play in your language
Item names, container titles and server messages follow the language you pick, as in vanilla. Today parts of the game stay in English.
<details><summary>Technical</summary>
Translatable components resolved through the selected language for items, containers and server text ([#1806](https://github.com/horizon-rust/horizon/issues/1806)), on top of the bundled languages the prepared resources carry ([#1594](https://github.com/horizon-rust/horizon/issues/1594)). Left: the client's own screens ([#1672](https://github.com/horizon-rust/horizon/issues/1672)) and the main menu with zh_cn.
</details>
### Rendering
#### A light default shader pack
@@ -668,6 +678,16 @@ A shared resource store keyed by the preparer's digest, no prepare intermediates
</details>
#### A launcher screen readers can read
Screen readers read the launcher's buttons, lists and settings, and every part of it works from the keyboard.
<details><summary>Technical</summary>
Issue [#4537](https://github.com/horizon-rust/horizon/issues/4537): the launcher is GPUI (not Electron), so it exposes nothing to UI Automation, NSAccessibility or AT-SPI yet. Needs an accessibility tree from GPUI (an AccessKit bridge) and a role and label on each widget.
</details>
#### A visible beta label
A clear 'beta' label in the launcher and the game, so nobody is surprised that Horizon is still being built.
@@ -690,6 +710,18 @@ Issue [#1709](https://github.com/horizon-rust/horizon/issues/1709): aarch64 buil
</details>
#### The launcher builds Horizon on other systems
On a system Horizon has no download for, such as FreeBSD, the launcher offers to build the game on your computer, as it already does for native mods.
<details><summary>Technical</summary>
Issue [#4354](https://github.com/horizon-rust/horizon/issues/4354): when no release asset matches the platform, the launcher fetches the tagged source and runs the same pinned-toolchain `cargo build --locked` its native-mod builds use, with a warning about the time it takes. The build is unsigned, so the launcher marks the version as built locally.
More: [docs/native-mods.md](docs/native-mods.md)
</details>
#### Horizon on Windows ARM64
A native Horizon for Windows on ARM computers, such as Snapdragon laptops.
@@ -710,6 +742,8 @@ Versions of Horizon for Android, iOS and web browsers. Nothing official yet; con
No official build. Android was first closed as not planned, with touch controls, a mobile interface, packaging and loading the player's own assets all missing; the roadmap now includes it, iOS and a build for the browser as long-term goals. First pieces from contributors: `horizon-dualsense` compiling on iOS ([#1813](https://github.com/horizon-rust/horizon/issues/1813)) and helper-thread limits on Android ([#1751](https://github.com/horizon-rust/horizon/issues/1751)). Preparing without Java ([#1750](https://github.com/horizon-rust/horizon/issues/1750)) matters here too. Horizon ships none of Mojang's files on any platform.
More: [#9134](https://github.com/horizon-rust/horizon/issues/9134) · [#8069](https://github.com/horizon-rust/horizon/issues/8069) · [#4153](https://github.com/horizon-rust/horizon/issues/4153)
</details>
#### Horizon on Intel Macs
@@ -752,6 +786,8 @@ Record, replay and edit videos without leaving the game: record in a keypress, r
Three stages. Recording: capture gameplay in a keypress with an encoder thread and little frame-time cost. Replay: a replay mode for recorded sessions. Editing: cuts, camera paths, titles and export. None of it exists yet; the agent-control socket ([docs/agent-control.md](docs/agent-control.md)) already lets tools drive the game for scripted shots.
More: [#10025](https://github.com/horizon-rust/horizon/issues/10025)
</details>
#### Game menus in the launcher's style
@@ -842,6 +878,8 @@ Every download Horizon makes is checked against the project's signature, includi
Releases carry signed checksums, and the launcher and `horizon-server update` refuse anything unsigned ([#1011](https://github.com/horizon-rust/horizon/issues/1011), [#1014](https://github.com/horizon-rust/horizon/issues/1014)). Left: the first install through `install.sh` and `install.ps1` still trusts only the checksum GitHub publishes, because verifying Ed25519 signatures needs OpenSSL 3, which macOS doesn't ship ([docs/security.md](docs/security.md)).
More: [#10354](https://github.com/horizon-rust/horizon/issues/10354)
</details>
## Help wanted
@@ -860,30 +898,11 @@ More: [https://playhorizon.dev/docs/performance](https://playhorizon.dev/docs/pe
</details>
### Gameplay parity
#### End city banners keep their pattern
Banners in End cities show as plain magenta instead of with their pattern.
<details><summary>Technical</summary>
Compare the banner block entities the End city templates place with vanilla's (`EndCityPieces`, the templates' block entity NBT, `BannerBlockEntity`'s patterns), in `crates/horizon-world/src/worldgen/structure/`. Issue [#1438](https://github.com/horizon-rust/horizon/issues/1438); possibly related: [#526](https://github.com/horizon-rust/horizon/issues/526).
</details>
#### Enchanted loot in End cities
Armour and tools in End city chests should come enchanted, as in vanilla.
<details><summary>Technical</summary>
`loot.rs` implements `enchant_with_levels`: find why the End city chest table's rolls come out bare, and compare them with vanilla's for a few seeds. Issues [#1437](https://github.com/horizon-rust/horizon/issues/1437), [#1660](https://github.com/horizon-rust/horizon/issues/1660).
</details>
## Recently shipped
- **Enchanted loot in End cities** (v0.2.73): Armour and tools in End city chests should come enchanted, as in vanilla. [#1437](https://github.com/horizon-rust/horizon/issues/1437) · [#1660](https://github.com/horizon-rust/horizon/issues/1660) · [#1911](https://github.com/horizon-rust/horizon/issues/1911) · [#2037](https://github.com/horizon-rust/horizon/issues/2037)
- **End city banners keep their pattern** (v0.2.67): Banners in End cities show as plain magenta instead of with their pattern. [#1438](https://github.com/horizon-rust/horizon/issues/1438) · [#526](https://github.com/horizon-rust/horizon/issues/526) · [#1928](https://github.com/horizon-rust/horizon/issues/1928)
- **Play in your language** (v0.2.65): Item names, container titles and server messages follow the language you pick, as in vanilla. Today parts of the game stay in English. [#1806](https://github.com/horizon-rust/horizon/issues/1806) · [#1594](https://github.com/horizon-rust/horizon/issues/1594) · [#1672](https://github.com/horizon-rust/horizon/issues/1672)
- **Chickens flap while they fall** (v0.2.67): Chickens should flap their wings while they fall, not stand still in the air. [#1706](https://github.com/horizon-rust/horizon/issues/1706) · [#1892](https://github.com/horizon-rust/horizon/issues/1892)
- **Ctrl+Backspace and scroll bars in menus** (v0.2.67): Ctrl+Backspace should delete a whole word in the creative search, and every list's scroll bar should be draggable. [#1764](https://github.com/horizon-rust/horizon/issues/1764) · [#1782](https://github.com/horizon-rust/horizon/issues/1782) · [#1884](https://github.com/horizon-rust/horizon/issues/1884) · [#1931](https://github.com/horizon-rust/horizon/issues/1931)
- **A pointing hand over buttons** (v0.2.67): The mouse pointer should turn into a pointing hand over menu buttons, as in vanilla 26.3. [#1347](https://github.com/horizon-rust/horizon/issues/1347) · [#1897](https://github.com/horizon-rust/horizon/issues/1897)
+44
View File
@@ -16,6 +16,8 @@ are in that folder (`LICENSE.MIT`, `LICENSE.APACHE`).
The GLSL frontend also resets the common type for each comma-separated declarator so one
variable's array specifier does not change later variables.
It also reads a struct member outside a constant expression through a pointer to the member,
not out of a loaded copy of the whole struct, so a member an atomic function uses can be read.
## libmimalloc-sys and mimalloc
@@ -168,3 +170,45 @@ BlueMap is licensed under the MIT license:
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.
## JOML
`crates/horizon-client-render/src/camera.rs` (`joml::Mat4::rotation`, `invert_orthonormal`) and
`crates/horizon-client-render/src/gfx/pack_uniforms.rs` (`invert_generic`, `invert_affine`, the
formulas of `pack_projection` and `shadow_ortho`) adapt the float arithmetic of
`Matrix4f.rotation(Quaternionfc)`, `invertOrthonormal`, `invertGenericNonThis`, `invertAffine`,
`setPerspective` and `setOrtho` from
[JOML](https://github.com/JOML-CI/JOML/tree/faf9eef2511242122beb6055fe3da92daed214c5) 1.10.9, the
library vanilla 26.3 uses, so that shader packs get the same matrices to the last bit. Changes:
ported to Rust; the pack_uniforms.rs functions drop JOML's property flags, and
`pack_projection` works the formula in doubles. The copyright line and license notice also head
both files.
`camera.rs` also adapts `Quaternionf.rotationX`, `rotationY` and `rotationZ`,
`Matrix4f.rotateAffine` and `Vector4f.mulAffine` (`joml::Quat::rotation_x`/`_y`/`_z`,
`joml::Mat4::rotate_affine`, `transform_affine`), for the sky's positions shader packs read.
Changes: ported to Rust; `rotate_affine` keeps only the affine case.
```
The MIT License
Copyright (c) 2015-2026 Richard Greenlees
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```
@@ -0,0 +1,42 @@
# Entity occlusion split measurements — 2026-11-02
These measurements came from the pre-split integration branch for PR #7054. That build contained the entity-occlusion rewrite and particle light lookup cache; Particle Culling was Off. They are useful comparison evidence, but they are not a benchmark of this isolated entity-only commit. The cache-only run in PR #7391 measured effectively the same CPU time on the ordinary scene, but the combined result still cannot isolate interactions.
Host: Intel Core i7-3770S, Intel HD Graphics 4000, Vulkan/Mesa, 675×727, render distance 4, VSync off, 260 FPS cap. Two opposite-order passes measured for 8 seconds after warmup. Values are means. CPU is harness average CPU ms per frame; p99 is frame time. The driver supplied no usable GPU timing.
## Ordinary scene
The scene had 24 mobs, roughly 9,400–10,100 rain particles and campfire smoke.
| Build and mode | FPS | CPU ms/frame | Frame p99 ms |
| --- | ---: | ---: | ---: |
| Main, entity culling Off | 88.59 | 1.829 | 24.51 |
| Pre-split integration branch, all culling Off | 88.33 | 2.063 | 24.51 |
| Main, entity culling Synchronous | 89.34 | 1.965 | 23.99 |
| Pre-split integration branch, entity Synchronous | 89.13 | 2.113 | 24.15 |
This normal-scene run does not show a large FPS change; its CPU differences are small relative to frame time and not attributable to the entity-only change by themselves.
## Opaque-wall scene
A 32×32 platform at Y=300, a 7-block wall with a one-block vertical opening, and 192 pigs spawned behind the wall (216 mobs in the captured scene). The player looked toward the opening from the near side. The same world and camera path were used for the main and integration builds.
| Build and mode | FPS | CPU ms/frame | Frame p99 ms |
| --- | ---: | ---: | ---: |
| Main, Off | 89.08 | 2.423 | 13.52 |
| Integration branch, Off | 89.23 | 2.355 | 13.27 |
| Main, Synchronous | 92.81 | 2.566 | 14.60 |
| Integration branch, Synchronous | 93.21 | 2.550 | 12.84 |
| Main, Asynchronous | 92.73 | 2.364 | 13.04 |
| Integration branch, Asynchronous | 92.62 | 2.314 | 13.18 |
The asynchronous mode reduced measured CPU time slightly in this wall scene but did not improve FPS beyond run-to-run spread. This is a synthetic occlusion-heavy scene, not a broad performance claim.
## Captures
The HUD performance overlay is visible in each capture. The integration-build screenshots were recorded with the same wall and camera position in Off, Synchronous, and Asynchronous modes.
- Main, Off: `main-off.png`
- Integration branch, Off: `branch-off.png`
- Integration branch, Synchronous: `branch-sync.png`
- Integration branch, Asynchronous: `branch-async.png`
Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

@@ -0,0 +1,42 @@
# Entity occlusion pipeline — release/OpenGL run (2026-11-04)
This is the final local smoke benchmark for the entity-occlusion pipeline before opening the PR. It was run from the branch after the release build completed, with the OpenGL backend selected explicitly. The same process created each scene, switched the culling mode, flew the camera for two seconds, collected frame/CPU telemetry, and captured the screenshot after the scene settled.
**Host:** Intel Core i7-3770S, Intel HD Graphics 4000, Mesa OpenGL 4.2
**Window:** 1280×720, offscreen, VSync disabled
**World:** fresh temporary home, 32×32 platform at Y=300; wall scenes add a solid stone wall and an optional one-block slit
**Entities:** 24 pigs in the normal scene, 192 pigs in wall scenes, 512 pigs in stress scenes
**Settings:** render distance 8, `entityCullingPrecheck:64`, FPS overlay on, no resource-pack occlusion assumptions
**Build:** `cargo build --release -p horizon-client`
## Results
The values below are branch-only measurements. They are useful for comparing the three modes under identical geometry and for checking that the worker is doing useful work. A fresh main binary was not built in this short pass, so these numbers do not claim a controlled main-to-branch delta.
| Scene | Off FPS / CPU p50 | Sync FPS / CPU p50 | Async FPS / CPU p50 | Async entities culled |
| --- | ---: | ---: | ---: | ---: |
| `entity_normal` (24 visible) | 240.5 / 0.586 ms | 233.5 / 0.762 ms | 239.3 / 0.686 ms | 0 |
| `entity_wall` (192, solid) | 228.8 / 1.245 ms | 318.6 / 1.342 ms | **324.5 / 0.478 ms** | 124,608 |
| `entity_wall_slit` (192, one-block slit) | 231.4 / 1.102 ms | 265.0 / 1.387 ms | **281.2 / 0.716 ms** | 102,826 |
| `entity_stress` (512, solid) | 190.3 / 2.467 ms | 299.2 / 2.593 ms | **325.5 / 0.691 ms** | 290,234 |
| `entity_stress_slit` (512, one-block slit) | 190.4 / 2.476 ms | 262.2 / 2.573 ms | **262.1 / 1.297 ms** | 227,301 |
Async telemetry confirmed that the render thread stayed non-blocking (`try_recv`/`try_send`, `worker_blocking_wait_ms: 0`). The latest wall/slit run completed 562 worker jobs, queued 3,982 queries, avoided about 4.0 million entity bytes, and had 379 ns recent p50 worker-job time. The solid 512-pig run culled 15,552 generated vertices; the slit run culled 120,290 generated vertices. The normal scene recorded no pre-model hides and kept the opt-in path close to Off.
## Captures
Every image includes the FPS performance overlay. The names are `<mode>-<scene>.png`:
- [Off — normal](screenshots/off-entity_normal.png), [wall](screenshots/off-entity_wall.png), [wall slit](screenshots/off-entity_wall_slit.png), [stress](screenshots/off-entity_stress.png), [stress slit](screenshots/off-entity_stress_slit.png)
- [Sync — normal](screenshots/sync-entity_normal.png), [wall](screenshots/sync-entity_wall.png), [wall slit](screenshots/sync-entity_wall_slit.png), [stress](screenshots/sync-entity_stress.png), [stress slit](screenshots/sync-entity_stress_slit.png)
- [Async — normal](screenshots/async-entity_normal.png), [wall](screenshots/async-entity_wall.png), [wall slit](screenshots/async-entity_wall_slit.png), [stress](screenshots/async-entity_stress.png), [stress slit](screenshots/async-entity_stress_slit.png)
## Reproduction
From the repository root, with prepared 26.3 resources available:
```sh
python3 tools/entity_occlusion_bench.py
```
The script uses fresh temporary game data, the release binary in `target/release`, OpenGL, the FPS overlay, and writes JSON plus captures under `/tmp/horizon-entity-occlusion-release-quick/`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

+870
View File
@@ -0,0 +1,870 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T13:00:15Z",
"app": {
"name": "horizon-client",
"version": "nightly-2026-11-04-67dad0d",
"commit": "67dad0d"
},
"hardware": {
"os": "linux",
"arch": "x86_64",
"cpu_threads": 24,
"os_version": "NixOS 26.05 (Yarara) (kernel 6.18.57)",
"cpu": "13th Gen Intel(R) Core(TM) i7-13700KF",
"ram_mb": 31904,
"gpu": {
"name": "NVIDIA GeForce RTX 4080",
"vendor": "nvidia",
"driver": "NVIDIA 595.71.05",
"backend": "vulkan",
"kind": "discrete"
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 12,
"simulation_distance": 12,
"vsync": false,
"offscreen": false,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 28.408,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 66658.183,
"stdev": 61839.502,
"min": 1827.42,
"max": 124993.694,
"ci95": 153630.237
},
"startup.world_ms": {
"n": 3,
"mean": 74754.411,
"stdev": 59189.587,
"min": 13600.651,
"max": 131761.773,
"ci95": 147046.952
},
"chunks.village.load_ms": {
"n": 3,
"mean": 5571.499,
"stdev": 1900.32,
"min": 3378.38,
"max": 6730.394,
"ci95": 4721.037
},
"chunks.village.meshed_count": {
"n": 3,
"mean": 15287.0,
"stdev": 527.501,
"min": 14788.0,
"max": 15839.0,
"ci95": 1310.49
},
"fps.village.avg_fps": {
"n": 3,
"mean": 2958.557,
"stdev": 376.543,
"min": 2526.453,
"max": 3216.434,
"ci95": 935.46
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 368.457,
"stdev": 90.668,
"min": 300.631,
"max": 471.438,
"ci95": 225.25
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 91.04,
"stdev": 40.506,
"min": 46.124,
"max": 124.796,
"ci95": 100.63
},
"memory.village.gpu_mb": {
"n": 3,
"mean": 581.0,
"stdev": 48.0,
"min": 533.0,
"max": 629.0,
"ci95": 119.248
},
"memory.village.rss_mb": {
"n": 3,
"mean": 1277.531,
"stdev": 25.546,
"min": 1253.879,
"max": 1304.623,
"ci95": 63.466
},
"tick.village.client_ms": {
"n": 3,
"mean": 0.529,
"stdev": 0.127,
"min": 0.396,
"max": 0.649,
"ci95": 0.315
},
"cpu.village.p50_ms": {
"n": 3,
"mean": 0.185,
"stdev": 0.034,
"min": 0.162,
"max": 0.224,
"ci95": 0.085
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 3318.3,
"stdev": 911.608,
"min": 2266.627,
"max": 3883.075,
"ci95": 2264.743
},
"chunks.forest.meshed_count": {
"n": 3,
"mean": 16565.333,
"stdev": 172.755,
"min": 16367.0,
"max": 16683.0,
"ci95": 429.182
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 2333.626,
"stdev": 569.124,
"min": 1745.365,
"max": 2881.457,
"ci95": 1413.897
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 336.615,
"stdev": 185.666,
"min": 208.133,
"max": 549.487,
"ci95": 461.257
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 160.734,
"stdev": 127.586,
"min": 39.777,
"max": 294.05,
"ci95": 316.967
},
"memory.forest.gpu_mb": {
"n": 3,
"mean": 666.333,
"stdev": 24.44,
"min": 645.0,
"max": 693.0,
"ci95": 60.718
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 1346.816,
"stdev": 42.279,
"min": 1305.547,
"max": 1390.037,
"ci95": 105.035
},
"tick.forest.client_ms": {
"n": 3,
"mean": 0.669,
"stdev": 0.084,
"min": 0.598,
"max": 0.761,
"ci95": 0.208
},
"cpu.forest.p50_ms": {
"n": 3,
"mean": 0.222,
"stdev": 0.032,
"min": 0.197,
"max": 0.258,
"ci95": 0.079
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 2463.434,
"stdev": 1056.0,
"min": 1527.516,
"max": 3608.283,
"ci95": 2623.462
},
"chunks.ocean.meshed_count": {
"n": 3,
"mean": 14226.667,
"stdev": 307.077,
"min": 14038.0,
"max": 14581.0,
"ci95": 762.883
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 2192.874,
"stdev": 1320.904,
"min": 673.829,
"max": 3071.415,
"ci95": 3281.573
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 345.406,
"stdev": 294.904,
"min": 8.977,
"max": 559.232,
"ci95": 732.642
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 172.573,
"stdev": 149.386,
"min": 1.038,
"max": 274.089,
"ci95": 371.125
},
"memory.ocean.gpu_mb": {
"n": 3,
"mean": 629.0,
"stdev": 32.0,
"min": 597.0,
"max": 661.0,
"ci95": 79.499
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 1352.223,
"stdev": 51.951,
"min": 1301.492,
"max": 1405.314,
"ci95": 129.065
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 0.512,
"stdev": 0.099,
"min": 0.406,
"max": 0.603,
"ci95": 0.246
},
"cpu.ocean.p50_ms": {
"n": 3,
"mean": 0.178,
"stdev": 0.038,
"min": 0.135,
"max": 0.206,
"ci95": 0.094
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 6260.675,
"stdev": 3077.348,
"min": 2976.342,
"max": 9077.55,
"ci95": 7645.173
},
"chunks.mountain.meshed_count": {
"n": 3,
"mean": 16015.333,
"stdev": 209.872,
"min": 15826.0,
"max": 16241.0,
"ci95": 521.393
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 1412.095,
"stdev": 962.145,
"min": 714.773,
"max": 2509.775,
"ci95": 2390.293
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 182.479,
"stdev": 177.563,
"min": 13.364,
"max": 367.429,
"ci95": 441.126
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 88.451,
"stdev": 87.578,
"min": 1.526,
"max": 176.667,
"ci95": 217.572
},
"memory.mountain.gpu_mb": {
"n": 3,
"mean": 799.667,
"stdev": 18.475,
"min": 789.0,
"max": 821.0,
"ci95": 45.899
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 1491.572,
"stdev": 36.719,
"min": 1459.775,
"max": 1531.76,
"ci95": 91.221
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 0.697,
"stdev": 0.155,
"min": 0.602,
"max": 0.876,
"ci95": 0.386
},
"cpu.mountain.p50_ms": {
"n": 3,
"mean": 0.325,
"stdev": 0.115,
"min": 0.239,
"max": 0.456,
"ci95": 0.286
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 1545.896,
"stdev": 30.591,
"min": 1524.965,
"max": 1581.004,
"ci95": 75.999
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 1348.389,
"stdev": 52.5,
"min": 1296.172,
"max": 1401.168,
"ci95": 130.428
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 37594,
"seconds": 12.0,
"avg_fps": 3132.783,
"low_1pct_fps": 471.438,
"low_01pct_fps": 124.796,
"p50_ms": 0.271,
"p95_ms": 0.494,
"p99_ms": 1.127,
"p999_ms": 2.15,
"max_ms": 155.204,
"cpu_p50_ms": 0.168,
"gpu_p50_ms": 0.104,
"histogram": [
37151,
394,
30,
8,
5,
2,
2,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 1,
"frames": 38598,
"seconds": 12.0,
"avg_fps": 3216.434,
"low_1pct_fps": 300.631,
"low_01pct_fps": 46.124,
"p50_ms": 0.267,
"p95_ms": 0.446,
"p99_ms": 1.034,
"p999_ms": 1.941,
"max_ms": 493.993,
"cpu_p50_ms": 0.162,
"gpu_p50_ms": 0.104,
"histogram": [
38177,
387,
16,
8,
4,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
1,
1,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 30318,
"seconds": 12.0,
"avg_fps": 2526.453,
"low_1pct_fps": 333.302,
"low_01pct_fps": 102.199,
"p50_ms": 0.305,
"p95_ms": 0.757,
"p99_ms": 1.69,
"p999_ms": 3.539,
"max_ms": 170.856,
"cpu_p50_ms": 0.224,
"gpu_p50_ms": 0.106,
"histogram": [
29368,
759,
139,
35,
11,
3,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 34578,
"seconds": 12.0,
"avg_fps": 2881.457,
"low_1pct_fps": 549.487,
"low_01pct_fps": 294.05,
"p50_ms": 0.32,
"p95_ms": 0.483,
"p99_ms": 1.213,
"p999_ms": 2.5,
"max_ms": 5.358,
"cpu_p50_ms": 0.197,
"gpu_p50_ms": 0.154,
"histogram": [
34119,
379,
64,
9,
5,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 28489,
"seconds": 12.0,
"avg_fps": 2374.055,
"low_1pct_fps": 208.133,
"low_01pct_fps": 39.777,
"p50_ms": 0.313,
"p95_ms": 0.937,
"p99_ms": 1.863,
"p999_ms": 4.318,
"max_ms": 571.48,
"cpu_p50_ms": 0.211,
"gpu_p50_ms": 0.151,
"histogram": [
27193,
1071,
140,
51,
19,
9,
1,
2,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 20945,
"seconds": 12.0,
"avg_fps": 1745.365,
"low_1pct_fps": 252.226,
"low_01pct_fps": 148.375,
"p50_ms": 0.343,
"p95_ms": 1.505,
"p99_ms": 2.758,
"p999_ms": 5.341,
"max_ms": 11.642,
"cpu_p50_ms": 0.258,
"gpu_p50_ms": 0.162,
"histogram": [
18145,
2293,
337,
106,
38,
13,
6,
5,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 36856,
"seconds": 12.0,
"avg_fps": 3071.415,
"low_1pct_fps": 559.232,
"low_01pct_fps": 274.089,
"p50_ms": 0.286,
"p95_ms": 0.492,
"p99_ms": 1.179,
"p999_ms": 2.711,
"max_ms": 6.718,
"cpu_p50_ms": 0.135,
"gpu_p50_ms": 0.127,
"histogram": [
36366,
405,
60,
13,
10,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 34001,
"seconds": 12.0,
"avg_fps": 2833.377,
"low_1pct_fps": 468.008,
"low_01pct_fps": 242.591,
"p50_ms": 0.295,
"p95_ms": 0.589,
"p99_ms": 1.41,
"p999_ms": 3.048,
"max_ms": 7.7,
"cpu_p50_ms": 0.206,
"gpu_p50_ms": 0.126,
"histogram": [
33314,
544,
106,
23,
10,
2,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 8086,
"seconds": 12.0,
"avg_fps": 673.829,
"low_1pct_fps": 8.977,
"low_01pct_fps": 1.038,
"p50_ms": 0.302,
"p95_ms": 0.665,
"p99_ms": 2.495,
"p999_ms": 15.716,
"max_ms": 4849.364,
"cpu_p50_ms": 0.194,
"gpu_p50_ms": 0.128,
"histogram": [
7808,
166,
50,
26,
8,
2,
3,
5,
2,
5,
1,
2,
2,
0,
0,
0,
0,
0,
0,
1,
1,
1,
1,
2
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 12141,
"seconds": 12.0,
"avg_fps": 1011.738,
"low_1pct_fps": 166.643,
"low_01pct_fps": 87.16,
"p50_ms": 0.641,
"p95_ms": 2.642,
"p99_ms": 4.468,
"p999_ms": 7.821,
"max_ms": 22.536,
"cpu_p50_ms": 0.456,
"gpu_p50_ms": 0.138,
"histogram": [
7840,
2996,
866,
278,
87,
38,
16,
11,
2,
5,
0,
0,
0,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 30118,
"seconds": 12.0,
"avg_fps": 2509.775,
"low_1pct_fps": 367.429,
"low_01pct_fps": 176.667,
"p50_ms": 0.316,
"p95_ms": 0.771,
"p99_ms": 1.931,
"p999_ms": 4.2,
"max_ms": 14.633,
"cpu_p50_ms": 0.239,
"gpu_p50_ms": 0.127,
"histogram": [
29101,
775,
173,
38,
16,
8,
4,
1,
0,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 8577,
"seconds": 12.0,
"avg_fps": 714.773,
"low_1pct_fps": 13.364,
"low_01pct_fps": 1.526,
"p50_ms": 0.416,
"p95_ms": 2.257,
"p99_ms": 4.36,
"p999_ms": 212.217,
"max_ms": 1051.428,
"cpu_p50_ms": 0.28,
"gpu_p50_ms": 0.132,
"histogram": [
7017,
978,
377,
111,
39,
24,
10,
6,
1,
0,
2,
2,
0,
0,
0,
0,
0,
0,
1,
0,
1,
2,
4,
2
]
}
],
"label": "horizon-desktop"
}
+870
View File
@@ -0,0 +1,870 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T14:47:19Z",
"app": {
"name": "horizon-client",
"version": "nightly-2026-11-04-67dad0d",
"commit": "67dad0d"
},
"hardware": {
"os": "linux",
"arch": "x86_64",
"cpu_threads": 24,
"os_version": "NixOS 26.05 (Yarara) (kernel 6.18.57)",
"cpu": "13th Gen Intel(R) Core(TM) i7-13700KF",
"ram_mb": 31904,
"gpu": {
"name": "NVIDIA GeForce RTX 4080",
"vendor": "nvidia",
"driver": "NVIDIA 595.71.05",
"backend": "vulkan",
"kind": "discrete"
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 32,
"simulation_distance": 12,
"vsync": false,
"offscreen": false,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 29.665,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 45386.16,
"stdev": 38970.921,
"min": 1077.276,
"max": 74343.156,
"ci95": 96816.948
},
"startup.world_ms": {
"n": 3,
"mean": 86585.608,
"stdev": 71331.593,
"min": 11201.581,
"max": 153020.143,
"ci95": 177211.803
},
"chunks.village.load_ms": {
"n": 3,
"mean": 11082.587,
"stdev": 5351.291,
"min": 7620.454,
"max": 17246.096,
"ci95": 13294.417
},
"chunks.village.meshed_count": {
"n": 3,
"mean": 48440.333,
"stdev": 656.16,
"min": 48060.0,
"max": 49198.0,
"ci95": 1630.124
},
"fps.village.avg_fps": {
"n": 3,
"mean": 853.775,
"stdev": 345.646,
"min": 524.913,
"max": 1214.059,
"ci95": 858.701
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 130.235,
"stdev": 117.39,
"min": 9.024,
"max": 243.388,
"ci95": 291.635
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 70.65,
"stdev": 68.628,
"min": 1.125,
"max": 138.345,
"ci95": 170.496
},
"memory.village.gpu_mb": {
"n": 3,
"mean": 2202.333,
"stdev": 33.307,
"min": 2165.0,
"max": 2229.0,
"ci95": 82.745
},
"memory.village.rss_mb": {
"n": 3,
"mean": 1652.647,
"stdev": 73.452,
"min": 1568.063,
"max": 1700.344,
"ci95": 182.478
},
"tick.village.client_ms": {
"n": 3,
"mean": 0.493,
"stdev": 0.169,
"min": 0.319,
"max": 0.657,
"ci95": 0.42
},
"cpu.village.p50_ms": {
"n": 3,
"mean": 0.692,
"stdev": 0.207,
"min": 0.531,
"max": 0.925,
"ci95": 0.513
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 15019.921,
"stdev": 12855.4,
"min": 5613.942,
"max": 29668.11,
"ci95": 31937.162
},
"chunks.forest.meshed_count": {
"n": 3,
"mean": 80224.667,
"stdev": 678.709,
"min": 79626.0,
"max": 80962.0,
"ci95": 1686.142
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 898.208,
"stdev": 446.3,
"min": 382.944,
"max": 1163.61,
"ci95": 1108.759
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 178.513,
"stdev": 107.096,
"min": 54.933,
"max": 244.255,
"ci95": 266.064
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 103.369,
"stdev": 78.403,
"min": 12.974,
"max": 152.87,
"ci95": 194.779
},
"memory.forest.gpu_mb": {
"n": 3,
"mean": 2201.667,
"stdev": 34.429,
"min": 2163.0,
"max": 2229.0,
"ci95": 85.532
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 1809.063,
"stdev": 93.103,
"min": 1707.98,
"max": 1891.305,
"ci95": 231.3
},
"tick.forest.client_ms": {
"n": 3,
"mean": 0.585,
"stdev": 0.147,
"min": 0.493,
"max": 0.754,
"ci95": 0.365
},
"cpu.forest.p50_ms": {
"n": 3,
"mean": 0.989,
"stdev": 0.725,
"min": 0.57,
"max": 1.826,
"ci95": 1.802
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 12399.047,
"stdev": 15052.073,
"min": 3174.115,
"max": 29768.486,
"ci95": 37394.441
},
"chunks.ocean.meshed_count": {
"n": 3,
"mean": 64737.0,
"stdev": 16149.666,
"min": 46089.0,
"max": 74089.0,
"ci95": 40121.232
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 735.22,
"stdev": 601.75,
"min": 108.108,
"max": 1307.912,
"ci95": 1494.952
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 134.654,
"stdev": 147.391,
"min": 1.252,
"max": 292.881,
"ci95": 366.169
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 62.062,
"stdev": 66.294,
"min": 0.202,
"max": 132.042,
"ci95": 164.697
},
"memory.ocean.gpu_mb": {
"n": 3,
"mean": 1942.333,
"stdev": 251.725,
"min": 1657.0,
"max": 2133.0,
"ci95": 625.369
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 1812.661,
"stdev": 102.065,
"min": 1697.912,
"max": 1893.313,
"ci95": 253.564
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 0.421,
"stdev": 0.227,
"min": 0.193,
"max": 0.646,
"ci95": 0.563
},
"cpu.ocean.p50_ms": {
"n": 3,
"mean": 0.513,
"stdev": 0.194,
"min": 0.303,
"max": 0.686,
"ci95": 0.483
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 31815.539,
"stdev": 36758.245,
"min": 9582.166,
"max": 74243.988,
"ci95": 91319.913
},
"chunks.mountain.meshed_count": {
"n": 3,
"mean": 90876.333,
"stdev": 1525.588,
"min": 89793.0,
"max": 92621.0,
"ci95": 3790.075
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 712.657,
"stdev": 252.815,
"min": 450.565,
"max": 955.041,
"ci95": 628.077
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 117.93,
"stdev": 85.922,
"min": 24.389,
"max": 193.34,
"ci95": 213.46
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 72.407,
"stdev": 65.468,
"min": 3.577,
"max": 133.895,
"ci95": 162.646
},
"memory.mountain.gpu_mb": {
"n": 3,
"mean": 2783.667,
"stdev": 48.881,
"min": 2741.0,
"max": 2837.0,
"ci95": 121.436
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 1931.359,
"stdev": 106.681,
"min": 1808.184,
"max": 1994.242,
"ci95": 265.031
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 1.03,
"stdev": 0.685,
"min": 0.564,
"max": 1.817,
"ci95": 1.702
},
"cpu.mountain.p50_ms": {
"n": 3,
"mean": 1.025,
"stdev": 0.315,
"min": 0.765,
"max": 1.376,
"ci95": 0.783
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 2051.333,
"stdev": 147.151,
"min": 1884.176,
"max": 2161.316,
"ci95": 365.573
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 1810.92,
"stdev": 94.623,
"min": 1707.342,
"max": 1892.828,
"ci95": 235.075
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 6301,
"seconds": 12.004,
"avg_fps": 524.913,
"low_1pct_fps": 9.024,
"low_01pct_fps": 1.125,
"p50_ms": 0.555,
"p95_ms": 2.072,
"p99_ms": 4.067,
"p999_ms": 282.455,
"max_ms": 3525.74,
"cpu_p50_ms": 0.531,
"gpu_p50_ms": 0.218,
"histogram": [
4878,
1060,
217,
86,
19,
15,
6,
2,
5,
0,
1,
2,
0,
0,
0,
0,
0,
0,
0,
2,
0,
4,
3,
1
]
},
{
"phase": "village",
"repeat": 1,
"frames": 9869,
"seconds": 12.001,
"avg_fps": 822.353,
"low_1pct_fps": 138.293,
"low_01pct_fps": 72.479,
"p50_ms": 0.976,
"p95_ms": 2.871,
"p99_ms": 5.065,
"p999_ms": 9.942,
"max_ms": 23.337,
"cpu_p50_ms": 0.925,
"gpu_p50_ms": 0.245,
"histogram": [
5262,
3404,
771,
248,
81,
49,
23,
11,
12,
3,
0,
3,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 14570,
"seconds": 12.001,
"avg_fps": 1214.059,
"low_1pct_fps": 243.388,
"low_01pct_fps": 138.345,
"p50_ms": 0.655,
"p95_ms": 1.486,
"p99_ms": 2.883,
"p999_ms": 5.366,
"max_ms": 26.145,
"cpu_p50_ms": 0.62,
"gpu_p50_ms": 0.239,
"histogram": [
11502,
2683,
257,
77,
25,
23,
1,
1,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 4595,
"seconds": 11.999,
"avg_fps": 382.944,
"low_1pct_fps": 54.933,
"low_01pct_fps": 12.974,
"p50_ms": 2.269,
"p95_ms": 5.193,
"p99_ms": 8.719,
"p999_ms": 21.933,
"max_ms": 243.247,
"cpu_p50_ms": 1.826,
"gpu_p50_ms": 0.502,
"histogram": [
273,
1654,
1395,
836,
172,
122,
52,
38,
26,
10,
7,
5,
0,
2,
1,
0,
0,
0,
1,
0,
1,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 13964,
"seconds": 12.001,
"avg_fps": 1163.61,
"low_1pct_fps": 244.255,
"low_01pct_fps": 152.87,
"p50_ms": 0.682,
"p95_ms": 1.524,
"p99_ms": 2.877,
"p999_ms": 5.801,
"max_ms": 7.977,
"cpu_p50_ms": 0.57,
"gpu_p50_ms": 0.467,
"histogram": [
11067,
2532,
235,
82,
23,
16,
6,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 13777,
"seconds": 12.0,
"avg_fps": 1148.071,
"low_1pct_fps": 236.351,
"low_01pct_fps": 144.264,
"p50_ms": 0.691,
"p95_ms": 1.637,
"p99_ms": 3.131,
"p999_ms": 5.794,
"max_ms": 9.301,
"cpu_p50_ms": 0.57,
"gpu_p50_ms": 0.465,
"histogram": [
10826,
2511,
284,
106,
23,
16,
5,
5,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 1224,
"seconds": 11.322,
"avg_fps": 108.108,
"low_1pct_fps": 1.252,
"low_01pct_fps": 0.202,
"p50_ms": 0.439,
"p95_ms": 3.015,
"p99_ms": 7.359,
"p999_ms": 2880.23,
"max_ms": 7005.436,
"cpu_p50_ms": 0.303,
"gpu_p50_ms": 0.258,
"histogram": [
1081,
31,
49,
10,
3,
7,
23,
12,
4,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
2
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 15695,
"seconds": 12.0,
"avg_fps": 1307.912,
"low_1pct_fps": 292.881,
"low_01pct_fps": 132.042,
"p50_ms": 0.624,
"p95_ms": 1.219,
"p99_ms": 2.173,
"p999_ms": 4.841,
"max_ms": 16.764,
"cpu_p50_ms": 0.551,
"gpu_p50_ms": 0.401,
"histogram": [
13558,
1932,
140,
37,
14,
6,
3,
1,
1,
1,
0,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 9476,
"seconds": 12.0,
"avg_fps": 789.641,
"low_1pct_fps": 109.83,
"low_01pct_fps": 53.941,
"p50_ms": 0.802,
"p95_ms": 3.406,
"p99_ms": 5.844,
"p999_ms": 14.977,
"max_ms": 22.481,
"cpu_p50_ms": 0.686,
"gpu_p50_ms": 0.403,
"histogram": [
5819,
2151,
787,
455,
112,
65,
36,
18,
7,
7,
7,
5,
4,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 5352,
"seconds": 11.878,
"avg_fps": 450.565,
"low_1pct_fps": 24.389,
"low_01pct_fps": 3.577,
"p50_ms": 1.431,
"p95_ms": 4.548,
"p99_ms": 7.601,
"p999_ms": 96.086,
"max_ms": 714.027,
"cpu_p50_ms": 1.376,
"gpu_p50_ms": 0.398,
"histogram": [
1412,
2323,
811,
460,
138,
84,
49,
34,
13,
9,
8,
2,
1,
1,
0,
1,
0,
0,
1,
2,
1,
1,
1,
0
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 8789,
"seconds": 12.001,
"avg_fps": 732.365,
"low_1pct_fps": 136.06,
"low_01pct_fps": 79.749,
"p50_ms": 0.972,
"p95_ms": 3.329,
"p99_ms": 5.68,
"p999_ms": 9.7,
"max_ms": 18.289,
"cpu_p50_ms": 0.935,
"gpu_p50_ms": 0.383,
"histogram": [
4529,
3007,
715,
287,
120,
61,
37,
16,
12,
1,
1,
2,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 11461,
"seconds": 12.001,
"avg_fps": 955.041,
"low_1pct_fps": 193.34,
"low_01pct_fps": 133.895,
"p50_ms": 0.787,
"p95_ms": 2.026,
"p99_ms": 3.953,
"p999_ms": 6.511,
"max_ms": 9.761,
"cpu_p50_ms": 0.765,
"gpu_p50_ms": 0.379,
"histogram": [
7754,
3114,
328,
165,
46,
33,
14,
5,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
}
],
"label": "horizon-desktop"
}
+854
View File
@@ -0,0 +1,854 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T12:44:40Z",
"app": {
"name": "horizon-client",
"version": "nightly-2026-11-04-67dad0d",
"commit": "67dad0d"
},
"hardware": {
"os": "macos",
"arch": "aarch64",
"cpu_threads": 14,
"os_version": "27.0.1",
"cpu": "Apple M4 Pro",
"ram_mb": 65536,
"gpu": {
"name": "Apple M4 Pro",
"vendor": "",
"driver": "",
"backend": "metal",
"kind": "integrated"
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 12,
"simulation_distance": 12,
"vsync": false,
"offscreen": true,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 17.39,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 1516.155,
"stdev": 34.227,
"min": 1476.782,
"max": 1538.813,
"ci95": 85.032
},
"startup.world_ms": {
"n": 3,
"mean": 4080.9,
"stdev": 748.712,
"min": 3273.516,
"max": 4752.301,
"ci95": 1860.054
},
"chunks.village.load_ms": {
"n": 3,
"mean": 3363.749,
"stdev": 224.552,
"min": 3105.622,
"max": 3514.059,
"ci95": 557.864
},
"chunks.village.meshed_count": {
"n": 3,
"mean": 15473.667,
"stdev": 339.602,
"min": 15123.0,
"max": 15801.0,
"ci95": 843.686
},
"fps.village.avg_fps": {
"n": 3,
"mean": 999.753,
"stdev": 41.901,
"min": 970.527,
"max": 1047.758,
"ci95": 104.095
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 336.821,
"stdev": 17.834,
"min": 318.068,
"max": 353.566,
"ci95": 44.306
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 263.782,
"stdev": 31.556,
"min": 227.767,
"max": 286.582,
"ci95": 78.395
},
"memory.village.rss_mb": {
"n": 3,
"mean": 1147.654,
"stdev": 31.82,
"min": 1116.875,
"max": 1180.422,
"ci95": 79.052
},
"tick.village.client_ms": {
"n": 3,
"mean": 0.414,
"stdev": 0.036,
"min": 0.373,
"max": 0.44,
"ci95": 0.089
},
"cpu.village.p50_ms": {
"n": 3,
"mean": 0.52,
"stdev": 0.071,
"min": 0.44,
"max": 0.577,
"ci95": 0.177
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 3685.368,
"stdev": 844.43,
"min": 2932.117,
"max": 4598.204,
"ci95": 2097.85
},
"chunks.forest.meshed_count": {
"n": 3,
"mean": 16795.333,
"stdev": 437.802,
"min": 16325.0,
"max": 17191.0,
"ci95": 1087.648
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 592.615,
"stdev": 20.339,
"min": 580.401,
"max": 616.094,
"ci95": 50.529
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 250.069,
"stdev": 31.227,
"min": 217.065,
"max": 279.149,
"ci95": 77.58
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 151.684,
"stdev": 77.064,
"min": 64.033,
"max": 208.811,
"ci95": 191.454
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 1064.344,
"stdev": 148.185,
"min": 901.203,
"max": 1190.609,
"ci95": 368.142
},
"tick.forest.client_ms": {
"n": 3,
"mean": 0.424,
"stdev": 0.003,
"min": 0.422,
"max": 0.427,
"ci95": 0.007
},
"cpu.forest.p50_ms": {
"n": 3,
"mean": 0.617,
"stdev": 0.016,
"min": 0.599,
"max": 0.629,
"ci95": 0.039
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 3594.311,
"stdev": 1381.026,
"min": 2011.153,
"max": 4551.541,
"ci95": 3430.936
},
"chunks.ocean.meshed_count": {
"n": 3,
"mean": 14527.0,
"stdev": 319.629,
"min": 14166.0,
"max": 14774.0,
"ci95": 794.068
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 789.003,
"stdev": 25.076,
"min": 766.83,
"max": 816.216,
"ci95": 62.297
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 276.887,
"stdev": 51.785,
"min": 217.373,
"max": 311.665,
"ci95": 128.651
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 169.278,
"stdev": 29.119,
"min": 137.156,
"max": 193.944,
"ci95": 72.341
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 982.995,
"stdev": 287.819,
"min": 651.109,
"max": 1164.063,
"ci95": 715.039
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 0.397,
"stdev": 0.028,
"min": 0.38,
"max": 0.429,
"ci95": 0.069
},
"cpu.ocean.p50_ms": {
"n": 3,
"mean": 0.569,
"stdev": 0.045,
"min": 0.533,
"max": 0.62,
"ci95": 0.112
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 5682.993,
"stdev": 1272.063,
"min": 4476.873,
"max": 7012.069,
"ci95": 3160.234
},
"chunks.mountain.meshed_count": {
"n": 3,
"mean": 15640.0,
"stdev": 204.105,
"min": 15507.0,
"max": 15875.0,
"ci95": 507.067
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 716.475,
"stdev": 8.139,
"min": 707.613,
"max": 723.617,
"ci95": 20.221
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 313.736,
"stdev": 3.759,
"min": 311.007,
"max": 318.023,
"ci95": 9.338
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 270.042,
"stdev": 19.572,
"min": 247.802,
"max": 284.642,
"ci95": 48.624
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 997.474,
"stdev": 231.733,
"min": 790.266,
"max": 1247.703,
"ci95": 575.704
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 0.385,
"stdev": 0.023,
"min": 0.359,
"max": 0.401,
"ci95": 0.057
},
"cpu.mountain.p50_ms": {
"n": 3,
"mean": 0.609,
"stdev": 0.044,
"min": 0.56,
"max": 0.645,
"ci95": 0.109
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 1213.255,
"stdev": 81.897,
"min": 1136.078,
"max": 1299.172,
"ci95": 203.461
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 1034.279,
"stdev": 188.802,
"min": 816.453,
"max": 1150.961,
"ci95": 469.049
},
"memory.footprint_peak_mb": {
"n": 3,
"mean": 2167.747,
"stdev": 14.039,
"min": 2153.721,
"max": 2181.799,
"ci95": 34.878
},
"memory.footprint_steady_mb": {
"n": 3,
"mean": 1889.468,
"stdev": 24.444,
"min": 1862.799,
"max": 1910.807,
"ci95": 60.727
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 12573,
"seconds": 12.0,
"avg_fps": 1047.758,
"low_1pct_fps": 353.566,
"low_01pct_fps": 286.582,
"p50_ms": 0.921,
"p95_ms": 1.077,
"p99_ms": 2.675,
"p999_ms": 2.946,
"max_ms": 5.36,
"cpu_p50_ms": 0.44,
"gpu_p50_ms": 0.914,
"histogram": [
11056,
1291,
218,
6,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 1,
"frames": 11646,
"seconds": 12.0,
"avg_fps": 970.527,
"low_1pct_fps": 318.068,
"low_01pct_fps": 227.767,
"p50_ms": 0.973,
"p95_ms": 1.264,
"p99_ms": 2.805,
"p999_ms": 3.808,
"max_ms": 5.305,
"cpu_p50_ms": 0.577,
"gpu_p50_ms": 0.969,
"histogram": [
7035,
4334,
230,
39,
6,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 11771,
"seconds": 11.999,
"avg_fps": 980.973,
"low_1pct_fps": 338.828,
"low_01pct_fps": 276.996,
"p50_ms": 0.985,
"p95_ms": 1.157,
"p99_ms": 2.779,
"p999_ms": 3.068,
"max_ms": 4.308,
"cpu_p50_ms": 0.542,
"gpu_p50_ms": 0.98,
"histogram": [
6880,
4663,
208,
18,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 7394,
"seconds": 12.001,
"avg_fps": 616.094,
"low_1pct_fps": 253.993,
"low_01pct_fps": 182.207,
"p50_ms": 1.565,
"p95_ms": 1.915,
"p99_ms": 3.329,
"p999_ms": 4.661,
"max_ms": 7.32,
"cpu_p50_ms": 0.599,
"gpu_p50_ms": 1.551,
"histogram": [
82,
6988,
144,
162,
14,
2,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 6965,
"seconds": 12.0,
"avg_fps": 580.401,
"low_1pct_fps": 217.065,
"low_01pct_fps": 64.033,
"p50_ms": 1.654,
"p95_ms": 1.983,
"p99_ms": 3.233,
"p999_ms": 3.893,
"max_ms": 81.863,
"cpu_p50_ms": 0.629,
"gpu_p50_ms": 1.645,
"histogram": [
11,
6622,
153,
174,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 6977,
"seconds": 12.001,
"avg_fps": 581.35,
"low_1pct_fps": 279.149,
"low_01pct_fps": 208.811,
"p50_ms": 1.659,
"p95_ms": 2.006,
"p99_ms": 3.273,
"p999_ms": 3.982,
"max_ms": 5.889,
"cpu_p50_ms": 0.623,
"gpu_p50_ms": 1.653,
"histogram": [
17,
6606,
164,
186,
1,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 9793,
"seconds": 11.998,
"avg_fps": 816.216,
"low_1pct_fps": 301.624,
"low_01pct_fps": 176.734,
"p50_ms": 1.171,
"p95_ms": 1.499,
"p99_ms": 2.928,
"p999_ms": 3.333,
"max_ms": 23.472,
"cpu_p50_ms": 0.533,
"gpu_p50_ms": 1.163,
"histogram": [
259,
9293,
182,
56,
2,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 9202,
"seconds": 12.0,
"avg_fps": 766.83,
"low_1pct_fps": 217.373,
"low_01pct_fps": 137.156,
"p50_ms": 1.226,
"p95_ms": 1.912,
"p99_ms": 3.396,
"p999_ms": 6.003,
"max_ms": 13.562,
"cpu_p50_ms": 0.62,
"gpu_p50_ms": 1.219,
"histogram": [
635,
8130,
261,
124,
27,
15,
7,
2,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 9408,
"seconds": 12.001,
"avg_fps": 783.964,
"low_1pct_fps": 311.665,
"low_01pct_fps": 193.944,
"p50_ms": 1.22,
"p95_ms": 1.541,
"p99_ms": 2.865,
"p999_ms": 3.298,
"max_ms": 13.861,
"cpu_p50_ms": 0.555,
"gpu_p50_ms": 1.214,
"histogram": [
100,
9066,
204,
34,
2,
0,
1,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 8618,
"seconds": 12.0,
"avg_fps": 718.194,
"low_1pct_fps": 312.177,
"low_01pct_fps": 277.681,
"p50_ms": 1.338,
"p95_ms": 1.664,
"p99_ms": 3.068,
"p999_ms": 3.364,
"max_ms": 4.008,
"cpu_p50_ms": 0.622,
"gpu_p50_ms": 1.329,
"histogram": [
62,
8301,
130,
125,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 8492,
"seconds": 12.001,
"avg_fps": 707.613,
"low_1pct_fps": 311.007,
"low_01pct_fps": 284.642,
"p50_ms": 1.35,
"p95_ms": 1.707,
"p99_ms": 3.08,
"p999_ms": 3.329,
"max_ms": 3.796,
"cpu_p50_ms": 0.645,
"gpu_p50_ms": 1.342,
"histogram": [
32,
8216,
111,
133,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 8684,
"seconds": 12.001,
"avg_fps": 723.617,
"low_1pct_fps": 318.023,
"low_01pct_fps": 247.802,
"p50_ms": 1.318,
"p95_ms": 1.699,
"p99_ms": 2.875,
"p999_ms": 3.573,
"max_ms": 5.186,
"cpu_p50_ms": 0.56,
"gpu_p50_ms": 1.306,
"histogram": [
46,
8389,
199,
49,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
}
],
"label": "horizon-macmini"
}
+854
View File
@@ -0,0 +1,854 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T13:17:26Z",
"app": {
"name": "horizon-client",
"version": "nightly-2026-11-04-67dad0d",
"commit": "67dad0d"
},
"hardware": {
"os": "macos",
"arch": "aarch64",
"cpu_threads": 14,
"os_version": "27.0.1",
"cpu": "Apple M4 Pro",
"ram_mb": 65536,
"gpu": {
"name": "Apple M4 Pro",
"vendor": "",
"driver": "",
"backend": "metal",
"kind": "integrated"
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 32,
"simulation_distance": 12,
"vsync": false,
"offscreen": true,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 17.712,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 1528.444,
"stdev": 181.21,
"min": 1319.58,
"max": 1643.783,
"ci95": 450.188
},
"startup.world_ms": {
"n": 3,
"mean": 12368.716,
"stdev": 6475.37,
"min": 7168.429,
"max": 19621.62,
"ci95": 16087.01
},
"chunks.village.load_ms": {
"n": 3,
"mean": 6926.208,
"stdev": 3712.691,
"min": 4208.555,
"max": 11156.419,
"ci95": 9223.581
},
"chunks.village.meshed_count": {
"n": 3,
"mean": 46790.333,
"stdev": 1381.89,
"min": 45236.0,
"max": 47880.0,
"ci95": 3433.082
},
"fps.village.avg_fps": {
"n": 3,
"mean": 453.505,
"stdev": 14.384,
"min": 441.517,
"max": 469.455,
"ci95": 35.735
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 222.897,
"stdev": 10.449,
"min": 212.173,
"max": 233.048,
"ci95": 25.96
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 173.55,
"stdev": 31.798,
"min": 142.967,
"max": 206.437,
"ci95": 78.996
},
"memory.village.rss_mb": {
"n": 3,
"mean": 1544.565,
"stdev": 26.422,
"min": 1516.75,
"max": 1569.328,
"ci95": 65.64
},
"tick.village.client_ms": {
"n": 3,
"mean": 0.528,
"stdev": 0.018,
"min": 0.511,
"max": 0.548,
"ci95": 0.045
},
"cpu.village.p50_ms": {
"n": 3,
"mean": 1.496,
"stdev": 0.049,
"min": 1.453,
"max": 1.549,
"ci95": 0.121
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 8297.116,
"stdev": 689.909,
"min": 7716.447,
"max": 9059.776,
"ci95": 1713.966
},
"chunks.forest.meshed_count": {
"n": 3,
"mean": 78617.667,
"stdev": 494.532,
"min": 78228.0,
"max": 79174.0,
"ci95": 1228.586
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 216.265,
"stdev": 6.372,
"min": 209.501,
"max": 222.156,
"ci95": 15.831
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 137.027,
"stdev": 3.787,
"min": 133.07,
"max": 140.617,
"ci95": 9.408
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 114.683,
"stdev": 7.985,
"min": 106.585,
"max": 122.551,
"ci95": 19.839
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 1420.638,
"stdev": 247.812,
"min": 1266.047,
"max": 1706.469,
"ci95": 615.649
},
"tick.forest.client_ms": {
"n": 3,
"mean": 0.423,
"stdev": 0.059,
"min": 0.355,
"max": 0.458,
"ci95": 0.148
},
"cpu.forest.p50_ms": {
"n": 3,
"mean": 1.605,
"stdev": 0.269,
"min": 1.295,
"max": 1.779,
"ci95": 0.668
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 8354.659,
"stdev": 2877.749,
"min": 5454.947,
"max": 11209.93,
"ci95": 7149.301
},
"chunks.ocean.meshed_count": {
"n": 3,
"mean": 74315.667,
"stdev": 63.106,
"min": 74246.0,
"max": 74369.0,
"ci95": 156.776
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 274.828,
"stdev": 4.993,
"min": 270.041,
"max": 280.004,
"ci95": 12.404
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 165.765,
"stdev": 7.21,
"min": 157.538,
"max": 170.985,
"ci95": 17.912
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 140.185,
"stdev": 9.74,
"min": 129.976,
"max": 149.377,
"ci95": 24.198
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 1306.578,
"stdev": 97.011,
"min": 1199.094,
"max": 1387.641,
"ci95": 241.007
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 0.416,
"stdev": 0.029,
"min": 0.392,
"max": 0.448,
"ci95": 0.071
},
"cpu.ocean.p50_ms": {
"n": 3,
"mean": 1.636,
"stdev": 0.121,
"min": 1.496,
"max": 1.708,
"ci95": 0.301
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 17124.873,
"stdev": 3461.08,
"min": 13599.217,
"max": 20517.544,
"ci95": 8598.494
},
"chunks.mountain.meshed_count": {
"n": 3,
"mean": 91909.0,
"stdev": 815.603,
"min": 91157.0,
"max": 92776.0,
"ci95": 2026.235
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 242.657,
"stdev": 3.405,
"min": 240.66,
"max": 246.588,
"ci95": 8.459
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 110.95,
"stdev": 14.622,
"min": 99.907,
"max": 127.532,
"ci95": 36.325
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 51.329,
"stdev": 28.253,
"min": 30.391,
"max": 83.465,
"ci95": 70.191
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 1451.688,
"stdev": 127.471,
"min": 1310.969,
"max": 1559.43,
"ci95": 316.681
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 0.614,
"stdev": 0.017,
"min": 0.594,
"max": 0.627,
"ci95": 0.043
},
"cpu.mountain.p50_ms": {
"n": 3,
"mean": 2.233,
"stdev": 0.05,
"min": 2.194,
"max": 2.289,
"ci95": 0.124
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 1697.984,
"stdev": 29.735,
"min": 1667.406,
"max": 1726.797,
"ci95": 73.871
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 1428.19,
"stdev": 120.658,
"min": 1333.0,
"max": 1563.891,
"ci95": 299.756
},
"memory.footprint_peak_mb": {
"n": 3,
"mean": 4921.753,
"stdev": 30.436,
"min": 4890.971,
"max": 4951.831,
"ci95": 75.613
},
"memory.footprint_steady_mb": {
"n": 3,
"mean": 4079.43,
"stdev": 10.978,
"min": 4068.049,
"max": 4089.956,
"ci95": 27.273
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 5634,
"seconds": 12.001,
"avg_fps": 469.455,
"low_1pct_fps": 223.469,
"low_01pct_fps": 171.246,
"p50_ms": 2.054,
"p95_ms": 2.492,
"p99_ms": 3.993,
"p999_ms": 5.425,
"max_ms": 6.645,
"cpu_p50_ms": 1.453,
"gpu_p50_ms": 1.965,
"histogram": [
0,
2082,
3324,
193,
24,
9,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 1,
"frames": 5395,
"seconds": 12.001,
"avg_fps": 449.543,
"low_1pct_fps": 233.048,
"low_01pct_fps": 206.437,
"p50_ms": 2.155,
"p95_ms": 2.611,
"p99_ms": 4.068,
"p999_ms": 4.456,
"max_ms": 5.305,
"cpu_p50_ms": 1.549,
"gpu_p50_ms": 2.043,
"histogram": [
0,
902,
4255,
206,
29,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 5298,
"seconds": 12.0,
"avg_fps": 441.517,
"low_1pct_fps": 212.173,
"low_01pct_fps": 142.967,
"p50_ms": 2.171,
"p95_ms": 3.249,
"p99_ms": 4.138,
"p999_ms": 5.591,
"max_ms": 9.676,
"cpu_p50_ms": 1.485,
"gpu_p50_ms": 2.092,
"histogram": [
0,
709,
4314,
229,
36,
6,
2,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 2606,
"seconds": 12.002,
"avg_fps": 217.137,
"low_1pct_fps": 140.617,
"low_01pct_fps": 106.585,
"p50_ms": 4.471,
"p95_ms": 5.926,
"p99_ms": 6.575,
"p999_ms": 7.735,
"max_ms": 12.187,
"cpu_p50_ms": 1.74,
"gpu_p50_ms": 4.238,
"histogram": [
0,
0,
0,
221,
2094,
172,
110,
8,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 2514,
"seconds": 12.0,
"avg_fps": 209.501,
"low_1pct_fps": 137.393,
"low_01pct_fps": 122.551,
"p50_ms": 4.627,
"p95_ms": 6.304,
"p99_ms": 6.93,
"p999_ms": 7.971,
"max_ms": 8.506,
"cpu_p50_ms": 1.779,
"gpu_p50_ms": 4.409,
"histogram": [
0,
0,
2,
35,
2097,
200,
162,
17,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 2667,
"seconds": 12.005,
"avg_fps": 222.156,
"low_1pct_fps": 133.07,
"low_01pct_fps": 114.913,
"p50_ms": 4.37,
"p95_ms": 5.756,
"p99_ms": 6.742,
"p999_ms": 8.444,
"max_ms": 8.984,
"cpu_p50_ms": 1.295,
"gpu_p50_ms": 4.152,
"histogram": [
0,
0,
0,
440,
1978,
144,
85,
16,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 3294,
"seconds": 12.003,
"avg_fps": 274.44,
"low_1pct_fps": 157.538,
"low_01pct_fps": 129.976,
"p50_ms": 3.521,
"p95_ms": 4.814,
"p99_ms": 5.592,
"p999_ms": 7.491,
"max_ms": 7.868,
"cpu_p50_ms": 1.703,
"gpu_p50_ms": 3.277,
"histogram": [
0,
4,
55,
2967,
130,
122,
10,
6,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 3241,
"seconds": 12.002,
"avg_fps": 270.041,
"low_1pct_fps": 168.771,
"low_01pct_fps": 141.201,
"p50_ms": 3.578,
"p95_ms": 4.942,
"p99_ms": 5.631,
"p999_ms": 6.498,
"max_ms": 7.601,
"cpu_p50_ms": 1.708,
"gpu_p50_ms": 3.323,
"histogram": [
0,
2,
8,
2970,
110,
144,
5,
2,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 3360,
"seconds": 12.0,
"avg_fps": 280.004,
"low_1pct_fps": 170.985,
"low_01pct_fps": 149.377,
"p50_ms": 3.477,
"p95_ms": 4.678,
"p99_ms": 5.462,
"p999_ms": 6.626,
"max_ms": 6.788,
"cpu_p50_ms": 1.496,
"gpu_p50_ms": 3.23,
"histogram": [
0,
1,
5,
3125,
129,
90,
10,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 2888,
"seconds": 12.0,
"avg_fps": 240.66,
"low_1pct_fps": 127.532,
"low_01pct_fps": 83.465,
"p50_ms": 3.984,
"p95_ms": 5.171,
"p99_ms": 6.823,
"p999_ms": 9.168,
"max_ms": 15.341,
"cpu_p50_ms": 2.194,
"gpu_p50_ms": 3.786,
"histogram": [
0,
0,
4,
2048,
638,
144,
33,
16,
3,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 2889,
"seconds": 12.001,
"avg_fps": 240.722,
"low_1pct_fps": 99.907,
"low_01pct_fps": 40.132,
"p50_ms": 3.961,
"p95_ms": 5.706,
"p99_ms": 7.364,
"p999_ms": 12.597,
"max_ms": 49.156,
"cpu_p50_ms": 2.289,
"gpu_p50_ms": 3.802,
"histogram": [
0,
5,
126,
1859,
599,
194,
72,
22,
8,
1,
2,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 2959,
"seconds": 12.0,
"avg_fps": 246.588,
"low_1pct_fps": 105.411,
"low_01pct_fps": 30.391,
"p50_ms": 3.851,
"p95_ms": 5.353,
"p99_ms": 6.248,
"p999_ms": 9.05,
"max_ms": 54.378,
"cpu_p50_ms": 2.216,
"gpu_p50_ms": 3.667,
"histogram": [
0,
2,
58,
2196,
462,
196,
32,
10,
1,
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
0,
0,
0,
0,
0
]
}
],
"label": "horizon-macmini"
}
+42
View File
@@ -0,0 +1,42 @@
# Horizon Nightly 2026-11-04 vs vanilla 26.3 on the Mac mini and the desktop (2026-11-04)
> **The Mac mini reports (`*-macmini-rd*.json`) are offscreen, not comparable.** Horizon rendered offscreen (no presenting)
> while vanilla drew into a window, so the Mac rows below are not a Horizon-vs-vanilla comparison, and the website does not
> publish them: it keeps the Mac mini's 2026-10-22 runs. They are kept here only as a record of that day's offscreen run.
The desktop runs are the ones behind playhorizon.dev/benchmarks as of 2026-11-04 (horizon-rust/horizon-site#58): `tools/perf_bench.py`
reports, render distance 12 and 32, three games each, every game from a fresh world (seed 1234), 1280x720, VSync off. Files:
`{horizon,vanilla}-macmini-rd{12,32}.json` and `{horizon,vanilla}-desktop-rd{12,32}.json` next to this page.
## Builds
- **Horizon:** the Nightly 2026-11-04 release bundle (67dad0d), as the launcher installs it: macOS arm64 on the Mac,
Linux x86_64 on the desktop. Its resources come from the bundle's own `horizon-prepare`. perf_bench ran from main
a55b9a2e52 and wrote that checkout's crate version and commit; `app.version` and `app.commit` were set to the bundle's by hand.
- **Vanilla:** the 26.3 client jar with Fabric Loader and the driver mod, Java 25, the official launcher's JVM flags.
## Machines and conditions
- **Mac mini, Apple M4 Pro (14 cores, 20-core GPU), macOS: offscreen, not comparable.** The session was locked, so Horizon ran `--offscreen`: a locked
session gives a window no frames, and windowed Horizon measured 0 FPS. Vanilla drew into its window. Offscreen skips
presenting, which favours Horizon, so its averages aren't held at the 144 Hz display as they were on 2026-10-22. Each game
started at a 1-minute load of 17-18 (the run waited for < 20); during the runs the load rose to 30-50.
- **Desktop, RTX 4080 + i7-13700KF, NixOS, NVIDIA 595.** Both games drew into a headless sway rendering on the RTX 4080 (never
on its screen), both through Wayland on Vulkan. Vanilla went through SDL's Wayland driver: through Xwayland each of its
frames took about 2 s on this day. The machine also runs CI and agents' builds: each game started at a load of 28-30 on
24 threads (the run waited for < 30) and the load rose to 40-70 during the runs. Vanilla at RD 32 crashed twice under that
load (`GPU timeout attempting to acquire next frame`) before a third series finished. Start-up times are inflated by the
load and by a cold disk on each series' first start (start → world up to 130-235 s).
- **Order:** RD 12 ran Horizon then vanilla; RD 32 ran vanilla then Horizon.
## Results (average over the flights of the three games; FPS average / 1% low)
| Machine | RD | Horizon | Vanilla | Horizon ÷ vanilla (avg, 1% low) |
|---|---|---|---|---|
| Mac mini (**offscreen, not comparable**) | 12 | 774 / 294 | 132 / 27 | — |
| Mac mini (**offscreen, not comparable**) | 32 | 297 / 159 | 68 / 16 | — |
| Desktop | 12 | 2,224 / 308 | 1,824 / 201 | 1.2x, 1.5x |
| Desktop | 32 | 800 / 140 | 696 / 64 | 1.15x, 2.2x |
Neither machine was idle, so these say how both games behave on busy machines. They are not clean ratios; for those, see
`windows-9800x3d-5080-2026-10-29.md`. The scripts and logs are in horizon-parity `shots/site-bench-2026-11-04/`.
+787
View File
@@ -0,0 +1,787 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T13:28:55Z",
"app": {
"name": "minecraft",
"version": "26.3",
"mods": [
{
"name": "fabric-loader",
"version": "0.19.5"
}
]
},
"hardware": {
"os": "linux",
"arch": "x86_64",
"cpu_threads": 24,
"os_version": "NixOS 26.05 (Yarara) (kernel 6.18.57)",
"cpu": "13th Gen Intel(R) Core(TM) i7-13700KF",
"ram_mb": 31904,
"gpu": {
"name": "NVIDIA GeForce RTX 4080",
"vendor": "NVIDIA",
"driver": "1.4.329 NVIDIA 595.71.05",
"backend": "vulkan",
"kind": ""
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 12,
"simulation_distance": 12,
"vsync": false,
"offscreen": false,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 28.788,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 121075.467,
"stdev": 30646.429,
"min": 93272.034,
"max": 153935.834,
"ci95": 76136.094
},
"startup.world_ms": {
"n": 3,
"mean": 191063.8,
"stdev": 34368.16,
"min": 153895.533,
"max": 221692.034,
"ci95": 85382.134
},
"chunks.village.load_ms": {
"n": 3,
"mean": 18724.322,
"stdev": 9639.048,
"min": 13107.549,
"max": 29854.376,
"ci95": 23946.655
},
"fps.village.avg_fps": {
"n": 3,
"mean": 2161.532,
"stdev": 287.109,
"min": 1830.069,
"max": 2332.81,
"ci95": 713.276
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 162.575,
"stdev": 29.799,
"min": 136.023,
"max": 194.804,
"ci95": 74.03
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 27.752,
"stdev": 3.186,
"min": 24.403,
"max": 30.746,
"ci95": 7.916
},
"memory.village.gpu_mb": {
"n": 3,
"mean": 13.0,
"stdev": 0.0,
"min": 13.0,
"max": 13.0,
"ci95": 0.0
},
"memory.village.rss_mb": {
"n": 3,
"mean": 2902.054,
"stdev": 158.708,
"min": 2747.244,
"max": 3064.393,
"ci95": 394.285
},
"tick.village.client_ms": {
"n": 3,
"mean": 0.691,
"stdev": 0.097,
"min": 0.601,
"max": 0.794,
"ci95": 0.241
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 8852.332,
"stdev": 530.809,
"min": 8350.811,
"max": 9408.241,
"ci95": 1318.709
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 1841.581,
"stdev": 1186.781,
"min": 471.209,
"max": 2529.847,
"ci95": 2948.365
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 255.087,
"stdev": 217.128,
"min": 5.798,
"max": 402.877,
"ci95": 539.419
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 92.882,
"stdev": 81.722,
"min": 0.61,
"max": 156.13,
"ci95": 203.024
},
"memory.forest.gpu_mb": {
"n": 3,
"mean": 13.0,
"stdev": 0.0,
"min": 13.0,
"max": 13.0,
"ci95": 0.0
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 3008.178,
"stdev": 149.07,
"min": 2875.082,
"max": 3169.256,
"ci95": 370.34
},
"tick.forest.client_ms": {
"n": 3,
"mean": 0.687,
"stdev": 0.132,
"min": 0.562,
"max": 0.825,
"ci95": 0.327
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 21215.745,
"stdev": 22821.547,
"min": 8034.077,
"max": 47567.797,
"ci95": 56696.441
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 1525.483,
"stdev": 1472.651,
"min": 633.984,
"max": 3225.275,
"ci95": 3658.563
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 191.398,
"stdev": 272.942,
"min": 10.346,
"max": 505.335,
"ci95": 678.08
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 49.506,
"stdev": 66.145,
"min": 1.104,
"max": 124.874,
"ci95": 164.326
},
"memory.ocean.gpu_mb": {
"n": 3,
"mean": 13.0,
"stdev": 0.0,
"min": 13.0,
"max": 13.0,
"ci95": 0.0
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 3062.136,
"stdev": 128.594,
"min": 2962.541,
"max": 3207.313,
"ci95": 319.472
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 1.029,
"stdev": 0.573,
"min": 0.595,
"max": 1.678,
"ci95": 1.423
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 33255.15,
"stdev": 20451.99,
"min": 16655.144,
"max": 56102.104,
"ci95": 50809.661
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 1769.12,
"stdev": 996.863,
"min": 818.512,
"max": 2806.557,
"ci95": 2476.544
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 195.916,
"stdev": 195.285,
"min": 11.586,
"max": 400.567,
"ci95": 485.154
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 52.202,
"stdev": 51.208,
"min": 1.461,
"max": 103.864,
"ci95": 127.217
},
"memory.mountain.gpu_mb": {
"n": 3,
"mean": 13.0,
"stdev": 0.0,
"min": 13.0,
"max": 13.0,
"ci95": 0.0
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 2957.948,
"stdev": 340.935,
"min": 2586.504,
"max": 3256.625,
"ci95": 846.998
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 1.436,
"stdev": 1.406,
"min": 0.462,
"max": 3.048,
"ci95": 3.494
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 3107.953,
"stdev": 141.888,
"min": 3020.105,
"max": 3271.645,
"ci95": 352.498
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 2985.317,
"stdev": 210.327,
"min": 2761.164,
"max": 3178.35,
"ci95": 522.523
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 21963,
"seconds": 12.001,
"avg_fps": 1830.069,
"low_1pct_fps": 136.023,
"low_01pct_fps": 30.746,
"p50_ms": 0.331,
"p95_ms": 1.153,
"p99_ms": 3.001,
"p999_ms": 9.997,
"max_ms": 326.36,
"histogram": [
20336,
1238,
167,
108,
40,
23,
12,
10,
8,
7,
5,
1,
2,
1,
2,
0,
1,
1,
0,
0,
0,
1,
0,
0
]
},
{
"phase": "village",
"repeat": 1,
"frames": 27994,
"seconds": 12.0,
"avg_fps": 2332.81,
"low_1pct_fps": 194.804,
"low_01pct_fps": 28.107,
"p50_ms": 0.321,
"p95_ms": 0.771,
"p99_ms": 1.356,
"p999_ms": 3.836,
"max_ms": 331.931,
"histogram": [
27270,
652,
34,
13,
5,
2,
3,
4,
3,
2,
0,
1,
0,
0,
0,
0,
1,
0,
1,
1,
0,
2,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 27861,
"seconds": 12.0,
"avg_fps": 2321.717,
"low_1pct_fps": 156.898,
"low_01pct_fps": 24.403,
"p50_ms": 0.281,
"p95_ms": 0.852,
"p99_ms": 1.73,
"p999_ms": 6.331,
"max_ms": 301.991,
"histogram": [
26932,
726,
124,
42,
4,
4,
3,
2,
9,
2,
3,
1,
1,
1,
0,
1,
1,
0,
2,
0,
2,
1,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 30359,
"seconds": 12.0,
"avg_fps": 2529.847,
"low_1pct_fps": 356.586,
"low_01pct_fps": 121.906,
"p50_ms": 0.353,
"p95_ms": 0.563,
"p99_ms": 1.405,
"p999_ms": 4.444,
"max_ms": 20.302,
"histogram": [
29756,
460,
57,
52,
11,
5,
3,
5,
2,
4,
1,
0,
2,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 5781,
"seconds": 12.268,
"avg_fps": 471.209,
"low_1pct_fps": 5.798,
"low_01pct_fps": 0.61,
"p50_ms": 0.359,
"p95_ms": 0.77,
"p99_ms": 1.78,
"p999_ms": 20.826,
"max_ms": 5834.37,
"histogram": [
5586,
158,
12,
11,
0,
1,
3,
2,
0,
0,
2,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
2,
3
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 30285,
"seconds": 12.0,
"avg_fps": 2523.687,
"low_1pct_fps": 402.877,
"low_01pct_fps": 156.13,
"p50_ms": 0.351,
"p95_ms": 0.539,
"p99_ms": 1.574,
"p999_ms": 3.683,
"max_ms": 16.022,
"histogram": [
29666,
490,
74,
35,
7,
2,
2,
1,
1,
5,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 11685,
"seconds": 16.293,
"avg_fps": 717.19,
"low_1pct_fps": 10.346,
"low_01pct_fps": 1.104,
"p50_ms": 0.299,
"p95_ms": 1.119,
"p99_ms": 2.568,
"p999_ms": 12.054,
"max_ms": 5250.537,
"histogram": [
10993,
502,
102,
39,
14,
8,
6,
4,
4,
1,
1,
2,
0,
2,
0,
0,
0,
0,
1,
0,
0,
2,
0,
4
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 38704,
"seconds": 12.0,
"avg_fps": 3225.275,
"low_1pct_fps": 505.335,
"low_01pct_fps": 124.874,
"p50_ms": 0.283,
"p95_ms": 0.36,
"p99_ms": 0.909,
"p999_ms": 2.015,
"max_ms": 132.801,
"histogram": [
38385,
279,
25,
3,
2,
0,
1,
0,
2,
4,
1,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 7609,
"seconds": 12.002,
"avg_fps": 633.984,
"low_1pct_fps": 58.513,
"low_01pct_fps": 22.539,
"p50_ms": 1.141,
"p95_ms": 4.438,
"p99_ms": 9.113,
"p999_ms": 27.496,
"max_ms": 64.424,
"histogram": [
3452,
2398,
956,
368,
143,
96,
61,
46,
20,
29,
6,
10,
6,
7,
7,
0,
1,
3,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 10168,
"seconds": 12.423,
"avg_fps": 818.512,
"low_1pct_fps": 11.586,
"low_01pct_fps": 1.461,
"p50_ms": 0.267,
"p95_ms": 0.892,
"p99_ms": 3.176,
"p999_ms": 198.757,
"max_ms": 1882.64,
"histogram": [
9733,
205,
121,
49,
12,
9,
8,
2,
2,
0,
1,
2,
1,
3,
0,
3,
2,
0,
0,
2,
4,
5,
1,
3
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 33679,
"seconds": 12.0,
"avg_fps": 2806.557,
"low_1pct_fps": 400.567,
"low_01pct_fps": 103.864,
"p50_ms": 0.324,
"p95_ms": 0.411,
"p99_ms": 1.167,
"p999_ms": 3.508,
"max_ms": 83.572,
"histogram": [
33272,
315,
46,
22,
2,
1,
4,
2,
9,
1,
2,
2,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 20188,
"seconds": 12.0,
"avg_fps": 1682.292,
"low_1pct_fps": 175.594,
"low_01pct_fps": 51.28,
"p50_ms": 0.374,
"p95_ms": 1.37,
"p99_ms": 2.821,
"p999_ms": 8.947,
"max_ms": 53.494,
"histogram": [
18109,
1592,
314,
83,
36,
19,
7,
6,
6,
1,
3,
1,
4,
2,
3,
0,
1,
1,
0,
0,
0,
0,
0,
0
]
}
],
"label": "vanilla-desktop"
}
+787
View File
@@ -0,0 +1,787 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T15:46:25Z",
"app": {
"name": "minecraft",
"version": "26.3",
"mods": [
{
"name": "fabric-loader",
"version": "0.19.5"
}
]
},
"hardware": {
"os": "linux",
"arch": "x86_64",
"cpu_threads": 24,
"os_version": "NixOS 26.05 (Yarara) (kernel 6.18.57)",
"cpu": "13th Gen Intel(R) Core(TM) i7-13700KF",
"ram_mb": 31904,
"gpu": {
"name": "NVIDIA GeForce RTX 4080",
"vendor": "NVIDIA",
"driver": "1.4.329 NVIDIA 595.71.05",
"backend": "vulkan",
"kind": ""
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 32,
"simulation_distance": 12,
"vsync": false,
"offscreen": false,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 28.799,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 112742.179,
"stdev": 27885.804,
"min": 81110.692,
"max": 133773.748,
"ci95": 69277.769
},
"startup.world_ms": {
"n": 3,
"mean": 235049.179,
"stdev": 31461.958,
"min": 203486.748,
"max": 266409.692,
"ci95": 78162.144
},
"chunks.village.load_ms": {
"n": 3,
"mean": 79828.05,
"stdev": 109107.365,
"min": 16685.972,
"max": 205814.266,
"ci95": 271059.597
},
"fps.village.avg_fps": {
"n": 3,
"mean": 525.667,
"stdev": 390.505,
"min": 101.073,
"max": 869.433,
"ci95": 970.146
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 44.209,
"stdev": 39.491,
"min": 1.644,
"max": 79.658,
"ci95": 98.109
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 10.635,
"stdev": 9.759,
"min": 0.686,
"max": 20.192,
"ci95": 24.244
},
"memory.village.gpu_mb": {
"n": 3,
"mean": 34.333,
"stdev": 36.95,
"min": 13.0,
"max": 77.0,
"ci95": 91.797
},
"memory.village.rss_mb": {
"n": 3,
"mean": 3296.229,
"stdev": 296.464,
"min": 2956.234,
"max": 3500.773,
"ci95": 736.517
},
"tick.village.client_ms": {
"n": 3,
"mean": 1.23,
"stdev": 0.449,
"min": 0.897,
"max": 1.74,
"ci95": 1.114
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 61172.527,
"stdev": 17434.762,
"min": 46779.931,
"max": 80559.429,
"ci95": 43313.846
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 599.408,
"stdev": 492.96,
"min": 64.173,
"max": 1034.813,
"ci95": 1224.68
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 61.923,
"stdev": 56.534,
"min": 0.737,
"max": 112.221,
"ci95": 140.449
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 15.771,
"stdev": 14.11,
"min": 0.098,
"max": 27.463,
"ci95": 35.054
},
"memory.forest.gpu_mb": {
"n": 3,
"mean": 34.333,
"stdev": 36.95,
"min": 13.0,
"max": 77.0,
"ci95": 91.797
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 3278.964,
"stdev": 301.465,
"min": 2947.268,
"max": 3536.27,
"ci95": 748.941
},
"tick.forest.client_ms": {
"n": 3,
"mean": 5.908,
"stdev": 8.813,
"min": 0.693,
"max": 16.084,
"ci95": 21.895
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 61762.098,
"stdev": 3569.17,
"min": 57919.097,
"max": 64972.973,
"ci95": 8867.026
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 1175.044,
"stdev": 131.94,
"min": 1093.801,
"max": 1327.281,
"ci95": 327.785
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 119.758,
"stdev": 68.419,
"min": 77.252,
"max": 198.683,
"ci95": 169.976
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 24.224,
"stdev": 18.982,
"min": 12.308,
"max": 46.114,
"ci95": 47.159
},
"memory.ocean.gpu_mb": {
"n": 3,
"mean": 34.333,
"stdev": 36.95,
"min": 13.0,
"max": 77.0,
"ci95": 91.797
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 3275.349,
"stdev": 317.864,
"min": 2924.113,
"max": 3543.23,
"ci95": 789.681
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 0.654,
"stdev": 0.066,
"min": 0.579,
"max": 0.699,
"ci95": 0.163
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 113439.529,
"stdev": 64746.162,
"min": 55046.887,
"max": 183068.386,
"ci95": 160851.363
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 485.832,
"stdev": 317.254,
"min": 121.312,
"max": 699.617,
"ci95": 788.165
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 28.612,
"stdev": 23.334,
"min": 2.229,
"max": 46.542,
"ci95": 57.97
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 4.56,
"stdev": 3.408,
"min": 0.75,
"max": 7.318,
"ci95": 8.467
},
"memory.mountain.gpu_mb": {
"n": 3,
"mean": 34.333,
"stdev": 36.95,
"min": 13.0,
"max": 77.0,
"ci95": 91.797
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 3248.708,
"stdev": 480.962,
"min": 2700.645,
"max": 3600.484,
"ci95": 1194.873
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 1.832,
"stdev": 2.076,
"min": 0.618,
"max": 4.229,
"ci95": 5.157
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 3371.958,
"stdev": 280.347,
"min": 3059.926,
"max": 3602.613,
"ci95": 696.476
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 3291.249,
"stdev": 316.441,
"min": 2935.975,
"max": 3542.848,
"ci95": 786.147
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 1248,
"seconds": 12.348,
"avg_fps": 101.073,
"low_1pct_fps": 1.644,
"low_01pct_fps": 0.686,
"p50_ms": 1.225,
"p95_ms": 11.161,
"p99_ms": 226.597,
"p999_ms": 1457.605,
"max_ms": 1457.874,
"histogram": [
548,
232,
97,
118,
51,
49,
24,
39,
16,
17,
13,
6,
3,
2,
5,
3,
2,
2,
4,
2,
3,
8,
1,
3
]
},
{
"phase": "village",
"repeat": 1,
"frames": 10435,
"seconds": 12.002,
"avg_fps": 869.433,
"low_1pct_fps": 79.658,
"low_01pct_fps": 20.192,
"p50_ms": 0.813,
"p95_ms": 2.537,
"p99_ms": 5.685,
"p999_ms": 13.292,
"max_ms": 351.251,
"histogram": [
7598,
2071,
393,
171,
55,
53,
22,
24,
20,
10,
9,
2,
2,
1,
3,
0,
0,
0,
0,
0,
0,
1,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 7278,
"seconds": 12.0,
"avg_fps": 606.495,
"low_1pct_fps": 51.326,
"low_01pct_fps": 11.026,
"p50_ms": 1.105,
"p95_ms": 3.854,
"p99_ms": 7.067,
"p999_ms": 24.484,
"max_ms": 443.95,
"histogram": [
3206,
2621,
800,
360,
112,
71,
34,
26,
12,
13,
3,
5,
2,
7,
2,
1,
1,
0,
1,
0,
0,
1,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 975,
"seconds": 15.193,
"avg_fps": 64.173,
"low_1pct_fps": 0.737,
"low_01pct_fps": 0.098,
"p50_ms": 0.864,
"p95_ms": 5.083,
"p99_ms": 77.689,
"p999_ms": 10225.237,
"max_ms": 10225.237,
"histogram": [
680,
99,
104,
12,
30,
13,
8,
4,
2,
3,
2,
1,
1,
3,
0,
1,
0,
1,
2,
3,
1,
2,
1,
2
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 8391,
"seconds": 12.0,
"avg_fps": 699.237,
"low_1pct_fps": 72.811,
"low_01pct_fps": 19.752,
"p50_ms": 1.087,
"p95_ms": 2.927,
"p99_ms": 5.909,
"p999_ms": 15.268,
"max_ms": 252.557,
"histogram": [
3342,
3998,
656,
231,
56,
26,
15,
19,
12,
12,
11,
6,
1,
3,
1,
0,
0,
1,
0,
0,
0,
1,
0,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 12418,
"seconds": 12.0,
"avg_fps": 1034.813,
"low_1pct_fps": 112.221,
"low_01pct_fps": 27.463,
"p50_ms": 0.837,
"p95_ms": 1.394,
"p99_ms": 3.491,
"p999_ms": 13.217,
"max_ms": 245.59,
"histogram": [
11155,
1004,
100,
64,
29,
16,
16,
10,
2,
6,
5,
3,
4,
2,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 13126,
"seconds": 12.0,
"avg_fps": 1093.801,
"low_1pct_fps": 77.252,
"low_01pct_fps": 12.308,
"p50_ms": 0.714,
"p95_ms": 1.402,
"p99_ms": 2.875,
"p999_ms": 12.067,
"max_ms": 238.158,
"histogram": [
11605,
1207,
191,
54,
17,
11,
7,
12,
4,
4,
2,
3,
2,
1,
0,
0,
0,
0,
1,
2,
3,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 15928,
"seconds": 12.0,
"avg_fps": 1327.281,
"low_1pct_fps": 198.683,
"low_01pct_fps": 46.114,
"p50_ms": 0.67,
"p95_ms": 1.172,
"p99_ms": 2.111,
"p999_ms": 7.067,
"max_ms": 193.743,
"histogram": [
14891,
856,
102,
42,
7,
6,
8,
6,
0,
5,
3,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 13249,
"seconds": 12.0,
"avg_fps": 1104.051,
"low_1pct_fps": 83.338,
"low_01pct_fps": 14.249,
"p50_ms": 0.711,
"p95_ms": 1.403,
"p99_ms": 3.046,
"p999_ms": 10.625,
"max_ms": 451.869,
"histogram": [
11824,
1165,
124,
58,
13,
16,
11,
14,
7,
7,
3,
2,
1,
0,
0,
0,
1,
0,
0,
1,
1,
1,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 1469,
"seconds": 12.109,
"avg_fps": 121.312,
"low_1pct_fps": 2.229,
"low_01pct_fps": 0.75,
"p50_ms": 2.12,
"p95_ms": 13.173,
"p99_ms": 125.189,
"p999_ms": 727.045,
"max_ms": 1939.723,
"histogram": [
196,
440,
438,
136,
50,
36,
24,
25,
22,
17,
17,
13,
6,
11,
8,
4,
1,
4,
4,
4,
3,
7,
2,
1
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 8396,
"seconds": 12.001,
"avg_fps": 699.617,
"low_1pct_fps": 46.542,
"low_01pct_fps": 7.318,
"p50_ms": 1.022,
"p95_ms": 2.433,
"p99_ms": 5.528,
"p999_ms": 13.993,
"max_ms": 305.464,
"histogram": [
3248,
4509,
361,
113,
52,
37,
21,
26,
14,
4,
3,
2,
0,
0,
0,
0,
0,
0,
1,
1,
2,
2,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 7639,
"seconds": 12.0,
"avg_fps": 636.566,
"low_1pct_fps": 37.064,
"low_01pct_fps": 5.612,
"p50_ms": 1.023,
"p95_ms": 3.0,
"p99_ms": 7.11,
"p999_ms": 17.521,
"max_ms": 268.877,
"histogram": [
3051,
3794,
413,
158,
66,
43,
35,
21,
31,
12,
3,
4,
1,
1,
0,
0,
0,
0,
0,
0,
4,
2,
0,
0
]
}
],
"label": "vanilla-desktop"
}
+771
View File
@@ -0,0 +1,771 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T12:54:29Z",
"app": {
"name": "minecraft",
"version": "26.3",
"mods": [
{
"name": "fabric-loader",
"version": "0.19.5"
}
]
},
"hardware": {
"os": "macos",
"arch": "aarch64",
"cpu_threads": 14,
"os_version": "27.0.1",
"cpu": "Apple M4 Pro",
"ram_mb": 65536,
"gpu": {
"name": "Apple M4 Pro",
"vendor": "Apple",
"driver": "4.1 Metal - 91.7",
"backend": "opengl",
"kind": ""
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 12,
"simulation_distance": 12,
"vsync": false,
"offscreen": false,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 17.628,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 12652.182,
"stdev": 3231.342,
"min": 9526.004,
"max": 15979.309,
"ci95": 8027.746
},
"startup.world_ms": {
"n": 3,
"mean": 31329.516,
"stdev": 10016.129,
"min": 20777.004,
"max": 40705.309,
"ci95": 24883.451
},
"chunks.village.load_ms": {
"n": 3,
"mean": 12623.084,
"stdev": 4958.724,
"min": 7251.986,
"max": 17026.894,
"ci95": 12319.147
},
"fps.village.avg_fps": {
"n": 3,
"mean": 92.729,
"stdev": 58.527,
"min": 28.713,
"max": 143.496,
"ci95": 145.402
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 16.52,
"stdev": 10.949,
"min": 4.001,
"max": 24.308,
"ci95": 27.2
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 11.515,
"stdev": 7.155,
"min": 3.549,
"max": 17.394,
"ci95": 17.774
},
"memory.village.rss_mb": {
"n": 3,
"mean": 1834.469,
"stdev": 329.198,
"min": 1455.688,
"max": 2051.516,
"ci95": 817.839
},
"tick.village.client_ms": {
"n": 3,
"mean": 1.903,
"stdev": 1.114,
"min": 1.131,
"max": 3.18,
"ci95": 2.767
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 14590.694,
"stdev": 3260.615,
"min": 10942.613,
"max": 17221.108,
"ci95": 8100.47
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 106.817,
"stdev": 104.558,
"min": 43.857,
"max": 227.513,
"ci95": 259.759
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 23.0,
"stdev": 26.394,
"min": 7.338,
"max": 53.473,
"ci95": 65.57
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 14.841,
"stdev": 15.773,
"min": 5.157,
"max": 33.042,
"ci95": 39.186
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 1814.245,
"stdev": 154.974,
"min": 1635.453,
"max": 1910.125,
"ci95": 385.007
},
"tick.forest.client_ms": {
"n": 3,
"mean": 1.452,
"stdev": 0.542,
"min": 0.826,
"max": 1.777,
"ci95": 1.346
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 13309.049,
"stdev": 8088.157,
"min": 6394.403,
"max": 22203.194,
"ci95": 20093.716
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 164.405,
"stdev": 141.4,
"min": 62.162,
"max": 325.77,
"ci95": 351.285
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 36.242,
"stdev": 37.051,
"min": 14.736,
"max": 79.024,
"ci95": 92.047
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 20.898,
"stdev": 17.899,
"min": 8.855,
"max": 41.466,
"ci95": 44.468
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 1724.453,
"stdev": 347.692,
"min": 1323.5,
"max": 1942.734,
"ci95": 863.784
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 0.866,
"stdev": 0.248,
"min": 0.58,
"max": 1.03,
"ci95": 0.617
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 17989.43,
"stdev": 3969.295,
"min": 13444.423,
"max": 20774.261,
"ci95": 9861.07
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 162.084,
"stdev": 160.057,
"min": 68.44,
"max": 346.896,
"ci95": 397.635
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 33.74,
"stdev": 36.146,
"min": 11.158,
"max": 75.429,
"ci95": 89.798
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 17.194,
"stdev": 14.232,
"min": 7.903,
"max": 33.579,
"ci95": 35.357
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 1977.602,
"stdev": 108.627,
"min": 1872.516,
"max": 2089.453,
"ci95": 269.866
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 0.772,
"stdev": 0.332,
"min": 0.463,
"max": 1.123,
"ci95": 0.825
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 2135.313,
"stdev": 54.617,
"min": 2072.344,
"max": 2169.828,
"ci95": 135.686
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 1836.44,
"stdev": 223.694,
"min": 1578.141,
"max": 1965.781,
"ci95": 555.732
},
"memory.footprint_peak_mb": {
"n": 3,
"mean": 3253.456,
"stdev": 68.323,
"min": 3178.409,
"max": 3312.05,
"ci95": 169.737
},
"memory.footprint_steady_mb": {
"n": 3,
"mean": 3144.363,
"stdev": 69.868,
"min": 3069.394,
"max": 3207.66,
"ci95": 173.576
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 1272,
"seconds": 12.002,
"avg_fps": 105.979,
"low_1pct_fps": 21.25,
"low_01pct_fps": 17.394,
"p50_ms": 7.527,
"p95_ms": 22.34,
"p99_ms": 40.461,
"p999_ms": 54.28,
"max_ms": 60.7,
"histogram": [
0,
0,
0,
56,
161,
170,
180,
167,
184,
118,
71,
61,
26,
25,
28,
12,
10,
3,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 1,
"frames": 1723,
"seconds": 12.007,
"avg_fps": 143.496,
"low_1pct_fps": 24.308,
"low_01pct_fps": 13.602,
"p50_ms": 5.317,
"p95_ms": 16.181,
"p99_ms": 29.771,
"p999_ms": 56.832,
"max_ms": 90.209,
"histogram": [
0,
0,
63,
413,
271,
273,
185,
148,
112,
89,
47,
43,
28,
22,
18,
4,
5,
1,
1,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 346,
"seconds": 12.05,
"avg_fps": 28.713,
"low_1pct_fps": 4.001,
"low_01pct_fps": 3.549,
"p50_ms": 23.684,
"p95_ms": 110.057,
"p99_ms": 225.236,
"p999_ms": 281.776,
"max_ms": 281.776,
"histogram": [
0,
0,
0,
4,
8,
6,
6,
12,
21,
20,
23,
25,
24,
32,
50,
28,
24,
27,
17,
11,
6,
2,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 589,
"seconds": 12.001,
"avg_fps": 49.081,
"low_1pct_fps": 8.19,
"low_01pct_fps": 6.325,
"p50_ms": 15.443,
"p95_ms": 53.004,
"p99_ms": 94.69,
"p999_ms": 158.114,
"max_ms": 158.114,
"histogram": [
0,
0,
0,
2,
7,
20,
30,
46,
53,
48,
56,
62,
61,
66,
63,
18,
22,
20,
10,
4,
1,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 2731,
"seconds": 12.004,
"avg_fps": 227.513,
"low_1pct_fps": 53.473,
"low_01pct_fps": 33.042,
"p50_ms": 3.801,
"p95_ms": 7.955,
"p99_ms": 14.177,
"p999_ms": 24.479,
"max_ms": 38.218,
"histogram": [
0,
0,
526,
1157,
432,
257,
151,
88,
46,
32,
13,
15,
6,
6,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 527,
"seconds": 12.016,
"avg_fps": 43.857,
"low_1pct_fps": 7.338,
"low_01pct_fps": 5.157,
"p50_ms": 16.784,
"p95_ms": 57.353,
"p99_ms": 111.984,
"p999_ms": 193.905,
"max_ms": 193.905,
"histogram": [
0,
0,
0,
1,
2,
9,
16,
29,
40,
61,
50,
54,
52,
62,
57,
35,
28,
11,
13,
5,
2,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 746,
"seconds": 12.001,
"avg_fps": 62.162,
"low_1pct_fps": 14.965,
"low_01pct_fps": 12.372,
"p50_ms": 13.76,
"p95_ms": 35.144,
"p99_ms": 57.63,
"p999_ms": 80.827,
"max_ms": 80.827,
"histogram": [
0,
0,
2,
13,
12,
41,
37,
53,
67,
82,
80,
99,
82,
76,
58,
17,
11,
12,
4,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 3910,
"seconds": 12.002,
"avg_fps": 325.77,
"low_1pct_fps": 79.024,
"low_01pct_fps": 41.466,
"p50_ms": 2.685,
"p95_ms": 5.125,
"p99_ms": 8.342,
"p999_ms": 20.782,
"max_ms": 30.571,
"histogram": [
0,
78,
2526,
892,
203,
95,
49,
27,
13,
11,
7,
3,
2,
3,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 1268,
"seconds": 12.044,
"avg_fps": 105.282,
"low_1pct_fps": 14.736,
"low_01pct_fps": 8.855,
"p50_ms": 6.542,
"p95_ms": 25.245,
"p99_ms": 50.465,
"p999_ms": 79.065,
"max_ms": 146.809,
"histogram": [
0,
0,
4,
171,
190,
169,
167,
133,
105,
93,
53,
43,
35,
41,
26,
13,
11,
10,
3,
1,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 822,
"seconds": 12.011,
"avg_fps": 68.44,
"low_1pct_fps": 14.632,
"low_01pct_fps": 10.101,
"p50_ms": 11.396,
"p95_ms": 35.387,
"p99_ms": 55.24,
"p999_ms": 99.005,
"max_ms": 99.005,
"histogram": [
0,
0,
0,
10,
42,
62,
55,
86,
89,
97,
64,
73,
74,
72,
48,
21,
17,
8,
4,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 4165,
"seconds": 12.006,
"avg_fps": 346.896,
"low_1pct_fps": 75.429,
"low_01pct_fps": 33.579,
"p50_ms": 2.541,
"p95_ms": 4.838,
"p99_ms": 7.998,
"p999_ms": 20.209,
"max_ms": 53.577,
"histogram": [
0,
640,
2307,
855,
178,
93,
34,
22,
15,
3,
3,
8,
2,
2,
2,
0,
0,
1,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 851,
"seconds": 12.0,
"avg_fps": 70.916,
"low_1pct_fps": 11.158,
"low_01pct_fps": 7.903,
"p50_ms": 10.128,
"p95_ms": 36.498,
"p99_ms": 68.434,
"p999_ms": 126.541,
"max_ms": 126.541,
"histogram": [
0,
0,
0,
38,
61,
87,
76,
69,
84,
100,
63,
53,
68,
53,
48,
15,
9,
16,
9,
2,
0,
0,
0,
0
]
}
],
"label": "vanilla-macmini"
}
+771
View File
@@ -0,0 +1,771 @@
{
"format": "horizon-benchmark-report",
"version": 1,
"kind": "game",
"created": "2026-11-04T13:08:37Z",
"app": {
"name": "minecraft",
"version": "26.3",
"mods": [
{
"name": "fabric-loader",
"version": "0.19.5"
}
]
},
"hardware": {
"os": "macos",
"arch": "aarch64",
"cpu_threads": 14,
"os_version": "27.0.1",
"cpu": "Apple M4 Pro",
"ram_mb": 65536,
"gpu": {
"name": "Apple M4 Pro",
"vendor": "Apple",
"driver": "4.1 Metal - 91.7",
"backend": "opengl",
"kind": ""
}
},
"conditions": {
"scenario": "perf_bench:village,forest,ocean,mountain",
"seed": "1234",
"render_distance": 32,
"simulation_distance": 12,
"vsync": false,
"offscreen": false,
"repeats": 3,
"warmup_s": 2.0,
"load_avg_1m": 18.159,
"window": [
1280,
720
]
},
"metrics": {
"startup.title_ms": {
"n": 3,
"mean": 8607.88,
"stdev": 949.596,
"min": 8010.323,
"max": 9702.854,
"ci95": 2359.119
},
"startup.world_ms": {
"n": 3,
"mean": 44182.88,
"stdev": 4031.933,
"min": 40172.323,
"max": 48235.854,
"ci95": 10016.685
},
"chunks.village.load_ms": {
"n": 3,
"mean": 17227.171,
"stdev": 5351.234,
"min": 13892.279,
"max": 23399.566,
"ci95": 13294.274
},
"fps.village.avg_fps": {
"n": 3,
"mean": 76.237,
"stdev": 45.661,
"min": 28.922,
"max": 120.041,
"ci95": 113.437
},
"fps.village.low_1pct_fps": {
"n": 3,
"mean": 17.319,
"stdev": 12.99,
"min": 5.593,
"max": 31.283,
"ci95": 32.272
},
"fps.village.low_01pct_fps": {
"n": 3,
"mean": 12.642,
"stdev": 9.463,
"min": 4.308,
"max": 22.93,
"ci95": 23.51
},
"memory.village.rss_mb": {
"n": 3,
"mean": 2674.987,
"stdev": 30.71,
"min": 2641.352,
"max": 2701.531,
"ci95": 76.294
},
"tick.village.client_ms": {
"n": 3,
"mean": 1.341,
"stdev": 0.407,
"min": 0.924,
"max": 1.737,
"ci95": 1.012
},
"chunks.forest.load_ms": {
"n": 3,
"mean": 34112.389,
"stdev": 19305.487,
"min": 22633.094,
"max": 56401.09,
"ci95": 47961.36
},
"fps.forest.avg_fps": {
"n": 3,
"mean": 72.848,
"stdev": 51.859,
"min": 18.307,
"max": 121.528,
"ci95": 128.837
},
"fps.forest.low_1pct_fps": {
"n": 3,
"mean": 22.096,
"stdev": 20.299,
"min": 3.973,
"max": 44.03,
"ci95": 50.429
},
"fps.forest.low_01pct_fps": {
"n": 3,
"mean": 16.093,
"stdev": 14.608,
"min": 3.688,
"max": 32.194,
"ci95": 36.291
},
"memory.forest.rss_mb": {
"n": 3,
"mean": 2519.729,
"stdev": 82.097,
"min": 2426.297,
"max": 2580.328,
"ci95": 203.957
},
"tick.forest.client_ms": {
"n": 3,
"mean": 1.115,
"stdev": 0.411,
"min": 0.726,
"max": 1.545,
"ci95": 1.02
},
"chunks.ocean.load_ms": {
"n": 3,
"mean": 19066.516,
"stdev": 9372.33,
"min": 13163.781,
"max": 29873.383,
"ci95": 23284.039
},
"fps.ocean.avg_fps": {
"n": 3,
"mean": 69.735,
"stdev": 46.656,
"min": 16.524,
"max": 103.635,
"ci95": 115.909
},
"fps.ocean.low_1pct_fps": {
"n": 3,
"mean": 17.636,
"stdev": 13.616,
"min": 3.179,
"max": 30.217,
"ci95": 33.827
},
"fps.ocean.low_01pct_fps": {
"n": 3,
"mean": 12.115,
"stdev": 10.798,
"min": 2.584,
"max": 23.842,
"ci95": 26.825
},
"memory.ocean.rss_mb": {
"n": 3,
"mean": 2557.143,
"stdev": 84.375,
"min": 2463.781,
"max": 2627.945,
"ci95": 209.616
},
"tick.ocean.client_ms": {
"n": 3,
"mean": 1.056,
"stdev": 0.233,
"min": 0.815,
"max": 1.281,
"ci95": 0.58
},
"chunks.mountain.load_ms": {
"n": 3,
"mean": 62214.712,
"stdev": 4884.13,
"min": 58937.045,
"max": 67828.14,
"ci95": 12133.83
},
"fps.mountain.avg_fps": {
"n": 3,
"mean": 51.809,
"stdev": 7.118,
"min": 45.235,
"max": 59.369,
"ci95": 17.685
},
"fps.mountain.low_1pct_fps": {
"n": 3,
"mean": 6.336,
"stdev": 1.097,
"min": 5.545,
"max": 7.588,
"ci95": 2.725
},
"fps.mountain.low_01pct_fps": {
"n": 3,
"mean": 3.458,
"stdev": 0.756,
"min": 2.827,
"max": 4.296,
"ci95": 1.878
},
"memory.mountain.rss_mb": {
"n": 3,
"mean": 2604.227,
"stdev": 15.043,
"min": 2588.234,
"max": 2618.094,
"ci95": 37.371
},
"tick.mountain.client_ms": {
"n": 3,
"mean": 0.957,
"stdev": 0.03,
"min": 0.926,
"max": 0.985,
"ci95": 0.074
},
"memory.rss_peak_mb": {
"n": 3,
"mean": 2731.016,
"stdev": 56.767,
"min": 2688.516,
"max": 2795.484,
"ci95": 141.03
},
"memory.rss_steady_mb": {
"n": 3,
"mean": 2577.339,
"stdev": 77.295,
"min": 2488.203,
"max": 2625.875,
"ci95": 192.028
},
"memory.footprint_peak_mb": {
"n": 3,
"mean": 4612.571,
"stdev": 20.245,
"min": 4595.363,
"max": 4634.879,
"ci95": 50.296
},
"memory.footprint_steady_mb": {
"n": 3,
"mean": 4271.925,
"stdev": 26.308,
"min": 4246.207,
"max": 4298.785,
"ci95": 65.357
}
},
"frames": [
{
"phase": "village",
"repeat": 0,
"frames": 349,
"seconds": 12.067,
"avg_fps": 28.922,
"low_1pct_fps": 5.593,
"low_01pct_fps": 4.308,
"p50_ms": 24.684,
"p95_ms": 92.764,
"p99_ms": 150.338,
"p999_ms": 232.136,
"max_ms": 232.136,
"histogram": [
0,
0,
0,
0,
0,
0,
2,
3,
13,
17,
26,
31,
40,
46,
50,
32,
25,
20,
30,
10,
4,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 1,
"frames": 1441,
"seconds": 12.004,
"avg_fps": 120.041,
"low_1pct_fps": 31.283,
"low_01pct_fps": 22.93,
"p50_ms": 7.267,
"p95_ms": 13.622,
"p99_ms": 23.449,
"p999_ms": 42.564,
"max_ms": 44.657,
"histogram": [
0,
0,
0,
0,
0,
64,
543,
397,
218,
100,
54,
24,
20,
7,
7,
5,
2,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "village",
"repeat": 2,
"frames": 958,
"seconds": 12.013,
"avg_fps": 79.749,
"low_1pct_fps": 15.082,
"low_01pct_fps": 10.689,
"p50_ms": 9.138,
"p95_ms": 28.848,
"p99_ms": 52.247,
"p999_ms": 93.55,
"max_ms": 93.55,
"histogram": [
0,
0,
0,
0,
0,
19,
144,
232,
148,
111,
64,
56,
59,
57,
32,
17,
9,
6,
4,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 0,
"frames": 220,
"seconds": 12.017,
"avg_fps": 18.307,
"low_1pct_fps": 3.973,
"low_01pct_fps": 3.688,
"p50_ms": 40.635,
"p95_ms": 157.363,
"p99_ms": 235.253,
"p999_ms": 271.153,
"max_ms": 271.153,
"histogram": [
0,
0,
0,
0,
0,
0,
0,
0,
1,
3,
6,
14,
17,
25,
25,
17,
27,
33,
24,
16,
11,
1,
0,
0
]
},
{
"phase": "forest",
"repeat": 1,
"frames": 1459,
"seconds": 12.005,
"avg_fps": 121.528,
"low_1pct_fps": 44.03,
"low_01pct_fps": 32.194,
"p50_ms": 7.381,
"p95_ms": 12.386,
"p99_ms": 18.354,
"p999_ms": 29.428,
"max_ms": 32.695,
"histogram": [
0,
0,
0,
0,
0,
0,
357,
642,
300,
80,
28,
28,
14,
7,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "forest",
"repeat": 2,
"frames": 945,
"seconds": 12.006,
"avg_fps": 78.709,
"low_1pct_fps": 18.284,
"low_01pct_fps": 12.397,
"p50_ms": 9.554,
"p95_ms": 27.657,
"p99_ms": 43.253,
"p999_ms": 80.663,
"max_ms": 80.663,
"histogram": [
0,
0,
0,
0,
0,
0,
18,
304,
191,
119,
67,
59,
53,
70,
34,
15,
10,
3,
2,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 0,
"frames": 201,
"seconds": 12.164,
"avg_fps": 16.524,
"low_1pct_fps": 3.179,
"low_01pct_fps": 2.584,
"p50_ms": 44.933,
"p95_ms": 155.019,
"p99_ms": 263.919,
"p999_ms": 387.044,
"max_ms": 387.044,
"histogram": [
0,
0,
0,
0,
0,
0,
0,
0,
1,
2,
4,
7,
20,
8,
29,
18,
25,
29,
29,
17,
8,
4,
0,
0
]
},
{
"phase": "ocean",
"repeat": 1,
"frames": 1244,
"seconds": 12.004,
"avg_fps": 103.635,
"low_1pct_fps": 30.217,
"low_01pct_fps": 23.842,
"p50_ms": 8.349,
"p95_ms": 17.445,
"p99_ms": 28.276,
"p999_ms": 38.037,
"max_ms": 45.85,
"histogram": [
0,
0,
0,
0,
0,
0,
162,
453,
312,
129,
71,
47,
32,
18,
16,
3,
1,
0,
0,
0,
0,
0,
0,
0
]
},
{
"phase": "ocean",
"repeat": 2,
"frames": 1069,
"seconds": 12.005,
"avg_fps": 89.047,
"low_1pct_fps": 19.512,
"low_01pct_fps": 9.92,
"p50_ms": 9.541,
"p95_ms": 20.819,
"p99_ms": 31.409,
"p999_ms": 99.668,
"max_ms": 101.942,
"histogram": [
0,
0,
0,
0,
0,
0,
64,
266,
283,
160,
112,
74,
51,
33,
17,
4,
1,
2,
1,
1,
0,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 0,
"frames": 551,
"seconds": 12.181,
"avg_fps": 45.235,
"low_1pct_fps": 5.545,
"low_01pct_fps": 3.252,
"p50_ms": 14.921,
"p95_ms": 50.78,
"p99_ms": 105.154,
"p999_ms": 307.476,
"max_ms": 307.476,
"histogram": [
0,
0,
0,
0,
0,
0,
0,
0,
42,
137,
72,
58,
50,
47,
57,
39,
20,
15,
8,
3,
2,
1,
0,
0
]
},
{
"phase": "mountain",
"repeat": 1,
"frames": 718,
"seconds": 12.094,
"avg_fps": 59.369,
"low_1pct_fps": 7.588,
"low_01pct_fps": 4.296,
"p50_ms": 12.178,
"p95_ms": 38.273,
"p99_ms": 65.694,
"p999_ms": 232.789,
"max_ms": 232.789,
"histogram": [
0,
0,
0,
0,
0,
0,
0,
0,
140,
202,
115,
72,
48,
47,
43,
19,
19,
6,
4,
0,
3,
0,
0,
0
]
},
{
"phase": "mountain",
"repeat": 2,
"frames": 610,
"seconds": 12.003,
"avg_fps": 50.822,
"low_1pct_fps": 5.875,
"low_01pct_fps": 2.827,
"p50_ms": 13.894,
"p95_ms": 44.208,
"p99_ms": 75.023,
"p999_ms": 353.686,
"max_ms": 353.686,
"histogram": [
0,
0,
0,
0,
0,
0,
0,
0,
67,
115,
125,
88,
55,
54,
43,
25,
13,
15,
7,
0,
1,
2,
0,
0
]
}
],
"label": "vanilla-macmini"
}
+5
View File
@@ -0,0 +1,5 @@
# `keys.contains(&KeyCode::ShiftLeft)` reads a mapping by its default key: ask by `Action`, so the
# mapping is named and every device that can hold it counts.
disallowed-methods = [
{ path = "horizon_client_input::held::HeldKeys::contains", reason = "use `held(Action::..)`, or `raw` for a key that is no mapping's default", allow-invalid = true },
]
+1 -1
View File
@@ -93,7 +93,7 @@ pub fn kind_of(path: &Path) -> Option<Kind> {
"hmod" => Some(Kind::Mod),
"hnmod" => Some(Kind::NativeMod),
"hpack" => Some(Kind::Modpack),
"zip" => Some(Kind::ResourcePack),
"zip" => Some(horizon_mods::index::zip_kind(path)),
"hshader" => Some(Kind::Shader),
_ => None,
}
@@ -108,6 +108,16 @@ impl<T> CompiledChoices<T> {
multipart: self.multipart.iter().map(|c| c.map(&mut f)).collect(),
}
}
/// A variant that needs no random selection and produces exactly one model.
pub fn single_variant(&self, selected: &SelectedModels) -> Option<&T> {
let SelectedModels::Variant(index) = selected else {
return None;
};
match self.variants.get(*index)? {
Choice::Single(value) => Some(value),
Choice::Weighted { .. } => None,
}
}
/// The variant `particleMaterial` comes from: the first entry of a weighted
/// list (`WeightedVariants`) and, for a multipart root, of its first
/// selector whether or not that selector applies (`MultiPartModel`).
+136 -12
View File
@@ -90,6 +90,9 @@ horizon-bots: load-test bots for a Minecraft Java Edition 26.3 server (offline m
--seed N default 12345
--explore-speed B blocks a second an explore bot flies; default 22 (as fast as chunks come)
--pvp B fight bots also attack each other (true or false); default true
--crits fight bots swing at a sword's full strength (every 13 ticks) and jump into every
other swing, landing it while falling: a critical hit, then a sweep from the
ground (give them swords); for PvP benches (crit and sweep particles)
--name PREFIX bot names: PREFIX0, PREFIX1...; default Bot
--brand B the client brand sent in configuration (minecraft:brand); default none
--height H blocks above spawn the bots fly at (walk, explore, idle); default 24
@@ -165,6 +168,8 @@ struct Options {
attack_after: f64,
attack_rounds: u32,
no_swing: bool,
/// --crits: fight bots alternate jumped critical hits and sweeps at a sword's cooldown.
crits: bool,
flood: usize,
no_read: bool,
no_loaded: bool,
@@ -202,6 +207,7 @@ fn options() -> Result<Options, String> {
attack_after: 5.0,
attack_rounds: u32::MAX,
no_swing: false,
crits: false,
flood: 100,
no_read: false,
no_loaded: false,
@@ -230,6 +236,10 @@ fn options() -> Result<Options, String> {
o.no_swing = true;
continue;
}
if arg == "--crits" {
o.crits = true;
continue;
}
if arg == "--no-loaded" {
o.no_loaded = true;
continue;
@@ -532,11 +542,84 @@ fn distance(a: [f64; 3], b: [f64; 3]) -> f64 {
((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2) + (a[2] - b[2]).powi(2)).sqrt()
}
/// A sword's attack speed is 1.6 a second: full strength 20 / 1.6 = 12.5 ticks after a swing.
const SWORD_COOLDOWN_TICKS: u32 = 13;
/// `LivingEntity.getJumpPower`: a jump's first upward motion, in blocks a tick.
const JUMP_POWER: f64 = 0.42;
/// `--crits`: a fight bot that swings at a sword's full strength and jumps into every other swing, so
/// it lands while falling (`Player.attack`'s critical hit: falling, not on the ground, full strength),
/// and the next one from the ground (a sweep: on the ground, full strength, a sword).
#[derive(Default)]
struct Crits {
/// Vertical motion while in the air (`LivingEntity.travel`: minus 0.08 a tick, times 0.98).
jump: Option<f64>,
ground: f64,
/// The next swing is the jumped one.
crit_next: bool,
}
impl Crits {
fn airborne(&self) -> bool {
self.jump.is_some()
}
/// One tick: walk up to the target and, with a full cooldown, strike; jump ahead of a crit's swing.
fn tick(
&mut self,
pos: &mut [f64; 3],
target: (i32, [f64; 3]),
since: &mut u32,
) -> Option<i32> {
if let Some(v) = self.jump {
pos[1] += v;
if pos[1] <= self.ground {
pos[1] = self.ground;
self.jump = None;
} else {
self.jump = Some((v - 0.08) * 0.98);
}
} else {
self.ground = pos[1];
// A jump takes 6 ticks to its top: start it so the cooldown ends on the way down.
if self.crit_next && *since + 8 >= SWORD_COOLDOWN_TICKS {
self.jump = Some(JUMP_POWER);
}
}
let falling = self.jump.is_some_and(|v| v < 0.0);
if self.crit_next && !falling {
*since = since.saturating_add(1);
fight_step(pos, target.1);
return None;
}
let hit = fight_tick(pos, target, since, SWORD_COOLDOWN_TICKS);
if hit.is_some() {
self.crit_next = !self.crit_next;
}
hit
}
}
/// One tick of a fighting bot with a target: walk up to it, and strike once the cooldown is over.
/// Returns the entity to attack. `since_attack` counts ticks since the last swing.
fn fight_tick(pos: &mut [f64; 3], target: (i32, [f64; 3]), since_attack: &mut u32) -> Option<i32> {
fn fight_tick(
pos: &mut [f64; 3],
target: (i32, [f64; 3]),
since_attack: &mut u32,
cooldown: u32,
) -> Option<i32> {
*since_attack = since_attack.saturating_add(1);
let (id, at) = target;
fight_step(pos, at);
if *since_attack >= cooldown && distance(*pos, at) <= ATTACK_REACH {
*since_attack = 0;
return Some(id);
}
None
}
/// Walk toward `at`, stopping `CLOSE_IN` short of it.
fn fight_step(pos: &mut [f64; 3], at: [f64; 3]) {
let (dx, dz) = (at[0] - pos[0], at[2] - pos[2]);
let horizontal = dx.hypot(dz);
if horizontal > CLOSE_IN {
@@ -544,11 +627,6 @@ fn fight_tick(pos: &mut [f64; 3], target: (i32, [f64; 3]), since_attack: &mut u3
pos[0] += dx / horizontal * step;
pos[2] += dz / horizontal * step;
}
if *since_attack >= ATTACK_COOLDOWN_TICKS && distance(*pos, at) <= ATTACK_REACH {
*since_attack = 0;
return Some(id);
}
None
}
/// One tick of walking to random points around the spawn, at sprint speed (5.6 blocks a second).
@@ -1124,6 +1202,7 @@ fn run_bot(
spawn[1] + o.height
};
let mut since_attack = ATTACK_COOLDOWN_TICKS;
let mut crits = Crits::default();
let heading = random.unit() * std::f64::consts::TAU;
let mut target = pos;
let mut next = Instant::now();
@@ -1181,11 +1260,16 @@ fn run_bot(
Behaviour::Fight => {
let near = lock(&bot.entities).nearest(pos, o.pvp, &bots);
if let Some(near) = near {
if let Some(victim) = fight_tick(&mut pos, near, &mut since_attack) {
let hit = if o.crits {
crits.tick(&mut pos, near, &mut since_attack)
} else {
fight_tick(&mut pos, near, &mut since_attack, ATTACK_COOLDOWN_TICKS)
};
if let Some(victim) = hit {
// The client sends the attack, then swings the arm.
let sent = send(
play::Serverbound::Attack(play::Attack { id: victim }).encode(),
) && send(raw(sb::PUNCH, |w| w.varint(0)));
) && send(play::Serverbound::Punch(Empty).encode());
if sent {
bot.attacks.fetch_add(1, Ordering::Relaxed);
}
@@ -1300,9 +1384,10 @@ fn run_bot(
x: pos[0],
y: pos[1],
z: pos[2],
// Fight bots stand on the ground; the others fly.
// Fight bots stand on the ground (but in a --crits jump); the others fly.
flags: u8::from(
matches!(behaviour, Behaviour::Fight | Behaviour::Stand) || o.ground,
(matches!(behaviour, Behaviour::Fight | Behaviour::Stand) && !crits.airborne())
|| o.ground,
),
})
.encode(),
@@ -1600,7 +1685,9 @@ mod tests {
let target = (3, [1.0, 64.0, 0.0]);
let mut since = ATTACK_COOLDOWN_TICKS;
let hits: Vec<u32> = (0..40)
.filter_map(|tick| fight_tick(&mut pos, target, &mut since).map(|_| tick))
.filter_map(|tick| {
fight_tick(&mut pos, target, &mut since, ATTACK_COOLDOWN_TICKS).map(|_| tick)
})
.collect();
assert_eq!(hits[0], 0, "a bot in reach strikes at once");
assert!(
@@ -1617,7 +1704,7 @@ mod tests {
let mut since = ATTACK_COOLDOWN_TICKS;
let mut first = None;
for tick in 0..100 {
if fight_tick(&mut pos, target, &mut since).is_some() {
if fight_tick(&mut pos, target, &mut since, ATTACK_COOLDOWN_TICKS).is_some() {
first = Some(tick);
break;
}
@@ -1627,6 +1714,43 @@ mod tests {
assert!(pos[0] < 10.0 - CLOSE_IN + 1e-9);
}
#[test]
fn crits_alternate_a_falling_swing_and_a_grounded_one() {
let mut pos = [0.0, 64.0, 0.0];
let target = (3, [1.0, 64.0, 0.0]);
let (mut since, mut crits) = (SWORD_COOLDOWN_TICKS, Crits::default());
let mut last_y = pos[1];
let mut hits = Vec::new();
for tick in 0..120u32 {
let hit = crits.tick(&mut pos, target, &mut since);
if hit.is_some() {
hits.push((tick, crits_state(pos[1], last_y)));
}
last_y = pos[1];
}
assert!(hits.len() >= 6, "{hits:?}");
for (i, w) in hits.windows(2).enumerate() {
assert!(
w[1].0 - w[0].0 >= SWORD_COOLDOWN_TICKS,
"full strength each time: {hits:?}"
);
assert_ne!(w[0].1, w[1].1, "swing {i} and the next alternate: {hits:?}");
}
// A crit lands in the air, on the way down; a sweep from the ground.
assert!(hits.iter().any(|h| h.1 == "falling") && hits.iter().any(|h| h.1 == "ground"));
assert!(pos[1] >= 64.0);
}
fn crits_state(y: f64, before: f64) -> &'static str {
if y == 64.0 {
"ground"
} else if y < before {
"falling"
} else {
"rising"
}
}
#[test]
fn percentiles_use_nearest_rank() {
assert_eq!(percentile(&[], 50), 0);
+28 -7
View File
@@ -489,15 +489,36 @@ pub fn parse_duration(text: &str) -> Option<i64> {
(count > 0).then(|| count.checked_mul(unit)).flatten()
}
/// `millis` for players, rounded up: minutes up to an hour, hours up to two days, then days.
pub fn duration_text(millis: i64) -> String {
let up = |unit: i64| (millis + unit - 1) / unit;
let (count, unit) = if millis <= 60 * MINUTE {
(up(MINUTE), "minute")
/// The unit used when presenting a moderation duration to a player.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DurationUnit {
Minute,
Hour,
Day,
}
/// A duration's rounded count and unit, shared by logs and translated replies.
pub fn duration_parts(millis: i64) -> (i64, DurationUnit) {
let (unit, kind) = if millis <= 60 * MINUTE {
(MINUTE, DurationUnit::Minute)
} else if millis <= 48 * 60 * MINUTE {
(up(60 * MINUTE), "hour")
(60 * MINUTE, DurationUnit::Hour)
} else {
(up(24 * 60 * MINUTE), "day")
(24 * 60 * MINUTE, DurationUnit::Day)
};
(
millis.div_euclid(unit) + i64::from(millis.rem_euclid(unit) > 0),
kind,
)
}
/// `millis` for logs, rounded up: minutes up to an hour, hours up to two days, then days.
pub fn duration_text(millis: i64) -> String {
let (count, unit) = duration_parts(millis);
let unit = match unit {
DurationUnit::Minute => "minute",
DurationUnit::Hour => "hour",
DurationUnit::Day => "day",
};
format!("{count} {unit}{}", if count == 1 { "" } else { "s" })
}
+16
View File
@@ -327,3 +327,19 @@ fn an_allow_line_without_letters_allows_nothing() {
assert!(f.matches("nigger"), "{line}");
}
}
#[test]
fn duration_parts_preserve_rounding_without_overflow() {
for (millis, expected) in [
(1, (1, DurationUnit::Minute)),
(60_000, (1, DurationUnit::Minute)),
(61_000, (2, DurationUnit::Minute)),
(3_600_000, (60, DurationUnit::Minute)),
(3_600_001, (2, DurationUnit::Hour)),
(172_800_000, (48, DurationUnit::Hour)),
(172_800_001, (3, DurationUnit::Day)),
(i64::MAX, (106_751_991_168, DurationUnit::Day)),
] {
assert_eq!(duration_parts(millis), expected);
}
}
@@ -0,0 +1,22 @@
//! NBT path parser output and byte cursor for the pinned Java oracle.
use horizon_client_core::nbt_path::NbtPath;
use std::io::{BufRead, Write};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let input = std::io::BufReader::new(std::fs::File::open(args.next().unwrap())?);
let mut output = std::io::BufWriter::new(std::fs::File::create(args.next().unwrap())?);
for line in input.lines() {
let text: String = serde_json::from_str(&line?)?;
match NbtPath::parse_prefix(&text) {
Ok((path, cursor)) => {
write!(output, "OK {cursor} ")?;
for value in path.as_str().as_bytes() {
write!(output, "{value:02x}")?;
}
writeln!(output)?;
}
Err(error) => writeln!(output, "ERR {} {}", error.cursor, error.key)?,
}
}
Ok(())
}
@@ -143,7 +143,9 @@ impl<'a> Fields<'a> {
/// `RandomizableContainer.tryLoadLootTable` / `trySaveLootTable`; true when the
/// block entity has a loot table (and so saves no items).
fn loot_table(&mut self) -> bool {
let Some(table) = text(self.input.get("LootTable")) else {
let Some(table) =
text(self.input.get("LootTable")).and_then(|id| crate::identifier::normalize_id(&id))
else {
return false;
};
self.put("LootTable", string(&table));
@@ -905,6 +907,16 @@ mod tests {
assert_eq!(out.get("LootTable"), Some(&string("minecraft:chests/x")));
}
#[test]
fn invalid_loot_table_leaves_items_intact() {
let out = canon(
"{id:\"minecraft:barrel\",x:1,y:2,z:3,LootTable:\"INVALID TABLE\",\
Items:[{Slot:0b,id:\"minecraft:stone\",count:1}]}",
);
assert!(out.get("Items").is_some());
assert!(out.get("LootTable").is_none());
}
#[test]
fn items_are_validated_and_sorted_by_slot() {
let out = canon(
+39 -5
View File
@@ -111,7 +111,15 @@ impl Parser<'_> {
map.insert(key, self.value()?);
self.ws();
match self.peek() {
Some(b',') => self.i += 1,
// SnbtGrammar: an entry may be followed by a comma before the brace.
Some(b',') => {
self.i += 1;
self.ws();
if self.peek() == Some(b'}') {
self.i += 1;
return Ok(Value::Object(map));
}
}
Some(b'}') => {
self.i += 1;
return Ok(Value::Object(map));
@@ -140,7 +148,15 @@ impl Parser<'_> {
list.push(self.value()?);
self.ws();
match self.peek() {
Some(b',') => self.i += 1,
// SnbtGrammar: a list or array may end with a comma.
Some(b',') => {
self.i += 1;
self.ws();
if self.peek() == Some(b']') {
self.i += 1;
return Ok(Value::Array(list));
}
}
Some(b']') => {
self.i += 1;
return Ok(Value::Array(list));
@@ -207,9 +223,11 @@ fn style_flag(v: Option<&Value>) -> Option<bool> {
}
}
fn untrusted_http_url(url: &str) -> bool {
// ExtraCodecs.UNTRUSTED_URI / Util.parseAndValidateUntrustedUri: http and
// https only, scheme case-insensitive.
/// ExtraCodecs.UNTRUSTED_URI / Util.parseAndValidateUntrustedUri: http and
/// https only, scheme case-insensitive. Every `open_url` a server sends must
/// pass it: anything else (`file:`, a UNC path, a custom protocol handler)
/// could start a program through the system's URL handler.
pub fn untrusted_http_url(url: &str) -> bool {
url.split_once(':').is_some_and(|(scheme, _)| {
scheme.eq_ignore_ascii_case("http") || scheme.eq_ignore_ascii_case("https")
})
@@ -495,6 +513,22 @@ pub fn name_text(tag: &Tag) -> Text {
#[cfg(test)]
mod tests {
use super::*;
/// SnbtGrammar: a compound or list may end with one comma; two in a row
/// stay an error.
#[test]
fn a_component_takes_one_trailing_comma() {
let parse = |s: &str| parse_snbt(s).map(|(v, _)| v);
assert_eq!(
parse("{text:\"a\",color:\"red\",}"),
parse("{text:\"a\",color:\"red\"}")
);
assert_eq!(
parse("[\"a\", {text:\"b\",} ,]"),
parse("[\"a\",{text:\"b\"}]")
);
assert!(parse("{text:\"a\",,}").is_err());
assert!(parse("[\"a\",,]").is_err());
}
#[test]
fn hex_color_follows_text_color_parse_color() {
+192 -3
View File
@@ -388,6 +388,36 @@ pub fn apply_deprecated(map: &mut HashMap<String, String>, json: &[u8]) {
}
}
/// `Language.loadFromJson` for one `lang/<code>.json`: its entries go into `map` in file order.
/// A file that is plain JSON reads as before; any other goes through Gson's lenient reader
/// (`#`, `//` and `/* */` comments, unquoted or single-quoted names and values, raw control
/// characters in strings, `=` or `=>` and `;`), which vanilla reads lang files with (expected
/// from `Language.loadFromJson`, Gson). A file that is not JSON even so adds nothing; a value
/// that is not a string, number or boolean (`GsonHelper.convertToString`) ends the file there.
pub fn read_lang_file(bytes: &[u8], map: &mut HashMap<String, String>) {
use horizon_world::datafix::json::{Json, parse_lenient};
if let Ok(entries) = serde_json::from_slice::<HashMap<String, String>>(bytes) {
map.extend(entries);
return;
}
let text = String::from_utf8_lossy(bytes);
// Gson's JsonReader skips a leading byte order mark.
let text = text.strip_prefix('\u{feff}').unwrap_or(&text);
let utf16: Vec<u16> = text.encode_utf16().collect();
let Some(Json::Obj(entries)) = parse_lenient(&utf16) else {
return;
};
for (key, value) in entries {
let value = match value {
Json::Str(s) => String::from_utf16_lossy(&s),
Json::Num(n) => n,
Json::Bool(b) => b.to_string(),
Json::Null | Json::Arr(_) | Json::Obj(_) => break,
};
map.insert(String::from_utf16_lossy(&key), value);
}
}
impl Language {
/// ClientLanguage.loadFrom: en_us first, then the selected language, each
/// from every namespace and every source in priority order (later sources
@@ -413,9 +443,7 @@ impl Language {
else {
continue;
};
if let Ok(entries) = serde_json::from_slice::<HashMap<String, String>>(&bytes) {
map.extend(entries);
}
read_lang_file(&bytes, &mut map);
}
}
}
@@ -875,4 +903,165 @@ mod tests {
["en_us", "fr_fr"]
);
}
fn read(body: &str) -> HashMap<String, String> {
let mut map = HashMap::new();
read_lang_file(body.as_bytes(), &mut map);
map
}
fn entries(pairs: &[(&str, &str)]) -> HashMap<String, String> {
pairs
.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.collect()
}
#[test]
fn a_plain_json_lang_file_reads_as_before() {
let body = "{\"a\": \"Apple \\\"quoted\\\" \\u00e9\", \"b\": \"\", \"a\": \"Again\"}";
let strict: HashMap<String, String> = serde_json::from_str(body).unwrap();
assert_eq!(read(body), strict);
assert_eq!(read(body), entries(&[("a", "Again"), ("b", "")]));
}
#[test]
fn lang_files_read_with_comments() {
let want = entries(&[("a", "Apple"), ("b", "Banana # not a comment // either")]);
for body in [
"# header\n{\n# a comment\n\"a\": \"Apple\",\n\"b\": \"Banana # not a comment // either\"\n}\n",
"// header\n{\"a\": \"Apple\", // after\n\"b\": \"Banana # not a comment // either\"}",
"/* header\n over lines */{\"a\": /* inline */ \"Apple\", \"b\": \"Banana # not a comment // either\"}",
] {
assert_eq!(read(body), want, "{body}");
}
}
#[test]
fn lang_files_read_unquoted_and_single_quoted_names_and_values() {
assert_eq!(
read("{a: 'Apple', 'b': \"Banana\", c: Cherry}"),
entries(&[("a", "Apple"), ("b", "Banana"), ("c", "Cherry")])
);
// A single-quoted string may hold a double quote, and the reverse.
assert_eq!(
read("{'a': 'say \"hi\"', \"b\": \"it's\"}"),
entries(&[("a", "say \"hi\""), ("b", "it's")])
);
}
#[test]
fn lang_files_read_raw_control_characters_in_strings() {
assert_eq!(
read("{\"a\": \"tab\there\", \"b\": \"two\nlines\"}"),
entries(&[("a", "tab\there"), ("b", "two\nlines")])
);
}
#[test]
fn lang_files_read_gson_separators_and_a_byte_order_mark() {
assert_eq!(
read("\u{feff}{\"a\" = \"Apple\"; \"b\" => \"Banana\"}"),
entries(&[("a", "Apple"), ("b", "Banana")])
);
// A trailing comma before `}` is not JSON even to Gson's lenient reader.
assert_eq!(read("{# c\n\"a\": \"Apple\",}"), HashMap::new());
}
#[test]
fn numbers_and_booleans_read_as_their_text() {
// GsonHelper.convertToString: a primitive reads as its text.
assert_eq!(
read("{\"a\": 1.50, \"b\": true, \"c\": -2e3}"),
entries(&[("a", "1.50"), ("b", "true"), ("c", "-2e3")])
);
}
#[test]
fn a_value_that_is_no_text_ends_the_file_there() {
assert_eq!(
read("# c\n{\"a\": \"Apple\", \"b\": [\"x\"], \"c\": \"Cherry\"}"),
entries(&[("a", "Apple")])
);
assert_eq!(read("{\"a\": null, \"b\": \"Banana\"}"), HashMap::new());
}
#[test]
fn a_lang_file_that_is_not_json_adds_nothing() {
for body in [
"# c\n{\"a\": \"Apple\"",
"[\"a\"]",
"\"a\"",
"",
"{\"a\": \"Apple\"} {",
] {
assert_eq!(read(body), HashMap::new(), "{body:?}");
}
}
#[test]
fn a_pack_lang_file_with_comments_layers_over_english() {
let base = Mem(HashMap::from([(
"assets/minecraft/lang/en_us.json".into(),
br#"{"a":"Apple","b":"Banana"}"#.to_vec(),
)]));
let pack = Mem(HashMap::from([(
"assets/minecraft/lang/fr_fr.json".into(),
b"{\n# Fruits\n\"a\": \"Pomme\"\n}\n".to_vec(),
)]));
let sources: [&dyn ResourceSource; 2] = [&base, &pack];
let fr = Language::load(&sources, "fr_fr");
assert_eq!(fr.get("a"), Some("Pomme"));
assert_eq!(fr.get("b"), Some("Banana"));
}
/// Every lang file of a downloaded pack, e.g. BACAP Language Pack:
/// `HORIZON_LANG_PACK_DIR=<pack>/assets/minecraft/lang cargo test -p horizon-client-core
/// lang::tests::every_lang_file_of_a_pack_reads -- --ignored --nocapture`.
#[test]
#[ignore = "needs a downloaded pack in HORIZON_LANG_PACK_DIR"]
fn every_lang_file_of_a_pack_reads() {
let Ok(dir) = std::env::var("HORIZON_LANG_PACK_DIR") else {
return;
};
let mut failed = Vec::new();
let mut paths: Vec<_> = std::fs::read_dir(dir)
.unwrap()
.map(|e| e.unwrap().path())
.collect();
paths.sort();
for path in paths {
let bytes = std::fs::read(&path).unwrap();
let strict = serde_json::from_slice::<HashMap<String, String>>(&bytes).is_ok();
let mut map = HashMap::new();
read_lang_file(&bytes, &mut map);
// An independent count: the distinct `"name":` that start a line.
let text = String::from_utf8_lossy(&bytes);
let names: std::collections::HashSet<&str> = text
.lines()
.filter_map(|line| {
let rest = line.trim_start().strip_prefix('"')?;
let mut escaped = false;
let end = rest.char_indices().find_map(|(i, c)| {
let close = c == '"' && !escaped;
escaped = c == '\\' && !escaped;
close.then_some(i)
})?;
rest[end + 1..]
.trim_start()
.starts_with(':')
.then(|| &rest[..end])
})
.collect();
println!(
"{}: strict {strict}, {} keys, {} names",
path.display(),
map.len(),
names.len()
);
if map.is_empty() || map.len() != names.len() {
failed.push(path);
}
}
assert!(failed.is_empty(), "{failed:?}");
}
}
+264 -37
View File
@@ -79,28 +79,65 @@ fn elements(tag: &Tag) -> Option<Vec<Tag>> {
_ => return None,
})
}
fn number(t: &Tag) -> Option<i64> {
match t {
Tag::Byte(v) => Some(i64::from(*v)),
Tag::Short(v) => Some(i64::from(*v)),
Tag::Int(v) => Some(i64::from(*v)),
Tag::Long(v) => Some(*v),
_ => None,
/// `DoubleTag.intValue` / `FloatTag.intValue` floor before the i32 conversion;
/// integral tags use their intValue narrowing. Byte arrays then keep the low byte.
pub fn int_value(t: &Tag) -> Option<i32> {
Some(match t {
Tag::Byte(v) => i32::from(*v),
Tag::Short(v) => i32::from(*v),
Tag::Int(v) => *v,
Tag::Long(v) => *v as i32,
Tag::Float(v) => f32::from_bits(*v).floor() as i32,
Tag::Double(v) => f64::from_bits(*v).floor() as i32,
_ => return None,
})
}
/// `FloatTag.longValue` truncates; `DoubleTag.longValue` floors before narrowing.
fn long_value(t: &Tag) -> Option<i64> {
Some(match t {
Tag::Byte(v) => i64::from(*v),
Tag::Short(v) => i64::from(*v),
Tag::Int(v) => i64::from(*v),
Tag::Long(v) => *v,
// FloatTag.longValue truncates; DoubleTag.longValue floors first.
Tag::Float(v) => f32::from_bits(*v) as i64,
Tag::Double(v) => f64::from_bits(*v).floor() as i64,
_ => return None,
})
}
#[derive(Clone, Copy)]
enum ElementKind {
List,
NumericArray,
}
impl ElementKind {
// Admission only: arrays require NumericTag; lists accept every tag type.
fn accepts(self, value: &Tag) -> bool {
matches!(self, Self::List)
|| matches!(
value,
Tag::Byte(_)
| Tag::Short(_)
| Tag::Int(_)
| Tag::Long(_)
| Tag::Float(_)
| Tag::Double(_)
)
}
}
/// Run `f` over the elements of a list/array, writing array results back.
/// Rust view adapter: run `f` over the elements, then write arrays using their numeric accessor.
/// None when `tag` is not list-like.
fn with_elements<R>(tag: &mut Tag, f: impl FnOnce(&mut Vec<Tag>) -> R) -> Option<R> {
fn with_elements<R>(tag: &mut Tag, f: impl FnOnce(&mut Vec<Tag>, ElementKind) -> R) -> Option<R> {
match tag {
Tag::List(l) => Some(f(l)),
Tag::List(l) => Some(f(l, ElementKind::List)),
Tag::ByteArray(_) | Tag::IntArray(_) | Tag::LongArray(_) => {
let mut v = elements(tag)?;
let r = f(&mut v);
let nums: Vec<i64> = v.iter().filter_map(number).collect();
let r = f(&mut v, ElementKind::NumericArray);
match tag {
Tag::ByteArray(a) => *a = nums.iter().map(|n| *n as u8).collect(),
Tag::IntArray(a) => *a = nums.iter().map(|n| *n as i32).collect(),
Tag::LongArray(a) => *a = nums,
// Float/double byteValue uses intValue's floor, then narrows.
Tag::ByteArray(a) => *a = v.iter().filter_map(int_value).map(|n| n as u8).collect(),
Tag::IntArray(a) => *a = v.iter().filter_map(int_value).collect(),
Tag::LongArray(a) => *a = v.iter().filter_map(long_value).collect(),
_ => {}
}
Some(r)
@@ -157,6 +194,20 @@ impl NbtPath {
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
/// Whether this selects a compound-field chain, including filtered or quoted fields.
/// Matching a root or child does not change that subtree's writer provenance.
pub fn is_field_path(&self, fields: &[&str]) -> bool {
let mut fields = fields.iter();
for node in &self.nodes {
match node {
Node::MatchRoot(_) => {}
Node::Child(name) | Node::MatchChild(name, _)
if fields.next().copied() == Some(name.as_str()) => {}
_ => return false,
}
}
fields.next().is_none()
}
fn not_found(&self, node: usize) -> PathError {
let end = self.ends[node];
PathError::new(
@@ -349,7 +400,7 @@ impl NbtPath {
});
removed
}
/// `insert` (also append and prepend): into every list the path reaches.
/// `NbtPathArgument.NbtPath.insert`: insert accepted tags into every reached collection.
pub fn insert(
&self,
index: i32,
@@ -372,19 +423,23 @@ impl NbtPath {
return;
}
let shown = crate::snbt::to_snbt(target);
let result = with_elements(target, |list| {
let result = with_elements(target, |list, kind| {
let start = if index < 0 {
list.len() as i64 + i64::from(index) + 1
} else {
i64::from(index)
};
let mut any = false;
for (n, v) in values.iter().enumerate() {
let at = start + n as i64;
let mut at = start;
for v in values {
if !kind.accepts(v) {
continue;
}
if at < 0 || at as usize > list.len() {
return Err(CollectionError::InvalidIndex(at as i32));
}
list.insert(at as usize, v.clone());
at += 1;
any = true;
}
Ok(any)
@@ -428,7 +483,8 @@ fn preferred_parent(node: &Node) -> Tag {
_ => Tag::Compound(Compound::new()),
}
}
/// `getOrCreateTag` (or `getTag` without `create`): the children of `parent`.
/// Children of `parent`; the array creation arm follows
/// `NbtPathArgument.AllElementsNode.getOrCreateTag` admission.
fn step_mut(
node: &Node,
parent: &mut Tag,
@@ -468,11 +524,14 @@ fn step_mut(
}
Node::AllElements => {
let make = create;
let _ = with_elements(parent, |list| {
let _ = with_elements(parent, |list, kind| {
if list.is_empty() {
if let Some(make) = make {
list.push(make());
out(&mut list[0]);
let value = make();
if kind.accepts(&value) {
list.push(value);
out(&mut list[0]);
}
}
} else {
for t in list.iter_mut() {
@@ -482,7 +541,7 @@ fn step_mut(
});
}
Node::Index(i) => {
let _ = with_elements(parent, |list| {
let _ = with_elements(parent, |list, _kind| {
let n = list.len() as i64;
let idx = if *i < 0 {
n + i64::from(*i)
@@ -518,7 +577,8 @@ fn step_mut(
}
}
}
/// `Node.setTag`: replace the child(ren) of `parent` with `value`.
/// Element setters follow `NbtPathArgument.AllElementsNode.setTag` and
/// `NbtPathArgument.IndexedElementNode.setTag`; object nodes replace their children.
fn set_tag(node: &Node, parent: &mut Tag, value: &Tag) -> usize {
match node {
Node::Child(name) => {
@@ -538,29 +598,36 @@ fn set_tag(node: &Node, parent: &mut Tag, value: &Tag) -> usize {
}
0
}
Node::AllElements => with_elements(parent, |list| {
if list.is_empty() {
list.push(value.clone());
Node::AllElements => with_elements(parent, |list, kind| {
let len = list.len();
if len == 0 {
if kind.accepts(value) {
list.push(value.clone());
}
// AllElementsNode returns 1 here even when addTag rejects.
return 1;
}
// Compare the original tags, before numeric conversion.
let changed = list.iter().filter(|t| *t != value).count();
if changed == 0 {
return 0;
}
for t in list.iter_mut() {
*t = value.clone();
list.clear();
if !kind.accepts(value) {
return 0;
}
list.resize(len, value.clone());
changed
})
.unwrap_or(0),
Node::Index(i) => with_elements(parent, |list| {
Node::Index(i) => with_elements(parent, |list, kind| {
let n = list.len() as i64;
let idx = if *i < 0 {
n + i64::from(*i)
} else {
i64::from(*i)
};
if (0..n).contains(&idx) && list[idx as usize] != *value {
if (0..n).contains(&idx) && list[idx as usize] != *value && kind.accepts(value) {
list[idx as usize] = value.clone();
1
} else {
@@ -586,6 +653,7 @@ fn set_tag(node: &Node, parent: &mut Tag, value: &Tag) -> usize {
Node::MatchRoot(_) => 0,
}
}
/// Removal arms, including `NbtPathArgument.AllElementsNode.removeTag`.
fn remove_tag(node: &Node, parent: &mut Tag) -> usize {
match node {
Node::Child(name) => match parent {
@@ -603,13 +671,13 @@ fn remove_tag(node: &Node, parent: &mut Tag) -> usize {
}
_ => 0,
},
Node::AllElements => with_elements(parent, |l| {
Node::AllElements => with_elements(parent, |l, _kind| {
let n = l.len();
l.clear();
n
})
.unwrap_or(0),
Node::Index(i) => with_elements(parent, |l| {
Node::Index(i) => with_elements(parent, |l, _kind| {
let n = l.len() as i64;
let idx = if *i < 0 {
n + i64::from(*i)
@@ -658,11 +726,16 @@ fn parse_node(text: &str, i: usize, first: bool) -> Result<(Node, usize), PathEr
Some(b']') => Ok((Node::AllElements, j + 1)),
_ => {
let start = j;
if b.get(j) == Some(&b'-') {
// StringReader.readInt scans all allowed number characters
// before parsing, including malformed signs and decimal points.
while b
.get(j)
.is_some_and(|c| c.is_ascii_digit() || matches!(c, b'-' | b'.'))
{
j += 1;
}
while b.get(j).is_some_and(u8::is_ascii_digit) {
j += 1;
if start == j {
return Err(PathError::new("parsing.int.expected", vec![], start));
}
let n: i32 = text[start..j].parse().map_err(|_| {
PathError::new(
@@ -724,6 +797,13 @@ fn read_quoted(text: &str, i: usize) -> Result<(String, usize), PathError> {
let mut escaped = false;
for (o, c) in text[i + 1..].char_indices() {
if escaped {
if c != quote && c != '\\' {
return Err(PathError::new(
"parsing.quote.escape",
vec![c.to_string()],
i + 1 + o,
));
}
out.push(c);
escaped = false;
} else if c == '\\' {
@@ -772,6 +852,59 @@ fn parse_compound(text: &str, i: usize) -> Result<(Tag, usize), PathError> {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quoted_escapes_and_index_tokens_match_brigadier() {
let cases = [
(r#""a\q""#, "parsing.quote.escape", vec!["q"], 3),
(r#"'a\x'"#, "parsing.quote.escape", vec!["x"], 3),
("[1.0]", "parsing.int.invalid", vec!["1.0"], 1),
("[.]", "parsing.int.invalid", vec!["."], 1),
("[--1]", "parsing.int.invalid", vec!["--1"], 1),
("[2147483648]", "parsing.int.invalid", vec!["2147483648"], 1),
("[+1]", "parsing.int.expected", vec![], 1),
(r#""é\q""#, "parsing.quote.escape", vec!["q"], 4),
(r#""é"[1.0]"#, "parsing.int.invalid", vec!["1.0"], 5),
];
let mut failures = Vec::new();
for (text, key, args, cursor) in cases {
match NbtPath::parse_prefix(text) {
Err(error) if error.key == key && error.args == args && error.cursor == cursor => {}
other => failures.push(format!("{text:?}: {other:?}")),
}
}
assert!(failures.is_empty(), "{}", failures.join("\n"));
}
#[test]
fn valid_quoted_escapes_still_select_the_exact_field() {
let root = Tag::compound([("a\"b", Tag::Int(7)), ("a\\b", Tag::Int(8))]);
for (path, value) in [(r#""a\"b""#, 7), (r#""a\\b""#, 8)] {
assert_eq!(
NbtPath::parse(path).unwrap().get(&root).unwrap(),
vec![Tag::Int(value)]
);
}
}
#[test]
fn field_provenance_handles_quotes_and_filters_without_matching_lists() {
for text in [
"blockState.properties",
"\"blockState\".\"properties\"",
"{source:1b}.blockState.properties",
"blockState{id:\"minecraft:piston_head\"}.properties",
] {
let path = NbtPath::parse(text).unwrap();
assert!(path.is_field_path(&["blockState", "properties"]));
assert!(!path.is_field_path(&["blockState"]));
}
assert!(NbtPath::parse("{source:1b}").unwrap().is_field_path(&[]));
assert!(
!NbtPath::parse("blockState[].properties")
.unwrap()
.is_field_path(&["blockState", "properties"])
);
}
fn tag(s: &str) -> Tag {
parse_snbt(s).unwrap().0
@@ -887,6 +1020,100 @@ mod tests {
));
}
#[test]
fn numeric_array_edits_use_the_destination_numeric_accessor() {
for (initial, value, inserted, indexed, all) in [
(
"{v:[B;1b,2b]}",
"-3.5f",
"{v:[B;1b,-4b,2b]}",
"{v:[B;-4b,2b]}",
"{v:[B;-4b,-4b]}",
),
(
"{v:[B;1b,2b]}",
"-3.5d",
"{v:[B;1b,-4b,2b]}",
"{v:[B;-4b,2b]}",
"{v:[B;-4b,-4b]}",
),
(
"{v:[I;1,2]}",
"-3.5f",
"{v:[I;1,-4,2]}",
"{v:[I;-4,2]}",
"{v:[I;-4,-4]}",
),
(
"{v:[I;1,2]}",
"-3.5d",
"{v:[I;1,-4,2]}",
"{v:[I;-4,2]}",
"{v:[I;-4,-4]}",
),
(
"{v:[L;1L,2L]}",
"-3.5f",
"{v:[L;1L,-3L,2L]}",
"{v:[L;-3L,2L]}",
"{v:[L;-3L,-3L]}",
),
(
"{v:[L;1L,2L]}",
"-3.5d",
"{v:[L;1L,-4L,2L]}",
"{v:[L;-4L,2L]}",
"{v:[L;-4L,-4L]}",
),
] {
let value = tag(value);
let mut root = tag(initial);
assert_eq!(
path("v")
.insert(1, &mut root, std::slice::from_ref(&value))
.unwrap(),
1
);
assert_eq!(root, tag(inserted));
let mut root = tag(initial);
assert_eq!(path("v[0]").set(&mut root, &value).unwrap(), 1);
assert_eq!(root, tag(indexed));
let mut root = tag(initial);
assert_eq!(path("v[]").set(&mut root, &value).unwrap(), 2);
assert_eq!(root, tag(all));
}
}
#[test]
fn numeric_arrays_reject_nonnumeric_insert_and_indexed_set() {
for initial in ["{v:[B;1b,2b]}", "{v:[I;1,2]}", "{v:[L;1L,2L]}"] {
let original = tag(initial);
let value = tag("\"bad\"");
for index in [0, 99, -99] {
let mut root = original.clone();
assert_eq!(
path("v")
.insert(index, &mut root, std::slice::from_ref(&value))
.unwrap(),
0
);
assert_eq!(root, original);
}
let mut root = original.clone();
assert_eq!(path("v[0]").set(&mut root, &value).unwrap(), 0);
assert_eq!(root, original);
}
}
#[test]
fn empty_array_wildcard_set_preserves_vanillas_count_on_rejection() {
for initial in ["{v:[B;]}", "{v:[I;]}", "{v:[L;]}"] {
let mut root = tag(initial);
assert_eq!(path("v[]").set(&mut root, &tag("\"bad\"")).unwrap(), 1);
assert_eq!(root, tag(initial));
}
}
#[test]
fn partial_comparison() {
let actual = tag("{a:1,b:{c:[1,2,3],d:\"x\"},e:2}");
@@ -173,6 +173,16 @@ impl Registries {
fill(&mut self.templates, "templates");
fill(&mut self.persistent_components, "persistent_components");
fill(&mut self.learnable_recipes, "learnable_recipes");
if self.summonable.is_empty() {
self.summonable = strings(&r["summonable"]);
self.summonable.sort();
self.summonable.dedup();
}
if self.entity_types.is_empty() {
self.entity_types = strings(&r["entity_types"]);
self.entity_types.sort();
self.entity_types.dedup();
}
const RELOADED: [&str; 5] = [
"minecraft:advancement",
"minecraft:recipe",
@@ -236,6 +246,16 @@ impl Registries {
fill(&mut self.templates, &e.templates);
fill(&mut self.persistent_components, &e.persistent_components);
fill(&mut self.learnable_recipes, &e.learnable_recipes);
if self.summonable.is_empty() {
self.summonable.clone_from(&e.summonable);
self.summonable.sort();
self.summonable.dedup();
}
if self.entity_types.is_empty() {
self.entity_types.clone_from(&e.entity_types);
self.entity_types.sort();
self.entity_types.dedup();
}
for (name, ids) in &e.by_registry {
if !self.by_registry.contains_key(name) {
self.by_registry.insert(name.clone(), ids.clone());
@@ -964,6 +964,10 @@ impl Scoreboard {
pub fn tracked(&self) -> Vec<String> {
self.holder_order.stream().map(str::to_owned).collect()
}
/// Every holder on a team (`teamsByPlayer.keySet()`).
pub fn team_members(&self) -> impl Iterator<Item = &str> {
self.team_of.keys().map(String::as_str)
}
// teams
pub fn team(&self, name: &str) -> Option<&Team> {
+115 -13
View File
@@ -8,11 +8,20 @@ pub use horizon_world::snbt_text::{java_double, java_float, quote_and_escape, to
// ---- the coloured form -----------------------------------------------------
/// Writer provenance for a live moving-piston command read, or its selected subtree.
#[derive(Clone, Copy)]
pub enum PistonPart {
Entity,
State,
Properties,
}
struct Pretty {
indentation: String,
result: Text,
indent_depth: usize,
depth: usize,
piston: Option<PistonPart>,
}
fn is_numeric(t: &Tag) -> bool {
matches!(
@@ -58,6 +67,10 @@ impl Pretty {
self.lit("]", None);
}
fn sub(&mut self, tag: &Tag, indent: bool) {
self.sub_with(tag, indent, None);
}
fn sub_with(&mut self, tag: &Tag, indent: bool, piston: Option<PistonPart>) {
let previous = std::mem::replace(&mut self.piston, piston);
if indent {
self.indent_depth += 1;
}
@@ -67,6 +80,7 @@ impl Pretty {
self.indent_depth -= 1;
}
self.depth -= 1;
self.piston = previous;
}
fn visit(&mut self, tag: &Tag) {
match tag {
@@ -157,7 +171,11 @@ impl Pretty {
"\n"
};
let n = c.len();
for (i, (k, v)) in java_hash_order(c).into_iter().enumerate() {
let entries = match self.piston {
Some(part) => piston_hash_order(c, part),
None => java_hash_order(c),
};
for (i, (k, v)) in entries.into_iter().enumerate() {
self.lit(&entry, None);
let key = string_of(k);
if !key.is_empty()
@@ -171,7 +189,12 @@ impl Pretty {
}
self.lit(":", None);
self.lit(" ", None);
self.sub(v, true);
let child = match (self.piston, key.as_str()) {
(Some(PistonPart::Entity), "blockState") => Some(PistonPart::State),
(Some(PistonPart::State), "properties") => Some(PistonPart::Properties),
_ => None,
};
self.sub_with(v, true, child);
if i + 1 < n {
self.lit(",", None);
self.lit(spacing, None);
@@ -186,22 +209,61 @@ impl Pretty {
}
}
}
fn hash_bucket(len: usize, key: &horizon_world::nbt::JavaString) -> usize {
let mut capacity = 16usize;
while len > capacity * 3 / 4 {
capacity *= 2;
}
let h = string_of(key)
.encode_utf16()
.fold(0u32, |h, ch| h.wrapping_mul(31).wrapping_add(u32::from(ch)));
((h ^ (h >> 16)) as usize) & (capacity - 1)
}
fn piston_hash_order(
c: &horizon_world::nbt::Compound,
part: PistonPart,
) -> Vec<(&horizon_world::nbt::JavaString, &Tag)> {
let mut entries: Vec<_> = c.iter().collect();
match part {
PistonPart::Entity => {
// saveAdditional, then the base's components and saveMetadata.
let fields = [
"blockState",
"facing",
"progress",
"extending",
"source",
"components",
"id",
"x",
"y",
"z",
];
entries.sort_by_key(|(k, _)| {
(
hash_bucket(c.len(), k),
fields
.iter()
.position(|s| *s == string_of(k))
.unwrap_or(fields.len()),
)
});
}
PistonPart::Properties => {
// StateDefinition builds property codecs in sorted-name order.
// DFU's nested map-pair encoder writes the second codec first.
entries.sort_by_key(|(k, _)| (hash_bucket(c.len(), k), std::cmp::Reverse(*k)));
}
PistonPart::State => return java_hash_order(c),
}
entries
}
/// The iteration order of the `HashMap<String, Tag>` a `CompoundTag` holds,
/// for a map that grew to its size by insertion: buckets by
/// `HashMap.hash(String.hashCode())`, keys sharing a bucket in key order.
pub fn java_hash_order(
c: &horizon_world::nbt::Compound,
) -> Vec<(&horizon_world::nbt::JavaString, &Tag)> {
let mut capacity = 16usize;
while c.len() > capacity * 3 / 4 {
capacity *= 2;
}
let bucket = |k: &horizon_world::nbt::JavaString| {
let h = string_of(k)
.encode_utf16()
.fold(0u32, |h, ch| h.wrapping_mul(31).wrapping_add(u32::from(ch)));
((h ^ (h >> 16)) as usize) & (capacity - 1)
};
// Keys sharing a bucket iterate in insertion order: the order vanilla's
// savers write them, as far as it is known here.
const WRITE_ORDER: &[&str] = &[
@@ -268,7 +330,7 @@ pub fn java_hash_order(
.unwrap_or(WRITE_ORDER.len())
};
let mut entries: Vec<_> = c.iter().collect();
entries.sort_by_key(|(k, _)| (bucket(k), rank(k)));
entries.sort_by_key(|(k, _)| (hash_bucket(c.len(), k), rank(k)));
entries
}
/// `NbtUtils.toPrettyComponent`: the coloured, unindented form.
@@ -281,6 +343,19 @@ pub fn pretty_indented(tag: &Tag, indentation: &str) -> Text {
result: Text::empty(),
indent_depth: 0,
depth: 0,
piston: None,
};
p.visit(tag);
p.result
}
/// Command-only writer order; ordinary pretty printing keeps its existing policy.
pub fn pretty_piston(tag: &Tag, part: PistonPart) -> Text {
let mut p = Pretty {
indentation: String::new(),
result: Text::empty(),
indent_depth: 0,
depth: 0,
piston: Some(part),
};
p.visit(tag);
p.result
@@ -295,6 +370,33 @@ mod tests {
to_snbt(&parse_snbt(s).unwrap().0)
}
#[test]
fn live_piston_writer_order_does_not_change_default_pretty() {
let tag = parse_snbt(
r#"{id:"minecraft:piston",blockState:{id:"minecraft:piston_head",properties:{facing:"east",short:"false",type:"normal"}},components:{},x:1,y:100,z:0,facing:5b,progress:0.0f,source:1b,extending:1b}"#,
).unwrap().0;
let ordinary = r#"{blockState: {id: "minecraft:piston_head", properties: {facing: "east", short: "false", type: "normal"}}, components: {}, x: 1, facing: 5b, progress: 0.0f, y: 100, z: 0, id: "minecraft:piston", source: 1b, extending: 1b}"#;
assert_eq!(pretty(&tag).plain(), ordinary);
assert_eq!(
pretty_piston(&tag, PistonPart::Entity).plain(),
r#"{blockState: {id: "minecraft:piston_head", properties: {short: "false", facing: "east", type: "normal"}}, components: {}, x: 1, facing: 5b, progress: 0.0f, y: 100, z: 0, source: 1b, id: "minecraft:piston", extending: 1b}"#,
);
let state = tag.get("blockState").unwrap();
assert_eq!(
pretty_piston(state, PistonPart::State).plain(),
r#"{id: "minecraft:piston_head", properties: {short: "false", facing: "east", type: "normal"}}"#,
);
let props = state.get("properties").unwrap();
assert_eq!(
pretty_piston(props, PistonPart::Properties).plain(),
r#"{short: "false", facing: "east", type: "normal"}"#,
);
assert_eq!(
pretty(props).plain(),
r#"{facing: "east", short: "false", type: "normal"}"#
);
assert_eq!(pretty(&tag).plain(), ordinary);
}
#[test]
fn compact_form_matches_vanilla() {
assert_eq!(
rt("{b:1b,a:\"x\",c:[1,2],d:1.5f,e:2d,f:3L,g:4s}"),
+146
View File
@@ -0,0 +1,146 @@
//! Every input the game names: vanilla's key mappings (`Options.keyMappings`) and Horizon's
//! pad-only actions. Held state is asked by [`Action`] ([`crate::held::HeldKeys::held`]), not by a
//! key: code that wants a mapping says which one, and every device that can hold it counts.
use winit::keyboard::KeyCode;
macro_rules! actions {
($($variant:ident, $name:literal, $key:expr;)*) => {
/// A key mapping, or a pad-only action.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Action {
$($variant,)*
}
impl Action {
/// In `Options.keyMappings` order, then Horizon's own mappings, the pad-only actions last.
pub const ALL: &[Action] = &[$(Action::$variant,)*];
/// The saved name: vanilla's for a mapping, `pad.*` for Horizon's own.
pub fn name(self) -> &'static str {
match self {
$(Action::$variant => $name,)*
}
}
/// The default key the game's held state keeps this action under. `None` for what
/// isn't held as a key there: a mouse mapping, one with no default, a pad-only action,
/// and the debug keys, which act on the physical key (`debug_keys.rs`) and whose
/// defaults are other mappings' keys.
pub fn key(self) -> Option<KeyCode> {
match self {
$(Action::$variant => $key,)*
}
}
}
};
}
actions! {
Attack, "key.attack", None;
Use, "key.use", None;
Forward, "key.forward", Some(KeyCode::KeyW);
Left, "key.left", Some(KeyCode::KeyA);
Back, "key.back", Some(KeyCode::KeyS);
Right, "key.right", Some(KeyCode::KeyD);
Jump, "key.jump", Some(KeyCode::Space);
Sneak, "key.sneak", Some(KeyCode::ShiftLeft);
Sprint, "key.sprint", Some(KeyCode::ControlLeft);
Drop, "key.drop", Some(KeyCode::KeyQ);
Inventory, "key.inventory", Some(KeyCode::KeyE);
Chat, "key.chat", Some(KeyCode::KeyT);
Playerlist, "key.playerlist", Some(KeyCode::Tab);
PickItem, "key.pickItem", None;
Command, "key.command", Some(KeyCode::Slash);
Friends, "key.friends", Some(KeyCode::KeyO);
SocialInteractions, "key.socialInteractions", Some(KeyCode::KeyP);
ToggleGui, "key.toggleGui", Some(KeyCode::F1);
ToggleSpectatorShaderEffects, "key.toggleSpectatorShaderEffects", Some(KeyCode::F4);
Screenshot, "key.screenshot", Some(KeyCode::F2);
TogglePerspective, "key.togglePerspective", Some(KeyCode::F5);
SmoothCamera, "key.smoothCamera", None;
Fullscreen, "key.fullscreen", Some(KeyCode::F11);
SpectatorOutlines, "key.spectatorOutlines", None;
SpectatorHotbar, "key.spectatorHotbar", None;
SwapOffhand, "key.swapOffhand", Some(KeyCode::KeyF);
SaveToolbarActivator, "key.saveToolbarActivator", Some(KeyCode::KeyC);
LoadToolbarActivator, "key.loadToolbarActivator", Some(KeyCode::KeyX);
Advancements, "key.advancements", Some(KeyCode::KeyL);
QuickActions, "key.quickActions", Some(KeyCode::KeyG);
DebugOverlay, "key.debug.overlay", None;
DebugModifier, "key.debug.modifier", None;
Hotbar1, "key.hotbar.1", Some(KeyCode::Digit1);
Hotbar2, "key.hotbar.2", Some(KeyCode::Digit2);
Hotbar3, "key.hotbar.3", Some(KeyCode::Digit3);
Hotbar4, "key.hotbar.4", Some(KeyCode::Digit4);
Hotbar5, "key.hotbar.5", Some(KeyCode::Digit5);
Hotbar6, "key.hotbar.6", Some(KeyCode::Digit6);
Hotbar7, "key.hotbar.7", Some(KeyCode::Digit7);
Hotbar8, "key.hotbar.8", Some(KeyCode::Digit8);
Hotbar9, "key.hotbar.9", Some(KeyCode::Digit9);
DebugReloadChunk, "key.debug.reloadChunk", None;
DebugShowHitboxes, "key.debug.showHitboxes", None;
DebugClearChat, "key.debug.clearChat", None;
DebugCrash, "key.debug.crash", None;
DebugShowChunkBorders, "key.debug.showChunkBorders", None;
DebugShowAdvancedTooltips, "key.debug.showAdvancedTooltips", None;
DebugCopyRecreateCommand, "key.debug.copyRecreateCommand", None;
DebugSpectate, "key.debug.spectate", None;
DebugSwitchGameMode, "key.debug.switchGameMode", None;
DebugDebugOptions, "key.debug.debugOptions", None;
DebugFocusPause, "key.debug.focusPause", None;
DebugDumpDynamicTextures, "key.debug.dumpDynamicTextures", None;
DebugReloadResourcePacks, "key.debug.reloadResourcePacks", None;
DebugProfiling, "key.debug.profiling", None;
DebugCopyLocation, "key.debug.copyLocation", None;
DebugDumpVersion, "key.debug.dumpVersion", None;
DebugProfilingChart, "key.debug.profilingChart", None;
DebugFpsCharts, "key.debug.fpsCharts", None;
DebugNetworkCharts, "key.debug.networkCharts", None;
DebugLightmapTexture, "key.debug.lightmapTexture", None;
DebugImprovedTransparency, "key.debug.improvedTransparency", None;
Emote, "key.emote", Some(KeyCode::KeyB);
HotbarPrevious, "pad.hotbarPrevious", None;
HotbarNext, "pad.hotbarNext", None;
}
impl Action {
pub fn parse(name: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|a| a.name() == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keybinds::{self, HORIZON_MAPPINGS, KeyBinds, MAPPINGS, all_mappings};
#[test]
fn every_mapping_is_an_action_in_order() {
let names: Vec<&str> = Action::ALL.iter().map(|a| a.name()).collect();
let mappings: Vec<&str> = MAPPINGS.iter().map(|m| m.0).collect();
assert_eq!(names[..mappings.len()], mappings[..]);
let horizon: Vec<&str> = HORIZON_MAPPINGS.iter().map(|m| m.0).collect();
let end = mappings.len() + horizon.len();
assert_eq!(names[mappings.len()..end], horizon[..]);
for action in &names[end..] {
assert!(action.starts_with("pad."), "{action}");
}
}
#[test]
fn an_actions_key_is_its_mappings_default_key() {
for (name, _, category) in all_mappings() {
let action = Action::parse(name).unwrap();
let want = if *category == "debug" {
None
} else {
keybinds::key_code(KeyBinds::default_of(name))
};
assert_eq!(action.key(), want, "{name}");
}
}
#[test]
fn every_bindable_action_is_an_action() {
for (name, _) in crate::gamepad::ACTIONS {
assert!(Action::parse(name).is_some(), "{name}");
}
}
}
File diff suppressed because it is too large. Load diff
+182
View File
@@ -0,0 +1,182 @@
//! The keys a player's mappings hold, as their vanilla default keys, and which
//! devices hold each one. A key stays held while any device holds it, so one
//! device letting go (splitscreen moving the keyboard to another player, say)
//! can't release what another still presses.
use crate::actions::Action;
use std::collections::HashMap;
use winit::keyboard::KeyCode;
/// Where a press came from.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Source {
/// The keyboard and mouse.
Keyboard,
Pad,
/// `horizon-ctl`'s `key` and `mouse` and the agent commands that walk and click.
Agent,
}
impl Source {
fn bit(self) -> u8 {
match self {
Source::Keyboard => 1,
Source::Pad => 2,
Source::Agent => 4,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HeldKeys {
held: HashMap<KeyCode, u8>,
}
impl HeldKeys {
/// Whether anything holds this mapping: every device, whatever the mapping is bound to.
pub fn held(&self, action: Action) -> bool {
action.key().is_some_and(|key| self.raw(&key))
}
/// Whether a key that is no mapping's default is held (Right Shift, the arrows, Alt, the system
/// key), or the key a caller already holds as a key: most code wants [`HeldKeys::held`].
pub fn raw(&self, key: &KeyCode) -> bool {
self.held.contains_key(key)
}
/// The old way of asking, by a mapping's default key; clippy refuses it (`clippy.toml`). It
/// stays so that a change written against it says what to use instead.
pub fn contains(&self, key: &KeyCode) -> bool {
self.raw(key)
}
/// `source` holds `key`. True when nothing held it before.
pub fn press(&mut self, key: KeyCode, source: Source) -> bool {
let sources = self.held.entry(key).or_insert(0);
let new = *sources == 0;
*sources |= source.bit();
new
}
/// `source` lets go of `key`. True when nothing holds it any more.
pub fn release(&mut self, key: &KeyCode, source: Source) -> bool {
let Some(sources) = self.held.get_mut(key) else {
return true;
};
*sources &= !source.bit();
if *sources == 0 {
self.held.remove(key);
true
} else {
false
}
}
pub fn held_by(&self, key: &KeyCode, source: Source) -> bool {
self.held
.get(key)
.is_some_and(|sources| sources & source.bit() != 0)
}
/// `source` lets go of everything: the keys nothing holds any more.
pub fn release_source(&mut self, source: Source) -> Vec<KeyCode> {
let keys: Vec<KeyCode> = self
.held
.iter()
.filter(|(_, sources)| *sources & source.bit() != 0)
.map(|(key, _)| *key)
.collect();
keys.into_iter()
.filter(|key| self.release(key, source))
.collect()
}
/// Held, by the keyboard unless a device already holds it: for callers
/// that don't say where the press came from.
pub fn insert(&mut self, key: KeyCode) -> bool {
if self.raw(&key) {
return false;
}
self.press(key, Source::Keyboard)
}
/// Let go of `key` whoever holds it.
pub fn remove(&mut self, key: &KeyCode) -> bool {
self.held.remove(key).is_some()
}
pub fn clear(&mut self) {
self.held.clear();
}
pub fn is_empty(&self) -> bool {
self.held.is_empty()
}
pub fn len(&self) -> usize {
self.held.len()
}
pub fn iter(&self) -> impl Iterator<Item = &KeyCode> {
self.held.keys()
}
}
impl IntoIterator for HeldKeys {
type Item = KeyCode;
type IntoIter = std::collections::hash_map::IntoKeys<KeyCode, u8>;
fn into_iter(self) -> Self::IntoIter {
self.held.into_keys()
}
}
impl<'a> IntoIterator for &'a HeldKeys {
type Item = &'a KeyCode;
type IntoIter = std::collections::hash_map::Keys<'a, KeyCode, u8>;
fn into_iter(self) -> Self::IntoIter {
self.held.keys()
}
}
impl FromIterator<KeyCode> for HeldKeys {
fn from_iter<I: IntoIterator<Item = KeyCode>>(keys: I) -> Self {
let mut held = HeldKeys::default();
for key in keys {
held.insert(key);
}
held
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_key_stays_held_while_any_device_holds_it() {
let mut keys = HeldKeys::default();
assert!(keys.press(KeyCode::Space, Source::Pad));
assert!(!keys.press(KeyCode::Space, Source::Keyboard));
assert!(keys.press(KeyCode::KeyW, Source::Keyboard));
assert_eq!(keys.release_source(Source::Keyboard), vec![KeyCode::KeyW]);
assert!(keys.held(Action::Jump) && !keys.held(Action::Forward));
assert!(keys.held_by(&KeyCode::Space, Source::Pad));
assert!(!keys.held_by(&KeyCode::Space, Source::Keyboard));
assert!(keys.release(&KeyCode::Space, Source::Pad));
assert!(keys.is_empty());
}
#[test]
fn a_script_holds_a_key_by_itself() {
let mut keys = HeldKeys::default();
keys.press(KeyCode::KeyW, Source::Agent);
keys.press(KeyCode::KeyW, Source::Keyboard);
assert!(!keys.release(&KeyCode::KeyW, Source::Keyboard));
assert!(keys.held(Action::Forward));
assert!(keys.release_source(Source::Pad).is_empty());
assert_eq!(keys.release_source(Source::Agent), vec![KeyCode::KeyW]);
assert!(!keys.held(Action::Forward));
}
#[test]
fn untagged_inserts_and_removes_act_as_before() {
let mut keys = HeldKeys::default();
keys.press(KeyCode::ShiftLeft, Source::Pad);
assert!(!keys.insert(KeyCode::ShiftLeft), "already held");
assert!(!keys.held_by(&KeyCode::ShiftLeft, Source::Keyboard));
assert!(
keys.remove(&KeyCode::ShiftLeft),
"remove lets go for everyone"
);
assert!(!keys.held(Action::Sneak));
assert!(keys.release(&KeyCode::KeyA, Source::Keyboard), "never held");
}
}
@@ -51,11 +51,6 @@ impl InteractionInput {
pub fn press(&mut self, button: Button) {
self.clicks[button as usize] += 1;
}
/// Whether a click waits for the next tick.
pub fn has_clicks(&self) -> bool {
self.clicks.iter().any(|&c| c > 0)
}
pub fn tick(&mut self, world: &mut impl InteractionWorld, attack_down: bool, use_down: bool) {
let [attack, use_item, pick] = std::mem::take(&mut self.clicks);
world.pick_hit_result();
+59 -14
View File
@@ -74,6 +74,17 @@ pub const MAPPINGS: &[(&str, &str, &str)] = &[
("key.debug.lightmapTexture", "key.keyboard.4", "debug"),
("key.debug.improvedTransparency", "key.keyboard.x", "debug"),
];
/// Horizon's own key mappings: bound in Controls like the vanilla ones, but kept in
/// `horizon-extras.txt` (`emoteKey`), never in `options.txt`, which stays exactly vanilla's
/// (`MAPPINGS` is what the options parity harness compares with the pinned client).
pub const HORIZON_MAPPINGS: &[(&str, &str, &str)] = &[
// The emote wheel (docs/cosmetics.md), on a key vanilla leaves free.
("key.emote", "key.keyboard.b", "multiplayer"),
];
/// Every mapping the game binds: vanilla's in `Options.keyMappings` order, then Horizon's.
pub fn all_mappings() -> impl Iterator<Item = &'static (&'static str, &'static str, &'static str)> {
MAPPINGS.iter().chain(HORIZON_MAPPINGS)
}
/// Mappings Options builds as `ToggleKeyMapping` (hold or toggle by option).
pub fn is_toggle(name: &str) -> bool {
matches!(name, "key.sneak" | "key.sprint" | "key.use" | "key.attack")
@@ -312,8 +323,7 @@ pub struct KeyBinds {
impl Default for KeyBinds {
fn default() -> Self {
Self {
keys: MAPPINGS
.iter()
keys: all_mappings()
.map(|(n, d, _)| (*n, (*d).to_owned()))
.collect(),
}
@@ -339,8 +349,7 @@ impl KeyBinds {
.map_or("key.keyboard.unknown", String::as_str)
}
pub fn default_of(name: &str) -> &'static str {
MAPPINGS
.iter()
all_mappings()
.find(|m| m.0 == name)
.map_or("key.keyboard.unknown", |m| m.1)
}
@@ -348,16 +357,24 @@ impl KeyBinds {
self.get(name) == Self::default_of(name)
}
pub fn set(&mut self, name: &str, key: &str) {
if let Some((n, _, _)) = MAPPINGS.iter().find(|m| m.0 == name) {
if let Some((n, _, _)) = all_mappings().find(|m| m.0 == name) {
self.keys.insert(n, key.to_owned());
}
}
/// The bindings `options.txt` holds: vanilla's mappings only.
pub fn saved(&self) -> Vec<(&'static str, String)> {
MAPPINGS
.iter()
.map(|(n, _, _)| (*n, self.get(n).to_owned()))
.collect()
}
/// Horizon's own bindings, for `horizon-extras.txt`.
pub fn horizon_saved(&self) -> Vec<(&'static str, String)> {
HORIZON_MAPPINGS
.iter()
.map(|(n, _, _)| (*n, self.get(n).to_owned()))
.collect()
}
pub fn reset_all(&mut self) {
*self = Self::default();
}
@@ -367,8 +384,7 @@ impl KeyBinds {
if key == "key.keyboard.unknown" {
return Vec::new();
}
MAPPINGS
.iter()
all_mappings()
.filter(|(n, _, _)| {
*n != name && self.get(n) == key && (!self.is_default(n) || !self.is_default(name))
})
@@ -382,8 +398,7 @@ impl KeyBinds {
/// Every mapping a physical input triggers, in list order: like
/// `KeyMapping.set`/`click`, one key drives all the mappings bound to it.
pub fn mappings_for<'a>(&'a self, key: &'a str) -> impl Iterator<Item = &'static str> + 'a {
MAPPINGS
.iter()
all_mappings()
.map(|m| m.0)
.filter(move |n| self.get(n) == key)
}
@@ -393,8 +408,7 @@ impl KeyBinds {
/// key and the binding itself); their defaults (A, S, D, T, L, 1 to 4...)
/// would otherwise turn the key back into its old gameplay meaning.
fn translated_mappings<'a>(&'a self, key: &'a str) -> impl Iterator<Item = &'static str> + 'a {
MAPPINGS
.iter()
all_mappings()
.filter(|m| m.2 != "debug")
.map(|m| m.0)
.filter(move |n| self.get(n) == key)
@@ -402,9 +416,7 @@ impl KeyBinds {
/// Whether a mapping (other than a debug key) whose default is `key` was
/// bound elsewhere.
fn default_moved(&self, key: &str) -> bool {
MAPPINGS
.iter()
.any(|(n, d, c)| *d == key && *c != "debug" && !self.is_default(n))
all_mappings().any(|(n, d, c)| *d == key && *c != "debug" && !self.is_default(n))
}
/// Translate a physical key into the default key of the mapping it is
/// bound to, so game code written against vanilla defaults follows the
@@ -440,6 +452,18 @@ impl KeyBinds {
}
vec![Ok(key)]
}
/// `Minecraft.handleKeybinds`: key.pickItem and key.spectatorHotbar share the middle button by
/// default and are separate mappings, so a press of what `input` is bound to acts only as the
/// one this player can use: the spectator hotbar for a spectator, pick block otherwise. (Pick
/// block does nothing a spectator would see.)
pub fn middle_acts(&self, input: &str, spectator: bool) -> bool {
let mapping = if spectator {
"key.spectatorHotbar"
} else {
"key.pickItem"
};
self.get(mapping) == input
}
/// Translate a mouse button the same way (keys bound to mouse buttons).
pub fn translate_mouse(&self, button: MouseButton) -> Option<Result<KeyCode, MouseButton>> {
self.translate_mouse_all(button).into_iter().next()
@@ -503,6 +527,27 @@ impl KeyBinds {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mappings_sharing_the_middle_button_act_as_themselves() {
let middle = "key.mouse.middle";
let z = "key.keyboard.z";
let mut keys = KeyBinds::default();
assert!(keys.middle_acts(middle, false) && keys.middle_acts(middle, true));
// The spectator hotbar on Z: the middle button only picks, and Z never picks.
keys.set("key.spectatorHotbar", z);
assert!(keys.middle_acts(middle, false));
assert!(!keys.middle_acts(middle, true));
assert!(keys.middle_acts(z, true));
assert!(!keys.middle_acts(z, false));
// Pick block on Z instead: the other way round.
keys.reset_all();
keys.set("key.pickItem", z);
assert!(keys.middle_acts(z, false));
assert!(!keys.middle_acts(z, true));
assert!(keys.middle_acts(middle, true));
assert!(!keys.middle_acts(middle, false));
}
#[test]
fn container_mouse_hotkeys_follow_slot_bindings_and_pick_item_priority() {
for button in [MouseButton::Back, MouseButton::Forward, MouseButton::Middle] {
+3
View File
@@ -3,8 +3,10 @@
//! options. horizon-client re-exports each module under its old path
//! (`horizon_client::keybinds`, ...).
#![forbid(unsafe_code)]
pub mod actions;
pub mod clipboard;
pub mod gamepad;
pub mod held;
pub mod input;
pub mod interaction_input;
pub mod key_names;
@@ -12,4 +14,5 @@ pub mod keybinds;
pub mod keymapping;
pub mod modifiers;
pub mod mouse;
pub mod snap;
pub mod window;
+202
View File
@@ -0,0 +1,202 @@
//! Where the controller's menu cursor settles once the stick is let go, and
//! how it gets there. Every rule's number lives in [`Tuning`], so the feel can
//! be adjusted in one place.
/// The snapping engine's knobs, in GUI pixels and seconds.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tuning {
/// A let-go cursor settles on a target this close to it.
pub radius: f32,
/// How long the glide onto a target takes.
pub glide_seconds: f32,
/// Movement shorter than this is a wobble: the cursor stays where it was.
pub min_travel: f32,
/// A flick that ends still nearest its starting target commits to the
/// neighbour it was heading for once it covered this fraction of the way.
pub commit: f32,
/// How far off the flick's direction a neighbour may lie (cosine of the
/// angle: 0.7 is about 45 degrees).
pub cone: f32,
/// The pointer's speed over a slot or control, as a fraction of its
/// speed elsewhere (1 leaves it unchanged).
pub over_target_speed: f32,
}
pub const TUNING: Tuning = Tuning {
radius: 16.0,
glide_seconds: 0.025,
min_travel: 1.5,
commit: 0.15,
cone: 0.7,
over_target_speed: 1.0,
};
type Point = (f32, f32);
fn distance(a: Point, b: Point) -> f32 {
((a.0 - b.0).powi(2) + (a.1 - b.1).powi(2)).sqrt()
}
/// The target nearest `at`, if one lies within `radius`.
pub fn nearest(at: Point, targets: &[Point], radius: f32) -> Option<usize> {
targets
.iter()
.enumerate()
.map(|(i, t)| (i, distance(at, *t)))
.filter(|(_, d)| *d <= radius)
.min_by(|a, b| a.1.total_cmp(&b.1))
.map(|(i, _)| i)
}
/// Where a stick movement from `start` to `end` settles among `targets`
/// (their centres), or `None` to leave the cursor where it is.
pub fn settle(start: Point, end: Point, targets: &[Point], tuning: &Tuning) -> Option<usize> {
let near = nearest(end, targets, tuning.radius);
let travel = (end.0 - start.0, end.1 - start.1);
let length = distance(start, end);
if length < tuning.min_travel {
return near;
}
let origin = nearest(start, targets, tuning.radius);
if near != origin {
return near;
}
// Still nearest where it started: a quick flick toward a neighbour.
let Some(o) = origin else {
return near;
};
let from = targets[o];
let heading = (travel.0 / length, travel.1 / length);
let next = targets
.iter()
.enumerate()
.filter(|(i, _)| *i != o)
.filter_map(|(i, t)| {
let to = (t.0 - from.0, t.1 - from.1);
let gap = distance(from, *t);
let aligned = (to.0 * heading.0 + to.1 * heading.1) / gap;
(gap > 0.0 && aligned >= tuning.cone).then_some((i, gap, to))
})
.min_by(|a, b| a.1.total_cmp(&b.1));
match next {
Some((i, gap, to)) => {
let progress = ((end.0 - from.0) * to.0 + (end.1 - from.1) * to.1) / (gap * gap);
if progress >= tuning.commit {
Some(i)
} else {
near
}
}
None => near,
}
}
/// Where a cursor left on a target goes once the targets moved: with most of
/// them if they moved together (a recipe book opened), else to the nearest
/// one (a creative tab with other slots), so it never rests between targets.
/// `None` when it wasn't on a target, or one is still there.
pub fn follow(at: Point, before: &[Point], after: &[Point]) -> Option<usize> {
const SAME: f32 = 0.5;
let on = before.iter().find(|t| distance(at, **t) <= SAME)?;
if after.iter().any(|t| distance(at, *t) <= SAME) {
return None;
}
let carried = |by: Point| {
before
.iter()
.filter(|b| {
let moved = (b.0 + by.0, b.1 + by.1);
after.iter().any(|a| distance(moved, *a) <= SAME)
})
.count()
};
let together = after
.iter()
.enumerate()
.map(|(i, a)| (i, carried((a.0 - on.0, a.1 - on.1))))
.max_by_key(|&(_, count)| count)
.filter(|&(_, count)| count * 2 > before.len());
together
.map(|(i, _)| i)
.or_else(|| nearest(at, after, f32::INFINITY))
}
/// The cursor `seconds` into a glide from `from` to `to`: eased out, so it
/// slows as it lands, and exactly `to` once the glide is over.
pub fn glide(from: Point, to: Point, seconds: f32, tuning: &Tuning) -> Point {
let t = (seconds / tuning.glide_seconds).clamp(0.0, 1.0);
let eased = 1.0 - (1.0 - t).powi(3);
(
from.0 + (to.0 - from.0) * eased,
from.1 + (to.1 - from.1) * eased,
)
}
#[cfg(test)]
mod tests {
use super::*;
/// A row of inventory slots, 18 pixels apart, and the slot below the first.
const SLOTS: [Point; 4] = [(8.0, 8.0), (26.0, 8.0), (44.0, 8.0), (8.0, 26.0)];
#[test]
fn follow_goes_with_targets_that_moved_together() {
let moved = SLOTS.map(|s| (s.0 + 77.0, s.1));
assert_eq!(follow(SLOTS[1], &SLOTS, &moved), Some(1));
}
#[test]
fn follow_takes_the_nearest_of_other_targets() {
let other = [(30.0, 50.0), (200.0, 200.0)];
assert_eq!(follow(SLOTS[1], &SLOTS, &other), Some(0));
}
#[test]
fn follow_leaves_a_free_cursor_and_a_target_still_there() {
let other = [(30.0, 50.0)];
assert_eq!(follow((100.0, 100.0), &SLOTS, &other), None);
assert_eq!(follow(SLOTS[0], &SLOTS, &SLOTS), None);
assert_eq!(follow(SLOTS[0], &SLOTS, &[]), None);
}
#[test]
fn a_short_flick_moves_to_the_neighbour_it_heads_for() {
// 4 pixels right of slot 0: still nearest slot 0, but heading for slot 1.
assert_eq!(settle((8.0, 8.0), (12.0, 8.0), &SLOTS, &TUNING), Some(1));
assert_eq!(settle((8.0, 8.0), (8.0, 12.0), &SLOTS, &TUNING), Some(3));
assert_eq!(settle((26.0, 8.0), (22.0, 8.0), &SLOTS, &TUNING), Some(0));
}
#[test]
fn a_wobble_stays_put() {
assert_eq!(settle((8.0, 8.0), (8.5, 8.4), &SLOTS, &TUNING), Some(0));
// Barely started toward the neighbour: not committed.
assert_eq!(settle((8.0, 8.0), (9.6, 8.0), &SLOTS, &TUNING), Some(0));
}
#[test]
fn a_long_move_settles_on_the_nearest_target() {
assert_eq!(settle((8.0, 8.0), (42.0, 9.0), &SLOTS, &TUNING), Some(2));
// Far from everything: left where it is.
assert_eq!(settle((8.0, 8.0), (200.0, 200.0), &SLOTS, &TUNING), None);
}
#[test]
fn a_flick_off_any_row_does_not_jump_sideways() {
// Up from slot 0: nothing lies that way, so it stays on slot 0.
assert_eq!(settle((8.0, 8.0), (8.0, 4.0), &SLOTS, &TUNING), Some(0));
}
#[test]
fn the_glide_eases_onto_its_target_and_stops_there() {
let (from, to) = ((0.0, 0.0), (20.0, -10.0));
assert_eq!(glide(from, to, 0.0, &TUNING), from);
let half = glide(from, to, TUNING.glide_seconds / 2.0, &TUNING);
assert!(
half.0 > 10.0 && half.0 < 20.0,
"eased out: past halfway at half time"
);
assert_eq!(glide(from, to, TUNING.glide_seconds, &TUNING), to);
assert_eq!(glide(from, to, 1.0, &TUNING), to);
}
}
+52 -17
View File
@@ -393,19 +393,38 @@ impl FullscreenState {
let reapply = self.set_exclusive(exclusive);
self.set_preferred_mode(preferred);
let mode_change = !in_video_screen && self.take_mode_change();
match request {
Some(false) => window.set_fullscreen(None),
Some(true) => window.set_fullscreen(Some(fullscreen_request(
if wanted && window.is_maximized() {
window.set_maximized(false);
window.set_fullscreen(Some(fullscreen_request(
window,
exclusive,
preferred.as_ref(),
))),
None if reapply || mode_change => window.set_fullscreen(Some(fullscreen_request(
window,
exclusive,
preferred.as_ref(),
))),
None => {}
)));
} else {
match request {
Some(false) => window.set_fullscreen(None),
Some(true) => {
if window.is_maximized() {
window.set_maximized(false);
}
window.set_fullscreen(Some(fullscreen_request(
window,
exclusive,
preferred.as_ref(),
)));
}
None if reapply || mode_change => {
if window.is_maximized() {
window.set_maximized(false);
}
window.set_fullscreen(Some(fullscreen_request(
window,
exclusive,
preferred.as_ref(),
)));
}
None => {}
}
}
}
@@ -592,13 +611,13 @@ pub fn fullscreen_request(
preferred: Option<&VideoMode>,
) -> winit::window::Fullscreen {
use winit::window::Fullscreen;
if !exclusive {
return Fullscreen::Borderless(None);
}
let Some(monitor) = window
let monitor = window
.current_monitor()
.or_else(|| window.primary_monitor())
else {
.or_else(|| window.primary_monitor());
if !exclusive {
return Fullscreen::Borderless(monitor);
}
let Some(monitor) = monitor else {
return Fullscreen::Borderless(None);
};
let wanted = preferred.and_then(|p| {
@@ -608,7 +627,7 @@ pub fn fullscreen_request(
});
match wanted.or_else(|| monitor_current_mode(&monitor)) {
Some(mode) => Fullscreen::Exclusive(mode),
None => Fullscreen::Borderless(None),
None => Fullscreen::Borderless(Some(monitor)),
}
}
@@ -628,6 +647,22 @@ pub fn icon() -> Option<winit::window::Icon> {
winit::window::Icon::from_rgba(bytes, info.width, info.height).ok()
}
/// Apply Horizon's icon to every window. Windows has a separate taskbar icon slot from the small
/// window icon; requiring the decoded icon there prevents a generic taskbar icon from appearing.
pub fn with_horizon_icon(
attributes: winit::window::WindowAttributes,
) -> winit::window::WindowAttributes {
let icon = icon();
#[cfg(target_os = "windows")]
let icon = Some(icon.expect("Horizon's icon must be available on Windows"));
let attributes = attributes.with_window_icon(icon.clone());
#[cfg(target_os = "windows")]
let attributes =
winit::platform::windows::WindowAttributesExtWindows::with_taskbar_icon(attributes, icon);
attributes
}
#[cfg(test)]
mod tests {
use super::*;
+4
View File
@@ -18,6 +18,8 @@ horizon-client-core = { path = "../horizon-client-core" }
horizon-client-input = { path = "../horizon-client-input" }
horizon-client-render = { path = "../horizon-client-render" }
horizon-command = { path = "../horizon-command" }
# Emotes pose the player model (`Mob::emote`, `mobs::anim::setup`).
horizon-cosmetics = { path = "../horizon-cosmetics" }
horizon-items = { path = "../horizon-items" }
horizon-core = { path = "../horizon-core" }
# `horizon-ctl mcp`: the Model Context Protocol, shared with the launcher and the server.
@@ -30,6 +32,8 @@ horizon-world = { path = "../horizon-world" }
horizon-protocol = { path = "../horizon-protocol" }
# server.properties, eula.txt and the user lists, shared with horizon-server.
horizon-server-files = { path = "../horizon-server-files" }
# The control endpoint's token (control.rs), as horizon-server's and the relay's.
getrandom = "0.3"
# The dedicated server's anticheat, off unless server.toml's [anticheat] turns it on (docs/anticheat.md).
horizon-anticheat = { path = "../horizon-anticheat" }
# The dedicated server's chat filter: [chat_filter], the slur list, mutes (docs/chat-filter.md).
@@ -66,9 +66,10 @@ fn fixture(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
false,
false,
true,
false,
))?;
assert!(!renderer.pack_entity_shadows(), "no selected pack");
renderer.set_shader_pack(0, None, &[]);
renderer.set_shader_pack(0, None, &[]).unwrap();
assert!(
!renderer.pack_entity_shadows(),
"selected pack without a compiled post"
@@ -165,6 +166,7 @@ fn fixture(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
}
let (_, notes) = renderer
.set_shader_pack(dimension, Some(post), &[])
.unwrap()
.unwrap();
if break_pipeline {
assert!(
@@ -190,8 +190,9 @@ fn replay(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
}
let one = [255u8; 4];
let zero = [0u8; 4];
let pixels = render.pixels.get();
let prepared = PreparedTextures::new(
&render.pixels,
&pixels,
render.sprite_size,
render.texture_layers,
&render.sprite_mips,
@@ -206,7 +207,7 @@ fn replay(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
let mut renderer = pollster::block_on(Renderer::new(
window,
true,
&render.pixels,
&pixels,
render.sprite_size,
render.sprite_minimum_size,
render.texture_layers,
@@ -223,7 +224,10 @@ fn replay(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
settings.translucent_batching,
false,
false,
false,
))?;
drop(pixels);
render.pixels.pack();
let state = &scene.renderer;
let [width, height] = o.size.unwrap_or(state.size);
renderer.resize(width, height);
@@ -645,6 +649,7 @@ fn remesh(
nether_shading: false,
shader_pack_lighting: false,
separate_ao: false,
shader_pack: false,
assets,
render,
tables: &tables,
@@ -79,7 +79,10 @@ fn pack(renderer: &mut Renderer, folder: &Path, valid: bool) {
let (properties, _) = horizon_shaders::read_properties(&pack, &mut macros);
let post = horizon_shaders::load_post(&pack, "", &properties, &macros, &renderer.pack_inputs());
assert_eq!(post.notes.is_empty(), valid, "{:?}", post.notes);
let (_, notes) = renderer.set_shader_pack(0, Some(post), &[]).unwrap();
let (_, notes) = renderer
.set_shader_pack(0, Some(post), &[])
.unwrap()
.unwrap();
assert!(
!valid || notes.iter().all(|n| !n.contains("skipped")),
"{notes:?}"
@@ -123,6 +126,7 @@ fn fixture(window: Arc<Window>) -> Result<(), Box<dyn std::error::Error>> {
false,
false,
true,
false,
))?;
renderer.resize(320, 180);
assert!(
@@ -352,6 +356,7 @@ fn fixture_frame(hud: &HudBatch) -> Frame<'_> {
outline: Vec::new(),
outline_after_terrain: false,
outline_high_contrast: false,
pvp: Default::default(),
debug_lines: &[],
debug_lines_on_top: &[],
debug_crosshair: &[],
@@ -364,6 +369,7 @@ fn fixture_frame(hud: &HudBatch) -> Frame<'_> {
visible_static_block_entity_sections: &[],
static_block_entity_stats: Default::default(),
entity_motion: &[],
entity_motion_posed: &[],
entity_spans: &[],
skin: None,
glowing: &[],
@@ -374,10 +380,12 @@ fn fixture_frame(hud: &HudBatch) -> Frame<'_> {
lightning: Default::default(),
additive: &[],
see_through: &[],
world_primitives: Default::default(),
shadow_only: &[],
celestial: &[],
stars: None,
hand: &[],
hand_items: &[],
hand_fov_degrees: 70.0,
screen: &[],
pack: Default::default(),
+103 -14
View File
@@ -6,13 +6,7 @@ use horizon_world::{
worldgen::{tags::TagDefinitions, terrain::TerrainBlockStates},
};
use serde_json::Value;
use std::{
collections::BTreeSet,
fs::File,
io::{self, BufReader},
path::Path,
sync::Arc,
};
use std::{collections::BTreeSet, io, path::Path, sync::Arc};
pub const MATERIALS: [&str; 6] = ["bedrock", "dirt", "stone", "sand", "oak_planks", "bricks"];
#[derive(Clone, Copy)]
@@ -84,7 +78,9 @@ pub struct Assets {
pub struct RenderAssets {
pub sprite_size: u32,
pub sprite_minimum_size: u32,
pub pixels: Vec<u8>,
/// The block sprites' RGBA, layer by layer: as built until a renderer has
/// them, then kept compressed ([`SpritePixels`]).
pub pixels: SpritePixels,
pub texture_layers: u32,
pub model_catalog: crate::models::StateModels,
/// Shared parsed resources; item-only validation errors surface at item preparation.
@@ -111,6 +107,76 @@ pub struct RenderAssets {
/// sprites are when it changes (`native_block_sprites_version`).
pub generation: u64,
}
/// The block sprites' pixels for a renderer to upload. Once a renderer has
/// them ([`Self::pack`]) they are kept lz4-compressed: the GPU holds the
/// texture array, and the copy only serves a renderer made again (another
/// window, a lost device, another graphics API), which waits for
/// [`Self::get`] to uncompress them. Shared by the window and the workers'
/// `RenderAssets`, so the compressed form replaces the raw one in place.
pub struct SpritePixels(Arc<PixelStore>);
struct PixelStore {
len: usize,
/// Set by the first `pack`: later calls cost one atomic read.
packing: std::sync::atomic::AtomicBool,
/// As built; dropped once `packed` holds them.
raw: std::sync::Mutex<Option<Arc<Vec<u8>>>>,
packed: std::sync::OnceLock<Vec<u8>>,
}
impl SpritePixels {
pub fn new(pixels: Vec<u8>) -> Self {
Self(Arc::new(PixelStore {
len: pixels.len(),
packing: std::sync::atomic::AtomicBool::new(false),
raw: std::sync::Mutex::new(Some(Arc::new(pixels))),
packed: std::sync::OnceLock::new(),
}))
}
/// Their size in bytes, uncompressed.
pub fn len(&self) -> usize {
self.0.len
}
pub fn is_empty(&self) -> bool {
self.0.len == 0
}
/// The pixels as built, uncompressed again once packed.
pub fn get(&self) -> Arc<Vec<u8>> {
let raw = self.0.raw.lock().unwrap_or_else(|e| e.into_inner()).clone();
raw.unwrap_or_else(|| {
let packed = self.0.packed.get().map(Vec::as_slice).unwrap_or_default();
Arc::new(
lz4_flex::decompress_size_prepended(packed)
.ok()
.filter(|p| p.len() == self.0.len)
.unwrap_or_else(|| vec![0; self.0.len]),
)
})
}
/// Keep them compressed from now on, once a renderer has uploaded them:
/// compressed on a thread of their own, the raw copy dropped after.
pub fn pack(&self) {
use std::sync::atomic::Ordering;
if self.0.len == 0 || self.0.packing.swap(true, Ordering::Relaxed) {
return;
}
let store = self.0.clone();
let job = move || {
let raw = store.raw.lock().unwrap_or_else(|e| e.into_inner()).clone();
if let Some(raw) = raw {
let _ = store.packed.set(lz4_flex::compress_prepend_size(&raw));
// `get` reads the compressed copy from here on.
*store.raw.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
};
let fallback = job.clone();
if std::thread::Builder::new()
.name("Sprite packing".into())
.spawn(job)
.is_err()
{
fallback();
}
}
}
/// The last `RenderAssets::generation` handed out.
static GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
fn invalid(message: &str) -> io::Error {
@@ -118,15 +184,12 @@ fn invalid(message: &str) -> io::Error {
}
impl Assets {
pub fn load(root: &Path) -> Result<Self> {
let manifest: Value = serde_json::from_reader(BufReader::new(File::open(
root.join("horizon-resources.json"),
)?))?;
let manifest: Value = horizon_world::json_file::read(&root.join("horizon-resources.json"))?;
if manifest["data_version"].as_i64() != Some(i64::from(DATA_VERSION)) {
return Err(invalid("resources do not match Horizon's pinned data version").into());
}
let (registry, flags) = horizon_world::level::data::load_registry_with_flags(root)?;
let bundle: Value =
serde_json::from_reader(BufReader::new(File::open(root.join("block-tags.json"))?))?;
let bundle: Value = horizon_world::json_file::read(&root.join("block-tags.json"))?;
let mut definitions = TagDefinitions::default();
for (name, text) in bundle
.as_object()
@@ -280,6 +343,7 @@ impl RenderAssets {
}
for stage in 0..10 {
sprites.insert(root, &format!("minecraft:block/destroy_stage_{stage}"))?;
sprites.insert(root, &format!("minecraft:block/destroy_oit_stage_{stage}"))?;
}
let fusion = if draws {
let _step = crate::spans::startup::step("core.fusion");
@@ -333,7 +397,7 @@ impl RenderAssets {
sprite_size: sprites.size,
sprite_minimum_size: sprites.minimum_size,
texture_layers: u32::try_from(sprites.count())?,
pixels: sprites.pixels,
pixels: SpritePixels::new(sprites.pixels),
model_catalog: models.catalog,
model_library,
model_placements: models.placements,
@@ -431,4 +495,29 @@ mod tests {
[ConsumeEffect::ApplyEffects { probability, .. }] if probability == 0.3
));
}
#[test]
fn sprite_pixels_come_back_the_same_once_packed() {
// Two layers of 16x16 RGBA with some structure, as sprites have.
let pixels: Vec<u8> = (0..2 * 16 * 16 * 4).map(|i| (i * 7 % 251) as u8).collect();
let stored = super::SpritePixels::new(pixels.clone());
assert_eq!(*stored.get(), pixels);
stored.pack();
stored.pack();
// The packing thread drops the raw copy once the compressed one is set.
let started = std::time::Instant::now();
while stored.0.raw.lock().unwrap().is_some() {
assert!(started.elapsed().as_secs() < 10, "packing never finished");
std::thread::yield_now();
}
assert!(
stored
.0
.packed
.get()
.is_some_and(|p| p.len() < pixels.len())
);
assert_eq!(*stored.get(), pixels);
assert_eq!(stored.len(), pixels.len());
}
}
+402 -54
View File
@@ -2,7 +2,9 @@
//! small mixer standing in for OpenAL: one channel per source with its gain,
//! pitch, linear distance attenuation (`AL_LINEAR_DISTANCE`, reference 0,
//! rolloff 1), looping, relative flag and source state, Ogg/Vorbis decoding
//! (streams on a worker thread) and OpenAL Soft's stereo panning.
//! (streams on a worker thread) and OpenAL Soft's mixing: mono sources panned
//! and attenuated, multi-channel buffers (stereo music and records) played
//! direct as OpenAL does.
use crate::Result;
use crate::sound_engine::{
Attenuation, Backend, Bound, ChannelId, InstanceId, PlayResult, SoundEngine, SoundInstance,
@@ -29,7 +31,7 @@ enum State {
}
/// An OpenAL source.
struct Channel {
samples: Option<(Arc<Vec<f32>>, u32)>,
samples: Option<(Arc<Vec<f32>>, u32, u16)>,
position: f64,
state: State,
gain: f32,
@@ -135,8 +137,8 @@ impl Drop for MixerBackend {
static SHARED_CHANNELS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
/// Two players' copies of one sound: started within this of each other.
const SAME_SOUND: std::time::Duration = std::time::Duration::from_millis(150);
/// Decoded samples and their rate (`None`: the file did not load).
type Samples = Option<(Arc<Vec<f32>>, u32)>;
/// Decoded interleaved samples, their rate and channel count (`None`: the file did not load).
type Samples = Option<(Arc<Vec<f32>>, u32, u16)>;
/// Decoded static sounds (`SoundBufferLibrary`'s buffers), dropped least
/// recently used first past `CACHE_BYTES`: kept forever, every sound heard in
/// a session stayed (up to 1.4 GiB of samples, villages playing hundreds).
@@ -177,7 +179,7 @@ impl SoundCache {
fn size(samples: &Samples) -> usize {
samples
.as_ref()
.map_or(0, |(s, _)| s.len() * std::mem::size_of::<f32>())
.map_or(0, |(s, _, _)| s.len() * std::mem::size_of::<f32>())
}
}
/// A static sound to decode for a channel waiting on it.
@@ -208,6 +210,18 @@ fn attach(mixer: &Mutex<Mixer>, channel: ChannelId, samples: Samples, looping: b
type Stream = cpal::Stream;
#[cfg(not(feature = "audio"))]
type Stream = std::convert::Infallible;
/// A level sound retained for the network broadcaster, in playback order.
#[derive(Clone, Debug)]
pub struct CapturedSound {
pub event: String,
pub position: [f64; 3],
pub volume: f32,
pub pitch: f32,
pub source: usize,
pub entity: Option<u32>,
pub except_entity: Option<u32>,
}
pub struct Audio {
_stream: Option<Stream>,
device_name: String,
@@ -224,7 +238,7 @@ pub struct Audio {
source_override: Option<usize>,
/// Positional sounds played since the last drain, kept while the world
/// is open to the network so guests hear them too.
pub capture: Option<Vec<(String, [f64; 3], f32, f32)>>,
pub capture: Option<Vec<CapturedSound>>,
/// How many times each positional sound played (the control socket's `status`),
/// with or without an audio device.
pub heard: std::collections::BTreeMap<String, u32>,
@@ -248,7 +262,7 @@ pub struct Audio {
#[cfg(feature = "audio")]
device_ticks: u32,
}
fn decode(path: &Path) -> Option<(Vec<f32>, u32)> {
fn decode(path: &Path) -> Option<(Vec<f32>, u32, u16)> {
use symphonia::core::{
audio::SampleBuffer, codecs::DecoderOptions, formats::FormatOptions, io::MediaSourceStream,
meta::MetadataOptions, probe::Hint,
@@ -272,7 +286,8 @@ fn decode(path: &Path) -> Option<(Vec<f32>, u32)> {
let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &DecoderOptions::default())
.ok()?;
let mut mono = Vec::new();
let mut samples = Vec::new();
let mut channels = 0u16;
while let Ok(packet) = format.next_packet() {
if packet.track_id() != id {
continue;
@@ -281,14 +296,34 @@ fn decode(path: &Path) -> Option<(Vec<f32>, u32)> {
continue;
};
let spec = *buffer.spec();
let channels = spec.channels.count().max(1);
let mut samples = SampleBuffer::<f32>::new(buffer.capacity() as u64, spec);
samples.copy_interleaved_ref(buffer);
for frame in samples.samples().chunks(channels) {
mono.push(frame.iter().sum::<f32>() / channels as f32);
let frame_channels = spec.channels.count().max(1);
let mut decoded = SampleBuffer::<f32>::new(buffer.capacity() as u64, spec);
decoded.copy_interleaved_ref(buffer);
if channels == 0 {
channels = frame_channels.min(2) as u16;
}
if frame_channels <= 2 {
samples.extend_from_slice(decoded.samples());
} else {
// OpenAL plays multi-channel buffers direct; a stereo output hears the odd/even split.
for frame in decoded.samples().chunks(frame_channels) {
let (mut left, mut right) = (0.0, 0.0);
let (mut lefts, mut rights) = (0.0f32, 0.0f32);
for (i, sample) in frame.iter().enumerate() {
if i % 2 == 0 {
left += sample;
lefts += 1.0;
} else {
right += sample;
rights += 1.0;
}
}
samples.push(left / lefts.max(1.0));
samples.push(right / rights.max(1.0));
}
}
}
Some((mono, rate))
(channels != 0).then_some((samples, rate, channels))
}
/// A sound file without its variant number (`mob/creeper/say2.ogg` is `mob/creeper/say`): each
/// player's game picks its own variant of the same event.
@@ -415,7 +450,7 @@ impl Backend for MixerBackend {
// A streamed sound loops by restarting its stream.
let mixer = self.mixer.clone();
std::thread::spawn(move || {
let samples = decode(&file).map(|(s, r)| (Arc::new(s), r));
let samples = decode(&file).map(|(s, r, c)| (Arc::new(s), r, c));
attach(&mixer, channel, samples, looping);
});
return;
@@ -436,7 +471,7 @@ impl Backend for MixerBackend {
// Queued twice before the first decode finished.
let cached = cache.lock().ok().and_then(|mut c| c.get(&job.key));
let samples = cached.unwrap_or_else(|| {
let samples = decode(&job.file).map(|(s, r)| (Arc::new(s), r));
let samples = decode(&job.file).map(|(s, r, c)| (Arc::new(s), r, c));
if let Ok(mut c) = cache.lock() {
c.insert(job.key, samples.clone());
}
@@ -572,9 +607,13 @@ fn mix(mixer: &mut Mixer, data: &mut [f32], channels: usize, rate: u32) {
let mut voices = std::mem::take(&mut mixer.voices);
voices.clear();
for (id, c) in &mixer.channels {
let Some((_, source_rate)) = &c.samples else {
let Some((_, source_rate, source_channels)) = &c.samples else {
continue;
};
// OpenAL plays a multi-channel buffer direct: no panning and no distance attenuation
// (`CalcVoiceParams`' non-spatialized path, vanilla's HRTF-off stereo context). Vanilla's
// music and records are stereo. A mono buffer is the positional one.
let direct = *source_channels > 1;
if c.state != State::Playing {
continue;
}
@@ -599,24 +638,33 @@ fn mix(mixer: &mut Mixer, data: &mut [f32], channels: usize, rate: u32) {
let d = if c.relative { at } else { away(listener) };
let distance = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt() as f32;
let gain = if camera.is_some() {
c.gain.clamp(0.0, 1.0) * distance_gain(c.linear, distance)
c.gain.clamp(0.0, 1.0)
* if direct {
1.0
} else {
distance_gain(c.linear, distance)
}
} else {
0.0
};
// Relative sources are already in OpenAL's listener space (-Z ahead).
let [mut left, mut right] = if c.relative {
stereo_gains(d[0] as f32, d[1] as f32, -d[2] as f32)
let [left, right] = if direct {
[gain, gain]
} else {
stereo_gains(dot(d, right), dot(d, up), dot(d, forward))
// Relative sources are already in OpenAL's listener space (-Z ahead).
let [mut left, mut right] = if c.relative {
stereo_gains(d[0] as f32, d[1] as f32, -d[2] as f32)
} else {
stereo_gains(dot(d, right), dot(d, up), dot(d, forward))
};
if shared {
// Several players on the speakers: half the stereo width, as left and right are
// nobody's in particular.
let middle = (left + right) / 2.0;
left = middle + (left - middle) / 2.0;
right = middle + (right - middle) / 2.0;
}
[left * gain, right * gain]
};
if shared {
// Several players on the speakers: half the stereo width, as left and right are
// nobody's in particular.
let middle = (left + right) / 2.0;
left = middle + (left - middle) / 2.0;
right = middle + (right - middle) / 2.0;
}
let [left, right] = [left, right].map(|pan| gain * pan);
let step = f64::from(*source_rate) / f64::from(rate) * f64::from(c.pitch.max(0.0));
voices.push((*id, [left, right, gain], step));
}
@@ -624,13 +672,15 @@ fn mix(mixer: &mut Mixer, data: &mut [f32], channels: usize, rate: u32) {
let Some(c) = mixer.channels.get_mut(&id) else {
continue;
};
let Some((samples, _)) = c.samples.clone() else {
let Some((samples, _, source_channels)) = c.samples.clone() else {
continue;
};
let source_channels = source_channels as usize;
let frames = samples.len() / source_channels;
for frame in data.chunks_mut(channels) {
let mut i = c.position as usize;
if i + 1 >= samples.len() {
if c.looping && samples.len() > 1 {
if i + 1 >= frames {
if c.looping && frames > 1 {
c.position = 0.0;
i = 0;
} else {
@@ -639,13 +689,24 @@ fn mix(mixer: &mut Mixer, data: &mut [f32], channels: usize, rate: u32) {
}
}
let t = (c.position - i as f64) as f32;
let s = samples[i] * (1.0 - t) + samples[i + 1] * t;
if channels >= 2 {
frame[0] += s * left;
frame[1] += s * right;
if source_channels > 1 {
let l = samples[i * 2] * (1.0 - t) + samples[(i + 1) * 2] * t;
let r = samples[i * 2 + 1] * (1.0 - t) + samples[(i + 1) * 2 + 1] * t;
if channels >= 2 {
frame[0] += l * left;
frame[1] += r * right;
} else {
frame[0] += (l + r) * 0.5 * gain;
}
} else {
// A mono output has no panning: the source's own gain.
frame[0] += s * gain;
let s = samples[i] * (1.0 - t) + samples[i + 1] * t;
if channels >= 2 {
frame[0] += s * left;
frame[1] += s * right;
} else {
// A mono output has no panning: the source's own gain.
frame[0] += s * gain;
}
}
c.position += step;
}
@@ -693,17 +754,27 @@ fn open_device(
) -> Result<(cpal::Stream, cpal::SupportedStreamConfig)> {
use cpal::SampleFormat as F;
let config = device.default_output_config()?;
let stream = match config.sample_format() {
F::F32 => build_stream::<f32>(device, &config, mixer.clone()),
F::F64 => build_stream::<f64>(device, &config, mixer.clone()),
F::I16 => build_stream::<i16>(device, &config, mixer.clone()),
F::I32 => build_stream::<i32>(device, &config, mixer.clone()),
F::I8 => build_stream::<i8>(device, &config, mixer.clone()),
F::U16 => build_stream::<u16>(device, &config, mixer.clone()),
F::U32 => build_stream::<u32>(device, &config, mixer.clone()),
F::U8 => build_stream::<u8>(device, &config, mixer.clone()),
let build = |stream_config: &cpal::StreamConfig| match config.sample_format() {
F::F32 => build_stream::<f32>(device, &config, stream_config, mixer.clone()),
F::F64 => build_stream::<f64>(device, &config, stream_config, mixer.clone()),
F::I16 => build_stream::<i16>(device, &config, stream_config, mixer.clone()),
F::I32 => build_stream::<i32>(device, &config, stream_config, mixer.clone()),
F::I8 => build_stream::<i8>(device, &config, stream_config, mixer.clone()),
F::U16 => build_stream::<u16>(device, &config, stream_config, mixer.clone()),
F::U32 => build_stream::<u32>(device, &config, stream_config, mixer.clone()),
F::U8 => build_stream::<u8>(device, &config, stream_config, mixer.clone()),
other => Err(format!("unsupported sample format {other}").into()),
}?;
};
let short = audio_stream_config(&config);
let stream = match build(&short) {
Ok(stream) => stream,
// A device that refuses the short fixed buffer still plays with cpal's default one.
Err(e) if short != config.config() => {
log::info!("Horizon: audio output refused a short buffer ({e}); using the default");
build(&config.config())?
}
Err(e) => return Err(e),
};
stream.play()?;
if let Ok(mut m) = mixer.lock() {
m.device_name = device.name().unwrap_or_default();
@@ -784,10 +855,58 @@ fn cards_changed(seen: Option<&str>, cards: Option<&str>) -> bool {
_ => true,
}
}
/// The buffer size to request for `open_device`. On Linux the ALSA backend's
/// `BufferSize::Default` means a ~100 ms buffer, so sound effects come out
/// about 100 ms late: request a short fixed buffer (1024 frames) by default.
/// An environment override `HORIZON_AUDIO_BUFFER` can specify `default` or a
/// custom frame count (e.g. 2048) when the default short buffer underruns
/// (e.g. PipeWire min-quantum >= 1024).
#[cfg(feature = "audio")]
fn target_buffer_size(
range: (u32, u32),
var: impl Fn(&str) -> Option<std::ffi::OsString>,
) -> Option<cpal::BufferSize> {
if let Some(val) = var("HORIZON_AUDIO_BUFFER").and_then(|v| v.into_string().ok()) {
if val == "default" {
return Some(cpal::BufferSize::Default);
}
if let Ok(frames) = val.parse::<u32>() {
return Some(cpal::BufferSize::Fixed(frames.clamp(range.0, range.1)));
}
}
#[cfg(target_os = "linux")]
{
Some(cpal::BufferSize::Fixed(1024u32.clamp(range.0, range.1)))
}
#[cfg(not(target_os = "linux"))]
{
None
}
}
/// The cpal `StreamConfig` to open an output stream with. On Linux the ALSA
/// backend's `BufferSize::Default` means a ~100 ms buffer, so sound effects
/// come out about 100 ms late: request a short fixed buffer there. Other
/// platforms keep cpal's default behaviour.
#[cfg(feature = "audio")]
fn audio_stream_config(config: &cpal::SupportedStreamConfig) -> cpal::StreamConfig {
let mut sc = config.config();
let range = match config.buffer_size() {
cpal::SupportedBufferSize::Range { min, max } => (*min, *max),
cpal::SupportedBufferSize::Unknown => (256, 4096),
};
if let Some(bs) = target_buffer_size(range, |name| std::env::var_os(name)) {
sc.buffer_size = bs;
}
sc
}
#[cfg(feature = "audio")]
fn build_stream<T: cpal::SizedSample + cpal::FromSample<f32>>(
device: &cpal::Device,
config: &cpal::SupportedStreamConfig,
stream_config: &cpal::StreamConfig,
mixer: Arc<Mutex<Mixer>>,
) -> Result<cpal::Stream> {
let rate = config.sample_rate().0;
@@ -795,7 +914,7 @@ fn build_stream<T: cpal::SizedSample + cpal::FromSample<f32>>(
let mut scratch = Vec::new();
let on_error = mixer.clone();
let stream = device.build_output_stream(
&config.config(),
stream_config,
move |data: &mut [T], _| {
scratch.resize(data.len(), 0.0f32);
if let Ok(mut m) = mixer.lock() {
@@ -1262,7 +1381,17 @@ impl Audio {
pub fn play_after(&mut self, event: &str, at: [f64; 3], volume: f32, pitch: f32, ticks: i32) {
let seed = self.random.next_long();
if let Some(capture) = self.capture.as_mut() {
capture.push((event.to_owned(), at, volume, pitch));
capture.push(CapturedSound {
event: event.to_owned(),
position: at,
volume,
pitch,
source: self.source_override.unwrap_or_else(|| {
crate::settings::sound_source(&crate::sound_events::normalize(event))
}),
entity: None,
except_entity: None,
});
}
*self.heard.entry(event.to_owned()).or_default() += 1;
self.sync_volumes();
@@ -1301,6 +1430,66 @@ impl Audio {
preview.preview(self, source, volume);
self.preview = preview;
}
/// An incoming entity-sound packet: retain its seed and category without rebroadcasting it.
pub fn play_entity_seeded(
&mut self,
event: &str,
source: usize,
volume: f32,
pitch: f32,
seed: i64,
entity: crate::sound_engine::SoundEntity,
) {
*self.heard.entry(event.to_owned()).or_default() += 1;
let event = crate::sound_events::normalize(event);
let instance = SoundInstance::entity_bound(
&event,
source,
volume,
pitch,
LegacyRandom::new(seed),
entity,
);
self.play_instance(instance, false);
}
/// A level's entity overload: preserve the emitter and excluded player for
/// the server broadcaster while the local sound follows the same object.
pub fn play_bound(
&mut self,
event: &str,
source: usize,
volume: f32,
pitch: f32,
entity: crate::sound_engine::SoundEntity,
except_entity: Option<u32>,
) {
if let Some(capture) = self.capture.as_mut() {
capture.push(CapturedSound {
event: event.to_owned(),
position: entity.state().position,
volume,
pitch,
source,
entity: Some(entity.id),
except_entity,
});
}
let seed = self.random.next_long();
self.play_entity_seeded(event, source, volume, pitch, seed, entity);
}
/// Removal belongs to the old object even if its numeric id is immediately reused.
pub fn remove_sound_entity(&mut self, id: u32) {
for entity in self
.engine
.bound_entities()
.into_iter()
.filter(|e| e.id == id)
{
let mut state = entity.state();
state.removed = true;
entity.update(state);
}
}
/// `play` with the seed a sound packet carries.
pub fn play_seeded(
&mut self,
@@ -1311,7 +1500,17 @@ impl Audio {
seed: i64,
) {
if let (Some(capture), Some(at)) = (self.capture.as_mut(), at) {
capture.push((event.to_owned(), at, volume, pitch));
capture.push(CapturedSound {
event: event.to_owned(),
position: at,
volume,
pitch,
source: self.source_override.unwrap_or_else(|| {
crate::settings::sound_source(&crate::sound_events::normalize(event))
}),
entity: None,
except_entity: None,
});
}
if at.is_some() {
*self.heard.entry(event.to_owned()).or_default() += 1;
@@ -1451,6 +1650,81 @@ impl Sounds for Audio {
mod tests {
use super::*;
#[test]
fn level_capture_keeps_order_category_entity_and_actor_exclusion() {
let mut audio = Audio::silent(Path::new("/nonexistent/horizon-entity-sound-test"));
audio.capture = Some(Vec::new());
let mut expected = audio.random.clone();
audio.play_on(
7,
"minecraft:block.amethyst_block.chime",
Some([1.0; 3]),
0.5,
1.0,
);
let entity = crate::sound_engine::SoundEntity::new(37, [2.123456789, 64.0, -3.0], false);
audio.play_bound(
"minecraft:block.amethyst_block.chime",
6,
2.0,
1.2,
entity,
Some(17),
);
let sounds = audio.capture.as_ref().unwrap();
assert_eq!(sounds.len(), 2);
assert_eq!(
(sounds[0].source, sounds[0].entity, sounds[0].except_entity),
(7, None, None)
);
let bound = &sounds[1];
assert_eq!(
(bound.source, bound.entity, bound.except_entity),
(6, Some(37), Some(17))
);
assert_eq!(bound.position, [2.123456789, 64.0, -3.0]);
assert_eq!((bound.volume, bound.pitch), (2.0, 1.2));
expected.next_long();
expected.next_long();
assert_eq!(audio.random.next_long(), expected.next_long());
}
#[test]
fn entity_sound_packets_neither_rebroadcast_nor_advance_the_local_seed_generator() {
let mut audio = Audio::silent(Path::new("/nonexistent/horizon-entity-sound-test"));
audio.capture = Some(Vec::new());
let mut expected = audio.random.clone();
let entity = crate::sound_engine::SoundEntity::new(7, [1.0, 2.0, 3.0], false);
audio.play_entity_seeded("minecraft:fixture.bound", 6, 0.7, 1.2, 42, entity);
assert!(audio.capture.as_ref().unwrap().is_empty());
assert_eq!(audio.random.next_long(), expected.next_long());
}
#[test]
fn removal_marks_only_existing_references_to_that_entity() {
use crate::sound_engine::SoundEntity;
let mut audio = Audio::silent(Path::new("/nonexistent/horizon-entity-sound-test"));
let old = SoundEntity::new(7, [0.0; 3], false);
let other = SoundEntity::new(8, [0.0; 3], false);
for entity in [old.clone(), other.clone()] {
audio.engine.add(
SoundInstance::entity_bound(
"minecraft:fixture.bound",
6,
1.0,
1.0,
LegacyRandom::new(42),
entity,
),
true,
);
}
audio.remove_sound_entity(7);
let replacement = SoundEntity::new(7, [10.0; 3], false);
assert!(old.state().removed);
assert!(!other.state().removed);
assert!(!replacement.state().removed);
}
#[test]
fn button_click_uses_vanilla_ui_parameters_without_advancing_world_random() {
let mut audio = Audio::silent(Path::new("/nonexistent/horizon-test-assets"));
@@ -1485,7 +1759,7 @@ mod tests {
fn channel(samples: Vec<f32>, linear: Option<f32>, at: [f32; 3], looping: bool) -> Channel {
Channel {
samples: Some((Arc::new(samples), 48_000)),
samples: Some((Arc::new(samples), 48_000, 1)),
position: 0.0,
state: State::Playing,
gain: 1.0,
@@ -1500,11 +1774,23 @@ mod tests {
}
}
/// A channel whose buffer is interleaved stereo.
fn stereo_channel(
samples: Vec<f32>,
linear: Option<f32>,
at: [f32; 3],
looping: bool,
) -> Channel {
let mut c = channel(Vec::new(), linear, at, looping);
c.samples = Some((Arc::new(samples), 48_000, 2));
c
}
#[test]
fn decoded_sounds_stay_within_the_budget_least_recently_used_out() {
let mut cache = SoundCache::default();
let quarter = CACHE_BYTES / 4 / std::mem::size_of::<f32>();
let sound = || Some((Arc::new(vec![0.0f32; quarter]), 44100));
let sound = || Some((Arc::new(vec![0.0f32; quarter]), 44100, 1));
for key in ["a", "b", "c", "d"] {
cache.insert(key.into(), sound());
}
@@ -1542,6 +1828,42 @@ mod tests {
);
}
#[test]
#[cfg(feature = "audio")]
fn buffer_size_override_and_defaults() {
let env = |pairs: &'static [(&'static str, &'static str)]| {
move |name: &str| {
pairs
.iter()
.find(|(k, _)| *k == name)
.map(|(_, v)| std::ffi::OsString::from(*v))
}
};
let range = (256, 4096);
assert_eq!(
target_buffer_size(range, env(&[("HORIZON_AUDIO_BUFFER", "default")])),
Some(cpal::BufferSize::Default)
);
assert_eq!(
target_buffer_size(range, env(&[("HORIZON_AUDIO_BUFFER", "2048")])),
Some(cpal::BufferSize::Fixed(2048))
);
assert_eq!(
target_buffer_size(range, env(&[("HORIZON_AUDIO_BUFFER", "100")])),
Some(cpal::BufferSize::Fixed(256))
);
assert_eq!(
target_buffer_size(range, env(&[("HORIZON_AUDIO_BUFFER", "10000")])),
Some(cpal::BufferSize::Fixed(4096))
);
#[cfg(target_os = "linux")]
assert_eq!(
target_buffer_size(range, env(&[])),
Some(cpal::BufferSize::Fixed(1024))
);
#[cfg(not(target_os = "linux"))]
assert_eq!(target_buffer_size(range, env(&[])), None);
}
#[test]
fn linear_attenuation_reaches_zero_at_the_max_distance() {
assert_eq!(distance_gain(None, 100.0), 1.0);
assert_eq!(distance_gain(Some(16.0), 0.0), 1.0);
@@ -1631,6 +1953,32 @@ mod tests {
}
}
#[test]
fn stereo_sounds_play_direct_without_panning_or_distance() {
// OpenAL Soft plays a multi-channel buffer direct (its non-spatialized path), so vanilla's
// stereo music and records keep their own left/right mix at full volume whichever way the
// camera faces and however far it is. Only mono buffers are panned and attenuated.
let wave = [0.1, -0.2, 0.3, -0.4, 0.5, -0.6]; // three stereo frames
let expected = [wave[0], wave[1], wave[2], wave[3]];
for (yaw, at, linear, listener) in [
(0.0, [100.0, 0.0, 0.0], Some(16.0), [0.0; 3]),
(180.0, [0.0, 0.0, -100.0], Some(16.0), [0.0; 3]),
(90.0, [2.0, 1.0, -3.0], Some(16.0), [5.0, 60.0, 5.0]),
(0.0, [0.0, 0.0, 0.0], None, [0.0; 3]),
] {
let mut m = Mixer {
listener,
listener_yaw: yaw,
..Mixer::default()
};
m.channels
.insert(1, stereo_channel(wave.to_vec(), linear, at, false));
let mut data = [0.0; 4];
mix(&mut m, &mut data, 2, 48_000);
assert_eq!(data, expected, "yaw {yaw}, source {at:?}");
}
}
#[test]
fn panning_follows_the_camera_rotation() {
let gains = |yaw: f32, pitch: f32, at: [f32; 3]| {
+4
View File
@@ -49,6 +49,10 @@ impl AuthSession {
access_token: access_token.to_owned(),
}
}
pub fn access_token(&self) -> &str {
&self.access_token
}
/// The session file's JSON: {"name", "uuid", "access_token", "user_type": "msa", "xuid"}.
pub fn parse(text: &str) -> Result<Self, String> {
let v: serde_json::Value =
+1 -1
View File
@@ -288,7 +288,7 @@ impl Biomes {
.collect();
files.sort_by_cached_key(|f| format!("{}.json", f.name));
for file in files {
let v: Value = serde_json::from_slice(file.bytes()?)?;
let v: Value = horizon_world::json_file::parse(&file.path, file.bytes()?)?;
list.push(BiomeInfo::from_json(
format!("minecraft:{}", file.name),
&v,
@@ -89,6 +89,7 @@ pub struct BlockEntities {
/// instance blocks, `shows_box`), so a frame looks at them without scanning the map.
/// Like `mods`, one taken out stays listed until `put` or `remove`.
boxes: FxHashSet<Pos>,
asleep: FxHashSet<Pos>,
}
/// A structure or test instance block: the block entities whose box the frame may draw.
@@ -229,6 +230,7 @@ impl BlockEntities {
}
/// `removeBlockEntityTicker`: no slot, made or pending.
fn cancel(&mut self, pos: &Pos) {
self.asleep.remove(pos);
self.freed |= self.live.remove(pos).is_some();
self.pending.remove(pos);
}
@@ -297,14 +299,25 @@ impl BlockEntities {
self.map.get(pos)
}
pub fn get_mut(&mut self, pos: &Pos) -> Option<&mut BlockEntity> {
self.asleep.remove(pos);
self.map.get_mut(pos)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pos, &mut BlockEntity)> {
self.asleep.clear();
self.map.iter_mut()
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut BlockEntity> {
self.asleep.clear();
self.map.values_mut()
}
pub fn sleep(&mut self, pos: Pos) {
if self.map.contains_key(&pos) {
self.asleep.insert(pos);
}
}
pub fn is_asleep(&self, pos: &Pos) -> bool {
self.asleep.contains(pos)
}
/// `LevelChunk.setBlockEntity` (+ `addAndRegisterBlockEntity`): a new
/// block entity may get a slot; one replacing another keeps its slot.
/// One of the same type rewrites it in place (a jukebox taking a disc,
@@ -320,6 +333,7 @@ impl BlockEntities {
self.attachments.remove(&pos);
}
let new_box = shows_box(&entity);
self.asleep.remove(&pos);
let old = self.map.insert(pos, entity);
if old.is_none() {
self.add(pos);
@@ -346,6 +360,7 @@ impl BlockEntities {
if absent {
self.add(pos);
}
self.asleep.remove(&pos);
let entity = self.map.entry(pos).or_insert_with(make);
if absent && matches!(entity, BlockEntity::Mod(_)) {
self.mods.entry(chunk_of(pos)).or_default().insert(pos);
@@ -358,6 +373,7 @@ impl BlockEntities {
/// `LevelChunk.removeBlockEntity`: the block entity and its slot.
pub fn remove(&mut self, pos: &Pos) -> Option<BlockEntity> {
self.attachments.remove(pos);
self.asleep.remove(pos);
self.cancel(pos);
let removed = self.map.remove(pos);
if matches!(removed, Some(BlockEntity::Mod(_))) {
@@ -369,6 +385,7 @@ impl BlockEntities {
/// The block entity at `pos`, out of the map for a while: its slot stays
/// for `put` to bring it back (a tick working on it).
pub fn take(&mut self, pos: &Pos) -> Option<BlockEntity> {
self.asleep.remove(pos);
let taken = self.map.remove(pos);
if taken.is_some() {
self.out.insert(*pos);
@@ -378,6 +395,7 @@ impl BlockEntities {
/// Brings back what `take` took, in its slot.
pub fn put(&mut self, pos: Pos, entity: BlockEntity) {
self.out.remove(&pos);
self.asleep.remove(&pos);
if matches!(entity, BlockEntity::Mod(_)) {
self.mods.entry(chunk_of(pos)).or_default().insert(pos);
}
@@ -389,6 +407,16 @@ impl BlockEntities {
self.add(pos);
}
}
/// [`Self::retain`] for unloading chunks: removes the block entities at
/// the positions `gone` names and changes none it keeps, so those that
/// sleep stay asleep (a moving player unloads chunks most ticks).
pub fn unload(&mut self, mut gone: impl FnMut(&Pos) -> bool) {
let asleep = std::mem::take(&mut self.asleep);
self.retain(|p, _| !gone(p));
self.asleep = asleep;
let map = &self.map;
self.asleep.retain(|p| map.contains_key(p));
}
/// Removes the block entities `keep` refuses, and their slots
/// (`clearAllBlockEntities` of unloaded chunks).
pub fn retain(&mut self, mut keep: impl FnMut(&Pos, &mut BlockEntity) -> bool) {
@@ -399,6 +427,7 @@ impl BlockEntities {
&mut self.attachments,
);
let (mods, boxes) = (&mut self.mods, &mut self.boxes);
self.asleep.clear();
self.map.retain(|p, e| {
let kept = keep(p, e);
if !kept {
@@ -416,6 +445,7 @@ impl BlockEntities {
self.map.clear();
self.mods.clear();
self.boxes.clear();
self.asleep.clear();
self.tickers = Arc::default();
self.live.clear();
self.added.clear();
@@ -433,6 +463,7 @@ impl BlockEntities {
let order = chunk_map_order(entities.iter().map(|(p, _)| *p));
for (p, e) in entities {
self.attachments.remove(&p);
self.asleep.remove(&p);
let new_is_mod = matches!(e, BlockEntity::Mod(_));
self.note_box(p, Some(&e));
let old = self.map.insert(p, e);
@@ -452,6 +483,7 @@ impl BlockEntities {
/// `LevelChunk.setBlockState` on a block entity's position, from state
/// `old` to `new`: a ticker made, kept or dropped.
pub fn state_changed(&mut self, pos: Pos, old: u32, new: u32) {
self.asleep.remove(&pos);
if self.states.is_empty() || !self.map.contains_key(&pos) {
return;
}
@@ -506,6 +538,11 @@ impl BlockEntities {
});
Arc::clone(&self.tickers)
}
/// How many block entities tick (`Level.blockEntityTickers`, the slots still live), as of
/// the last [`Self::tickers`]: what a diagnostics mod counts.
pub fn ticking_len(&self) -> usize {
self.live.len()
}
/// Whether a slot from `tickers` still ticks.
pub fn is_live(&self, (pos, slot): (Pos, u32)) -> bool {
self.live.get(&pos) == Some(&slot) && self.map.contains_key(&pos)
@@ -536,12 +573,75 @@ mod tests {
.collect()
}
/// Unloading a chunk removes its block entities and leaves the others'
/// sleep alone; `retain`, which may change what it keeps, wakes them all.
#[test]
fn unloading_a_chunk_keeps_the_others_asleep() {
let mut b = level();
let (kept, gone) = ((1, 64, 1), (17, 64, 1));
b.insert(kept, hopper());
b.insert(gone, hopper());
b.sleep(kept);
b.sleep(gone);
b.unload(|p| p.0 >> 4 == 1);
assert!(b.get(&gone).is_none());
assert!(!b.is_asleep(&gone));
assert!(b.is_asleep(&kept));
b.retain(|_, _| true);
assert!(!b.is_asleep(&kept));
}
fn near(b: &BlockEntities, x: f64, z: f64, radius: f64) -> Vec<Pos> {
let mut found: Vec<Pos> = b.mods_near(x, z, radius).copied().collect();
found.sort();
found
}
#[test]
fn a_sleeping_block_entity_wakes_on_any_change() {
let p = (1, 64, 1);
let mut b = level();
b.sleep(p);
assert!(!b.is_asleep(&p));
b.insert(p, hopper());
let asleep = |b: &mut BlockEntities, wake: &dyn Fn(&mut BlockEntities)| {
b.sleep(p);
assert!(b.is_asleep(&p));
wake(b);
b.is_asleep(&p)
};
assert!(asleep(&mut b, &|_| {}));
assert!(!asleep(&mut b, &|b| {
b.get_mut(&p);
}));
assert!(!asleep(&mut b, &|b| {
b.insert(p, hopper());
}));
assert!(!asleep(&mut b, &|b| {
b.get_or_insert_with(p, hopper);
}));
assert!(!asleep(&mut b, &|b| {
let held = b.take(&p).unwrap();
b.put(p, held);
}));
assert!(!asleep(&mut b, &|b| b.state_changed(p, 1, 1)));
assert!(!asleep(&mut b, &|b| b.forget(&p)));
assert!(!asleep(&mut b, &|b| {
b.iter_mut().count();
}));
assert!(!asleep(&mut b, &|b| {
b.values_mut().count();
}));
assert!(!asleep(&mut b, &|b| b.retain(|_, _| true)));
assert!(!asleep(&mut b, &|b| b.load_chunk(vec![(p, hopper())])));
assert!(!asleep(&mut b, &|b| {
b.remove(&p);
b.insert(p, hopper());
}));
b.sleep(p);
b.clear();
assert!(!b.is_asleep(&p));
}
#[test]
fn mod_block_entities_are_listed_by_chunk_until_they_go() {
let mut b = level();
let modded = || BlockEntity::Mod(Box::default());
@@ -939,6 +939,14 @@ impl BlockEntityAnims {
/// Drops the state of block entities that are gone or changed kind:
/// `present` holds the loaded block entities' tickers, sorted by position.
pub fn retain_present(&mut self, present: &[([i32; 3], Ticker)]) {
self.retain_animations(present, false);
}
/// An integrated server owns bell state independently of renderer visibility.
/// Its world ticker removes bells when their actual block changes.
pub fn retain_present_preserving_bells(&mut self, present: &[([i32; 3], Ticker)]) {
self.retain_animations(present, true);
}
fn retain_animations(&mut self, present: &[([i32; 3], Ticker)], keep_bells: bool) {
let is = |p: &[i32; 3], f: fn(&Ticker) -> bool| {
present
.binary_search_by_key(p, |(q, _)| *q)
@@ -948,7 +956,9 @@ impl BlockEntityAnims {
self.books.retain(|p, _| is(p, |t| *t == Ticker::Book));
self.shulkers
.retain(|p, _| is(p, |t| matches!(t, Ticker::Shulker { .. })));
self.bells.retain(|p, _| is(p, |t| *t == Ticker::Bell));
if !keep_bells {
self.bells.retain(|p, _| is(p, |t| *t == Ticker::Bell));
}
self.conduits
.retain(|p, _| is(p, |t| *t == Ticker::Conduit));
self.skulls
@@ -7,10 +7,11 @@
//! the block plus `minecraft_mix_light` with the level's two diffuse lights.
use crate::{
Result,
bidi::visual_str,
bidi::visual,
block_entity_anim::{self, EnchantingBook},
breaking::entity_geometry,
fast_hash::FxHashMap,
font::Glyph,
gfx::mark_oit,
hud::Rect,
mesher::{CULL_BACK, Vertex},
@@ -270,10 +271,10 @@ pub const DYES: [(&str, u32); 16] = [
("black", 0x1D1D21),
];
fn dye_prefix(name: &str) -> Option<(usize, &str)> {
// Longest names first so "light_gray" wins over "gray".
let mut order: Vec<usize> = (0..16).collect();
order.sort_by_key(|&i| std::cmp::Reverse(DYES[i].0.len()));
order.into_iter().find_map(|i| {
// Longest names first; this is the same stable order as sorting DYES by
// descending prefix length, without a Vec allocation or comparison sort.
const ORDER: [usize; 16] = [3, 8, 2, 1, 4, 10, 0, 12, 13, 15, 5, 6, 7, 9, 11, 14];
ORDER.into_iter().find_map(|i| {
name.strip_prefix(DYES[i].0)
.and_then(|rest| rest.strip_prefix('_'))
.map(|rest| (i, rest))
@@ -638,7 +639,9 @@ fn sign_text_near(pos: [i32; 3], camera: DVec3) -> bool {
}
/// `AbstractSignRenderer.submitSignText`: four centred lines, glowing text
/// full-bright with an 8x outline in the dark colour.
/// full-bright with an 8x outline in the dark colour. The font colours each
/// glyph by its style (`Font.StringRenderOutput`): a line's own colour wins,
/// else the sign's. A line is one call per run of one colour.
fn sign_text_calls(
scene: &Scene,
data: &SignData,
@@ -660,26 +663,54 @@ fn sign_text_calls(
} else {
(dark, 0, vertex_light(packed))
};
let advance = |c: char| (scene.glyph)(c).1;
let width = |s: &str| s.chars().map(advance).sum::<f32>().ceil();
text.lines
.iter()
.enumerate()
.map(|(i, line)| {
// The line that fits, in the order it is drawn (getVisualOrder).
let shown = first_line(line, data.max_line_width(), &advance);
let shown = visual_str(&shown).into_owned();
TextCall {
let advance = |c: char| (scene.glyph)(c).advance;
let mut calls: Vec<TextCall> = Vec::with_capacity(text.lines.len());
for i in 0..text.lines.len() {
// The line that fits, in the order it is drawn (getVisualOrder).
let line = text.line_text(i);
let line = line.resolved();
let flat: String = line.chars().map(|(c, _)| c).collect();
let fits = first_line(&flat, data.max_line_width(), &advance);
let shown = line.slice(0, fits.chars().count());
let shown = visual(&shown);
let width = shown.chars().map(|(c, _)| advance(c)).sum::<f32>().ceil();
let x = -((width as i32) / 2) as f32;
let y = i as f32 * line_height - midpoint;
let first = calls.len();
let mut pen = 0.0;
for span in shown.spans.iter().filter(|s| !s.text.is_empty()) {
let run = span
.style
.color
.map_or(color, |rgb| 0xFF00_0000 | (rgb & 0xFF_FFFF));
match calls[first..].last_mut() {
Some(last) if last.color == run => last.text.push_str(&span.text),
_ => calls.push(TextCall {
matrix: m,
x: x + pen,
y,
text: span.text.clone(),
color: run,
outline,
light,
}),
}
pen += span.text.chars().map(advance).sum::<f32>();
}
if calls.len() == first {
// A line with no text is still a call, of no glyphs.
calls.push(TextCall {
matrix: m,
x: -((width(&shown) as i32) / 2) as f32,
y: i as f32 * line_height - midpoint,
text: shown,
x,
y,
text: String::new(),
color,
outline,
light,
}
})
.collect()
});
}
}
calls
}
/// How far the text of an outlined call sits in front of its outline, in
@@ -695,14 +726,31 @@ const TEXT_OVER_OUTLINE: f32 = 0.05;
/// reader instead (a twentieth of a font pixel, invisible) rather than
/// z-fighting with the outline under it.
fn draw_text(scene: &Scene, call: &TextCall, out: &mut Vec<Vertex>) {
text_vertices(scene.glyph, call, out);
}
/// `draw_text` with the font's glyph cells and advances from `glyph`: the
/// text of `call` as glyph quads in its matrix's plane, 1 unit a font pixel,
/// y down as the font's, with its outline when it has one. A native mod's
/// block entity draws its text this way (`native_draw_text`).
pub fn text_vertices(glyph: &dyn Fn(char) -> Glyph, call: &TextCall, out: &mut Vec<Vertex>) {
let mut emit = |dx: f32, dy: f32, z: f32, argb: u32| {
let mut x = call.x + dx;
let y0 = call.y + dy;
for ch in call.text.chars() {
let (cell, advance) = (scene.glyph)(ch);
if let Some(r) = cell {
let corners = [(x, y0 + 8.0), (x + 8.0, y0 + 8.0), (x + 8.0, y0), (x, y0)];
let uvs = [[r.u0, r.v1], [r.u1, r.v1], [r.u1, r.v0], [r.u0, r.v0]];
let g = glyph(ch);
if let Some(cell) = g.rect {
// BakedSheetGlyph: the glyph's own box from the pen, not a
// fixed 8x8 cell (a Unifont glyph is 0.5 units per texel).
let (x0, x1) = (x + g.left, x + g.right);
let (top, bottom) = (y0 + g.top, y0 + g.bottom);
let corners = [(x0, bottom), (x1, bottom), (x1, top), (x0, top)];
let uvs = [
[cell.u0, cell.v1],
[cell.u1, cell.v1],
[cell.u1, cell.v0],
[cell.u0, cell.v0],
];
let c =
0xFF00_0000 | (argb & 255) << 16 | (argb >> 8 & 255) << 8 | (argb >> 16 & 255);
for ((cx, cy), uv) in corners.into_iter().zip(uvs) {
@@ -721,7 +769,7 @@ fn draw_text(scene: &Scene, call: &TextCall, out: &mut Vec<Vertex>) {
});
}
}
x += advance;
x += g.advance;
}
};
if call.outline != 0 {
@@ -818,11 +866,14 @@ pub struct Scene<'a> {
/// profile → skin): a downloaded `skins/<sha1>` slot or the profile's
/// `DefaultPlayerSkin` fallback; None keeps `SKIN_BY_TYPE` (`DefaultPlayerSkin.getDefaultTexture`, slim steve).
pub skull: &'a dyn Fn([i32; 3]) -> Option<String>,
/// Font glyph cell (none for blank glyphs such as the space) and advance.
pub glyph: &'a dyn Fn(char) -> (Option<Rect>, f32),
/// Font glyph: atlas cell (none for blank glyphs such as the space), box and advance.
pub glyph: &'a dyn Fn(char) -> Glyph,
/// Changes whenever `glyph` may answer differently (`Font::epoch`): sign
/// text laid out under another value is laid out again.
pub glyphs: u64,
/// Per-block visibility for dynamic, animated, or uncached models. Bounds
/// are world-space and conservatively include model animation extents.
pub dynamic_visible: Option<&'a dyn Fn([i32; 3], u32, DVec3, DVec3) -> bool>,
}
/// Empty by default (a dedicated server): no state has a block entity model.
@@ -1392,6 +1443,167 @@ impl BlockEntityRenderer {
fn kind(&self, state: u32) -> &Kind {
self.kinds.get(state as usize).unwrap_or(&Kind::None)
}
fn dynamic_bounds(
&self,
scene: &Scene,
pos: [i32; 3],
kind: &Kind,
chest_openness: Option<(f32, Option<[i32; 3]>)>,
) -> (DVec3, DVec3) {
let [x, y, z] = pos.map(f64::from);
let mut lo = DVec3::new(x, y, z);
let mut hi = DVec3::new(x + 1.0, y + 1.0, z + 1.0);
match kind {
Kind::Chest { double_with, .. } => {
if let Some(offset) = double_with {
lo.x += f64::from(offset[0].min(0));
lo.z += f64::from(offset[2].min(0));
hi.x += f64::from(offset[0].max(0));
hi.z += f64::from(offset[2].max(0));
}
let is_open = chest_openness.map_or_else(
|| {
(scene.lid)(pos) != 0.0
|| double_with.is_some_and(|offset| {
let other = [
pos[0].saturating_add(offset[0]),
pos[1].saturating_add(offset[1]),
pos[2].saturating_add(offset[2]),
];
(scene.lid)(other) != 0.0
})
},
|(open, _)| open != 0.0,
);
if is_open {
// The lid pivots up from the rear edge. Its longest corner
// rises by less than one block in the fully open pose.
hi.y += 0.8;
}
}
Kind::Shulker { facing, .. } => {
let offset = step(facing);
lo.x += f64::from(offset[0].min(0)) * 0.52;
lo.y += f64::from(offset[1].min(0)) * 0.52;
lo.z += f64::from(offset[2].min(0)) * 0.52;
hi.x += f64::from(offset[0].max(0)) * 0.52;
hi.y += f64::from(offset[1].max(0)) * 0.52;
hi.z += f64::from(offset[2].max(0)) * 0.52;
}
Kind::Banner { wall: false, .. } => {
hi.y += 1.0;
lo.x -= 0.05;
lo.z -= 0.05;
hi.x += 0.05;
hi.z += 0.05;
}
Kind::Banner { wall: true, .. } => {
lo.y -= 1.0;
lo.x -= 0.05;
lo.z -= 0.05;
hi.x += 0.05;
hi.z += 0.05;
}
Kind::Sign { hanging: true, .. } => {
lo.y -= 1.0;
lo.x -= 0.15;
lo.z -= 0.15;
hi.x += 0.15;
hi.z += 0.15;
}
Kind::Sign { hanging: false, .. } => {
hi.y += 0.3;
lo.x -= 0.02;
lo.z -= 0.02;
hi.x += 0.02;
hi.z += 0.02;
}
Kind::EnchantingTable | Kind::Lectern { .. } => {
// The book and statue models rise above the block's collision
// cube; include their full rendered height and rotated corners.
hi.y += 1.0;
lo.x -= 0.25;
lo.z -= 0.25;
hi.x += 0.25;
hi.z += 0.25;
}
Kind::CopperGolem { .. } => {
// The statue pose rotates around the root, so some variants
// extend below the block as well as above it.
lo.y -= 1.0;
hi.y += 1.0;
lo.x -= 0.25;
lo.z -= 0.25;
hi.x += 0.25;
hi.z += 0.25;
}
Kind::Conduit => {
// Its animated cage is centered above the conduit block and
// the wind model spans multiple cells. The caller bypasses
// voxel occlusion for envelopes this tall, but retains frustum
// culling with this conservative volume.
lo.x -= 1.0;
lo.y -= 1.0;
lo.z -= 1.0;
hi.x += 1.0;
hi.y += 1.0;
hi.z += 1.0;
}
Kind::Bell => {
// The bell's pivoted swing moves its corners slightly outside
// the block even though the model starts centered in it.
lo.x -= 0.1;
lo.y -= 0.1;
lo.z -= 0.1;
hi.x += 0.1;
hi.y += 0.1;
hi.z += 0.1;
}
Kind::Skull { .. } => {
// Wall-mounted and dragon heads can protrude from their block.
lo.x -= 0.25;
lo.y -= 0.25;
lo.z -= 0.25;
hi.x += 0.25;
hi.y += 0.25;
hi.z += 0.25;
}
Kind::EndGateway { .. } => {
let anim = (scene.anim)(pos);
let beam = gateway_beam(anim.gateway, scene.partial, scene.max_y, scene.game_time);
let height = f64::from(beam.height.max(0));
lo.y -= height;
hi.y += height;
}
_ => {}
}
(lo, hi)
}
fn is_dynamic_visible(
&self,
scene: &Scene,
shapes: &Arc<BlockEntityShapes>,
pos: [i32; 3],
state: u32,
kind: &Kind,
chest_openness: Option<(f32, Option<[i32; 3]>)>,
) -> bool {
let Some(visible) = scene.dynamic_visible else {
return true;
};
if matches!(kind, Kind::EndGateway { .. }) {
return true;
}
// A pack model may extend outside the vanilla envelope. Keep that
// geometry on the uncullable path unless its model bounds are known.
if !Arc::ptr_eq(shapes, &self.vanilla) {
return true;
}
let (lo, hi) = self.dynamic_bounds(scene, pos, kind, chest_openness);
visible(pos, state, lo, hi)
}
fn cached_entry_is_current(
&self,
scene: &Scene,
@@ -1401,6 +1613,7 @@ impl BlockEntityRenderer {
options: CacheOptions,
entry: &StaticEntry,
section_revision: StaticSectionRevision,
chest_openness: Option<(f32, Option<[i32; 3]>)>,
) -> bool {
if entry.key.state != state
|| entry.key.nether != scene.nether
@@ -1412,15 +1625,24 @@ impl BlockEntityRenderer {
}
match kind {
Kind::Chest { .. } => {
let (open, other) = self.chest_openness(
pos,
kind,
&|p| scene.world.block(p[0], p[1], p[2]).0,
scene.lid,
);
// Double chests depend on the neighboring half's light too;
// let those uncommon models take the full key path.
open == 0.0 && other.is_none()
let (open, other) = chest_openness.unwrap_or_else(|| {
self.chest_openness(
pos,
kind,
&|p| scene.world.block(p[0], p[1], p[2]).0,
scene.lid,
)
});
if open != 0.0 {
return false;
}
let mut light = scene.world.light(pos[0], pos[1], pos[2]);
if let Some(other) = other {
let other_light = scene.world.light(other[0], other[1], other[2]);
light = (light >> 4).max(other_light >> 4) << 4
| (light & 15).max(other_light & 15);
}
entry.key.light == light
}
Kind::Shulker { .. } => {
let (progress, old) = (scene.anim)(pos).shulker;
@@ -1683,6 +1905,17 @@ impl BlockEntityRenderer {
}
let local = static_local_index(pos, section_min_y);
let current_section_revision = active_section_revision.unwrap_or_default();
let chest_openness = (cache.is_some()
&& options.chests
&& matches!(kind, Kind::Chest { .. }))
.then(|| {
self.chest_openness(
pos,
kind,
&|p| scene.world.block(p[0], p[1], p[2]).0,
scene.lid,
)
});
if cache_only && section_revision.is_some() && active_section_revision.is_none() {
break 'draw;
}
@@ -1698,6 +1931,7 @@ impl BlockEntityRenderer {
options,
&active_entries[index].1,
current_section_revision,
chest_openness,
)
{
let entry = &mut active_entries[index].1;
@@ -1717,7 +1951,9 @@ impl BlockEntityRenderer {
..*vertex
}));
}
if matches!(kind, Kind::Banner { .. }) {
if matches!(kind, Kind::Banner { .. })
&& self.is_dynamic_visible(scene, &shapes, pos, state, kind, None)
{
let packed = scene.world.light(pos[0], pos[1], pos[2]);
let origin =
(DVec3::new(f64::from(pos[0]), f64::from(pos[1]), f64::from(pos[2]))
@@ -1738,11 +1974,25 @@ impl BlockEntityRenderer {
}
break 'draw;
}
if cache_only && chest_openness.is_some_and(|(open, _)| open != 0.0) {
if let Some(cache) = &mut cache
&& let Ok(index) =
active_entries.binary_search_by_key(&local, |(index, _)| *index)
{
active_entries.remove(index);
cache.len -= 1;
cache.mark_section_dirty(section);
}
break 'draw;
}
let origin = (DVec3::new(f64::from(pos[0]), f64::from(pos[1]), f64::from(pos[2]))
- scene.camera)
.as_vec3();
let mut packed = scene.world.light(pos[0], pos[1], pos[2]);
if matches!(kind, Kind::Sign { .. }) && scene.texts.is_none() {
if !self.is_dynamic_visible(scene, &shapes, pos, state, kind, None) {
break 'draw;
}
if options.signs {
self.sign_text(scene, pos, state, origin, packed, out);
} else {
@@ -1761,8 +2011,15 @@ impl BlockEntityRenderer {
break 'draw;
}
if let Some(cache) = &mut cache
&& let Some(key) =
self.static_key(scene, pos, state, kind, options, &mut packed)
&& let Some(key) = self.static_key(
scene,
pos,
state,
kind,
options,
&mut packed,
chest_openness,
)
{
let slot = active_entries.binary_search_by_key(&local, |(index, _)| *index);
let entry = match slot {
@@ -1847,7 +2104,9 @@ impl BlockEntityRenderer {
..*vertex
}));
}
if matches!(kind, Kind::Banner { .. }) {
if matches!(kind, Kind::Banner { .. })
&& self.is_dynamic_visible(scene, &shapes, pos, state, kind, None)
{
self.render_banner(
&shapes,
scene,
@@ -1874,6 +2133,9 @@ impl BlockEntityRenderer {
if cache_only {
break 'draw;
}
if !self.is_dynamic_visible(scene, &shapes, pos, state, kind, chest_openness) {
break 'draw;
}
let base = Mat4::from_translation(origin);
self.render_one(scene, pos, kind, base, packed, lights, false, true, out);
}
@@ -1940,6 +2202,7 @@ impl BlockEntityRenderer {
kind: &Kind,
options: CacheOptions,
packed: &mut u8,
chest_openness: Option<(f32, Option<[i32; 3]>)>,
) -> Option<StaticKey> {
let mut sprite_rects = [None; 5];
let mut sprite_count = 0;
@@ -1957,12 +2220,14 @@ impl BlockEntityRenderer {
part,
..
} if options.chests => {
let (open, other) = self.chest_openness(
pos,
kind,
&|p| scene.world.block(p[0], p[1], p[2]).0,
scene.lid,
);
let (open, other) = chest_openness.unwrap_or_else(|| {
self.chest_openness(
pos,
kind,
&|p| scene.world.block(p[0], p[1], p[2]).0,
scene.lid,
)
});
if open != 0.0 {
return None;
}
@@ -3142,7 +3407,9 @@ impl BeamPart {
(at(b, self.end), [s.u0, v1]),
] {
out.push(Vertex {
normal: [0; 4],
// The beacon beam render types keep the default back-face culling: seen
// from inside a beam, its walls must not show.
normal: CULL_BACK,
pos: p.to_array(),
uv,
layer: 6,
@@ -3232,6 +3499,7 @@ fn portal_cube(m: Mat4, faces: &[Direction], layer: u32, out: &mut Vec<Vertex>)
mod tests {
use super::*;
use crate::assets::Assets;
use horizon_world::nbt::Tag;
#[test]
#[ignore = "requires prepared models/textures in HORIZON_TEST_RESOURCES"]
fn entity_block_particles_and_breaking_use_exported_models() -> Result<()> {
@@ -3342,8 +3610,9 @@ mod tests {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
// Compare the cached world path with the original model renderer,
// including atlas replacement, current light and animated fallback.
@@ -3610,7 +3879,13 @@ mod tests {
w: 8,
h: 8,
};
(Some(rect), 6.0)
Glyph {
rect: Some(rect),
right: 8.0,
bottom: 8.0,
advance: 6.0,
..Glyph::default()
}
};
let scene = Scene {
camera: DVec3::ZERO,
@@ -3632,6 +3907,7 @@ mod tests {
skull: &|_| None,
glyph: &glyph,
glyphs: 0,
dynamic_visible: None,
};
let mut call = TextCall {
matrix: Mat4::IDENTITY,
@@ -3660,6 +3936,202 @@ mod tests {
assert!(out.iter().all(|v| v.pos[2] == 0.0));
}
/// BakedSheetGlyph draws a glyph over its own box from the pen
/// (x + left .. x + right, y + up .. y + down), not a fixed 8x8 cell. With
/// Force Unicode Font a Latin letter is an 8x16 Unifont glyph at half
/// scale: 4 units wide and 8 tall, advancing 5 (#8699).
#[test]
fn sign_text_draws_each_glyph_over_its_own_box() {
let world = World::new(BlockStateId(0), 1);
let sprites = HashMap::new();
let glyph = |_: char| Glyph {
rect: Some(Rect {
u0: 0.0,
v0: 0.0,
u1: 0.0625,
v1: 0.125,
w: 8,
h: 16,
}),
left: 0.0,
top: 0.0,
right: 4.0,
bottom: 8.0,
advance: 5.0,
offset: 0.5,
};
let scene = Scene {
camera: DVec3::ZERO,
orientation: Quat::IDENTITY,
anim: &|_| BlockAnim::default(),
texts: None,
partial: 0.0,
game_time: 0,
max_y: 320,
world: &world,
sprites: &sprites,
emissive: &Default::default(),
translucent_sprites: &HashSet::new(),
atlas_revision: 1,
nether: false,
lid: &|_| 0.0,
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &glyph,
glyphs: 0,
dynamic_visible: None,
};
let call = TextCall {
matrix: Mat4::IDENTITY,
x: 10.0,
y: 2.0,
text: "ab".into(),
color: 0xFF00_0000,
outline: 0,
light: 240 | 240 << 8,
};
let mut out = Vec::new();
draw_text(&scene, &call, &mut out);
let boxes: Vec<_> = out
.chunks(4)
.map(|q| {
let xs = q.iter().map(|v| v.pos[0]);
let ys = q.iter().map(|v| v.pos[1]);
[
xs.clone().fold(f32::MAX, f32::min),
xs.fold(f32::MIN, f32::max),
ys.clone().fold(f32::MAX, f32::min),
ys.fold(f32::MIN, f32::max),
]
})
.collect();
assert_eq!(boxes, [[10.0, 14.0, 2.0, 10.0], [15.0, 19.0, 2.0, 10.0]]);
}
/// The front text calls of a sign whose every glyph advances 6.
fn front_calls(data: &SignData, near: bool) -> Vec<(String, f32, f32, u32, u32)> {
let world = World::new(BlockStateId(0), 1);
let sprites = HashMap::new();
let glyph = |_: char| Glyph {
advance: 6.0,
..Glyph::default()
};
let scene = Scene {
camera: DVec3::ZERO,
orientation: Quat::IDENTITY,
anim: &|_| BlockAnim::default(),
texts: None,
partial: 0.0,
game_time: 0,
max_y: 320,
world: &world,
sprites: &sprites,
emissive: &Default::default(),
translucent_sprites: &HashSet::new(),
atlas_revision: 1,
nether: false,
lid: &|_| 0.0,
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &glyph,
glyphs: 0,
dynamic_visible: None,
};
sign_text_calls(&scene, data, &data.front, Mat4::IDENTITY, 0, near)
.into_iter()
.map(|c| (c.text, c.x, c.y, c.color, c.outline))
.collect()
}
fn sign_with(lines: [Tag; 4]) -> SignData {
let mut data = SignData::default();
data.decode(&Tag::compound([(
"front_text",
Tag::compound([("messages", Tag::List(lines.to_vec()))]),
)]));
data
}
fn colored(text: &str, color: &str) -> Tag {
Tag::compound([
("text", Tag::String(text.into())),
("color", Tag::String(color.into())),
])
}
/// `Font.StringRenderOutput`: a glyph takes its style's colour and the
/// sign's dark text colour only when the style has none (#9783). A line
/// of two colours is two calls, the second where the first ends, the
/// whole line centred.
#[test]
fn sign_text_takes_each_lines_own_colors() {
let mut two = colored("ab", "red");
let Tag::Compound(parts) = &mut two else {
unreachable!()
};
parts.insert("extra".into(), Tag::List(vec![colored("cd", "blue")]));
let data = sign_with([
Tag::String("plain".into()),
colored("Online", "green"),
two,
Tag::String("".into()),
]);
// Black dye: the dark text colour is black. Sign lines are 10 apart
// from 20 above the middle.
assert_eq!(
front_calls(&data, false),
[
("plain".to_owned(), -15.0, -20.0, 0xFF00_0000, 0),
("Online".to_owned(), -18.0, -10.0, 0xFF55_FF55, 0),
("ab".to_owned(), -12.0, 0.0, 0xFFFF_5555, 0),
("cd".to_owned(), 0.0, 0.0, 0xFF55_55FF, 0),
(String::new(), 0.0, 10.0, 0xFF00_0000, 0),
]
);
}
/// A glowing sign's own colour still wins over its dye, and the outline
/// stays the dye's dark colour. `§` codes in a line colour it too, and
/// `§r` goes back to the sign's colour.
#[test]
fn glowing_sign_text_keeps_its_outline_under_a_lines_own_colors() {
let mut data = sign_with([
Tag::String("plain".into()),
colored("Online", "green"),
Tag::String("§cRed§rX".into()),
Tag::String("".into()),
]);
data.front.color = 14;
data.front.glowing = true;
let (red, outline) = (0xFFFF_0000, 0xFF66_0000);
assert_eq!(
front_calls(&data, true),
[
("plain".to_owned(), -15.0, -20.0, red, outline),
("Online".to_owned(), -18.0, -10.0, 0xFF55_FF55, outline),
("Red".to_owned(), -12.0, 0.0, 0xFFFF_5555, outline),
("X".to_owned(), 6.0, 0.0, red, outline),
(String::new(), 0.0, 10.0, red, outline),
]
);
}
/// The text that fits the sign is drawn, in the colour it had.
#[test]
fn a_long_colored_line_is_cut_where_it_stops_fitting() {
// 90 wide: fifteen glyphs of 6.
let data = sign_with([
colored("abcdefghijklmnopqrst", "gold"),
Tag::String("".into()),
Tag::String("".into()),
Tag::String("".into()),
]);
assert_eq!(
front_calls(&data, false)[0],
("abcdefghijklmno".to_owned(), -45.0, -20.0, 0xFFFF_AA00, 0)
);
}
#[test]
fn cached_sign_text_matches_and_follows_changes() {
let renderer = BlockEntityRenderer {
@@ -3714,7 +4186,13 @@ mod tests {
w: 8,
h: 8,
};
((c != ' ').then_some(rect), wide.get())
Glyph {
rect: (c != ' ').then_some(rect),
right: 8.0,
bottom: 8.0,
advance: wide.get(),
..Glyph::default()
}
};
let (first_sign, changed_sign, glowing_sign) = (
|_| Some(&first_text),
@@ -3741,6 +4219,7 @@ mod tests {
skull: &|_| None,
glyph: &glyph,
glyphs: 0,
dynamic_visible: None,
};
let check = |scene: &Scene, pos: [i32; 3], state: u32| {
let mut cached = Vec::new();
@@ -3800,6 +4279,23 @@ mod tests {
/// submits the unit cube's two Y faces. Both are wound outward, so of each
/// face only the side looking at the camera draws: the portal shows one
/// surface from above and one from below, never both at once.
/// `RenderPipelines.BEACON_BEAM_OPAQUE` / `_TRANSLUCENT` keep the default
/// back-face culling, so a camera inside a beam sees no wall of it.
#[test]
fn beacon_beam_faces_cull_their_backs() {
let rect = Rect {
u0: 0.0,
v0: 0.0,
u1: 1.0,
v1: 1.0,
w: 16,
h: 16,
};
let mut out = Vec::new();
beacon_beam(Vec3::ZERO, &[(0xFFFFFF, 4)], 0.0, 1.0, rect, &mut out);
assert_eq!(out.len(), 32, "solid and glow, four quads each");
assert!(out.iter().all(|v| v.normal == CULL_BACK));
}
#[test]
fn the_end_portal_cube_is_one_sided() {
let mut out = Vec::new();
@@ -3879,8 +4375,9 @@ mod tests {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let mut verts = Vec::new();
renderer.render_block(&scene, [0; 3], state.0, Mat4::IDENTITY, 0xff, &mut verts);
@@ -3964,8 +4461,9 @@ mod tests {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
// A closed single chest of `block` drawn by `renderer`: positions and UVs.
let draw = |renderer: &BlockEntityRenderer, block: &str, texture: &'static str| {
@@ -4,6 +4,7 @@ use crate::{
ModelPart, SIGN_TEXT_KEEP, Scene, StaticSectionRevision, classify,
},
chunk::Chunk,
font::Glyph,
hud::Rect,
mesher::Vertex,
signs::{SignData, SignText},
@@ -355,8 +356,9 @@ fn batched_callbacks_preserve_entity_ranges_and_allow_immediate_culling() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
// Reverse local-position order to check that cache indexing does not
// change the order of emitted entities.
@@ -443,8 +445,9 @@ fn static_cache_qualifies_local_positions_by_section_and_drops_old_entries() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let options = CacheOptions {
shulker_boxes: true,
@@ -497,8 +500,9 @@ fn gpu_static_cache_uses_section_local_chests_and_keeps_visibility_separate() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([100, 70, -32], 0)];
let other_block = [([116, 70, -32], 0)];
@@ -651,18 +655,19 @@ fn sign_text_cache_can_be_disabled_and_reenabled_without_changing_output() {
};
let sign_data = &data;
let sign = move |_: [i32; 3]| Some(sign_data);
let glyph = |_: char| {
(
Some(Rect {
u0: 0.0,
v0: 0.0,
u1: 0.0625,
v1: 0.0625,
w: 8,
h: 8,
}),
6.0,
)
let glyph = |_: char| Glyph {
rect: Some(Rect {
u0: 0.0,
v0: 0.0,
u1: 0.0625,
v1: 0.0625,
w: 8,
h: 8,
}),
right: 8.0,
bottom: 8.0,
advance: 6.0,
..Glyph::default()
};
let scene = Scene {
camera: DVec3::new(2.25, 64.5, -3.75),
@@ -684,6 +689,7 @@ fn sign_text_cache_can_be_disabled_and_reenabled_without_changing_output() {
skull: &|_| None,
glyph: &glyph,
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([0, 64, 0], 0)];
let enabled = CacheOptions {
@@ -735,8 +741,9 @@ fn shulker_cache_tracks_settled_poses_and_does_not_freeze_opening_animation() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([0, 64, 0], 0)];
let options = CacheOptions {
@@ -836,8 +843,9 @@ fn shulker_cache_covers_all_colors_and_facing_directions() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let options = CacheOptions {
shulker_boxes: true,
@@ -890,8 +898,9 @@ fn cached_bells_keep_shaking_until_the_animation_ends() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([0, 64, 0], 0)];
let options = CacheOptions {
@@ -951,8 +960,9 @@ fn cached_closed_chests_match_the_direct_model_and_rebuild_when_opening() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([0, 64, 0], 0)];
let options = CacheOptions {
@@ -1046,8 +1056,9 @@ fn closed_chest_cache_covers_all_materials_and_atlas_reloads() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let pos = [0, 64, 0];
let options = CacheOptions {
@@ -1134,8 +1145,9 @@ fn double_chest_cache_bypasses_both_halves_while_either_lid_is_open() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let pair = [([0, 64, 0], 0), ([1, 64, 0], 1)];
let options = CacheOptions {
@@ -1209,8 +1221,9 @@ fn decorated_pot_cache_tracks_sherds_and_bypasses_wobble_animation() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([0, 64, 0], 0)];
let options = CacheOptions {
@@ -1287,8 +1300,9 @@ fn banner_cache_keeps_the_pole_static_while_the_flag_and_patterns_update() {
sign: &|_| None,
banner: &banner_patterns,
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let blocks = [([0, 64, 0], 0)];
let options = CacheOptions {
@@ -1353,8 +1367,9 @@ fn cached_wall_banner_poles_use_the_wall_model() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let pos = [0, 64, 0];
let options = CacheOptions {
@@ -1400,8 +1415,9 @@ fn copper_statue_cache_rebuilds_when_its_pose_state_changes() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let options = CacheOptions {
copper_golem_statues: true,
@@ -1505,8 +1521,9 @@ fn copper_statue_cache_covers_weathering_waxing_and_all_poses() {
sign: &|_| None,
banner: &|_| Vec::new(),
skull: &|_| None,
glyph: &|_| (None, 0.0),
glyph: &|_| Glyph::default(),
glyphs: 0,
dynamic_visible: None,
};
let options = CacheOptions {
copper_golem_statues: true,
@@ -0,0 +1,249 @@
//! Every block state's facts and looks by its id, for a client mod drawing blocks or a map off the main thread
//! (`native_block_states`; docs/native-mods.md): what the game knows of each state, what its model draws on each side
//! and the texture behind each of those sprites. A copy, so the game's own tables can change shape under it.
use crate::{
block_sprites::{BlockSprites, Lookup, SpriteRef},
streaming::WorkerContext,
};
use horizon_items::blocks::{BlockFacts, MAP_COLORS, RenderShape};
use horizon_world::{
block_collision::BlockCollisionRegistry, fluid::FluidState, registry::BlockStateId,
};
use std::{collections::HashMap, sync::Arc};
/// What one block state is, as a renderer or a map reads it.
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct BlockStateFacts {
/// Its block, without properties (`minecraft:grass_block`).
pub name: Arc<str>,
/// `BlockState.isAir`: air, cave air or void air.
pub air: bool,
/// No model draws it (render shape `INVISIBLE`): air, barriers, light, and water and lava, drawn as fluids.
pub invisible: bool,
/// `BlockState.getFluidState`: the fluid in it, a waterlogged block's and kelp's included; empty for none.
pub fluid: FluidState,
/// Its `waterlogged` property is true.
pub waterlogged: bool,
/// The light it gives, 0 to 15.
pub emission: u8,
/// `BlockState.getShape`, the outline the cursor picks, as block-local boxes
/// `[min_x, min_y, min_z, max_x, max_y, max_z]`: `[0, 0, 0, 1, 1, 1]` for a whole block, none for air. Without
/// the random offset of flowers and grass.
pub outline: Arc<[[f64; 6]]>,
/// `MapColor.col` (`0xRRGGBB`), its colour on a vanilla map; none for `MapColor.NONE`, which maps leave out.
pub map_color: Option<u32>,
/// It can collide at all: false for a state whose collision shape is always empty (air, flowers, water).
pub collides: bool,
}
// A mod builds its tables from these on a worker thread.
const _: () = {
const fn shareable<T: Send + Sync + 'static>() {}
shareable::<BlockStates>()
};
/// Every block state by its id: its facts, and what its model draws with the resources loaded when it was made.
#[derive(Clone)]
pub struct BlockStates {
states: Arc<[BlockStateFacts]>,
context: Arc<WorkerContext>,
/// Texture names by sprite layer, across every array.
textures: Arc<HashMap<u32, String>>,
split: u32,
}
impl BlockStates {
/// The states of `context`'s registry, with their `facts` and `collision`, and the sprites of `context`'s
/// resources at `split` layers to an array (`Renderer::sprite_split`).
pub fn new(
facts: &BlockFacts,
collision: &BlockCollisionRegistry<'_>,
context: Arc<WorkerContext>,
split: u32,
) -> Self {
let registry = context.assets.registry;
let states = facts
.states
.iter()
.enumerate()
.map(|(i, f)| {
let id = BlockStateId(i as u32);
BlockStateFacts {
name: f.name.clone(),
air: facts.is_air(id),
invisible: f.render_shape == RenderShape::Invisible,
fluid: registry
.state(id)
.and_then(|state| FluidState::from_block(state).ok())
.unwrap_or(FluidState::EMPTY),
waterlogged: f.waterlogged,
emission: f.emission,
outline: f.outline.clone(),
map_color: MAP_COLORS
.get(usize::from(f.map_color))
.copied()
.filter(|_| f.map_color != 0),
collides: collision.may_collide(id),
}
})
.collect();
// Vanilla's names go without their namespace, as `native_draw_cube` takes them.
let textures = context
.render
.sprite_layers
.iter()
.map(|(name, layer)| (*layer, name.trim_start_matches("minecraft:").to_owned()))
.collect();
Self {
states,
context,
textures: Arc::new(textures),
split: split.max(1),
}
}
/// How many states there are: their ids run from 0 to one less.
pub fn len(&self) -> usize {
self.states.len()
}
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
/// `state`'s facts; none for an id past the last state.
pub fn get(&self, state: BlockStateId) -> Option<&BlockStateFacts> {
self.states.get(state.0 as usize)
}
/// Every state with its id, in id order.
pub fn iter(&self) -> impl Iterator<Item = (BlockStateId, &BlockStateFacts)> {
self.states
.iter()
.enumerate()
.map(|(i, facts)| (BlockStateId(i as u32), facts))
}
/// What `state` draws on each side, as `native_block_sprites` says for a block by name; none for an id past the
/// last state.
pub fn sprites(&self, state: BlockStateId) -> Option<BlockSprites> {
self.get(state)?;
Some(Lookup::new(&self.context, self.split).state(state))
}
/// The texture `sprite` shows, by the name `native_texture_sprites` takes (`block/grass_block_top`; a mod's with
/// its namespace, `mymod:block/gear`).
pub fn texture(&self, sprite: SpriteRef) -> Option<&str> {
let layer = sprite
.array
.checked_mul(self.split)?
.checked_add(sprite.layer)?;
self.textures.get(&layer).map(String::as_str)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
assets::Assets, biome::Biomes, block_sprites::parse_state, light::LightTables,
mesher::MeshTables,
};
use horizon_assets::model::Direction;
use horizon_world::fluid::FluidKind;
use std::{path::PathBuf, sync::OnceLock};
/// Far fewer layers to an array than a GPU takes, so most sprites lie past array 0.
const SMALL_SPLIT: u32 = 64;
/// The game's own states from real prepared resources, loaded once for every test here.
fn states() -> &'static BlockStates {
static STATES: OnceLock<BlockStates> = OnceLock::new();
STATES.get_or_init(|| {
let root = PathBuf::from(
std::env::var_os("HORIZON_TEST_RESOURCES").expect("HORIZON_TEST_RESOURCES"),
);
let assets = Arc::new(Assets::load(&root).unwrap());
let render = Arc::new(assets.reload(&root).unwrap());
let facts = BlockFacts::load(&root, assets.registry).unwrap();
let collision = crate::collisions::load(&root, assets.registry).unwrap();
let visibility = crate::models::load_visibility(&root, assets.registry).unwrap();
let context = Arc::new(WorkerContext {
light: Arc::new(LightTables::load(&root, assets.registry, &facts).unwrap()),
tables: Arc::new(MeshTables::new(&assets, &render, &facts, visibility).unwrap()),
assets: assets.clone(),
render,
biomes: Arc::new(Biomes::load(&root).unwrap()),
});
BlockStates::new(&facts, &collision, context, SMALL_SPLIT)
})
}
fn facts(text: &str) -> &'static BlockStateFacts {
let states = states();
let id = parse_state(states.context.assets.registry, text).expect(text);
states.get(id).unwrap()
}
#[test]
#[ignore = "requires prepared resources in HORIZON_TEST_RESOURCES"]
fn a_grass_block_is_a_whole_solid_grass_coloured_block() {
let grass = facts("grass_block");
assert_eq!(&*grass.name, "minecraft:grass_block");
assert!(!grass.air && !grass.invisible && !grass.waterlogged);
assert_eq!(grass.fluid.kind(), FluidKind::Empty);
assert_eq!(grass.emission, 0);
assert_eq!(&*grass.outline, [[0.0, 0.0, 0.0, 1.0, 1.0, 1.0]]);
assert_eq!(grass.map_color, Some(0x7F_B238), "MapColor.GRASS");
assert!(grass.collides);
}
#[test]
#[ignore = "requires prepared resources in HORIZON_TEST_RESOURCES"]
fn air_and_light_are_not_drawn() {
let air = facts("air");
assert!(air.air && air.invisible && !air.collides);
assert!(air.outline.is_empty());
assert_eq!(air.map_color, None);
let light = facts("light[level=15]");
assert!(!light.air && light.invisible);
assert_eq!(light.emission, 15);
}
#[test]
#[ignore = "requires prepared resources in HORIZON_TEST_RESOURCES"]
fn a_slabs_outline_is_its_half() {
assert_eq!(
&*facts("stone_slab[type=bottom]").outline,
[[0.0, 0.0, 0.0, 1.0, 0.5, 1.0]]
);
assert_eq!(
&*facts("stone_slab[type=top]").outline,
[[0.0, 0.5, 0.0, 1.0, 1.0, 1.0]]
);
let wet = facts("stone_slab[type=bottom,waterlogged=true]");
assert!(wet.waterlogged && wet.collides);
assert_eq!(wet.fluid.kind(), FluidKind::Water);
assert!(wet.fluid.is_source());
}
#[test]
#[ignore = "requires prepared resources in HORIZON_TEST_RESOURCES"]
fn a_face_names_its_texture() {
let states = states();
let id = parse_state(states.context.assets.registry, "grass_block").unwrap();
let grass = states.sprites(id).unwrap();
let top = grass.sides[Direction::Up as usize][0].sprite;
assert_eq!(states.texture(top), Some("block/grass_block_top"));
let north = &grass.sides[Direction::North as usize];
assert_eq!(
states.texture(north[0].sprite),
Some("block/grass_block_side")
);
assert_eq!(
states.texture(north[1].sprite),
Some("block/grass_block_side_overlay")
);
}
}
+114 -3
View File
@@ -90,9 +90,20 @@ pub fn effect_lines<'a>(
);
}
let instant = effect(e).is_some_and(|d| d.instant);
let scaled = (duration as f32 * scale) as i32;
if !instant && scaled > 20 {
line = format!("{line} ({})", format_duration(scaled));
if !instant {
if duration == -1 {
// MobEffectInstance.isInfiniteDuration: formatDuration says
// the infinite sign, not a time.
line = format!(
"{line} ({})",
translate("effect.duration.infinite").unwrap_or_else(|| "∞".to_owned())
);
} else {
let scaled = (duration as f32 * scale) as i32;
if scaled > 20 {
line = format!("{line} ({})", format_duration(scaled));
}
}
}
(line, effect(e).map_or(Category::Neutral, |d| d.category))
})
@@ -340,6 +351,17 @@ impl BrewingStand {
}
out
}
pub fn idle(&self) -> bool {
let bottles = [
self.items[0].is_some(),
self.items[1].is_some(),
self.items[2].is_some(),
];
self.brew_time <= 0
&& self.items[3].is_none()
&& (self.fuel > 0 || self.items[4].is_none())
&& self.last_bottles == Some(bottles)
}
fn brew(&mut self, brewing: &Brewing, items: &Items, out: &mut BrewTick) {
let Some(ingredient) = self.items[3].clone() else {
return;
@@ -455,6 +477,84 @@ mod tests {
assert_eq!(stand.brew_time, 0);
}
#[test]
fn a_brewing_stand_is_idle_only_while_its_tick_changes_nothing() {
let mut stand = BrewingStand::new();
assert!(!stand.idle());
stand.last_bottles = Some([false; 3]);
assert!(stand.idle());
stand.items[0] = Some(ItemStack::new("minecraft:potion", 1));
assert!(!stand.idle());
stand.last_bottles = Some([true, false, false]);
assert!(stand.idle());
stand.items[4] = Some(ItemStack::new("minecraft:blaze_powder", 1));
assert!(!stand.idle());
stand.fuel = 3;
assert!(stand.idle());
stand.items[3] = Some(ItemStack::new("minecraft:nether_wart", 1));
assert!(!stand.idle());
stand.items[3] = None;
stand.brew_time = 5;
assert!(!stand.idle());
}
/// The stand sleeps while `idle`, so its tick must change nothing then: run the real
/// tick on idle stands and compare, and check a stand with fuel to take wakes.
#[test]
fn an_idle_brewing_stand_tick_changes_nothing() {
let items = Items::for_tests(std::collections::HashMap::new());
let brewing = Brewing {
reagents: ["minecraft:nether_wart".to_owned()].into_iter().collect(),
inputs: HashSet::new(),
recipes: vec![BrewingRecipe {
id: "awkward".into(),
input: PotionIngredient {
items: ["minecraft:potion".to_owned()].into_iter().collect(),
potions: Some(["minecraft:water".to_owned()].into_iter().collect()),
},
reagent: PotionIngredient {
items: ["minecraft:nether_wart".to_owned()].into_iter().collect(),
potions: None,
},
output: ItemStack::new("minecraft:potion", 1).with_potion("minecraft:awkward"),
}],
};
let providers = Providers::empty();
let water = || Some(ItemStack::new("minecraft:potion", 1).with_potion("minecraft:water"));
let stand = |bottle: bool, fuel: i32, powder: bool| {
let mut s = BrewingStand::new();
s.fuel = fuel;
if bottle {
s.items[1] = water();
}
if powder {
s.items[4] = Some(ItemStack::new("minecraft:blaze_powder", 1));
}
s.last_bottles = Some([false, bottle, false]);
s
};
for before in [
stand(false, 0, false),
stand(true, 0, false),
stand(true, 5, false),
stand(true, 5, true),
] {
assert!(before.idle(), "{before:?}");
let mut after = before.clone();
assert_eq!(
after.tick(&brewing, &items, &providers),
BrewTick::default()
);
assert_eq!(format!("{after:?}"), format!("{before:?}"));
}
// A brewable ingredient starts the brew: not idle, and the tick changes it.
let mut brewing_now = stand(true, 5, false);
brewing_now.items[3] = Some(ItemStack::new("minecraft:nether_wart", 1));
assert!(!brewing_now.idle());
brewing_now.tick(&brewing, &items, &providers);
assert_eq!(brewing_now.brew_time, 400);
}
/// #3312: `PotionContents.getPotionDescription` names the effects with
/// translatable components, so a Russian game reads them from ru_ru.json
/// (the prepared one; skipped without it), as the tooltip's other lines.
@@ -473,4 +573,15 @@ mod tests {
assert!(lines[0].0.starts_with("Замедление IV ("), "{lines:?}");
assert!(lines[1].0.starts_with("Сопротивление III ("), "{lines:?}");
}
#[test]
fn infinite_effects_say_the_infinite_sign() {
// MobEffectInstance.isInfiniteDuration: a -1 duration says the
// infinite sign, not a time; a short one still says nothing.
let translate = |key: &str| (key == "effect.duration.infinite").then(|| "∞".to_owned());
let lines = effect_lines([("minecraft:resistance", -1, 0)], 1.0, &translate);
assert_eq!(lines[0].0, "resistance (∞)");
let lines = effect_lines([("minecraft:resistance", 15, 0)], 1.0, &translate);
assert_eq!(lines[0].0, "resistance");
}
}
Loaded 100 of 2081 files, more files were not shown because too many files have changed in this diff. Show more