Each ticking section's block read is a cache miss, waited for one after the
other between the ticks. tick_chunk_random now reads the positions the ticks
will draw first, all together: the misses overlap and the ticks find their
blocks in cache. The positions come from the same Level.randValue sequence,
read without being advanced; the draws, the ticks and their order are
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the player half of onEquippedItemBroken (hands 47/48, armour, shield, guest breaks broadcast); complements #5317 (mobs) without touching creatures.rs
This reverts commit 3d5138cfd: #5392 broadcasts the player's equipment
breaks (both hands, armour, shield, a guest's included) through
onEquippedItemBroken, so with both the guest's main-hand break would be
sent twice. This PR stays the mobs' half.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
control.rs: main's control_command_inner takes the loop handle
(&dyn EventLoopCtl) like the rest of the game's methods.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: read the transport slice: custom component IDs behind a OnceLock table, checked in the fallback arm only (vanilla ids unchanged), unnegotiated stacks dropped; two integration tests
#5226 added the proxy, game arguments, release headers and sponsor strings
to the other languages; ja, pt-br and tr (added beside it) never got them,
so the launcher's every_language_has_every_string_the_code_asks_for test
fails on main and on every PR's launcher check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main into the reviewed head (clippy had failed on main's old unreachable S::Attack arm, fixed in 825a6bbcf). es_es.json completed (198/198 keys, placeholders kept, JSON checked); asset only, no code.
🤖 Agent: merged main into the reviewed head (clippy had failed on main's old unreachable S::Attack arm, fixed in 825a6bbcf). read the diff against vanilla's SulfurCube.hurtServer: a burning arrow or trident lights an explosive cube's long fuse at the hit (tnt_explodes), after the hurt; pending sounds become flags so primed and hit both play; unit tests.
🤖 Agent: merged #4445's new head (86e0d83db, main merged) up the stack; #4790 also merges current main (lib.rs: shader_options beside main's split_profiles); top of stack builds clean (client lib+bin).
🤖 Agent: merged #4445's new head (86e0d83db, main merged) up the stack; #4790 also merges current main (lib.rs: shader_options beside main's split_profiles); top of stack builds clean (client lib+bin).
`LivingEntity.onEquippedItemBroken` broadcasts the equipment-break event for
the hand, so every client plays the break and its crumbs. A guest's tool
breaks on the host (`Game::damage_held` acting for the guest), and until now
nothing told the other clients: it was heard only by the window that
happened to be simulating. The break now goes through
`Game::lan_entity_event` with `equipment_break_event(MainHand)` (47).
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
`LivingEntity.entityEventForEquipmentBreak` has a client-side inverse here
already (`item_break::equipment_break_slot`); the wear added for #5317 spelled
the event ids out itself. `item_break::equipment_break_event` is that method
by name, `hurt_mob_armor` asks it for the slot's id, and a test pins the two
as inverses of each other over every slot.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
`LivingEntity.doHurtEquipment` covers all the equipment a living entity
wears, not only a horse's body armour: `Game::hurt_mob_armor` now takes
`max(1, damage / 4)` off the helmet, chestplate, leggings and boots too,
Unbreaking and the fire-resistant spare applied, and a piece that reaches
its maximum empties its slot and broadcasts the equipment-break event for
it (`LivingEntity.entityEventForEquipmentBreak`: 49 to 52 for the armour,
65 for the body).
`tools/port_map`: `LivingEntity.entityEventForEquipmentBreak` is `inline`
now, with what it still misses written in its note.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
`Horse.hurtArmor` (`LivingEntity.doHurtEquipment`) was not modelled at all:
`Mob::damage_with` had no notion of worn durability, so a horse's armour
kept its full durability however often the horse was hit, and the piece
never broke (the row of #3512).
`damage_with` now reports the hit's dealt amount for a mob wearing body
armour (`MobEvent::ArmorHurt`, which carries the damage kind), and the host
applies it in `game/creatures.rs::Game::hurt_mob_armor` the way the player's
own `hurt_armor` does: `max(1, damage / 4)` with Unbreaking applied, no
wear for a piece that does not wear when hurt or for a fire hit on a
fire-resistant one, and at the item's maximum the slot empties and the
equipment-break event (65, the body slot) goes out. A wolf's armour keeps
its own rule (`Wolf.actuallyHurt`, `hurt_wolf_armor`).
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Contributors' agents that rebase and force-push over maintainer fixups
erase them (#5188, #5203). Ask for merges of main instead, and say what
happens if the fixes get erased anyway.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: re-read after the author's update: only crates/horizon-mod-registry/Cargo.lock regenerated against main (adds zip and its deps), so mod-registry's --locked clippy passes; the launcher strings reached main elsewhere.
🤖 Agent: read the diff: a software (DeviceType::Cpu) adapter skips the GPU occlusion pass by default, HORIZON_GPU_CULL still forces it either way; hardware unchanged; measured +5 % FPS on llvmpipe; unit test. Merged main, dropping the branch's duplicate #5251 fixes.
🤖 Agent: read the diff: screens read Shift/Ctrl from the OS modifier word (InputWithModifiers.hasShiftDown), the word reconciles stale physical keys and held latches on ModifiersChanged (toggles, pad holds and rebinds kept), Shift-drag distributes a carried stack, pad Y quick-moves, Ctrl+Q reads the same word, horizon-ctl key/release keep it in sync; new lan_e2e checks. Merged main: #5208's per-frame heal reads the same word, so the two agree; cargo check -p horizon-client --lib --bins --tests clean.
LivingEntity.onEquippedItemBroken broadcasts entityEventForEquipmentBreak
(47 hand, 48 off hand, 49 to 52 armour) and each client plays breakItem: the
stack's break sound and the crumbs. The acting guest's breaks go out with
their entity id when their turn ends; the shield's sound is its break_sound
component, not a sound the server plays.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: read the diff: lan_e2e/agent_control_e2e run on Windows (junction fallback on WinError 1314, no vscreen), horizon-ctl launch clears HANDLE_FLAG_INHERIT on its std handles before spawning the game (cfg(windows) only); Linux/macOS unchanged; Python tests. Proof on Windows 11 in the body.
🤖 Agent: read the diff against vanilla's ChatScreen.normalizeChatMessage: String.trim, normalizeSpace (Java whitespace) and the 256 UTF-16 unit cut before sending and before the history; unit tests.
🤖 Agent: survival: a replaced armour piece that doesn't fit is thrown from the eyes like Player.drop(stack, false), as the other Inventory.add-else-drop paths; 2 lines after #4231 took the rest; rdbtCVS approved. Parity rerun (lunge_motion failure unrelated).
Main merged #5148, which appends its stock-layout test to the same place at
the end of `game::tests` where this PR appends its two, so the hunk
conflicted. Both sides are whole `#[test]` functions: kept both, each with
its own closing brace, in main's order after this PR's.
`cargo test -p horizon-client --bins` runs all three green
(`a_mod_screen_on_a_machine_menu_takes_the_stock_layout_out` from #5148, and
this PR's `an_interact_packet_clicks_the_entity_it_names_not_the_nearest` and
`an_interact_packet_cannot_name_a_mob_the_pick_would_refuse`).
The scan over live and queued particles only runs once there are at least
ParticleLimit.SPORE_BLOSSOM of them in all, so a spore blossom costs nothing
extra in the common case.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main into the reviewed head (clippy had failed on main's old unreachable S::Attack arm, fixed in 825a6bbcf). read the diff against vanilla's Level.sendBlockUpdated / ChunkHolder.broadcastChanges: native_block_entity_sync puts a loaded position in the block log, so the hub sends the block and its update tag once a tick; lan_bot test; tested in game by arismoko. Merged main, API number moved to a hook fragment.
🤖 Agent: merged main into the reviewed head (clippy had failed on main's old unreachable S::Attack arm, fixed in 825a6bbcf). read the diff: native_menu_button_v2 fires right after native_menu_button with the clicking player's cursor stack (clickMenuButton(Player, int)); a change is written back and sent with SetCursorItem; API number from a hook fragment. Marked ready.
🤖 Agent: merged main into the reviewed head (clippy had failed on main's old unreachable S::Attack arm, fixed in 825a6bbcf). read the diff against vanilla: PiglinBrute.wantsToPickUp (golden axe only), StartAdmiringItemIfSeen(119) with StopAdmiringIfItemTooFarAway(9) and StopAdmiringIfTiredOfTryingToReachItem(200, 200), wasHurtBy clearing the memory and ADMIRING_DISABLED for a player's blow; unit tests.
🤖 Agent: merged main and resolved debug_keys.rs: main's F3 Density line (#4389) is now debug_entry_lines' chunk_generation_stats arm, unchanged; cargo check -p horizon-client --bins clean. Reviewed earlier: vanilla's DebugEntry* lines and six debug renderers, profile datafixes; nothing extra per frame with F3 closed.
🤖 Agent: secure chat 3/3 after #4376: the client signs chat and /msg on enforce-secure-profile servers (fixes the official server's 'missing profile public key'); Chat Delay flush marks messages processed, command signing waits for the server's tree, reconfiguration ack as vanilla; tested by 4404c with a real account; main merged by the author.
The rebase dropped 24349e636's fixes to native_move_fluids (inverted put-back
check, a tank marked with no fluid in it, other tanks written away; and a
room limit so a cauldron's thirds never need to go back) and #5188's newer
head. This puts them back on #5188's head, with the move in fluids_move.json
as in the rebase.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A tank dropped for an invalid capacity shifted the later tanks' numbers, so
the face and fill lists reached the wrong tanks (rdbtCVS's review). Also fixes
write_tanks' doc: an empty tank is written as minecraft:empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ShulkerBullet.hurtServer: the hurt sound, fifteen crit particles and destroy.
The bullet is pickable and attackable (no pick radius, unlike
AbstractHurtingProjectile), so the crosshair finds it, a guest's attack
packet reaches it, and the blow plays Player.attack's sounds for a target
that is not a mob.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- test_table_distinguishes_missing_scene_load_from_zero: #4407's later
commit makes table() refuse reports without matching conditions, so its
fixtures now carry the same full conditions as the contract tests.
- test_the_repos_list_is_the_nightlys: the list names Qwerner88880 since
f35210d2d; 10c71a65d updated test_nightly_set but not this test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Shader API [reflection] option and mirror-eye facing culling after #4379; only native shader packs see it; rdbtCVS's points fixed; merged up the stack, shader-api tests and a horizon-client check pass.
🤖 Agent: read the diff against vanilla's DownloadedPackSource (DOWNLOADED_PACK_SELECTION): active server/world packs show in the Resource Packs list as required, fixed at the top, and their server/ ids stay out of options.txt; unit test.
🤖 Agent: read the diff against vanilla's FallingBlockRenderer.shouldRender: a falling block whose state equals its cell's is not drawn; one block read per falling block per frame; unit test.
🤖 Agent: re-read the author's rebase onto main (only context in lan.rs changed). read the diff against vanilla: model light probed at the pose's eye height (crouch render offset undone, Joansitoh's review addressed), setPosToBed and xRot 0 on entering and while sleeping, stopSleeping's findStandUpPosition + teleport on wake for the local player and guests. CI failures were main's Audio build break (#5251).
🤖 Agent: LockCode.canUnlock (spectator or the main-hand item) and createMenu's refusal (action bar + block.chest.locked, both halves of a double chest) match vanilla; the lock stays in the loader's source tag, so saves keep it; approved by rdbtCVS's agent
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff against vanilla: a UUID target resolves through the level's entities (ServerLevel.getEntity), not only mobs; the scoreboard holder lookup shares it; execute on owner keeps only living owners (EntityReference.getLivingEntity). Corpus cases added to command_exec; CI parity runs them. CI failures were main's Audio build break (#5251).
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff against the 26.3 tag sulfur_cube_with_block_immune_to: the eight missing damage types added to the cube's immune list (fireworks left to #4997); unit tests. CI failures were main's Audio build break (#5251).
🤖 Agent: read the diff: flat draws now exist wherever the device supports them, but frames without a pack keep per-section draws off Metal/Intel Gen11 (min_sections usize::MAX), so only a pack's terrain and shadow pass draw flat; gives packs gl_Normal and mc_Entity on NVIDIA/AMD, measured black-box. CI green, parity runs on the head merged into main.
🤖 Agent: read the diff: with a shaders/ pack (oldLighting unset) terrain and fluid vertices drop the baked face shade, measured black-box against Iris with a probe pack (no source read); without a pack, lighting::cardinal as before. CI green, parity runs on the head merged into main.
🤖 Agent: read the diff: while a mod screen is shown on a hopper or 3x3 menu, the stock grid no longer answers first in slot_position, so the mod's slots and inventory_y apply; unchanged without a mod screen. Tested in game by arismoko; CI green, parity runs on the head merged into main.
default no longer needs the template's properties
Both points arismoko raised by reading at e7f4440f0.
1. The NBT tables and the collision overrides carried one row per *template*
state, not per combination, so `facts.json` and the tables disagreed about
how many states a block has.
`nbt` (`states` lists, `cached_shapes` arrays) now emits a row per
combination in the order `states` numbers them in — a template state's
combinations are one run, and `Rename::id` maps the template state to the
first of it — with the declared properties on each row (the `properties`
compound `BlockState::to_nbt` writes; without them `BlockTable::check`
fails with "state identities differ" even once the lengths agree).
`collision_shapes` wrote `first + i`, so only the first combination of each
template state got a shape and the rest kept the table's -1. It now covers
every combination of every template state, all pointing at that state's one
shape: the geometry is the template's, the properties are the block's.
`structure-blocks.json` declared `states` as the template's states alone;
it now counts them times the fan-out, so the declared total and the lists
agree.
2. A `default_state` key the block declares itself was looked up on the
template anyway, so `{"template": "minecraft:stone",
"state_properties": {"lit": ...}, "default_state": {"lit": "true"}}` was
refused with "template default has no state properties" — stone has one
state and no properties at all. `combinations` already checks a declared
key's value, and only a key the template has narrows which of its states is
the default, so the lookup now happens only when such a key is present.
Three tests, each checked red before green: with the old `nbt` the table gets
4 rows where `facts.json` has 6; with the old `collision_shapes` the block's
last state maps to -1; with the old count `structure-blocks.json` says 4 where
its own lists hold 6; and with the old `default_state` guard the property-less
template is refused.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
native_move_fluids moves up to its max between two blocks, one fluid,
native_move_items' shape: what the target won't take goes back into the
source, past the fill sides' leave. The source's tanks just gave the fluid
up, so they have the room for exactly what left them, and a cauldron
levels back up by the thirds it gave, which a drain hands out whole.
Co-Authored-By: ZCode AI agent (GLM-5.3)
Merges origin/main (#5167's join hook beside this leave hook). Main's blast
on experience orbs, a mob picking up a whole stack and native_set_item_entity
emptying a stack now tell native_entity_leave_level too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: read the workflow: verify runs its python, fmt/clippy and test steps in one job again (one runner per run instead of four, the runner queue was 300 deep); the required check is still verify, the only one branch protection requires; no game code.
🤖 Agent: read the diff against vanilla: the dispenser's spawn egg now runs the same EntityType.updateCustomEntityTag step (apply_spawn_egg_entity_data, with its guards) a player's egg does.
boot_paint.rs imports Win32::Foundation, Win32::Graphics::Gdi and
Win32::UI::WindowsAndMessaging, none of which were in the dependency's
feature list, so every Windows build of main fails at horizon-threads
(E0433, the items are configured out). Linux CI never compiles this
file, so it stays green while Windows builds, including the launcher's,
are broken. The same file passed the font count mutably where the API
takes it by const - Linux clippy cannot see that either.
(The hub.rs duplicate S::Attack arm this pull request first also fixed
has since landed on main by itself.)
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: Ludovic's decision (2026-10-31), one line in .github/trusted-reviewers.txt; merged main (verify had failed on main's Audio break, fixed by #5251).
🤖 Agent: read the diff: queued native mod messages are flushed after native_server_stopping while the host and players are still there, on the integrated and dedicated servers; in-game proof on both paths in the body.
🤖 Agent: its owner merged main into the reviewed head (95cdad8b3, unchanged). read the diff: helpers only (per-player sidebar objective, Tab header/footer, tick stats, sleep-count opt-out for AFK players, yaw/pitch on NativeServerPlayer), nothing per tick; the hook fragment is stamped after merge. pr_ready green and the SMP proof on a dedicated server with two clients, per the body.
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff against vanilla: SulfurCube.knockback sets needsSync before setDeltaMovement; unit test.
- appimage_path: only when this binary runs from inside $APPDIR, so a launcher started by another
AppImage's program never overwrites that program's file.
- AppRun no longer exports LD_LIBRARY_PATH (the game and every other child inherited it):
patchelf sets the binary's and libraries' run paths; CI installs patchelf.
- Agent (MCP) configs and desktop shortcuts start update_target(), not the copy in the mount.
- The AppImage is built from $BIN like the archives.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff: x / 0.0 and sqrt of a negative literal go through a private hz_zero global naga can't fold (GL's run-time inf/NaN), texelFetch1D/2D/3D map to texelFetch; unit test. Shader-pack translator only.
🤖 Agent: merged main again (the earlier CI failure was main's broken Audio initializer, fixed by f80c0360d); clippy -D warnings and the horizon-bots tests pass on the merge. Approved by rdbtCVS; bots-only, no game code.
🤖 Agent: read the diff: remaining launcher strings (proxy, game arguments, releases, patch notes, sponsor, mod verdicts) go through tr() with entries in the seven catalogs; no behaviour change.
🤖 Agent: read the diff against vanilla's ShearsItem.useOn: shears on a growing plant head below age 25 set getMaxAgeState with block.growing_plant.crop, a block_change game event and one wear; bodies and max-age heads untouched. New use_item_on cases; CI parity runs them.
🤖 Agent: a resumed dig keeps the first START's clock (ServerPlayerGameMode destroyPos/destroyProgressStart, 0.7 split) on the server and the client; dig_clock, dig_stop and instant_digging LAN checks pass per the author; elythepreciouscat's remote_mining fix included; rdbtCVS approved; verify, clippy, tests green. Parity runs on the merged head.
🤖 Agent: read the diff against vanilla's PlayerHeadItem.getName: a profile with a name titles the head block.minecraft.player_head.named in tooltips and command feedback; custom name still wins; unit test.
🤖 Agent: FSR 3.1 frame interpolation (MIT, AMD FidelityFX 1.1.4) ported to WGSL, still unused (skip-release); rdbtCVS's div_ceil point fixed in 58b2c98d3; naga-validated, GPU tests by the author; verify green.
🤖 Agent: verified: Zip::open now keeps the file and reads only the central directory onward (as Java's ZipFile), entries read at their offsets with no shared cursor; parsing unchanged; tests cover lazy reads, long directories and broken END counts. Thanks!
🤖 Agent: read the diff: the shaders/ preprocessor's #if evaluates float literals (integer / and % kept without one), and an unreadable condition is a warning reading false instead of skipping the program; unit tests. CI green, parity runs on the head merged into main.
🤖 Agent: read the diff: sky, celestial and cloud lightmap coords, the sky normal, cloud face normals (w 0, the game's own clouds unchanged) and blockEntityId -1 by default, as a shaders/ pack reads them in Iris (observed with a probe pack, no source read). CI green, parity runs on the head merged into main.
🤖 Agent: None now samples terrain as vanilla (linear clamped sampler, sampleNearest unless RGSS; terrain.fsh), and distant tiles repeat their sprite per block, which also fixes RGSS/Anisotropic stripes on main since #4843 (checked in game by the author). rdbtCVS's change request was addressed (dismissed with his re-review). verify green.
🤖 Agent: GL backend: indirect execution without compute shaders, batched indirect draws with the same strides/offsets; rdbtCVS checked fmt/clippy on third_party/wgpu-hal by hand and approved. CI green.
🤖 Agent: HDR tiers for lightning, beacons, crystals, fireworks, orbs; marks follow their draws on classic and OIT paths; checked in game by the author; rdbtCVS approved. CI green, parity pending.
🤖 Agent: read the diff: video modes are listed once per monitor instead of at every slider step (about 28 ms each on Windows); with_option keeps the cached list. Unit test; approved by rdbtCVS; CI green, parity runs on the head merged into main.
The comment claimed a knot is a block under the crosshair rather than an
entity id. On a server it is both: `knot_ids` (`game/lan_entities.rs`)
gives every fence knot a `LeashFenceKnotEntity` id, so a guest's Interact
naming one arrives with an id that `interact_knot` cannot use, because it
starts from the block pick.
The comment now says that instead of stating a false reason. No behaviour
changes: knot ids were not resolved before this PR either.
rdbtCVS's open nit at 44c54ba8b. Also rewords the same claim in the body.
The previous merge point (`06485bfef`) is ten commits behind main, and it
still has what `825a6bbcf` fixed: two `S::Attack` arms in `Hub::process_inbox`,
the second unreachable, which is what makes `clippy -D warnings` fail on this
branch as it did on main. Merging current main takes that fix.
No conflicts, and no file this PR changes is resolved by hand here.
A content block's states were exactly its template's, copied row for row, and
`default_state` could only overlay values on properties the template already
had. So a pipe could not have a connection per side, and a machine could not
have `facing` and `lit`.
A block file may now declare `state_properties` — a boolean, or an enum of
values — and the block's states become its template's states times their
combinations, appended after the template's run so a block's states stay one
contiguous run in registry order.
The combination order puts every property at its declared default first, and
`Rename` carries how many states a block gives each of its template's, so a
template state still moves to exactly one state: the block's default. The
`facts.json` defaults and `render-definitions.json` mappings are unchanged.
Refused: a name its template already has, a bad name or value, a
`default_state` value the property does not have, and more combinations than
a world holds (2^20) — the last before the product is built.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
management-server-* in server.properties ([properties] in server.toml) turns on
vanilla 26.3's management protocol, served by the supervisor so it answers while
the game restarts. As net.minecraft.server.jsonrpc does: the secret as a bearer
token or in the minecraft-v1 subprotocol from an allowed origin (401 bodies
included), JSON-RPC errors and batches as Connection handles them, by-name and
positional parameters as IncomingRpcMethod checks them, a generated secret
written back, and a wrong secret or TLS setting stopping the start.
This first part: the WebSocket (std only, websocket.rs) and rpc.discover
(vanilla's OpenRPC document, management_schema.rs). The other methods answer an
internal error saying they aren't in Horizon yet; they come next.
tools/test_management_protocol_parity.py sends the vanilla server and Horizon's
the same messages and handshakes and compares the answers, and rpc.discover with
the data generator's json-rpc-api-schema.json.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's DebugEntryChunkGeneration line (#4389) was drawn inline in
draw_debug_overlay_under, which this branch replaced by debug_entry_lines:
it is now that function's chunk_generation_stats arm, unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#5149's handle_chat_message_error had no tracker to tell. With one, a signed message that
failed validation never reached it, and the client's last-seen window drifted from the
server's. It now marks the signature as ChatListener.handleChatMessageError does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ChatScreen.handleChatInput` and `ChatScreen.normalizeChatMessage` had no
verdict at all: the ledger reads them as unported rather than as the port they
are. Both point at what the message becomes on the way to the server now.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
A chat line went out as typed: Horizon sent the raw box text, where vanilla
sends `ChatScreen.normalizeChatMessage(s)` — `String.trim`, then
`StringUtils.normalizeSpace` (one space for every run of whitespace), then
`StringUtil.trimChatMessage` (the first 256 UTF-16 code units, the length
`ServerboundChatPacket`'s `stringUtf8(256)` decodes).
Two player-visible differences:
- `send_chat` cut the message with `chars().take(256)`, so 256 emoji passed as
512 UTF-16 units and the server refused the packet; a cut at 256 units is
what vanilla does (129 emoji become 128).
- runs of spaces stayed: `hello there` reached other players with its three
spaces, and a line of nothing but spaces was sent, where vanilla sends
nothing and remembers nothing in the history.
The normalization sits in `Game::handle_chat_input` (ChatScreen.handleChatInput),
so the chat box, the chat drafts and the control socket's `chat` all send what
a vanilla chat screen would. `java_whitespace` is reused from `ui.rs`, so
U+00A0 stays (Java whitespace excludes the no-break spaces) and U+3000 and
Java's U+001C..U+001F separators collapse.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
`EntityType.updateCustomEntityTag` was ported for a player's egg
(`block_entity_item::spawn_egg_entity_data` then
`creatures.rs::Game::apply_spawn_egg_entity_data`) but not for the
dispenser's, whose `dispense_spawn_egg` stops after
`entity_nbt::apply_item_components`: a configured egg
(`/give @s pig_spawn_egg[minecraft:entity_data={…}]`) dispensed onto a block
spawned an unconfigured mob, which is the half of the
`EntityType.updateCustomEntityTag` row that pointed at
`game/dispenser.rs`.
`apply_spawn_egg_entity_data` becomes `pub(super)` and the dispenser calls it
after the components, as the player's path does.
`tools/port_map/game-005.tsv`: that row is `port` now.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
The anticheat's Attack arm and #5123's arm that keeps moves queued behind
an attack both landed in process_inbox; the second was unreachable, so
clippy -D warnings failed on main and #5123's behaviour was lost. The
anticheat arm now also holds the queued move when it forwards the attack.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
f2a99d623 split verify into python, clippy and tests jobs plus a gathering
verify: four GitHub-hosted runners per checks run instead of one. With every
pull request push and every merge to main starting checks, that filled the
organisation's shared runner pool, and every workflow's jobs (labels, overlap
notes, other pull requests' checks) queued behind it for tens of minutes.
verify runs the public-tree check, the Python tests, rustfmt, clippy and the
Rust tests one after another again, on one runner. A pull request's
public-tree check still reads only its own commits (--range base..head).
Closes#5302
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main. Main's 45c36d872 had added the client side of the same rule (RiddenBoat in motion/fluids.rs), so I kept main's type and logic and this PR now fills it for served players (lan_actions.rs → boats.rs riding_boat_fluid_box), where the air supply and drowning are decided; port map row updated. cargo check -p horizon-client --bins passes, fmt clean. Approved by rdbtCVS.
horizon-mods gained the zip crate, but the registry's separate lockfile
wasn't updated, so Railway's `cargo build --locked` refused to update it
and every deploy of horizon-mod-registry failed (one failure mail per
merge to main). Minimal update: only the new packages are added.
Checked: the Dockerfile's exact build (same files copied, --release
--locked) finishes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff: on focus loss a held (non-toggle) sneak/sprint/attack/use mapping is released before the re-sync, toggles keep their latch (ToggleKeyMapping.setDown/release); each frame a Shift/Control the keyboard's modifier state reports up clears its stale physical key and held latch. CI failures were main's Audio build break (#5251).
🤖 Agent: merged main (clean) on top of Domanffe's 2b2c2893e, which fixes dimaniojk's two findings (drag off the strip, the CLI's hue); earlier CI failures were main's broken Audio initializer. Launcher-only; cargo fmt clean. Approved by rdbtCVS.
🤖 Agent: F3 Density line as NoiseBasedChunkGenerator.addDebugScreenInfo under the chunk_generation_stats entry, sampled on a new feet block from the server thread's generator; main merged (conflicts with the per-entry F3 and explorer maps resolved, both kept), horizon-client checks clean; rdbtCVS approved.
🤖 Agent: Shader API: the material stage gets opaque_depth/opaque_color copies taken before translucent terrain and MaterialInput.screen (fragment position over resolution, as Teiwazik showed); rdbtCVS approved after merging #4186's tags; main merged. Only native shader packs see it.
🤖 Agent: merged main into the branch (main's broken Audio initializer made the earlier CI fail, fixed on main by f80c0360d); cargo check -p horizon-client-render passes. Change read: two adapter passes, GPUs first, software only with a warning. Approved by rdbtCVS.
🤖 Agent: functions tick before the levels (MinecraftServer.tickChildren) and an item added this tick first ticks on the next (EntityTickList), so a fresh drop reports Age 0; the freeze dimaniojk found is fixed (only items with a later id are skipped); type_specific/player checks ServerPlayer and input.sneak on the server. rdbtCVS approved; main merged.
The lid animated but the collision queries always answered "no box": a
closed box collided as its plain block and an opening one never grew. The
collision view now reads the boxes' lid progress, the level ticks those
lids on its server pass (not only the client-only animation), and an
opening lid applies moveCollidedEntities to the bodies the game moves.
Vanilla: ShulkerBoxBlock.getCollisionShape / getShape (isClosed),
ShulkerBoxBlockEntity.getBoundingBox / getProgressDeltaAabb /
moveCollidedEntities.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: merged main (port map: main's suspicious stew rows kept, this PR's variant rows). Mooshroom variant component as MushroomCow.get; test fails without it.
🤖 Agent: read the diff: placement checked before the spawn-list lookup (both pure reads, no RNG), species placement cached in MobData, LocalMobCapCalculator's lists flattened; draws and positions unchanged; A/B on playhorizon −18 % spawn_natural. CI parity runs on the head merged into main.
🤖 Agent: perf_bench --table prints the missing-value marker when a report has no scene-load measurement, keeps a measured 0.0 and the mean of measured scenes; regression test covers all three. Tooling only; rdbtCVS approved; CI green. Marked ready from draft.
🤖 Agent: verified: horizon-launcher completions <shell> via clap_complete over the existing command tree, no network or file writes, test per shell; approved as code owner. Thanks!
🤖 Agent: verified: vanilla's ENTITY_SHADOW / OIT_ENTITY_SHADOW pipelines take RenderPipeline.Builder's default cull(true); the shadow quads wind +Y (test added), so the underside is culled. Thanks!
🤖 Agent: verified against NbtIo.readUnnamedTag (StringTag.skipString): a file root's name that isn't modified UTF-8 reads as empty; keys and truncated names still fail. Proof: CI parity. Thanks!
Merges origin/main, moves the living_incoming_damage hook to
tools/native-hook-fragments (main's API stays as is), and drops the attackers
a canceled hit noted, so the next hit of that damage type isn't credited to
them (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
nbt.rs: the Writer keeps both flags, this branch's empty_long_strings
and main's canonical_nan (both on for the disk writer, off for the
network one).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Q over a slot and Q in play decided whether to throw the whole stack from
physical_keys, while every other screen check reads Control from the OS
modifier word (has_control_down). The two could disagree after a lost or
lagging key event. drop_whole_stack now takes the modifier word; a latched
sprint toggle still never counts as Control.
The control socket's `release` cleared keys, physical keys and modifiers
but not the sneak/sprint toggle mappings, so a sneak pressed through
`key shift down` came back on the next sync_toggle. It now releases both
mappings as their key's release does (a toggled one stays latched) unless a
controller still holds the action, then resyncs the held keys.
Also:
- `key` reports an unknown action, with its name, from the single early
check; the later match arm that repeated it could no longer run.
- lan_e2e check_recipe_shift reads the recipe buttons before the tab loop,
so a book without a 35x27 tab fails on its assertion and target dump
instead of a NameError.
Validation on macOS arm64:
- cargo test --locked -p horizon-client --bin horizon-client: 359 passed,
3 ignored (including stack_drop_requires_ctrl_even_with_sprint_latched,
now driven through modifiers::set_key).
- cargo clippy --locked -p horizon-client --lib --bins --tests
-- -D warnings, cargo fmt --all --check, lan_e2e.py syntax and
tools/check_public_tree.py: passed.
Still to run with prepared resources: lan_e2e --check recipe-shift
--check modifier-release, and Ctrl+Q in an inventory and in play.
Refs #4166
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Screens now read Shift from the OS modifier word (has_shift_down), but two
synthesized shift-clicks still only inserted ShiftLeft into physical_keys:
the controller's Y quick-move in an inventory (pad_menu.rs) and horizon-ctl's
`station ... shift` click (station_control.rs). Both became plain pickups.
They now go through modifiers::hold/restore, which sets the SHIFT bit with
the physical key and afterwards puts back exactly what was held before:
a Right Shift already down, or an OS word set without a physical key, stays.
The control socket's `key <mod> up` returned before releasing the modifier
when a server dialog or the sign editor was open, or with no world, leaving
SHIFT set so every later inventory click became a QUICK_MOVE. The release
that already ran after `tap` whatever the handler did now runs after `up`
too (released_modifier), so no early return can keep a modifier down.
Tests: modifiers::a_held_click_restores_the_previous_state and
control::taps_and_ups_always_release_their_key.
Validation on macOS arm64:
- cargo test --locked -p horizon-client-input --lib: 64 passed.
- cargo test --locked -p horizon-client --bin horizon-client control::tests:
passed.
- cargo clippy --locked -p horizon-client-input -p horizon-client --lib
--bins --tests -- -D warnings, cargo fmt --all and
tools/check_public_tree.py: passed.
Still to run on a machine with prepared resources: controller Y on a slot
in the survival and creative inventories, and lan_e2e --check
modifier-release --check clicks.
Refs #4166
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Read Shift and Control for screens from the OS modifier word instead of
inferring them from cached physical key events. This follows Minecraft
Java 26.3 InputWithModifiers.hasShiftDown/hasControlDown and prevents a
missed key release from turning subsequent inventory clicks into transfers.
Reconcile lost releases only for physical modifiers that were recorded as
held and are still assigned to the affected sneak/sprint mapping. Preserve
rebound non-modifier keys, controller-held actions and keyboard/controller
toggles. Include controller releases delayed until the next simulation tick.
This avoids cancelling crouch bound to C when the player presses Alt, or
cancelling a controller hold because the keyboard does not hold Shift.
Keep synthesized modifier presses and releases consistent with the OS word.
Releasing one side preserves the bit while the other side is still held.
Validate control key actions before mutating state. Run tap cleanup after
the command handler even if it returns early or fails, and clear the modifier
word on the control socket's release command. This prevents key shift tap
or key shift down followed by release from leaving later clicks modified.
Add five input tests covering modifier press/release, left/right holds,
rebindings, missing physical keys, controller holds and controller toggles.
Add the modifier-release LAN check: after a Shift tap or release, a normal
inventory click must pick up ten diamonds and closing must preserve the
server's count. Remove the obsolete pad-toggle getters introduced by the
previous broad reconciliation, since ToggleKeys already handles toggles.
Validation on macOS arm64 on main 9069aea3e:
- cargo test --locked -p horizon-client-input --lib: 63 passed.
- cargo clippy --locked -p horizon-client-input --all-targets -- -D warnings:
passed.
- cargo clippy --locked -p horizon-client --lib --bins --tests -- -D warnings
-A unreachable-patterns: passed. The allowed lint is main's duplicate
S::Attack arm in lan/hub.rs, a file this branch does not touch.
- cargo fmt --all --check, Python harness syntax, git diff --check and
tools/check_public_tree.py: passed.
Prepared Minecraft resources are absent here. The new modifier-release
check and final game-level verification remain pending on the dedicated
machine; prior LAN results must not be treated as validation of this change.
Rebuild binaries from this commit, then run:
python3 tools/lan_e2e.py --check modifier-release --check clicks \
--check shift-double-click --check recipe-shift --check shift-drag \
--check crafting --check wheel --out .local/validation-4166-review
Also rerun pr_ready.py with the appropriate main remote as its base.
Manual checks still required: hold rebound crouch/sprint while pressing
and releasing Alt; repeat with controller holds and toggle modes; confirm
lost Shift releases still recover with the default sneak binding. Verify
Shift taps and release before normal inventory clicks. Do not use the
deprecated in-frame path; keep game, launcher and XDG homes in fresh
scratch directories as required by AGENTS.md.
Refs #4166
Co-Authored-By: OpenAI Codex gpt-6.1-sol <[email protected]>
A carried stack already starts QUICK_CRAFT in Clicks::press, but
inventory_press only initialized its hovered-slot collection without Shift.
Holding Shift therefore prevented the distribution gesture from completing.
Start the drag with either modifier state. Keep PICKUP_ALL restricted to
non-Shift double-clicks, and leave the Shift drag initially empty so a
press/release without any dragged slot retains its QUICK_MOVE behavior.
Follow Minecraft Java 26.3 AbstractContainerScreen.mouseClicked,
mouseDragged and mouseReleased; use the existing menu and server click path.
Add three LAN checks driven by real pointer events, rather than click-slot:
- shift-drag: spread 20 diamonds across three crafting slots as 6/6/6 with
two carried, or 1/1/1 with seventeen carried, with and without Shift.
- shift-double-click: transfer chest stacks of 8, 13 and 21 diamonds while
keeping a carried stack of 32; spaced clicks transfer only the first pile.
- recipe-shift: place two diamond-block recipe sets from 20 diamonds using
the recipe book, then Shift-click the result to produce two blocks and
retain two diamonds.
The latter two behaviors already worked and receive regression coverage.
Resolve pointer targets from the current UI and include movement inside the
starting slot so the drag actually traverses all selected slots. Check
carried stacks, grid contents and server inventory counts after closure.
Evidence reported by the dedicated-machine agent before this rebase:
Shift drags failed on the old base and passed after the fix; clicks,
shift-double-click, recipe-shift, shift-drag, crafting and wheel passed.
The container_screen, menu_click, place_recipe, recipe_book and
recipe_book_view differential harnesses were also reported passing.
Vanilla expectations came from the pinned sources and Java oracles; the
vanilla graphical client was not exercised. Those game runs cannot be
repeated on this checkout because its prepared resources are absent.
Refs #4166
Co-Authored-By: OpenAI Codex gpt-6.1-sol <[email protected]>
Conflicts: datapack.rs takes both sides' kinds (trade_set,
villager_trade and dialog); game.rs saves through main's
write_level_for_role, then each placed player to their own file;
lan.rs syncs dialog ids, then sends the world's own biome table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PiglinBrute.wantsToPickUp takes only a golden axe, through Mob.aiStep's
looting loop. StartAdmiringItemIfSeen sets ADMIRING_ITEM (119 ticks) for a
loved item in sight, and StopAdmiringIfItemTooFarAway(9) and
StopAdmiringIfTiredOfTryingToReachItem(200, 200) end it. A blow from a
player disables admiring for 400 ticks (ADMIRING_DISABLED).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: read the diff at 7fc284b (rdbtCVS's main merges + Escape-as-Cancel fix): the splitscreen profile page as a model with its store and tests in horizon-client-render, not wired into play yet; dimaniojk's paging finding fixed. Part of the splitscreen series whose earlier parts merged.
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff: an items-only content mod now writes the identity marker, and the identity hashes the mods' item and mob effect entries per registry; a root without mod entries keeps its block-only hash (unit test). Fails safe with the content-mods mismatch message across versions.
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. two spans (spawn_natural, tick_creature_entities) inside tick_creatures, timed only when spans are on; no behaviour change. CI failures were main's Audio build break (#5251).
🤖 Agent: merged main into the reviewed head (the earlier clippy/tests failures were main's Audio build break, fixed by #5251); no other change. read the diff against vanilla: spear is in #minecraft:no_knockback, so LivingEntity.hurtServer skips dealDefaultKnockback for a stab; Damage::no_knockback now lists Spear; unit tests. CI failures were main's Audio build break (#5251).
🤖 Agent: merged main (both sides added tests at the end of mod_content_tests.rs: kept both); cargo check of the lib and tests passes; review points fixed by the author (02a7c1553 cherry-picked); marked ready
Launcher: Language::Turkish (code tr, Türkçe, picked from a tr-* system
locale), tr.json brought up to main's strings (25 native-mods strings
translated, 5 the launcher no longer asks for dropped, one {version}
placeholder fixed). Game: tr_tr.json joins the bundled Horizon strings.
Built on #5035's Japanese wiring so the two don't conflict.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: finished by me (94027c278): merged main, API number as a fragment, the canceled-join crash fixed (just_spawned_item), and a same-level End gateway trip no longer joins again. native_ tests pass.
🤖 Agent: the review follow-ups from #3996–#4289 (screen only for the block's own menu, entity menus clear the block target); review points answered; marked ready
🤖 Agent: read the diff: Superflat layer and preset icons draw the item's GUI model (as CreateFlatWorldScreen's renderItem), clipped exactly to the list; other screens unchanged. Approved by rdbtCVS; CI green, parity runs on the head merged into main.
🤖 Agent: one partner_hidden setting hides the sidebar card and the Servers banner; the 3-minute seen gate now counts on every page. rdbtCVS approved; launcher CI green.
🤖 Agent: rename-aside Windows self-update with rollback replaces self_replace's self-deleting copy that Defender flags; start-up clean-up removes only this exe's .old/.update leftovers. Read in full; rdbtCVS approved; CI's launcher job builds Windows. A real Windows update after release is the remaining check.
startRiding(vehicle, true, true) skips canAddPassenger, so a boat under
water still takes the rider; Horizon's boats seat at most max_passengers,
so a full one still refuses. Horizon's minecarts carry no mobs yet, said
in a comment. A player put in a boat or minecart now fires
minecraft:started_riding (sendEventAndTriggers).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#5235 keeps the address in horizon_telemetry::server_status::OFFICIAL_ADDRESS:
it is now play.playhorizon.dev, with OFFICIAL_PREVIOUS beside it; the
launcher's defaults and the game use both. The launcher's official player
count pings the address from links.json through its SRV record
(horizon_protocol::server_redirect + server_status::players_at): port 25565
on that host is another server. The telemetry server, which looks no SRV
up, defaults to the SRV target (OFFICIAL_STATUS_ADDRESS). The game moves
servers.dat entries for an earlier address once, like the launcher.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
pushAndPickupEntities only runs for a rideable cart moving at
horizontalDistanceSqr >= 0.01; an activator rail ejects mob passengers
too; a player can't board a cart a mob already rides (isVehicle).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A failed signature and failed OS randomness are logged instead of hidden, and the
ChatListener.flushQueue ledger row points at flush_chat_queue.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#4935 landed as API 45, and API numbers now come from
tools/native-hook-fragments (stamp_native_hooks.py after the merge): the
hand-written API 44 and its docs paragraph become
native-block-entity-sync.json. lan_bot keeps both new tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The official server moves to play.playhorizon.dev (SRV to port 24454).
launcher/links.json now carries `official_server` and
`official_server_previous`, read through the existing Links mechanism with
the same built-in defaults, so a later move is a links.json edit, no release.
server_list::add_official offers the address from links, and when the
address differs from the one the marker file recorded, rewrites entries for
a previous address to the new one (player's name, picture and fields kept),
once. No duplicate when the new address is already listed; a player who
removed the server is not offered it again. The Home list re-runs it when
links.json arrives. The game's own built-in copy and docs/smp.md follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The hook asks for its API number with tools/native-hook-fragments/entity_leave_level.json
(#5161); NATIVE_HOOKS_API, the histories and native-hooks.json are main's again.
- An End gateway keeps its traveller in the level (teleportSameDimension), so an item
going through one no longer reports leaving it (arismoko's and rdbtCVS's point).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: horizon-prepare names the game files one by one (--version-json, --client-jar, --assets-dir…), still checked against research-lock.json pins; merged #3574's fix and main; horizon-prepare tests and clippy pass.
🤖 Agent: requeued at the author's rebase on main (same change, one line less): vanilla's handlePlayerChat checks (global index, unknown sender, SignedMessageValidator) with chat.validation_error.
🤖 Agent: read the diff: Velocity modern forwarding only with forwarding.secret and online-mode false (the proxy does the Microsoft login); HMAC-SHA256 checked in constant time (RFC 4231 vectors), version, lengths and trailing bytes checked, empty secret stops the start, in-memory joins never forwarded. Wire format from black-box recordings of Velocity 4.3.1. verify green; merging once parity passes.
🤖 Agent: read the diff against vanilla's SulfurCube.knockback helpers (applyHorizontalHitAngleScale, applyVerticalHitAnglePowerTransfer, the position rotation) and Projectile.calculateHorizontalHurtKnockbackDirection; unit test matches the 26.3 helpers to 1e-9, author ran mob_ai arrow damage_source living_hurt server_knockback mob_attributes. CI green, parity runs on the head merged into main.
🤖 Agent: read the diff: an attack or interaction now holds back a move queued behind it until the next tick, as PlayerAction and UseItemOn already do, so the game sees the player as vanilla's in-order packet handling does (falling crit). lan_bot test added; CI green, parity runs on the head merged into main.
#4186's precipitation/temperature/downfall/wetness come first in Frame (they are on main);
this branch's frame-motion fields follow at 416..512. Reference regenerated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Three things kept tools/lan_e2e.py (and agent_control_e2e.py) from running
on Windows:
- The prepared resources were linked into the scratch home with os.symlink,
which needs Developer Mode or an administrator (WinError 1314). The new
lab.link_dir falls back to a directory junction there, which needs
neither; shutil.rmtree removes a junction without touching its target.
- agent_control_e2e.game_env ran `vscreen up`, which doesn't exist on
Windows. Windows has no virtual screen, so it takes plain lab.game_env,
as macOS does.
- `horizon-ctl launch` never returned when its output was captured:
Windows hands a child every inheritable handle of its parent, so the game
kept the caller's stdout pipe open until it quit. horizon-ctl now clears
the inherit flag on its own standard handles before it starts the game
(horizon_threads::keep_stdio_from_children). Linux and macOS are
unchanged.
Fixes#5274
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's 45c36d872 added RiddenBoat for the client's own fluid box. The
served player's tick (lan_actions.rs) now fills the same type from the
ridden boat, so the server-side air supply follows it too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The hook asks for its API number with tools/native-hook-fragments/entity_join_level.json
(#5161); NATIVE_HOOKS_API, the histories and native-hooks.json are main's again.
- A mod canceling an item's join no longer crashes a throw: spawn_item's callers
touch the new item only if it joined (just_spawned_item), not whatever item is
last (throw_from_eyes unwrapped it; allays, shearing and /give's overflow
edited an unrelated item).
- An End gateway keeps its traveller in the same level (teleportSameDimension), so
the item doesn't join again on arrival (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tick_mobs is half of a crowded server's tick, and its two big parts
(NaturalSpawner and the entity ticks) move independently with the players'
spread. Two nested spans let the slow-tick log and horizon-report tell them
apart; with spans off they cost one relaxed load each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's AbstractContainerMenu.clickMenuButton(Player, int) reaches the player's carried stack;
native_menu_button(pos, entity, id) does not, so a mod's widget outside the slots (a tank taking a
bucket) cannot take or give what the player carries. native_menu_button_v2 fires right after it with
`carried: &mut Option<ItemStack>`; a change is written back and sent to the player (SetCursorItem).
Single player's player is a guest of its own host, so one path covers it and LAN guests.
native_menu_button is unchanged (frozen). The API number comes from the fragment when stamped.
Closes#5246
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: read the diff: native-mod builds run under an ASCII path (resolved data dir, 8.3 short name, or a ProgramData folder named by hash) so MinGW-w64's ANSI tools work for a non-ASCII Windows user name (#5189); tests for the prefix stripping and ASCII check. Approved as code owner; CI green.
🤖 Agent: native mods' block entities reach clients in the chunk and BlockEntityData (empty update tags dropped as BlockEntityInfo.create does); arismoko's replaced-block case fixed and checked in game; main merged and the hook API renumbered to 43. Merging once CI and parity pass.
🤖 Agent: merged main into the branch: main's dedicated event records (chat, join, leave) moved into the shared lan_process_events, and main's flush order (#4765) kept with the packet gate reset at the end of tick_lan. Merging once CI and parity pass.
🤖 Agent: merged main into the branch (item_tooltip.rs: kept both the tropical fish bucket lines and main's map_id_lines). 15 mob fixes, rdbtCVS's points fixed; merging once CI and parity pass on this head.
🤖 Agent: mod registry: a mod may turn native once; a retry takes up its own form branch, proven by an HMAC submission tag under its own derived key (elythepreciouscat's fix for rdbtCVS's point). Registry fmt/clippy/tests pass.
🤖 Agent: #4923 finished: calibration ends off the Options screen and its fullscreen is not saved as the option (rdbtCVS's review). HDR off by default; merging once CI and parity pass.
🤖 Agent: one macOS keychain item per account (the 1 KB split was a Windows Credential Manager limit); rdbtCVS's point fixed in f7f274772: a refused delete writes the whole old layout back, tested halfway and at once. verify green.
🤖 Agent: read the diff: the three recipe helpers now return nothing only on a client with no player, so a server's Game answers them. Tested in game by arismoko (before/after), approved by rdbtCVS; CI green, parity runs on the head merged into main.
🤖 Agent: CI green on 8c913dff6 (clippy, launcher, tests, python, verify), merges cleanly on main after #5217. Telemetry server checked in its own workspace (fmt, clippy -D warnings, tests); the status ping was checked against the real official server. Official SMP count in the launcher and its history on the telemetry server.
Wire ja.json into Language (code ja, name 日本語), translate the native
mods strings ja.json and pt-br.json lacked and drop the five strings the
launcher no longer asks for, so every language has every string again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A failed session join goes on for a LAN entry only when an account is
signed in, as vanilla's client always has one (Mojang's rules: no play
without an account that owns the game).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes on top of the hue strip:
- A drag released outside the strip now writes settings.json too. on_mouse_up
only fires while the element is hovered, so the accent stayed in memory and
was lost on restart; on_mouse_up_out covers the release off it, and
hue_dragging keeps a click elsewhere on the page from saving.
- The contrast test sweeps every half degree from 0 to 359.5 on both bases
instead of two hand-picked hues, so the guarantee is tested where it is
claimed.
- `settings accent <value>` names the degrees a value may be, not only the
seven names.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
Resolve title.rs (Outcome::Connect gained `lan`), handle main's new
graphics_ui::Action::Calibrate in the wheel and key paths, and let
Escape close Video Settings on the first press.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main added three strings to every language (`finding_friend`,
`opening_world`, `cancelling_world`); es_es keeps its 198 translations
and gains those three, for 201/201 keys in en_us order.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Ports the 26.3 DebugEntry* classes the debug options screen listed but
the overlay never drew: memory, targeted block/fluid/entity state and
tags, chunk render/source stats, entity and particle stats, player
speed, light levels, heightmaps, biome, local difficulty, day count,
spawn counts, sound mood and cache, post effects and GPU utilization,
in DebugScreenEntryList's order (by identifier) with vanilla's text and
priority/line/group placement. The integrated server reports the lines
that read the server level.
Renderers: water levels, heightmap, collision boxes, block and sky light
levels, solid faces. The list's scrollbar can be dragged, and
debug-profile.json gets vanilla's three datafixes by DataVersion.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`x / 0.0` and `sqrt(-1.0)` are GL's infinity and NaN at run time (Mellow's vec4(-1.0 / 0.0),
Bloop's sqrt of negative constants). naga's frontend folds them into constants and refuses the
result, so the program was skipped. They now divide by `hz_zero`, a private float the frontend
can't fold: `x / hz_zero`, and `hz_zero / hz_zero` for the square root.
texelFetch1D/2D/3D (GL_EXT_gpu_shader4's names, Bliss) become texelFetch, as texture2D and
the other old names already do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts in options_ui.rs (main's HDR calibration page beside the profile
page) and lib.rs's re-export list: kept both. Escape on the list now sets
choice = Some(None), as the Cancel button does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test picture and borderless window stayed when the screen left Options
without a Close event (a server opening the death screen), and
observe_fullscreen saved the calibration's own fullscreen as the player's
Fullscreen option. Review points from #4923.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mob_team scanned every mob for each call, and mob_teams (every tick) and the
name tags and outlines (every frame) call it once per mob: O(n^2) whenever a
scoreboard team exists. The own team is checked first, the tick builds the
tamed owners in one pass, and callers holding the mob pass it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AABB.ofSize(center, size, size, size), not the box grown by its size on
every side, which let a buried orb jump three times as far as vanilla's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#3727's Audio::share predates #4527's device tracking fields, and #4515's stew
particles call lan_send_particles with #4761's old arguments.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep command signing disabled until the server command tree arrives, count chat length in UTF-16 units, and acknowledge delayed chat during configuration transitions.
Co-Authored-By: Codex <[email protected]>
Vanilla's DownloadedPackSource puts active server packs in the selection
as required, fixed at the top (pack.source.server). Horizon already
applied them but never listed them; they also stay out of options.txt.
Fixes#5218
Co-Authored-By: Cursor Agent <[email protected]>
🤖 Agent: CI green on eccfe3d9b (clippy, launcher, tests, python, verify). Telemetry server checked in its own workspace (fmt, clippy -D warnings, tests). Exact live count, the live count's history and /v1/live/history; #5235 is stacked on it.
Ludovic (2026-10-31) asked to add x3 кто (Discord vladislav_hagen,
GitHub Qwerner88880, the native-mod hooks author).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A kill leaves the old id in the UUID map, so the first match could be an entity the level no longer holds. on owner follows getLivingEntity, which does not return a marker or a display.
Co-authored-by: Cursor <[email protected]>
Ludovic (2026-10-31): replace the old invite everywhere. The launcher
reads launcher/links.json from main, so installed launchers pick it up.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The detached encoder for saved chunks past the view distance (lan::far)
keeps the mods' update tag hook, which may then run off the server thread.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeutralMob.readPersistentAngerSaveData hands the saved anger back through
Mob.setTarget, and setTarget runs asValidTarget, which turns a target the
mob cannot attack into no target at all. IronGolem.canAttack refuses every
player when a player built the golem. Horizon's world load wrote the target
directly, so a player-built golem reloaded from a save kept its builder as
its target, and its melee goal never re-checks canAttack.
The golem rule becomes Mob::built_by_player, used by both the restore and
spares_players, with tests pinning the targets a built golem does and does
not take and the crack sound it already plays.
Preserve the client's destroyBlockPos after a completed prediction so a replacement at the same position can resume the retained target, matching the isDestroying split.
Co-Authored-By: Codex <[email protected]>
handleBlockBreakAction writes destroyPos in the branch that keeps digging,
after the creative destroy and the instant-mine check, so a creative or
one-hit START leaves no block behind for a later STOP to break.
Reported in review: destroy_pos was set for every accepted START.
MultiPlayerGameMode.stopDestroyBlock opens with `if (!isDestroying) return`,
so it zeroes the progress once, on the stop that ends a running break; every
later missed pick is a no-op, the dig identity survives, and the client keeps
accumulating until the progress passes 1.0 and the STOP it sends is measured
from the first START.
Clearing the accumulated progress on every miss restarted the dig each jump,
so the client never reached 1.0 and the server's retained clock was never
read. The LAN regression now breaks the stone in 100 ticks with the dig
accumulating to 27 ticks across falls.
The verifier asked for the server-thread regression this change needs: a
survival guest under a block it can only reach near the jump apex, holding
attack and jumping, with the block's dig clock measured from the first
START.
It currently fails: the block survives 308 ticks of held attack. The
client aborts on every fall and the STOP that would read the retained
clock is only sent when the client's own progress completes, which a
five-tick in-reach window cannot reach. Reporting rather than papering
over it.
tests/test_remote_mining.py compiles an extracted copy of
stop_remote_mining against a stub, so the stub needs the new flag and its
assertion must stop expecting a stop to clear the target, which vanilla's
stopDestroyBlock does not do.
A click on the block a stopped break still names has to start the dig
again, so the server re-anchors its clock, while a held button carries the
same target on and keeps it. That is vanilla's "isDestroying &&
sameDestroyTarget" guard: the target test stays pos plus item, and the
running flag is what the start guard adds.
The STOP decision moves into a small pure function over the retained
destroy_pos, the elapsed ticks and the block's per-tick progress, so the
0.7 break, the delayed arming from the same anchor and the cases that must
not inherit a clock are covered without a Game.
Vanilla keeps ServerPlayerGameMode.destroyPos and destroyProgressStart after
an abort or a STOP that arrives out of reach: only a START writes them, the
abort clears isDestroyingBlock alone, and tick does not check distance. On
the client, stopDestroyBlock keeps destroyBlockPos and destroyingItem, so
sameDestroyTarget still matches after a fall, no second START is sent, and
the server's clock keeps running. That is the "break it a bit each jump"
behaviour the report asks for.
Horizon had the formula but not the state: the ABORT and too-far STOP
branches cleared the dig, guest_stop_digging required an active dig, and
the client's own target died on every stop, so each landing re-sent START
and re-anchored dig_start.
The server now retains destroy_pos (written only by an accepted START,
cleared by a respawn) and measures a STOP from the START that named the
block. The client splits isDestroying out of the target, so the target
survives a stop while a running break is still the thing that suppresses a
new START.
- horizon_telemetry::server_status: the server-list status ping (handshake, status
request, JSON answer) and OFFICIAL_ADDRESS, the one place the official server's
address is kept (the launcher's and the game's server lists use it).
- Launcher: "· M on the official SMP" beside "N playing now", pinged once a minute on its
own thread with a 3-second timeout, hidden when it doesn't answer; it opens the Servers
page.
- Telemetry server: pings the official server every minute (HORIZON_TELEMETRY_OFFICIAL_SERVER)
and keeps its player count as the "official" series, like the live count;
GET /v1/live/history takes series=players|official and answers the latest minute too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GL drivers evaluate a float in #if as a number (I Like Vanilla's #define AMBIENT_CEL_AMOUNT 0.0
tested with #if AMBIENT_CEL_AMOUNT != 0), and Iris loads such packs; Horizon refused the line and
skipped the program. The evaluator now computes in f64: float literals count, a fraction is true,
and / and % stay integer division unless a float literal appears.
A condition that still can't be evaluated already reads as false, but it was a diagnostic, so the
whole program was skipped (Pastel). It is now a warning in the loader's notes, and the program
loads with that branch off, as Iris runs those packs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
For a public server's SMP mod: native_sidebar shows one player a sidebar of
up to 15 lines (a text, and one where the score would be) that only their
client holds, native_tab_list sets the lines over and under their Tab
list, native_tick_stats reads /tick query's rate and average tick time,
and native_sleep_ignored leaves a player out of players_sleeping_percentage's
count until they are in bed. Server players now carry yaw and pitch, for
telling who is away from the keyboard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlayerHeadItem.getName answers block.minecraft.player_head.named with the
profile's name, so a head given for someone reads their name rather than
"Player Head". Horizon showed the plain item name in the tooltip and in
command feedback.
A stem has no block item, so vanilla's pick-block returns the seed it grew
from. Horizon fell back to the block's own item, found air and picked
nothing, so middle-clicking a grown pumpkin or melon stem did nothing.
ShearsItem.useOn on a growing-plant head below its top age plays
block.growing_plant.crop, sets the head to age 25, fires a block change,
wears the shears and succeeds. Horizon had no such arm, so shears on kelp
or a vine did nothing.
The arm sits with the pumpkin carve and the beehive shear and reuses their
helpers.
`es_es.json` had 13 of the 198 keys `en_us.json` ships, so the other 185
fell back to English on every Spanish screen. Translate them, keeping the
13 existing strings and every `%s`/`%.2f` placeholder.
The file also serves `es_ar`, `es_cl`, `es_ec`, `es_mx`, `es_uy` and
`es_ve`, which `lang.rs` aliases to it.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
The same lost key release that latched sneak also leaves the physical
modifier down, and a screen reads the physical state: every inventory
click becomes a shift-click, every throw a whole-stack drop.
Tests a_home_behind_a_link_takes_its_packs and a_link_below_the_base_is_refused imported std::os::unix without #[cfg(unix)], breaking cargo test on Windows.
Fixes#5213
Co-Authored-By: Antigravity <[email protected]>
Ports DebugOptionsScreen, DebugScreenEntries and DebugScreenEntryList
from 26.3: every entry can be off, in the overlay or always shown, the
default and performance profiles, debug-profile.json beside options.txt.
The overlay, F3+B, F3+G and the 3D crosshair follow the entries, and the
screen's GUI Scale button edits debugGuiScale.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use a raw mouse click instead of the deferred attack agent task, which releases movement keys before reporting. Prove the click landed from the victim's server hurt log while measuring the first post-hit speed sample.
Co-Authored-By: Codex <[email protected]>
native_block_entity_sync(pos) puts the position in the level's block log,
as Level.sendBlockUpdated marks ChunkHolder.blockChanged: the hub sends the
block and the block entity's update tag to the players who see its chunk,
once per position a tick. NATIVE_HOOKS_API 24. A lan_bot test checks the
chunk's tag, one BlockEntityData for two syncs, and none without a tag.
Refs #4615
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- GET /v1/live serves exact playing, lan, versions and platforms (live::round is gone);
rare versions are still summed under "other".
- The telemetry server writes the live count down once a minute (totals only: playing,
lan, per platform) into live_minute (7 days), live_hour (90 days) and live_day (kept).
- GET /v1/live/history?range=24h|7d|30d|all serves the series ([start, average, peak]),
the range's peak, the all-time peak and the average per platform, cached a minute, on
the live count's rate limit.
- The launcher's "N playing now" opens playhorizon.dev/stats.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Dispatch HealthEvent::DamageBroadcast and MotionEvent::HurtSound for guests in leave_guest and guest_server_tick (along with record_combat and wear_armor in leave_guest) so every unblocked player hurt immediately sends ClientboundDamageEventPacket with its exact DamageTypeId and broadcasts the hurt sound to nearby players.
- Run tick_guests and the deferred guest attack/effect flushes before lan_flush_effects and lan_swings in tick_lan so captured sounds and mob damage events from guest turns are flushed on the same tick and in the right dimension.
- Attack live mobs directly via attack_mob in guest_attack after within_attack_range instead of turning the guest's view to the mob's center with look_at_entity and re-running raycast pick checks.
- Handle ClientboundSoundEntityPacket in remote.rs and prefer inline RegistryData effects in hurt_sound_for.
Fixes#4330
Co-Authored-By: Antigravity (Gemini) <[email protected]>
Keep the dynamic per-map bounds introduced for prefilled heightmaps, but restore the shared MAPS loop so the optimization does not duplicate the two heightmap paths.
Co-Authored-By: Codex <[email protected]>
random_uuid() seeded each UUID from the clock alone, so calls in the same
clock tick (a microsecond on macOS) returned the same UUID: a pack of mobs
generated together shared one, and a vanilla client keeps one entity per
UUID, so it never moved the others. Seed once, then step one shared
counter through SplitMix64.
Fixes#5207.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured with a probe pack in Minecraft 26.3 + Iris 1.11.7 against Horizon on the same world and
camera:
- gbuffers_skybasic, gbuffers_skytextured and gbuffers_clouds get full light, lightmap
coordinates (240, 240); Horizon gave (0, 240).
- gbuffers_skybasic's normal is (0, 0, 1); Horizon gave (0, 1, 0).
- Clouds get their face's normal (down for the bottom, out for the sides); Horizon gave up for
every face. The cloud mesh now carries it (w 0: the game's own clouds still shade by colour).
- blockEntityId is -1 outside block entities; Horizon gave 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`perf_bench.windows_version` asked subprocess for text, which decodes with
the locale encoding: `pr_ready` runs the tests with PYTHONUTF8=1, so the
reader thread decoded `cmd /C ver`'s OEM output as UTF-8, failed, and left
`stdout` None. `test_report_without_a_load_average` then raised, so every
Windows contributor's self-check showed `Python tests: fail`.
Decode the command's output as OEM, with replacement for stray bytes, and
keep the `platform.version()` fallback for an empty result. The regression
test runs the import in a child process with PYTHONUTF8=1 and checks it still
prints a Windows version; it fails on main with a CalledProcessError.
Fixes#5201
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
🤖 Agent: verified: rebased on main after #5166/#5186, tools/server_admin_e2e.py passed (45 checks: the console attaches, runs say/list, shows their lines, detaches), pr_ready green, CI green.
🤖 Agent: proven on fdfa4fbfa (rebased on main 125875b84): pr_ready --quick green (1427 tests, clippy, fmt, 495 Python tests); CI green (parity 35/35 after a rerun of the known lunge_motion flake, untouched here); anticheat_e2e default set (combat with no server.toml, ground, off, prediction, timer) green; a real vanilla 26.3 client hits a cow with combat on and is never flagged; MSPT 60 bots: anticheat off 3.68/3.69, default 4.11/4.13, 0 flags.
BlockEntityInfo.create sends no tag when getUpdateTag is empty, for every
block entity. The client replaces a mod block entity when the block changes
to another one with the same block entity id, as LevelChunk.setBlockState
removes the old block's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A release does not always reach the mapping: a screen reads presses only, and
an unfocused window is given no release at all. The latch then keeps the player
sneaking or sprinting until the key is tapped again, which is what a short
alt-tab did on Wayland. The modifier state is what the keyboard itself asserts,
and winit reports it before any screen or gameplay handler runs, so a per-frame
check settles the question instead of waiting for a release that already got
away.
A mapping that toggles, or one a pad latched, is left alone: set_down(false,
needs_toggle) is a no-op for those, and a toggle the player asked for is meant
to outlive the key. A mapping rebound away from its modifier is skipped too,
since the modifier says nothing about it.
A cauldron's drain now hands out only whole thirds that fit within the
amount asked, as IFluidHandler.drain's maxDrain: asking 1 or 500 of a full
one takes one third (334) or none, so a caller moving into the room it has
never receives more than it can hold. Its fill compares each level's whole
third against what is left of the amount instead of summing, so a huge
amount can't overflow. A save with more tanks than the block now declares
reads the tanks that still exist instead of all empty, which the next write
would have made permanent.
Review: Aria's agent swarm (Dyna) in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
BlockItem.useOn falls back to Item.use for a consumable whose placement
failed. The server already did, but a server client ended the click
without starting the use, so sweet berries, glow berries, carrots and
potatoes were eaten with no animation, and a released click couldn't stop
it. In adventure mode ItemStack.useOn passes, so UseItem now goes out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Windows user name like "Кириченко Павло" puts the launcher's data under
a non-ASCII path, and the build's MinGW-w64 tools — gcc, as, ld, dlltool —
open files through ANSI APIs, so linking failed with exit code 101 and a
player's own redirection of %APPDATA% changed nothing (dirs::data_dir
reads the known folder, not the variable).
Every path the build's tools see — the toolchain, its Cargo home, the
native/ build tree and now the tools' temp dir, where gcc, as and
cargo install put their scratch files — comes from backend::build_dir:
the data folder resolved (so a junction to a drive with an ASCII path
already works), and when that still isn't ASCII, the folder's short (8.3)
name, or a folder of ProgramData's named for it.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
On macOS, cpal's output_devices() opens every device to see whether it
plays: about 90 ms with twenty devices, once a second on the render
thread. Naming the devices takes 2 or 3 ms, so card_fingerprint now
returns their names there, and the list is looked at only when they
change, as on Linux.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
JSON-RPC 2.0 on stdin and stdout, one message per line, as horizon-ctl mcp.
Each tool is one control API call on the server running in the folder, with
the token read from .horizon/api.json: status, players, run_command, logs,
events, kick, ban, pardon, ban_ip, pardon_ip, op, deop, whitelist, backups,
restore, get_config, set_config, start, restart and stop. stop, restore and
delete_backup need confirm: true; tools that send someone away or can't be
undone carry destructiveHint. The admin e2e bans, pardons, reads events and
is refused an unconfirmed stop through it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: resource packs install for a home behind a link (Silverblue /home, macOS /var), asked for by the orchestrator. directory(base, path) accepts links at or above the owning folder and still refuses a link below it; the canonical path must stay inside the canonical folder. 2 new tests; #5094's test workaround reverted (the tests run under the linked /var again). pr_ready --quick green (port-pack 99, launcher 316); CI green. Builds on gravitacia's #4579 check, credited there.
A catch-up plays every tick recorded since the last one, and `play` moves the
replay to the position each tick claimed. A tick farther than the tolerance
was counted, but a tick after it that agreed from the spot the lie claimed
still replaced `agreed`, so `guest_replay` read a setback point already at the
cheated position: a one-tick lie arriving in a burst with the honest tick
after it counted once and set nothing back.
A catch-up that counts a tick now keeps the agreed point where it was for the
rest of that catch-up. The regression test plays ticks 20 and 21 in one
catch-up, tick 20 claiming +3.0 and tick 21 honest from there, next to
`a_cheat_counts_from_where_it_started`.
Fixes#5132
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
The recent log, then every new line as it happens (the control API's event
stream), a header with the TPS, MSPT, players and RAM refreshed every second on
a terminal (an ANSI scroll region; no TUI framework), and a command line whose
lines run as console commands. Ctrl-D detaches and leaves the server running;
when stdout isn't a terminal it prints plain lines. The admin e2e attaches
through a pipe and runs two commands.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A position's placement (isSpawnPositionOk) is tested before its spawn list
is looked up again (canSpawnMobAt: structures reaching it, the zoomed biome,
a string-keyed list). Both only read the level, so their order cannot change
the answer, and the few block reads of the placement turn most positions
away. The placement is read from a table by species, not by an id built
with format! after a linear search (from server-tick-2's
perf/spawn-placement-first).
- LocalMobCapCalculator's players near a chunk are runs of one list, not a
Vec per chunk, and the counts per player a Vec by player index, not a hash
map: thousands of chunks ask every tick on a full server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The fixture exports vanilla's colours, whose alpha is 255: the fluid face in
the high nibble is Horizon's own, as the emission in the low one is. A test
now checks the material stage's decode against the mesher's encoders.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
🤖 Agent: verified: pr_ready green (36 harnesses affected, all passed), the e2e's 39 event checks passed on the Mac mini; CI caught a race in the stream test (an event between the headers and the first cursor read was skipped), fixed in bef1eff27, CI green.
🤖 Agent: verified: rebased on main after #5165, pr_ready green (64 horizon-server tests), tools/server_admin_e2e.py passed with the CLI checks on the Mac mini, CI green.
resourcepacks::directory refused a link anywhere up to /, so a launcher
home behind one (Fedora Silverblue's /home -> /var/home, macOS's /var ->
/private/var) installed no packs. It now takes the folder that owns the
packs (the work folder or the profile home): links at or above it are the
player's layout and are accepted, every step below it must be an ordinary
directory, and the canonical path must stay inside the canonical folder.
Builds on gravitacia's check (#4579), revised by levzzz5154 (b49925118).
#5094's test-only workaround (resolving the temp dir) is reverted: the
tests run under macOS's linked /var again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MobEffectInstance.CODEC saves ambient and the NBT loader dropped it. A mob keeps
ambient_effects now: loaded, saved, kept through conversion and expiry,
set by /effect give as an instance with ambient false, and the faint
swirls and DATA_EFFECT_AMBIENCE_ID follow it. add_effect_with applies
MobEffectInstance.update's rules for the flags of an effect already there, and
the cloud applies the creeper's effects with theirs.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A short alt-tab (under 500 ms, so no pause screen opens) leaves the player
sneaking until Shift is tapped again. The latch in `toggles` survives the focus
loss and #4139's `sync_toggle` puts ShiftLeft back into `keys`, while an
unfocused window never receives the key release that would clear it.
Release each mapping that does not toggle, so a held sneak, sprint, attack or
use cannot survive a focus change, and pass the option through for the ones
that do: `set_down(false)` is a no-op for a toggling mapping and for a pad
latch, so the latched state #4139 was protecting still crosses the alt-tab.
ServerLevel.sendBlockUpdated compares the old and new collision shapes once
per write and only then asks each navigating mob's shouldRecomputePath. The
mob loop rebuilt both shapes for every pathing mob and every write; the shapes
use no entity context and the world is fixed during the loop, so the list of
changed positions is the same for every mob.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ludovic, 10-31: turn the anticheat on. Checks a vanilla client can't trip act by default; the
others run but only log until real players show no false positive. `[anticheat] enabled = false`,
or `enabled = false` in a check's table, turns them off.
New `combat` check, acting by default: vanilla's client attacks only from Minecraft.startAttack,
once a client tick, on the entity of the tick's hitResult, and swings (ServerboundPunchPacket) with
each attack. A client tick with more attacks than swings, or a second entity attacked in the same
tick, flags; from `setback` (2) on the attacks are dropped, `kick` 20. The swing counts before or
after the attack (older clients behind a version proxy swing first).
`timer`, `ground` and `prediction` default to setback, alert and kick 0 (log only); a log-only ground
check keeps the claim. The thresholds once trusted are in docs/anticheat.md.
horizon-bots swing after each attack as vanilla does; --no-swing and --attack A,B are the cheats for
tools/anticheat_e2e.py --check combat, which runs with no server.toml (the defaults).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An event emitted between the reply's headers and the stream's first read of the
cursor was skipped (CI caught it in a_stream_sends_what_happens).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`players [--json]` shows who is online, the operators, the whitelist and the
bans (a stopped server's from its files); `kick`, `ban`, `pardon`, `ban-ip`
and `pardon-ip` moderate the running server through the control API. The
admin e2e runs them too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On macOS, cpal's output_devices() opens every device to see whether it
plays: about 90 ms with twenty devices, once a second on the render
thread. Naming the devices takes 2 or 3 ms, so card_fingerprint now
returns their names there, and the list is looked at only when they
change, as on Linux.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The layer list and the preset list drew the flat item/<name> sprite, so a block
showed as the first texture of its model. They now resolve the item and draw it
as GuiGraphics.renderItem does (isometric for blocks, buckets for water and lava),
clipped exactly to the list; the sprite stays as the fallback.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
dealDefaultKnockback takes the direction from the projectile's motion
(Projectile.calculateHorizontalHurtKnockbackDirection, negated), not from its
position. The mob takes it from Mob::projectile_motion, set where a
projectile's hit is dealt (arrows, thrown items, the trident), for every mob
and for the sulfur cube's SulfurCube.knockback and LivingEntity.knockback
branches. Checked on six blows against the vanilla helpers, two from an arrow
with its motion.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A shaders/ pack's terrain program reads each quad's face normal and mc_Entity only in flat draws
(PackAtlas::terrain_inputs(flat)); otherwise it gets (0, 1, 0) and -1. Flat draws existed only
on Metal and Intel Gen11's Vulkan driver, so on NVIDIA and AMD every pack lit all terrain as if
it faced up and knew no block from block.properties. Flat draws now exist wherever the device
supports them; frames without a pack keep per-section draws outside Metal and Gen11
(flat::for_vanilla), so only a pack's terrain and shadow pass draw flat there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
service::tests::units_run_the_supervisor expected the agent's log path
as `/srv/R&D/logs/supervisor.log`, but `launchd_plist` builds it with
`dir.join("logs/supervisor.log")`, which Windows prints with a `\` where
the join adds a separator. The code is right (the launchd agent only
exists on macOS); the test assumed the separator, so it failed on Windows,
where CI only builds and runs no tests.
The test builds the expected string from the same `Path::join`, with the
`&` escaped as `&`, and keeps the literal macOS path under
`#[cfg(unix)]`.
Fixes#5168
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
HoglinAi and Zoglin initFightActivity (MeleeAttack 40 and 15), AbstractCubeMob
push and dealDamage (hurt_touching_golems), EndCrystal.defineSynchedData
(both sides), Enderman.dropCustomDeathLoot (carried_block_drops) and
Strider.getDismountLocationForPassenger (strider_dismount_location). Each
read against the vanilla method.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Switching from spectator to creative always left the player flying. In
ServerPlayerGameMode.changeGameModeForPlayer a flying player who leaves
spectator mode stops flying when isInRangeOfGround holds: no collider
touches the player's box and Entity.getAvailableSpaceBelow(1.0) is under
one block.
Orbs had no health and ignored blasts. Vanilla gives an orb 5 health
(ExperienceOrb.health) and ServerExplosion.hurtEntities calls
ExperienceOrb.hurtServer with the blast damage, which takes it off as an
int and discards the orb at 0 or below; pushFromExplosion then moves the
orbs that stay, from the eyes at 0.85 of the 0.5 high box. The health is
saved and loaded as "Health". Fire and lava damage are not ported yet.
Farmland that dried out or lost its support, and a path with a block over
it, became dirt and left whoever stood on it sunk a sixteenth into the
new block. FarmlandBlock.turnToBaseBlock and PathBlock.turnToDirt call
Block.pushEntitiesUp (Block.java): every entity touching the collision
the new state adds is teleported up so it rests on it. The level now
reports the swap and the game lifts the players, guests, mobs and items
the same way.
Only the player broke an egg by stepping on it. TurtleEggBlock.stepOn
(TurtleEggBlock.java) runs for every entity on the ground that does not
step carefully: canDestroyEgg refuses turtles and bats and needs mob
griefing for other living entities, then the level random rolls 1 in 100
and decreaseEggs plays the break sound (0.7, 0.9 + random * 0.2) and
removes one egg or the block. A mob now rolls that in its block effects
and the game cracks the egg.
A wind charge put a lit candle out with the fire-extinguish sound and no
block-change game event. AbstractCandleBlock.onExplosionHit calls
extinguish(null, ...) (AbstractCandleBlock.java): setBlock lit=false with
flags 11, the candle.extinguish sound at 1.0/1.0 and a block_change game
event; the smoke is a level.addParticle, which a server level ignores.
Use the world's extinguish_candle, which does exactly that.
A wind charge opened a gate by running the click path, which swings the
gate's facing towards the player when it faces them. Vanilla's
FenceGateBlock.onExplosionHit (FenceGateBlock.java) only toggles OPEN of
an unpowered gate with setBlockAndUpdate, plays the open or close sound
at pitch random * 0.1 + 0.9 and posts block_open or block_close with the
old state as context and no entity.
A ravager only trampled wheat, carrots, potatoes, beetroots and
torchflowers; PitcherCropBlock.entityInside (PitcherCropBlock.java) does
the same for a pitcher crop: with mobGriefing a server level destroys the
block with drops for any Ravager inside it.
Using an op's spawn egg with entity data on a trial spawner only set the
mob id and dropped the data. TrialSpawnerBlockEntity.setEntityData runs
TrialSpawner.overrideEntityToSpawn(TypedEntityData), whose
FullConfig.overrideEntityData / TrialSpawnerConfig.withSpawning build each
config's spawn data from the egg's data (TypedEntityData.loadInto), then
the state goes to inactive.
Horizon had only the empty-hand take-out of FlowerPotBlock, so no
potted plant could be made. FlowerPotBlock.useItemOn (FlowerPotBlock.java):
an empty pot clicked with a block item that has a potted variant
(getFullPot) becomes that pot, sends the block_change game event,
awards the pot_flower statistic and consumes one item; any other item
tries the empty hand.
MobEffectInstance's copy keeps isVisible, so an effect the creeper had
with hideParticles stays hidden on the player or mob the cloud affects.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ServerEntity.sendChanges (ServerEntity.java) updates a frame's map only when the framed item is a MapItem; lan_map_updates took any framed stack with a map_id component, so /give stone[map_id=1] in a frame got map packets. That check followed a NeoForge-patched decompile, which replaces the test with if (true).
native_entity_leave_level(dimension, entity, kind) fires as vanilla's
onTrackingEnd: wherever an item entity or orb is removed (picked up by a
player or a guest, merged, despawned, burnt, blown up, killed, taken by a
hopper or a mob, gone through a portal) and when its chunk unloads,
including a level closing empty. Single removals go through
remove_dropped / remove_orb; the ticks and sweeps compare IDs before and
after. Not told at a server stop, as in vanilla. API 41.
Part of #5162 and #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SulfurCube.knockback reads source.getEntity(): the player, a mob, a
projectile's owner. Game::attacker_aim gives its eye, view direction and feet
for the blows the cube takes from them (melee, spear, mace, arrows, thrown
items, trident, a mob's attack), and the rotation by height uses the
entity's feet, not the projectile's position. As hurtServer does, a blow of
a type tagged no_knockback (explosions, falls...) does not knock the cube,
and one with no causing entity goes through LivingEntity.knockback.
Checked against vanilla's helper bodies on six blows, two from an arrow.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
machine_screen() answered Hopper or Dispenser by the menu's type even when
a mod drew its own screen over it, so the player inventory and the menu's
slots sat where HopperScreen puts them (inventory at y 51), not where the
mod's screen said. It now answers none while a mod screen shows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clearing image.* happened after the shadow and shadowcomp programs ran, wiping what they
stored; it now opens the frame. A bufferObject.* larger than the device binds is made at the
largest size it takes, with a warning, and its per-pixel size no longer overflows.
gbufferModelView holds the nausea spin the world turns with; the hand's projection now takes
it out again, as vanilla's hand never spun. Tests cover bufferObject.*, image.*,
iris.features.* and buffer blocks with and without a binding.
A composite or deferred program that samples colortexN and stores to colorimgN now samples
a copy taken before it runs, as compute programs did; it sampled an empty texture. Whether
vanilla's sky and sun are drawn depends on the pack having the program, not on it building a
pass.
workGroups needs three numbers and workGroupsRender two (or three, the last taken as 1);
another count fails the program with that message instead of running it 1x1. size.buffer.*
factors must be finite and positive, and every size resolves to at most 8192.
Its size is checked against the device's largest texture of its dimension and its texels
against that size before anything is created, so a pack asking for too much loses that
texture instead of failing validation at upload.
- bufferObject.<n>: a storage buffer of n bytes (or bytes per pixel of the render size). Buffer blocks are bound in set 4 and lose their access qualifiers so that every stage binds them alike.
- image.<name>: 2D and 3D images of the pack's, with their format, size (or the render size scaled), sampler name and whether they clear before each frame.
- iris.features: SSBO and CUSTOM_IMAGES are supported.
- A 3D image (iimage3D) is declared as 3D.
- naga: atomicAdd and the other atomic functions on buffers and shared memory, imageAtomic*.
Nothing binds them yet.
- Compute programs dispatch before their composite-style program, over workGroups or workGroupsRender times the target's size; their images bind the side colortexN reads, and a pass that samples and stores one buffer samples a copy taken first. The MC_GL_KHR_shader_subgroup_* macros are defined when the device has subgroups.
- size.buffer: a pass draws at the size of the buffers it writes, and those buffers clear apart from the blit.
- A gbuffers_skybasic or gbuffers_skytextured that writes nothing still hides vanilla's sky or sun and moon, and the world's background is colortex0's clear colour when the pack sets it: vanilla's fog colour was added to Photon's sky, and hid its stars.
Photon v1.4 at 2560x1440 on an M5: 8 to 50 FPS (GPU 125 to 21.5 ms), its sky light from the compute program, quarter-resolution clouds.
- Compute programs (<name>.csh, then _a to _z) are translated and carried before their composite-style program: images (colorimgN in its own format), workGroups and workGroupsRender, shared memory, barriers and subgroups. Compute-only programs are noted as not supported yet. Nothing dispatches them yet.
- size.buffer.<buffer>: a size of its own, relative to the render size (floats) or in pixels (integers).
- shaders.properties and the program switches see the pack's options at their defaults (switches on, value options): Photon sizes its cloud buffers only under #if CLOUDS_TEMPORAL_UPSCALING == 4.
- naga: subgroup operations, memory barriers, and barrier() as a work group barrier.
- The custom textures texture.* names are uploaded and bound to the samplers they name: one texture per dimension of a unit (Photon's 3D scattering LUT under depthtex0), and the lightmap and block atlas by resource location. A sampler with nothing to read gets a stand-in of its own dimension instead of failing its pipeline.
- Outputs past the last buffer RENDERTARGETS names go nowhere, as in GL, instead of an attachment the pass lacks (a crash).
- A shader the driver's compiler refuses (an internal error) skips its program as a validation error does.
Photon v1.4 now loads all 27 overworld programs and draws its lighting, shadows, fog and volumetric clouds.
A prepare, deferred, composite or shadowcomp program of compute stages only (.csh without .vsh and .fsh) runs its stages in order where the program would draw. shadowcomp programs also run the compute stages they carry.
- IS_IRIS is defined: packs take Iris's extensions (compute programs, images, custom textures) only where it is, and Rethinking Voxels draws an error screen without it. iris.features.optional defines IRIS_FEATURE_<name> for the features Horizon has; a required one it lacks is noted.
- A global initialized with the size on its type (ivec3[6] offsets = ivec3[](...)) kept only the size as its type.
- naga: constant shifts drop the bits shifted out, as GLSL says (1 << 31).
- Storage buffers are zeroed once and kept across frames, bound in set 4 to every program that declares the buffer block.
- The pack's images are made at their size, cleared before each frame when asked, and bound as images by name and as textures by their sampler name.
- Images an atomic function uses bind for atomics; the device asks for texture atomics, storage buffers in vertex shaders and texture clears where the GPU has them.
Rethinking Voxels r0.1-beta9 loads 10 programs and draws; its shadow pass (a geometry stage) and compute-only programs remain.
Packs invert only gbufferProjection's perspective terms when they rebuild positions, so a bob inside it moved every rebuilt position, and the lighting with it, while walking. The bob and the nausea spin are now part of the model-view, where GL drew them; the product is the same, and the hand's projection no longer adds the bob a second time.
gbufferModelViewInverse.1.0 read row 1, column 0: the transpose. Packs turn a view direction to the world as inverse.0.0 * x + inverse.1.0 * y + ..., so Photon's sun direction (from sunPosition) turned with the camera: the sky lit as another time of day when looking around, swung with view bobbing, and the sun was missing.
shaders.properties is preprocessed before its lines join: a value continued with \ just before #else (Photon's moon_phase_brightness) swallowed the directive and the other branch, so the uniform was never defined. Directives now apply while lines join, and lines of branches they turn off are dropped.
Each dimension of a TEXTURE_* entry is at most 16384, and the bytes it needs are counted
without overflowing, so a pack asking for a huge volume gets a note instead of a wrapped
length check.
Photon translated 45 of its 88 composite-style programs and none of its overworld terrain, sky set-up or final pass; now all 88 translate and every dimension loads its full set.
- A struct passed between stages (flat out CloudsParameters p) keeps the pack's declaration as a private global and travels as its members packed into vectors, copied by our main.
- sampler, texture2D and Vulkan GLSL's other texture and sampler type names are free names in desktop GLSL (sampler2D sampler); they are renamed unless called.
- naga's GLSL frontend lacked textureGather, sampled implicit levels in vertex shaders, broke struct constructors with array members, vecN(bool) and vector % scalar: naga is patched with the fixes until a release has them.
- real_packs prints every loader note with SHOW_ERRORS.
texture.<stage>.<name> and texture.noise name the files a pack binds to its samplers: PNG images, and raw 1D, 2D and 3D data with their size, format and type, each with its .mcmeta (blur, clamp). Post carries them, with a note for each that can't be read; nothing draws them yet.
Shader packs need fixes to naga's GLSL frontend that no release has yet. They go on top of this
copy in the commits that need them, as wgpu-hal's fix did, instead of a git dependency.
- A fragment output declared float, vec2 or vec3 (int and uint too) is written as the vec4 of its kind, alpha 1: wgpu's targets take four components.
- inverse() of a mat2, mat3 or mat4 is a function of ours (the adjugate over the determinant): naga writes it neither for Metal nor for WGSL.
- alphaTestRef is a constant of the program: its alpha test's reference.
- Solid and cutout terrain draw together, so terrain gets the cutout test (alpha > 0.1) unless alphaTest.* says otherwise.
- The naga pin moves to the fixes for pointers into vectors passed as out arguments and field selection on a matrix column.
A function-like macro called across lines (Photon's clamp01(\n ... \n)) was left unexpanded. Its lines now join the line the call starts on; the lines after it stay, empty, so line numbers still map back.
GET /api/v1/events streams server-sent events: every log line, the
supervisor's phase, and the game's typed join, leave, kick, chat and death
events with the player's name and UUID (?types= picks some; Last-Event-ID or
?since= resumes). Without Accept: text/event-stream it answers the events
after since as JSON. The dedicated server records the typed events where it
logs them and answers `server-events SINCE` on its control endpoint; the
supervisor fetches them every 250 ms. The admin e2e streams a bot's joins,
chat (a raw chat packet), kick, leave and death.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Creeper.spawnLingeringCloud: right after the blast an AreaEffectCloud of
radius 2.5, wait time 10 and 300 ticks stays where the creeper stood,
shrinking by radius / duration a tick and by 0.5 for each entity it
affects, with the creeper's active effects at 0.25 of their remaining
duration. The potion cloud takes its radius per tick and its effects as
data now, and keeps an infinite effect infinite (withScaledDuration).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
native_entity_join_level(dimension, entity, kind, pos, loaded_from_disk,
canceled) fires as vanilla's PersistentEntitySectionManager.addEntity:
from spawn_item, add_orb, /summon and chunk loads. loaded_from_disk is
true only for an entity that comes back with its chunk; portal and
gateway arrivals and /summon go through the chunk-load queue, so they are
marked there as fresh adds. A cancel takes the entity back out, and a
/summon of it fails with commands.summon.failed.uuid. API 41.
Part of #5162 and #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_block_entity_fluids declares a mod block's tanks, as its container
declares its slots: each tank's capacity, the tanks each face reaches, and
which a fill may reach and a drain may take. The tanks' contents live in
the entity's saved compound under `fluids`, so they survive restarts and
the block's own tick reads them there.
native_fluid_tanks reads them through a side, native_fluid_insert fills
(first into tanks holding the same fluid, then into empty ones) and answers
what did not fit, native_fluid_extract drains and answers what it took.
Both also work a cauldron: it holds thirds of a bucket, a lava one only
whole, and powder snow none, as it is no fluid. Water and lava are the
fluids; a tank holds one at a time.
Co-Authored-By: ZCode AI agent (GLM-5.3)
SulfurCube.knockback with a block now applies applyHorizontalHitAngleScale
(1.6) and applyVerticalHitAnglePowerTransfer (0.5) with the attacker's eye
and view, then the position rotation, in vanilla's order and float
arithmetic (Vec2.rotate through Mth's tables, Math.atan2). The player's
melee, spear and mace blows carry their aim. Checked against the bodies of
the vanilla helpers run on the real Vec2, Vec3 and Mth classes.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review found that the by-id path dropped what `mob_hit` applies: a mob in its
death animation, the mob the player rides and the camera entity are never
clicked by the pick, so naming one in a packet must not reach the feed, shear,
leash or name paths either. `named_mob_pickable` is that rule, shared by both
paths and unit-tested on its own, since the pick's copy cannot be tested
without a world.
Generated with DeepSeek Harness (deepseek-v4.1-flash)
Co-Authored-By: DeepSeek Harness <[email protected]>
ServerGamePacketListenerImpl.handleInteract resolves packet.entityId(), checks
it and the range, and hands that same entity to Player.interactOn. Horizon aimed
the guest at the named entity and then picked one again from the view, so a mob
that moved between the eye and the target took the click, and a block in the way
stopped an interaction the packet had already validated (#4651).
GuestUse::Entity now carries the packet's id and location, and the interact_
helpers use it as it is; the local player still picks from the crosshair, the way
Minecraft.hitResult does.
Generated with DeepSeek Harness (deepseek-v4.1-flash)
Co-Authored-By: DeepSeek Harness <[email protected]>
native_item_entities(dimension, min, max) lists the item entities whose
box meets a box (Level.getEntitiesOfClass(ItemEntity.class, box), as a
hopper's getItemsAtAndAbove asks); native_set_item_entity(dimension, id,
stack) changes one's stack or removes it when empty (ItemEntity.setItem,
discard), as a hopper's addItem does. On the level in place. API 41.
Fixes#5150. Part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`slot_position` asks the hopper's and dispenser's stock layout before its
native-screen arm, so a mod screen on a `minecraft:hopper` or
`minecraft:generic_3x3` menu never reached the slots it declares, and its
`inventory_y` was ignored: the mod drew its screen while every slot stayed on
the stock grid (#5141, item from #4953).
Only that layout stands back, and only while a mod screen is shown; the rest of
those menus was already the mod's, since `draw_native_screen` owns the
background and `screen_height` the height. With no mod screen the stock grid
answers exactly as before.
Generated with DeepSeek Harness (deepseek-v4.1-flash)
Co-Authored-By: DeepSeek Harness <[email protected]>
native_level_random(dimension) hands a mod the level's own random
(vanilla's Level.random, level.getRandom()), the stream Horizon's ports
of vanilla draw from, for the level in place and for a parked one. None
on a client of a server and for a level that isn't open. API 41.
Fixes#5145. Part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's handleConfigurationFinished builds a new ClientPacketListener,
whose constructor makes an empty ClientWaypointManager. Horizon kept the
last server's waypoints, so behind a proxy the locator bar stayed up on
a server with the locator_bar game rule off.
Refs #4760
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handlePlayerChat checks the global chat index (a mismatch
disconnects), the sender's PlayerInfo, and the sender's SignedMessageValidator:
keyed by the chat session the services' profile-key keys vouch for, or the
fallback of enforcesSecureChat. A message that fails shows the red italic
chat.validation_error in its chat type, tagged chatError
(ChatListener.handleChatMessageError); one a validator takes without a
signature shows as unsigned. The services' keys are fetched once per
connection off the game thread.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
native_server_about_to_start(dedicated) fires before the worlds load (the
start of MinecraftServer.loadLevel; NeoForge's ServerAboutToStartEvent),
native_server_stopping(dedicated) when an orderly stop begins (the start of
MinecraftServer.stopServer; ServerStoppingEvent), with the players still
connected and not yet saved. Both on the single player server thread and on
a dedicated server. API 41.
Fixes#5138. Part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Migrate legacy side files before loading End chunks and retain gateway source chunks while resolving new exits.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
recipe_files sorts by the recipe's id, as vanilla lists resources in a map
keyed by it: crushing before crushing/iron, where a path's components put the
subfolder's file first. A subfolder or an entry that can't be listed is warned
and skipped, as PathPackResources keeps what it found; only the top folder's
read still fails. RecipeIndex::load walks the same files, so vanilla's
brewing/ recipes (279 of them, minecraft:brewing/<name>) reach the recipe
book's ids; a brewing recipe has no display of its own, like the special ones.
Review: reservedbytes in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
Horizon keeps two ports of that method. crates/horizon-world/src/swimming.rs's
`update` carries `!is_passenger()` in both branches and is compared against the
real class by tools/test_swimming_parity.py, whose oracle varies the flag; the
client's copy in motion/fluids.rs::fluid_base_tick left the term out (#4427).
Only the term and the flag that feeds it: main already sits a riding guest up
through passenger_pose_tick, so this drops the duplicate of that.
The new test warms the fluid tick once first — fluid_base_tick samples
was_eye_in_water before it updates the fluids, so a fresh body could not report
is_under_water and the test panicked wherever .local/ existed.
Generated with DeepSeek Harness (deepseek-v4.1-flash)
Co-Authored-By: DeepSeek Harness <[email protected]>
Settings apply at every pointer move of a dragged slider, and since the
Exclusive Fullscreen rows that listed the monitor's video modes each time:
about 28 ms on Windows, so the handle lagged and jumped. The list is now
kept until the window's monitor changes, in game and on the startup menu.
horizon-ctl options-set keeps it too (with_option rebuilt Settings without it).
Fixes#5133
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The last filtered pick()s that compare a distance: a painting or mob
behind a wall in the band between block and entity interaction range
could still be broken or attacked through it. before_block() runs the
same comparison against pick_unfiltered(), as the other entity picks do.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
set_item wrote the merged secret over key before removing key.1, key.2,
...; a removal that then failed left the merged item beside leftover
pieces, and the next read joined them into a secret that doesn't parse.
The account looked signed out, and the fold retried on the corrupted
string until a delete went through and made it permanent. The same gap
was in set_pieces' leftover clean-up on Windows.
Both now snapshot the items first and write the old layout back when a
delete is refused, so a failed write leaves the store reading the
secret it answered before — pieces removed before the failure included.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`GameRenderer.pick` bounds the entity ray by the block hit before
`filterHitResult` drops it past block reach: a wall between the two
ranges still hides the entity. `pick_unfiltered` keeps that traversal
for `before_block` and the deflect and mob-interact occlusion checks;
`pick` keeps the filter only where a block target is consumed.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
With a shaders/ pack, Iris gives terrain, translucent terrain and fluids their colour without
vanilla's face shading: packs light faces themselves (oldLighting, off unless the pack turns it
on). Horizon baked the shading in, so packs shaded faces twice: a probe pack's colours per face,
Horizon over Iris, were 0.8 north and south, 0.6 east and west, 1.0 up. Sections now mesh
without it while a shaders/ pack runs (Mesher::shader_pack_lighting), and remesh when that
changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A click on a block that opened nothing left use_target set, so a menu an
entity opened next (a chest minecart's generic_9x3) could take the machine's
screen when the machine declared the same menu (arismoko on #5061). Using an
entity now clears it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's ItemStackTemplate.CODEC (and TransmuteResult.CODEC) is
Codec.withAlternative(<id, count, components>, Item.CODEC): a recipe's
"result" is either the stack's fields or just the item's id, so
"result": "mod:item" makes that item. Horizon read only result.id, so a
recipe written that way loaded and made minecraft:air.
horizon_world::loot::recipe_result_id reads both forms, and every place
that reads a recipe's result uses it: Template::parse (shaped, shapeless,
cooking and the special recipes), the banner duplicate and shield
decoration recipes, smithing and stonecutting, the recipe book, the
stonecutter and advancement packets, and the smelting loot modifier.
A result with no "id" (such as the "item" key of older versions) still
loads here as an empty stack; vanilla refuses the recipe. That is left
for its own change.
Tests: the helper, Recipes::load with a bare result, and a content mod
whose recipes make its own items either way (the full content root test).
Fixes#4904
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla writes network NBT (FriendlyByteBuf.writeNbt) through Netty's
ByteBuf, whose writeFloat and writeDouble keep a NaN's bits; only the
disk path (NbtIo.write through DataOutputStream) writes the canonical
NaN. Horizon canonicalized on both. The writer now canonicalizes only for
write_named, and the old test that asserted a canonical network NaN
checks both paths against vanilla 26.3's bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The connect flow drew its phase texts as literals on the net thread:
"Connecting to the server..." (also the Remote's initial status), "Joining
world..." and "Finding your friend's world..." stayed English in every
language while the middle phases ("Logging in...", "Encrypting...") were
already vanilla keys. The three now go through `connect.connecting`,
`connect.joining` and Horizon's new `horizon.multiplayer.finding_friend`
(all six bundles), and the local world-open progress line says
`horizon.loading.opening_world` / `horizon.loading.cancelling_world`
instead of hardcoded "Opening world" / "Cancelling world".
`NOT_SIGNED_IN` stays literal on purpose: `reconnect::allowed` matches
disconnect reasons with English keywords ("online mode", "microsoft
account"), so a translated message would slip past the never-retry list.
Matching on keys instead of texts is its own fix.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
`remote_mob_attributes` puts `minecraft:scale` on any living entity, but
`awake_dims_in` grows only avatars' boxes: a scaled strider's `height()`
stays unscaled, so un-divide the default attachment only for them.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Builds keep #4590's profiles, plan and timing and gain #3964's crate
check and post-build steps; post-build programs build with the chosen
profile. The strings #3855 retired stay out of the translations.
#3855 (in #3891) moved Native mods from Settings to the Mods page: the
build's time, how it ended and the Release build switch move with it, to
the instance's Native build row. The install notice #3855 removed stays
removed; the build record keeps #3891's per-mod sha256.
The window's server thread publishes the generators; end.locator is only
set on the deprecated in-frame path, which is not extended.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A display entity carries shadow_radius and shadow_strength, and vanilla
interpolates both across its interpolation window and casts a shadow when
the radius is above zero, through the same gates a mob's shadow uses.
Horizon parsed and blended neither: the two fields sat unread and the
shadow pass collected only mobs, dropped items and experience orbs.
The blend now carries them, the gate arithmetic lives in one function that
both casters call, and text, block and item displays join the pass.
ClientPacketListener.handleProjectilePowerPacket finds the entity by id and,
when it is a hurting projectile, takes the power it accelerates by. Horizon
decoded the packet and dropped it, so a deflected fireball - whose power
halves - kept the spawn default on the client between tracker updates. Our
own server already sends it bundled with the motion.
DensityCache holds DebugEntryChunkGeneration's lastPos and result; the test pins
the sampling on a new block or dimension, the cleared line without a
generator, and the clear when another world opens.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
native_chunk_load, with whether the chunk is new, and native_chunk_unload,
including a level that closes for being empty. native_player_logged_in and
native_player_logged_out come in pairs however a player leaves: a disconnect, a
kick, the server stopping or failing. At a stop they're told before the save,
still listed, and a last message goes out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_recipes_of_type, native_recipe_ingredient and native_recipe_result
treated a Game with no player as outside a world. A server's Game (the
server thread, a dedicated server, a LAN host) never has one, so a machine
ticking there got no recipes, though their docs say they are the server's
table. Outside a world now means a client with no player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use the vehicle's own position for CONTAINER_OPEN and CONTAINER_CLOSE: vanilla
Entity.gameEvent calls level().gameEvent(source, event, this.position) and
AbstractChestBoat/MinecartChest.stopOpen use position(), not y + 0.5.
Close a vehicle menu through one helper: store the menu's slots back, remove the
shared placeholder, and fire CONTAINER_CLOSE for a chest boat or chest minecart
(no-op for a hopper minecart). The ServerPlayer.tick stillValid close
(out of reach or vehicle gone) now uses it too, so it no longer leaves the
placeholder behind or skips the event, matching closeContainer -> removed ->
stopOpen.
Co-Authored-By: DeepSeek Harness (deepseek-flash) <[email protected]>
DebugEntryChunkGeneration adds its group only in the server-level branch. With no
generator (a remote server, a LAN world joined, the server thread not there
yet) the cached line of the last world was still drawn; it is cleared
there, and when another world opens.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
smooth() starts from the first value it sees. The load-time dry run that finds unknown names
evaluates every custom uniform with the built-ins at zero, so each smooth() started there:
Photon's biome_temperature (smooth over 1000 s) read -0.97 on the plains where Iris reads 0.6 at
once, and took minutes to get there. Smoothing now restarts after the dry run, and on frames
before the camera's biome is known (its chunk not loaded yet).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
edits_the_lists_while_stopped failed now and then on Linux CI with "a server
is already running in /tmp/horizon-server-lists-<pid>". The folder's lock is
an flock taken with try_lock; while another test in the same binary spawns a
process (tar, id, the engine), the child holds a copy of the lock's
descriptor between its fork and exec, so the lock is briefly taken and
try_lock answers WouldBlock. one_supervisor_per_folder already retried for
that window (4513af899); the lists test did not, neither for its own lock nor
for the edits, which take the lock inside lists::run.
The retry moves into supervisor::retried, a test-only helper (50 tries, 20 ms
apart), used by one_supervisor_per_folder and by the lists test for its lock
and for the edits that must succeed. The edits that must fail while the test
holds the lock are unchanged, and so is lists::run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use the active world recipe tables when handling remote recipe placement, and let the server own remote grid and ghost updates instead of predicting them on the client.
Co-Authored-By: Codex <[email protected]>
LZ4BlockInputStream decompresses each block with lz4-java's
LZ4JavaSafeFastDecompressor and calls it corrupted unless the decompressor
reads exactly the block. That decompressor has its own end conditions: a
sequence whose literals leave less than 8 bytes of output, or of input
past them, must be the last and end the output, and an offset of 0 fills
the match with zeros. lz4_flex's decoder disagrees both ways, so Horizon
loaded some streams lz4-java refuses (with whatever they decoded to) and
refused some it loads.
decompress_lz4_java ports that decompressor, fed from a buffer kept as
LZ4BlockInputStream keeps compressedBuffer (it may read past the block,
up to the buffer's end), and decode_lz4 requires it to read the whole
block. lz4_flex still compresses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DebugEntryChunkGeneration.display keeps lastPos and result and recomputes
only when the feet BlockPos changes; F3 sampled every frame. The line reads
the pipeline's samplers only, so it now goes through the generator and no
longer takes the shared query worker (no try_lock, no frame without the line).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
native_container_slots, native_extract_item and native_move_items follow the
rules hoppers use; a move puts a refused item back, so none is lost.
native_dimension names the level for a machine's tick, which isn't told it.
native_insert_item goes one item at a time and tells the container once. It
no longer overfills an empty slot, leaves a client's copy alone, and can't
reach a machine's slots during its own tick, where what went in was lost.
Hoppers change with it, as vanilla has them: a shulker box doesn't go into a
shulker box, a stack's max_stack_size component sets its limit, and comparators
read it. A comparator's fill ratio is clamped to full, where vanilla's
count-at-most-limit invariant keeps it: a stack a mod wrote over its own
limit can't push the signal past 15. A slot whose block entity went away
keeps the item, rather than losing it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: ZCode AI agent (GLM-5.3)
Three fixes for text the language screen could not translate:
- The merchant screen drew its title, villager profession, trade level
("Novice".."Master"), the Trades caption, the inventory caption and the
out-of-stock hint through `Enchantments::translate` — a pinned en_us map
loaded once at startup — so they stayed English in every other language.
They go through `lang::get_or` now, like the rest of the menus.
- The reconfiguring screen's button used the key `gui.disconnect`, which
no language file has (vanilla's button is `CommonComponents.GUI_DISCONNECT`
= `menu.disconnect`), so the raw key showed on screen. It is
`menu.disconnect` now.
- Horizon's own strings had no zh_cn bundle: the settings, graphics, HDR,
controller, extras, shader packs, mods and AI agent pages, and the
multiplayer invite dialog, were English for a Chinese player. A complete
zh_cn bundle ships now (the five other bundles already do; the language
is picked up by `Language::load`'s source scan like the rest).
Checked the whole game for the first two classes with a key audit: every
key a `lang` call passes now resolves in vanilla's en_us or Horizon's
bundle, vanilla-namespace keys all have zh_cn values, and no production
text draw keeps a hardcoded English literal. `test_instance_block.invalid_test`
looks like a missing key but is vanilla's own (`TestInstanceBlockEntity`
translates it without the key existing in any language file), so it stays.
Horizon-only command feedback (/raid, /profiler, the agent's messages)
still speaks literal English by house style; localizing those would need
new keys across all six bundles and is left alone here.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
The integrated server's owner is saved in the world's own player file;
#4866 stopped writing it on the server thread and nothing caught it
(#5065, #5075). The check moves both players onto glass blocks with
items, saves and quits, reopens and compares (the guest's own file too).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RegionFile.write and clear end in writeHeader, which writes both header
sectors from memory. The constructor has already zeroed the entries it
found invalid there (overlapping the header, empty, past the end of the
file), so a write leaves them zero on disk. Horizon wrote only the
touched slot's offset and timestamp, so stale invalid entries stayed in
the file for other tools to read.
write_header replaces write_header_entry and writes both sectors.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RecipeBookComponent.mouseClicked keeps the recipe a click placed as
lastRecipe (slot clicks keep it, unlike lastPlacedRecipe), and
RecipeBookComponent.keyPressed places it again on Enter, Space or numpad
Enter (isSelection), key repeats included, with the key's Shift. Holding
Space and shift-clicking the result crafts an inventory quickly. Horizon
had neither the field nor the key path. horizon-ctl's key command takes the
same path, as for the merchant's offers.
lan_e2e: recipe-reselect crafts wool three times from one book click (Shift
held Space, then Enter), on the host and on a guest.
Fixes#5099
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod usually has its own itemGroup.<mod id> already (reservedbytes on
#5066): "title" is looked up as a lang key first, English text when it is
none. itemGroup.<mod>.<name> still overrides it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
cpal 0.16 lists ALSA devices by opening a hardcoded set of names
("default", "pipewire", "pulse", "jack", "oss") for playback and
capture. Every missing plugin prints two ALSA errors, so the per-second
device check in Audio::should_change_device printed six "Unknown PCM"
lines and spent 5-9 ms (spiking to ~70 ms) enumerating a list that only
changes when the ALSA cards do.
Read /proc/asound/cards (a few microseconds) and enumerate only when it
changes; without a fingerprint, keep checking as before. Open the
default output first and enumerate only when it cannot open.
Co-Authored-By: Big Pickle (opencode) <[email protected]>
Only Blaze and Magma Cube (and burning mobs) were lit fully, and with the sky
light forced to 15 as well. AllayRenderer, VexRenderer and WitherBossRenderer
override getBlockLightLevel to 15 like BlazeRenderer, and EntityRenderer's
default gives a burning entity 15; getPackedLightCoords keeps the sky light
the level's. These mobs were dark in caves.
A phantom's size was never read: /summon phantom {size:N} and a server's
phantoms stayed size 0, so they were never drawn larger (PhantomRenderer.scale
uses 1 + 0.15 * size). Phantom.setPhantomSize (Phantom.java) stores 0 to 64 in
ID_SIZE and updatePhantomSizeInfo sets the attack damage base to 6 + size and
refreshes the box, scaled by 1 + 0.15 * size in getDefaultDimensions. The size
is now loaded and saved as "size", sent to LAN guests as data 16 and read from
a server's.
A block display of a chest, banner, skull, shulker box, bell, conduit,
decorated pot, enchanting table or the end portal and gateway showed only the
empty block model. BuiltInBlockModels gives these blocks a special model, and
CompositeBlockModel.update adds it to the block model's quads. The display now
draws it through the block-entity renderer, as a chest minecart does its
chest.
A dolphin holding an item showed nothing: its model has no arm bones for
ItemInHandLayer's hand lookup. DolphinCarryingItemLayer.submit
(DolphinCarryingItemLayer.java) draws the main hand's item in the GROUND
context at (0, 1, -1), then moves it with |pitch| / 60: y by -0.5 and z by
+0.5 for a negative pitch, y by +0.8 and z by +0.2 otherwise.
Mining a conduit or an enchanting table drew no crack stage on their
block-entity models. ConduitRenderer.submit and EnchantTableRenderer.submit
call submitCrumblingOverlay after the shell (inactive) or cage (active) and
after the book; the wind and eye are not overlaid. render_breaking now
covers both.
A copper golem's item sat at the humanoid hand offset. CopperGolemModel.translateToHand
(CopperGolemModel.java) turns the item 90 degrees about the arm and moves it
0.125 forward while the golem is idle, and otherwise scales it to 0.55 and
moves it (-0.125, 0.3125, -0.1875). Both run before ItemInHandLayer's own
offset.
An enderman with a target (isCreepy) stood still. EndermanRenderer.getRenderOffset
(EndermanRenderer.java) adds nextGaussian() * 0.02 * scale to the render
position's x and z every frame. The offset is added to the entity's render
origin, seeded per mob and frame so the body and carried block agree.
A zombie or skeleton in sunlight with a helmet was simply spared, so the
helmet never lost durability and never broke. Mob.burnUndead hurts the
item in sunProtectionSlot (head; body for a zombie horse or nautilus) by
random.nextInt(2) when it is damageable and only ignites the mob when the
slot is empty. A broken item leaves the slot and
LivingEntity.onEquippedItemBroken broadcasts the break event (49 head,
65 body) for the sound and crumbs.
A grown tadpole was replaced by a fresh frog that kept only yaw, so a
name tag, silence and the like were lost. Tadpole.ageUp calls
Mob.convertTo with ConversionParams.single(this, false, false), which
copies those through ConversionType.convertCommon, then finalizes the
frog for the conversion and makes it persistent.
A piglin, piglin brute or hoglin that zombified outside the Nether was
replaced by a fresh mob that kept only baby, yaw and persistence, so the
name tag, armour, held items, effects and flags were lost. Vanilla runs
Mob.convertTo with ConversionParams.single (piglins keep equipment and the
pick-up-loot flag, hoglins keep equipment only) and then adds nausea for
10 seconds (AbstractPiglin.finishConversion, Hoglin.finishConversion).
ConversionType.convertCommon calls setBaby(true) on the new mob and only
copies the exact age between two AgeableMobs, so a baby hoglin becomes a
non-ageable baby zoglin instead of keeping its growing age.
EnderDragon.aiStep calls currentPhase.doClientTick() on a client and
Horizon never did. DragonLandingPhase spawns 8 dragon-breath puffs from the
head each tick, DragonSittingFlamingPhase 8 x 6 on every second of the first
10 ticks, DragonDeathPhase an explosion emitter every 10 ticks, and
DragonSittingAttackingPhase replays the growl on every tick instead of once.
Vanilla throws away the results of look.yRot, so the puffs keep one direction.
Run them from tick_remote_dragon and drop the one growl the server played.
Breeze.tick (Breeze.java) calls emitGroundParticles while it stands, shoots
or inhales, 20 of them while it slides, and emitJumpTrailParticles for the
first five ticks of a long jump. tick_breeze_effects drew them, but only
tick_creatures called it, so a client of a server (single player included)
saw none. Call it from the remote entity tick too.
The server drew Witch.aiStep's 7.5E-4 roll but never broadcast entity
event 15, and nothing handled it, so no witch ever puffed. Witch.handleEntityEvent
(Witch.java) spawns nextInt(35) + 10 witch particles around 0.5 above its
box, redrawing the count on every loop pass, with gaussian offsets of 0.13.
A zoglin only grunted angrily on its ambient timer. Zoglin.updateActivity
(Zoglin.java) plays entity.zoglin.angry as soon as the brain switches from
idle to the fight activity.
Only players ever drew sprint dust. Vanilla IronGolem.canSpawnSprintParticle
(IronGolem.java) replaces the sprinting test: when its horizontal motion
exceeds 2.5000003E-7 and a one in five roll hits, Entity.spawnSprintParticle
streams the block under its feet back from it, scaled to its 1.4 width.
The killer bunny hop needs only where its target stands, so server_ai_step takes that instead of the whole MobWorld, and the existing tests keep calling it without a world.
Variant 99 (from /summon or a save) only changed the texture. Vanilla
Rabbit.setVariant(EVIL) (Rabbit.java) sets armor to 8, adds the bite goal
(MeleeAttackGoal 1.4), the hurt-by and player/wolf target goals, a plus 5
attack modifier and the Killer Bunny name, and customServerAiStep makes it
face and hop at a target within 4 blocks.
A bucket of tropical fish had no pattern or colour lines. Vanilla
TropicalFish.Pattern.addToTooltip (TropicalFish.java) names one of the 22
common variants, or shows the pattern name and then the base colour (with
the pattern colour when it differs), in italic gray, just before the
instrument line.
Every camel drew its lead at the default offset (eye height, 0.4 of the
width ahead). Vanilla Camel.getLeashOffset (Camel.java) uses the front
body anchor, which sinks while the camel sits, less 0.2 times the age
scale, and 0.56 of the width ahead.
A horn knocked off by a ramming goat was a plain goat horn (always the
ponder tune) thrown with the generic item motion. Vanilla Goat.createHorn
(Goat.java) picks a random instrument from #regular_goat_horns, or
#screaming_goat_horns for a screaming goat, with a random seeded from the
goat's UUID hash, and Goat.dropHorn throws it with deltaX/Z in -0.2..0.2
and deltaY in 0.3..0.7 drawn from the goat's random.
Tadpoles swam around at speed 1.0 and ignored players holding food.
Vanilla TadpoleAi.initIdleActivity (TadpoleAi.java) strolls with
RandomStroll.swim(0.5F) and runs FollowTemptation at 1.25 for items in
the frog_food tag.
NbtIo.readUnnamedTag skips the root's name with StringTag.skipString
and never decodes it, so a file whose root name isn't modified UTF-8
loads in vanilla. Horizon decoded it and refused the whole file; for
level.dat that meant the world opened with fresh defaults (game mode,
cheats, time) saved over the real ones.
read_named and read_named_streaming read the root name leniently: a
name that isn't modified UTF-8 is read as empty. Every other string,
compound keys included, is still checked, as DataInput.readUTF does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LockCode.canUnlock lets a spectator or a player whose main hand matches the
lock's item predicate open a container; BaseContainerBlockEntity.createMenu
refuses anyone else with an action-bar message naming the container and one
chest-locked sound. Horizon kept the tag on disk and read it nowhere, so a
locked chest opened for anyone, /data get did not show the lock, and
/setblock could not make one.
The lock now lives where the loader already put it, one place reads and
writes it, and the open funnel refuses with vanilla's message and sound.
Mob.aiStep walks the items within one block of a mob with CanPickUpLoot and
mob_griefing on, and pickUpItem equips what beats what it holds: armour by
armour and toughness, a weapon by attack damage, an equal item by
enchantments, damage and name, with the preferred-weapon tags skeleton and
drowned mobs carry. The replaced piece is dropped unless the drop chance
saves it, and the mob becomes persistent.
Horizon rolled the flag at spawn and nothing read it: a zombie stood beside
a diamond sword and left it there. The comparison rules live in a lib
module so they can be tested without a game, and the pass reuses the panda
and piglin pickup plumbing rather than adding a third copy.
After a /reload that disabled a world's packs, the cached SyncData was
marked as read without packs while still holding the kept registries with
the packs' dialogs, so the next world opened without packs would have been
served them. The cache key now also counts kept pack entries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A forwarding.secret file in the server folder turns it on for an offline
server: the login asks the proxy on velocity:player_info, checks the
HMAC-SHA256 signature and joins the player with the forwarded UUID, name,
properties and address. A login that doesn't answer, or is signed with
another secret, is refused; an empty secret file stops the start.
The format was worked out by recording a real Velocity 4.3.1 with a
stand-in backend (tools/velocity_forwarding_probe.py), see
docs/velocity-forwarding.md.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Lightning, additive quads and translucent particles draw in the OIT stages
there, and only the OIT entities got a mark after them. The firework mark
also takes the same base as the draw it follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NbtIo.write goes through StringFallbackDataOutput: a writeUTF that would
exceed 65535 encoded bytes logs "Failed to write NBT String" and writes
an empty string, and the rest of the tree is written. Horizon's
write_named failed instead, and since a save batch's records are encoded
before any is written, one block entity with such a string (a long
command block command, a book) made the whole batch fail: every chunk
changed since the last save was lost, not only that one.
The disk writer (write_named) now writes such a string empty; the
network writer still refuses it, as vanilla's does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's MinecraftServer.reloadResources builds the new
ReloadableServerResources against the server's existing
LayeredRegistryAccess (TagLoader.loadTagsForExistingRegistries); the
synchronized registries RegistryDataLoader read at server start never
reload. Players keep the registries of their configuration phase, and
later joiners get the same ones.
Horizon's /reload read the SyncData again with the current packs, so a
dialog file added or removed shifted the dialog indices (vanilla's too,
as pack ids sort among them): /dialog show sent the wrong index, the
re-broadcast dialog tags pointed at wrong entries and new joiners got
another registry.
Now /reload under a hub reloads the SyncData (recipes, advancements,
command tree) but keeps the hub's synchronized registries and their
names, and reads the tags against them (SyncData::keep_registries,
lan::reload_sync_data); the commands' dialog ids are not touched. A
pack's new dialog shows after a restart, as in vanilla. Without a hub,
/reload forgets the cached data as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
changeGameModeForPlayer clears flight on top of the mode abilities when the
new mode is not spectator and the player is in range of the ground:
no collider joins the body, and the block below the feet is within one
block. Horizon copied GameType.updatePlayerAbilities in both the local and
the guest funnel and stopped there, so a spectator switched to creative on
the floor kept hovering.
One probe, in the world crate where the collision scan lives, used by both
funnels.
EntityType.updateCustomEntityTag loads the egg's entity_data component into
the mob it just spawned, after the components, guarded by the type matching
and by the op rule for the three op-only types. Horizon applied the egg's
components and not this one, so a NoAI or Silent egg spawned an ordinary
mob.
The loader is the one /data merge already uses, and the op-only set is now
shared with the tooltip warning that already listed it.
ResourceManagerRegistryLoadTask.load registers the entries sorted by the
keys of FileToIdConverter.listMatchingResources, the file Identifiers
(dialog/<path>.json), path first, then namespace (Identifier.compareTo).
The ".json" counts: x-y comes before x since '-' sorts before '.'. Every
synchronized registry's entries, the game's own included, now sort by
"<path>.json", then namespace, instead of the bare id.
The cached SyncData remembers whether it was read with data packs. A world
without packs opened after one with packs read it again instead of keeping
the first world's dialogs, recipes and advancements; a world without packs
after another, or after the world-screen preload, still reuses it, and
SyncPreload.adopt replaces only what was read with packs.
The commands' dialog ids are set from the SyncData lan_host and /reload
send, not in finish_open_world: there it loaded SyncData on the server
thread before SyncPreload.adopt, wasting the preload.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DebugStickItem.useOn returns FAIL when handleInteraction does not
succeed (no game-master permission, a block without properties), and
handleUseItemOn fires ANY_BLOCK_USE only when the result consumes the
action. use_hand now sets use_failed on the stick's failure, and on a
click a native mod canceled, which uses nothing.
ChiseledBookShelfBlock.useWithoutItem returns CONSUME on an empty slot
without a book, so ServerPlayerGameMode.useItemOn fires DEFAULT_BLOCK_USE
there too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ChunkMap.skipPlayer is a spectator with the rule off, and a skipped player
holds no distance-manager ticket: the world does not load or generate
around them, though they still receive the chunks other players load.
Horizon declared and saved the rule and never read it, so a spectator was a
chunk source like any other player in all three places that matter.
One helper now answers it, and the ticket sources, the LAN streaming areas
and the dedicated server's centre all ask it.
RegionFileStorage.read parses the root straight off the decompressing
stream (NbtIo.read on the chunk's DataInputStream) and closes it, so
bytes after the root and a damaged stream past it are never read.
Horizon decompressed the whole record first and then refused trailing
bytes, so such a chunk failed to load, read_record took it as missing,
and the game generated it again and saved over what had been built.
ChunkRecord::nbt now parses the root from whatever decompressed before
an error (Compression::decode_into keeps it; an LZ4 block is kept only
once checked, as lz4-java hands it out) and ignores what follows. A root
cut short still fails with the decompression error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The window's world has no mod containers (the server tick installs them), so
mod_menu_at was always none there and no mod screen opened. Ask
native_block_entity_container for the clicked block instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A world with the `trade_rebalance` feature flag now plays the flag's
built-in pack: the overridden villager trade tags and trades, the
librarian enchantment trades and the changed chest loot, as vanilla's
server does. `MinecraftServer.configurePackRepository` force-enables a
feature pack whose requested features the world enables
(`enableForcedFeaturePacks`) and drops a selected pack asking for
missing ones: `datapack::enabled_ids` now takes the world's
`enabled_features` and does both, so the pack's files reach the world's
content overlay (`/datapack list` shows it as enabled).
The content tables then merge the pack over the resource root:
`Trades::load` takes the packs' `villager_trade`/`trade_set` files and
their tags (`replace` clears, `TagLoader`), and `Enchantments::load_with`
takes the packs' enchantment tags, so the librarian trades'
`#minecraft:trades/<biome_common>` pools resolve. Along the way:
- `PackOverlay::tag_kind` stripped a leading `:` that is not part of
the `tags/<ns>:<registry>/<path>` key shape, so it never matched and
every pack tag overlay silently no-opped; loot tables' tag overlays
were affected too.
- `/loot give ... loot <table>` rolled the resource root's table when
the table had a random sequence (`self.res.items()`), not the world's
own — the world's tables are used for the sequence branch now.
- The world's flags feed the worldgen overrides and the biome table
(`packed_biomes`, `level_shapes`, the preload reads them from the
world's level.dat), and the integrated open path passes them through.
- Fixes the stacked world-save commit leaving `integrated.rs` calling
`packed_biomes` without its `resources` argument (the bin did not
compile).
Vanilla reference: `MinecraftServer.configurePackRepository`,
`enableForcedFeaturePacks`, `TagLoader.load`,
`SimpleJsonResourceReloadListener`, the built-in
`data/minecraft/datapacks/trade_rebalance` pack.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
The first try (key == target) never held for a mod block: `holds` knows only
vanilla block entities, so every mod screen stayed shut. The screen now opens
when the menu is the one the clicked block's container declares
(`mod_menu_at`), which a chest menu from a command over a stale target is not.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractWidget.renderScrollingString / TextAlignment.CENTER uses
x + w/2 - width/2. Flooring (w - width) / 2 sat odd-width labels one
pixel left of vanilla on even-width buttons.
Co-Authored-By: Grok 4.6 <[email protected]>
mob_to_nbt dropped an enderman's carriedBlockState whenever the mob carried
none, but a caller that passes no registry never read the key either, so a
structure template's compound lost a field it had. Reported in review: the
key is only removed when a registry could have written it.
RideCommand.mount took only a mob as the vehicle. A player or a mob now
mounts a boat with a free seat, and a player any minecart no other player
is in, through the same state as a click. /ride <player> dismount goes
through stop_riding, and Entity.getVehicle names a minecart as one.
Part of #4968.
The overlay's key hashed each enabled zip in full, on the assumption that one
file reads in a moment: with a 9 GB pack that read 9 GB at every launch.
A zip over 250 MiB, vanilla's limit on a server pack, is now keyed by its
length, modified time and central directory. Smaller ones keep their bytes, so
a server pack's touched time still doesn't rebuild the overlay, and an edit
that a zip's empty checksums wouldn't show is still seen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
- A mod screen opens only when the clicked block holds the menu: use_target
outlives its use, so a menu opened later (a command's, another mod's) got the
last clicked machine's screen, and its clicks and tooltips (arismoko on #4106
and #4289).
- native_screen_tooltip runs over an empty slot, as its doc says, so a mod can
say what an input takes (arismoko on #4289).
- A mod block whose template has no block entity gets none, with a warning:
breaking it never removed the block entity, which stayed in the save
(reservedbytes and DUzzL on #3996).
- A GUI texture that doesn't decode is named in the log, not skipped silently
(reservedbytes on #4106).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From reservedbytes' test on #4331: a block's item was added to search twice
(blocks' items are already among the plan's items); the page buttons stretched
the 200-wide button sprite into 20 px and lost their sides (now blit_sprite, as
vanilla's Button draws); turning the page while on search, saved hotbars or the
inventory keeps that tab; a mod tab's title comes from itemGroup.<mod>.<name>
in its lang files, the file's title being the English fallback.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handleUpdateMobEffect calls skipBlending when the packet
carries no blend flag, and BlendState.setImmediate then sets the factor from
hasEffect rather than fading up. That is the path the effect list takes on
joining, respawning and changing dimension, so vanilla shows the warp at
once. Horizon had this for darkness and not for nausea, whose warp ramped
from zero over a hundred and fifty ticks.
One branch beside the darkness one, in the same place, with the blend-out
guard now shared.
A content mod declares tabs in data/<id>/horizon/creative_tab/*.json: a
title, an icon and its items. They come after vanilla's, ten to a page
(the top row's first five columns, then the bottom row's), with < and >
buttons above the screen; search, saved hotbars and the inventory show on
every page. Its new items join the search tab under their English names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
VanillaTerrain::column read every block of a generated section with
ProtoChunk::block (a section lookup, coordinates and a palette read per
block, ~98k calls a chunk) before repacking it. Light views for the ring
past the view distance are built on the server thread, so flying kept a
steady stream of them there. A whole generated section is now unpacked
word by word and mapped through its palette (TerrainChunk::section_states,
BlockSection::fill_states, PackedStorage::unpack_into); a partly generated
one still goes block by block. Same values, same packing.
Fixes#5038
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Listing the packs opened every zip in resourcepacks/ twice, once for its
pack.mcmeta and once for its pack.png, and each open read the whole file
into memory on the render thread. With 22 GB of packs in the folder, enabled
or not, opening the options screen froze the game for five to seven minutes.
A Zip now keeps the file open and holds only the central directory on, with
the END record normalized as before, the way Java's ZipFile works. Entries
are read at their positions, so clones and threads don't share a cursor.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
MinecartTNT.getBlockExplosionResistance and shouldBlockExplode: while the
cart is primed (fuse > -1), a rail or a block with a rail above has no
resistance to its blast and is never blown up. Horizon broke both, so a
cart primed on an activator rail tore up its own track and the block
holding it. An unprimed cart (a wall hit, a fall, fire) blasts rails
like any explosion, as before.
Harness: command_exec_corpus `tnt minecart rails`.
Fixes#4992
flush_chat_queue now goes through accept_chat, as ChatListener.flushQueue runs each handler.
Without it a flushed signed message never reached the last-seen tracker, and vanilla's
server disconnected the client at its next message (checksum mismatch). The harness now
sets Chat Delay, holds a message back and flushes it. send_command parses over chat_tree.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
chunk_io's PENDING is process-wide, and save tests that fail a chunk load
or write report into it while the chunk_io tests take and compare it, so
those tests failed now and then (#4854). The newest-chunk rules move into
Pending::load_failed and save_failed, which the tests call on a local
value; warn_on_low_disk_space returns what it found. Only the
low-disk-space packet test still reads PENDING, and it checks a flag
nothing else in the lib clears.
Fixes#4854
Written with Claude Opus 5.5 in OpenCode.
LocalPlayer.isSprintingPossible is false while Player.isMobilityRestricted
(blindness). sprint::Conditions already honoured mobility_restricted, but
nothing set it. PlayerMotion now carries the flag into the sprint phase,
and sync_player_body sets it from the effects every tick, on the server
thread's client as well.
agent_control_e2e's consume scenario now holds the sprint key while the
server gives and clears blindness.
Fixes#4831
Written with Claude Opus 5.5 in OpenCode.
EntitySelector.findEntities with a UUID takes ServerLevel.getEntity,
whatever the entity is. target_with_uuid kept only mobs, so /tp, /kill
and /data get entity with a display's, armor stand's or item's UUID
found nothing; the scoreboard's holder lookup kept mobs and items. Both
now go through level_entity.
Fixes#4966
`DataPackCommand` gates feature packs on the world's flags: a pack whose
requested features are not enabled is not available, cannot be enabled
("required flags are not enabled in this world"), and a feature pack can
never be disabled — the flag stays on for the world's life. Horizon
refused every enable and allowed every disable regardless. The command
now reads the world's `enabled_features`: with the flag on, a feature
pack enables and its data loads; without it, it stays hidden and the
refusal names the missing flag.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Vanilla's server save runs `PlayerList.saveAll`: every connected player
is saved to `playerdata/<their own uuid>.dat` — there is no world-owned
player file. The integrated server's save now does the same for its
placed guests (`playerdata::save`), on every save and when a player
leaves, and the owner joins through `PlayerDataStorage.load` on their
own uuid first — the world's own player state is only the fallback for a
first join. Previously the owner's state was stored into the level and
then dropped (`write_without_player`), so position and inventory were
lost on every reload, and the stale single-player file shadowed the
newest-file fallback on load.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
The integrated server's owner plays as the world's own player
(`Owner`): on leave their state is stored into the host's `Level`
(`playerdata::store`), but the server saves with `write_without_player`
(it runs as `dedicated`) and the window's own save is skipped while it
is connected, so the state never reached a file and position and
inventory were lost on every reload. The integrated role now writes the
world's own player file (`players/data/<singleplayer_uuid>.dat`) with
the level, as single player does (`PlayerDataStorage.save`); a real
dedicated server keeps saving its players on their own.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Review of the port against the decompile found three deviations that
stalled carts at corners and broke climbing:
- `stepAlongTrack` ends with `setDeltaMovement(movement)` — the movement
turns onto the exit's direction at the same speed. Without it the
velocity never turned, so a corner's exit selection kept the old
direction and the cart stopped.
- `TrackIteration`'s slope, halt and boost speeds apply once per tick
and carry their flags across the iteration; the port re-applied them
on every rail.
- the player-input push does not replace the natural slowdown and the
speed cap (vanilla applies both), and a stuck step
(did-not-move-but-would-have) zeroes the movement.
Also `isDecending` reads the step's movement (the exit's direction),
not the stale velocity, for the hill's Y projection.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
`AbstractMinecart` chooses its behavior by the world's features: with
`minecart_improvements` the new behavior runs — the slide on slopes
grows with the speed (`max(0.0078125, h * 0.02)`) so a descending cart
keeps its pace, the empty cart slows half as fast (0.975 over 0.96),
the speed cap is a vector normalization instead of a per-axis clamp,
a powered rail boosts by `+0.06` along the movement or pushes a resting
cart 0.2 away from the signal's side, an iteration carries leftover
distance across rails, a hill projects the Y onto the slope, and a V of
opposing slopes rests a slow cart (`restAtVShape`). Without the flag
the old physics runs as before.
Stacked on #4975 (← #4957 ← #4948 ← #4619 ← #4577), whose
`enabled_features` the gate reads.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Vanilla's level.dat carries `WorldDataConfiguration`'s `enabled_features`
flat in the Data compound (`FeatureFlags.REGISTRY`'s codec: a list of the
whole set's namespaced ids, `minecraft:vanilla` included, the tag omitted
when the flags are vanilla's default) — Horizon wrote nothing, so a world
made with experiments told vanilla nothing and forgot its flags itself.
`Level.enabled_features` is written with the selection (the flags of the
picked feature packs over vanilla) and read back from level.dat, so a
vanilla-made world keeps its flags too. The behaviors behind the three
flags are still separate work; this is the data they will gate on.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Vanilla reads the world generation data behind a loading screen before
the Create New World screen opens, again after its Data Packs or
Experiments screen applied a selection, and warns before applying a
selection that asks for experimental features. Horizon showed none of
that.
- `createWorld.preparing` (`GenericMessageScreen`: the menu background
and the centred message on a black box with a 1-pixel border and 12
pixels of padding, no progress): Create New World reads
`vanillagen::shared_data` on a worker while the title shows it, and
opens the create screen when the read is done; the Data Packs screen's
Done and the Experiments screen's Done re-run the read the same way
(`applyNewPackConfig`).
- `ConfirmExperimentalFeaturesScreen`: when the Data Packs screen's Done
picks a feature pack, the warning comes up first — the title, the red
message wrapped to 310 and centred, a 100-wide Details button, Proceed
and Back in the footer, the whole block centred, Escape declining. Its
`DetailsScreen` lists the picked packs bold with the flags they ask
for (`selectWorld.experimental.details.entry`). Proceed applies the
selection, Back reopens the Data Packs screen on the selection it was
opened with (`tryApplyNewDataPacks`'s `onAbort`).
Stacked on #4619, whose Experiments screen and feature-pack plumbing the
confirm screen builds on.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Review fixes on #4619:
- The More tab's `options.press` guard caught button 30 before the
Experiments arm could, so the button did nothing; the guard lets 30
through now.
- Create World's `open_world` overwrote the `datapacks.json`
`datapack::install` had just written, losing the unselected world
packs' `disabled` entries, so a pack the player left off came on. The
write now only happens when `install` did not run (only Experiments
used), and it creates the world folder and logs failures instead of
dropping the error.
- `level.dat`'s `DataPacks.Disabled` also lists the world packs the Data
Packs screen held back, as vanilla's `DataPackConfig` does.
- The completion registries' pack list gains the feature packs, so a
pack enabled through Experiments completes its worldgen ids.
- The feature packs on the Data Packs screen compared against the
resource pack format and drew red; they compare against the data pack
format now.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Vanilla's More tab offers Game Rules, Experiments and Data Packs; the
Experiments button was drawn disabled ("Experiments have no screen yet").
It now opens vanilla's ExperimentsScreen (`title/experiments.rs`): every
feature pack the resources root ships under `data/minecraft/datapacks`
(`ServerPacksSource`, `PackSource.FEATURE`), each with a 44-wide on/off
switch at the right of its row, its description grey underneath, the red
warning wrapped to 310 and centred, Done and Cancel in the footer —
vanilla's `HeaderAndFooterLayout` + `SwitchGrid` geometry. Switching a
pack adds or removes its id in the world's data pack selection, which the
Data Packs screen and the world's DataPacks then see; Done keeps the
other packs and rejoins the picked feature packs last, reversed
(`onDone`), Cancel and Escape leave the selection as it was.
The feature packs also join the world's repository, so a picked pack
reaches the game:
- `datapack::feature_packs` scans the resources root's feature packs and
`screen_packs` offers them on the Data Packs screen (available, off),
as `PackSelectionScreen` does;
- a world remembers its selection in `datapacks.json` (the pack stack's
own file) and `level.dat`'s `DataPacks` (`DataPackConfig`);
- `pack_stack`, `packed_biomes` and `world_worldgen_overrides` resolve
the ids against the resources root, and a feature pack stays off
unless the world enabled it (vanilla starts `PackSource.FEATURE`
unselected), so existing worlds behave exactly as before;
- `/datapack` shows an enabled feature pack with the feature source and
can disable it again, while `enable` still refuses one
(`commands.datapack.enable.failed.no_flags`) — the three flags'
behaviors are not implemented yet, but the packs' data files
(trade_rebalance's loot tables and tags) reach the content tables
through the pack stack.
Dropping a pack on the window (`PackSelectionScreen.onFilesDrop`): a
zip, a pack folder, or a folder holding exactly one pack (`PackDetector`)
is copied into the create screen's pack folder, where the Data Packs
screen's existing watcher lists it within a second; anything else is
refused and logged.
Stacked on #4577, whose unified pack list the resolution goes through.
Verified headless: a world with `trade_rebalance` in `datapacks.json`
lists it enabled ("[trade_rebalance (feature)]"), disables it with
`/datapack disable`, and the other feature packs stay available.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Co-Authored-By: ZCode Agent <[email protected]>
The reload replaces the atlas and the renderer, so the pack's sprite tags
named stale layers and the pack stopped drawing until its files changed.
Suggested by rdbtCVS's review on #4186.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QS9x9HatX1QRXMqxeb
Review fixes on #4577:
- The window's Game on the integrated-server path kept `world_biomes`
unset: the chunks it draws carry the packs' biome ids past the built-in
list, but nothing named them. `open_integrated` builds the world's
table now, as `finish_open_world` does for the load path.
- The integrated path's own encoders read `res.biomes()` past the world's
table: `lan_host`'s chunk encoder and `/execute if biome`'s naming go
through `world_biomes!` now, so a packed biome keeps its name and its
place on the wire (a guest still needs the table sent over the
connection; a remote server join clears the last world's table so no
stale names linger).
- A tag in a sub-directory of its registry never matched: `content_id`
attached the namespace to the file's last segment
(`tags/worldgen/biome/has_structure/minecraft:village_plains`), while
the lookups build `tags/{registry}/{ns}:{path}`. The store key is the
tag id now (`tags/worldgen/biome/test:sub/mytag`), `PackOverlay`
gained `tag_kind` for the registry walks, and the readers match.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
`WorldgenOverrides` and `Biomes::with_packs` both take the world's packs,
but the client built the biome table and the worldgen from different
lists: `content_overlay` reads `datapacks.json` (empty until a world
writes it) while the world's worldgen uses `datapack::discover` with
`enabled_ids`. A pack's biomes therefore never reached the table
`WorldgenOverrides::finish` tests against, so it dropped every new biome
("cannot add biomes yet") and the generator then failed on a material
rule naming one ("unknown material biome").
`world_loading::packed_biomes` builds the table from the same list the
worldgen is built from, and the completion registries read the same
overridden ids instead of `DataStore`'s files, so `/locate biome` accepts
and completes a packed biome.
Verified with Terralith 2.6.5 as the world's only pack: the world opens,
`/locate biome terralith:alpine_highlands` finds it, and `/locate biome
#c:is_lush` reports `terralith:cave/underground_jungle`.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
`Biomes::with_packs` (the built-in table with a world's pack files over
it, as `RegistryDataLoader` layers registries) had no callers, so a
packed biome had no client data and `Biomes::id` resolved it to plains —
every packed biome rendered, spawned and saved as the fallback.
The table is built when a world opens, from the same pack overlay the
content tables use, and every biome lookup goes through it
(`world_biomes!`, a field-level borrow for the loops that also take
`&mut self`). `world_loading::make_streaming` builds the same table for
the generator, so the ids the terrain names and the ids the client draws
agree.
A world with more than 128 biomes needs the palette's 8 bits, which the
earlier commit gives it.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
`command-registries.json` lists the game's own ids, so `/locate biome`
refused a packed biome before searching and offered no packed biome or
tag in its completions. The packs' `worldgen/biome`, `structure`,
`structure_set`, `template_pool` and `processor_list` ids now join those
registries, and so do their worldgen tag ids.
The tag lookup behind `#tag` (`/execute if biome`, `/locate biome #tag`)
read only `data/<ns>/tags/worldgen/biome` from the resource root; it now
reads the world's packs too, in resource-stack order, with a `replace`
file dropping what the lower sources put in (`TagLoader.load`). That also
makes a pack's structure tags work.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
`Strategy::biomes(7)` tops out at 128 biome ids; vanilla ships 67 and a
pack like Terralith adds a hundred more (`Biomes::with_packs`), which the
global palette's 8 bits hold.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Right-clicking an empty flower pot with a flower, sapling, mushroom, or other potted plant item now places it in the pot matching vanilla FlowerPotBlock.useItemOn behavior:
- Construct potted_by_content map on Behaviours caching all 39 potted blocks
- Support empty pot plant insertion returning InteractionResult::Success
- Consume 1 held plant item, increment pot_flower stat, and award item_used_on_block
- Clicking a potted pot with a plant returns InteractionResult::Consume
- Add unit test coverage for potting flowers, saplings, and mushrooms
- Update port map entries in game2-013.tsv
Fixes#4844
Co-Authored-By: Antigravity <[email protected]>
/item weapon.mainhand now goes through onEquipItem, as vanilla's setItemSlot
does; hotbar.N stays the inventory's. A content mod's recipes in a subfolder
of its recipe folder load, as a data pack's do. native_net_send_to_server
refuses while the server reconfigures the player. The hook's doc says what
scatters at death isn't item_dropped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
updateFallFlyingMovement takes the look direction from getLookAngle
(calculateViewVector: Mth.sin/Mth.cos, 0.017453292f), the pull-up's
-sin(lean) from Mth.sin, the lift from Math.cos of the widened lean, and
drags by multiply(0.99F, 0.98F, 0.99F). Horizon used exact f32 sin/cos,
f32::to_radians and 0.99/0.98, so a long glide landed up to ~0.11
blocks from vanilla's spot.
Fixes#4618
The sky reads inverse_view * ray, so its mirror is F * inverse_view; the column operation used for view_proj * F left a pitched camera's sky wrong: looking down 40 degrees mirrored a sky looking down. A test compares the rays.
A scan for the water plane reads up to 60 000 blocks on the game thread; it now runs when the camera moves 8 blocks or every 5 seconds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A normal within 0.99 of an axis (about 8 degrees) snapped to it, which flattened flowing water's gentlest slope (about 6 degrees). The snap now reaches about 3.6 degrees, still enough for distant block faces, and the doc says so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
NativeFrame::write skips title frames, so the first world frame after a title screen, a pause or a world load reprojected from a stale camera with a delta_time of seconds. Past half a second the frame counts as the first. The easing is a pure function, tested.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A server's experience orb was only eased towards each position update and
did not move on its own in between, so orbs jumped every update (#4488).
Entity keeps a NO_OP interpolation handler for an orb, so the update puts it
there at once, and ExperienceOrb.tick runs on a client too: gravity,
buoyancy, collisions, friction and followNearbyPlayer towards the nearest
living player within 8 blocks (ExperienceOrb.java). The orb now takes the
update's position, ground flag and motion, and runs the existing
ExperienceOrb::tick each tick. Merging stays on the server.
The local player skipped the fluid update while riding, so its eye flags
froze at whatever they were on boarding and the air bar stayed up inside a
floating boat.
Entity.getFluidInteractionBox asks the vehicle:
AbstractBoat.modifyPassengerFluidInteractionBox gives no box when the boat
is not underwater and reaches the passenger's head, and otherwise only the
part above the boat's top. The rider's tick now samples fluids in that box
(Entity.baseTick) through the existing interaction update.
A client of a server (single player included) clicks the fence block, never
the knot, so the guest's use only reached the block and item turns. The
port of LeadItem.useOn and LeashFenceKnotEntity.interact, Game::interact_knot,
ran only from the host's own interact_entity, so no lead could be tied to a
fence, taken back off a knot or snipped with shears on the server thread.
Vanilla: LeadItem.useOn binds the player's leashed mobs to the clicked fence
(LeadItem.bindPlayerMobs), and LeashFenceKnotEntity.interact handles a click
on an existing knot (LeashFenceKnotEntity.java). A guest's block use now
offers the click to interact_knot first, as the host's frame does.
A guest respawning at a bed or anchor that was gone had the
"block.minecraft.spawn.not_valid" message put into the host's chat, and
never saw it itself. PlayerList.respawn sends the player
ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, and
ClientPacketListener.handleGameEvent turns it into that system message.
Send the event from both guest respawn paths when the point cannot be
resolved, and show the message when the remote client receives it.
Game::tick_scoreboard recorded these criteria only for the in-frame
player, so a guest's (and a single-player owner's, who is a placed guest)
health, food, armor, experience and level scores never moved: a
`health` objective shown below the name or in the tab list stayed at 0.
ServerPlayer.doTick calls updateScoreForCriteria for every player when
the value differs from lastRecorded*. Move that part into
record_player_scores, used by tick_scoreboard and by each guest's tick
with a per-guest record in GuestPlay (reset with a new body).
Minecraft.startUseItem returns when LocalPlayer.isHandsBusy (rowing with
the movement keys held). The in-frame path checks it in use_with_hand;
remote_use did not, so on a server a right click while rowing still ate,
placed or threw. remote_use now returns the same way.
Item.use runs Equippable.swapWithEquipmentSlot on the client too
(MultiPlayerGameMode.useItem), so the worn piece and the hand change at
once and the server's slot packets only confirm. remote_use_item sent the
packet and waited, so on a slow server the swap showed a ping late.
It now calls equip_from_hand like the in-frame path. A client spawns no
dropped item (the server does) and takes its attribute modifiers from the
server's update packet, so equip_from_hand skips both when remote.
Main reached NATIVE_HOOKS_API 36. native_visuals and native_emissive
(API 8 and 9 on this branch) now share API 37, in the module doc and
docs/native-mods.md. The emissive layer moves to the terrain shader's
@location(9), after main's fade, local and visibility.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Zombie.finalizeSpawn and AbstractSkeleton.finalizeSpawn put a carved pumpkin (or jack o'lantern) on an empty head 25% of the time on 31 October. Structure spawn already did this; natural, spawner, summon and raid paths never ran it.
Batch movement, teleport and attack commands so the check proves that a hit occurred instead of overshooting the target between control calls. Assert the first post-hit sample is below sprint speed.
Co-Authored-By: Codex <[email protected]>
zip 9's enclosed_name turns ../x into x instead of refusing it, so the
escape test main added passed the entry through. Check the stored name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
IronGolem.canAttack answers no for a creeper as well as for a player, and
that answer is asked by the retaliation goal, by target continuation and by
the nearest-target search. Horizon mirrored only the player half, so a
creeper that hit a golem became its target and the golem swung back.
The rule now covers any target and is asked by the retaliation goal and by
target continuation, the two places that had no answer.
The hand pass draws with the camera's view, whose rotation spreads a ridden
boat's turn over the frames of a tick (LocalPlayer.getViewYRot). hand_geometry
posed the hands with the end-of-tick yaw instead, so they swung by that turn
20 times a second. HandSway::to_world takes the camera now.
Fixes#4583.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeutralMob.isAngryAtAllPlayers is the gamerule universal_anger plus a mob
that is still angry with no anger target left, and isAngryAt asks it before
the named target. Horizon registered the rule and stored it, but the wolf's
player target goal only checked the named target, so the rule did nothing.
The wolf's predicate now asks the rule, and the world it reads carries the
gamerule the same way mob_griefing and mob_drops do.
Use the entity kind for the RemotePlayer client-side attack path so profile bookkeeping cannot suppress the vanilla attacker slowdown. Restore the client-side mob warning and leave sprint synchronization to the normal end-of-tick movement send.
Co-Authored-By: Codex <[email protected]>
A DialogScreen is a screen in vanilla, so KeyMapping.releaseAll runs.
Horizon kept Screen::Playing, dropped key and right-button releases, and
left W and use held after the dialog closed.
zip 2 refused an archive whose entry claims method 99 without an AES
field; zip 9 opens it and only the entry fails, which mirror now skips.
A name that runs past the archive still fails the open, and the build.
Also rustfmt for upgrade/mod.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fills in the 135 strings the table lacked (Mods browser, native mods from
GitHub, AI agents, data collection), drops the unused "Sign in", names pt-br
in the language setting's hint, and formats parse().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
world_wide_plans_stay_small_and_cover_every_level only printed node counts.
It now checks that the roots reach MAX_LEVEL, that each plan stays under
16 384 nodes (10 655 at most today, at a corner of the world), that reduced
has no more nodes than full, and that every level from the finest one to
the roots has tiles. The mesh test's corner check bound as (A && B) || C;
it now needs the far corner. Main's mesher materials take a position: the
heightmap's fluid test passes the tile origin, as fill_buried_gaps does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Dynamically clamp the occlusion graph search box height to the highest non-air section across columns in the search radius, skipping empty air section exploration.
- Fall back to column reachability for open air queries above the clamped search box.
- Add unit test vertical_clamping_bounds_height_and_reaches_open_sky.
Fixes#4985
Co-Authored-By: Antigravity <[email protected]>
Mob.aiStep's looting loop runs for every server mob, AI or not, and an
allay's pickup gates (canPickUpLoot, the held item, mob griefing, the
inventory room) never ask for AI. So a NoAI allay still takes a matching
drop that lands in its getPickupReach, which Allay overrides to (1, 1, 1)
— it only never walks to one (the sensor is the brain's) and never throws
one (the delivery goal is too).
The blanket skip becomes that split: the reach is one helper, the search
uses it when AI is off, and delivery stays behind the AI gate.
AbstractContainerMenu SWAP button 40 is the off hand even when the
open menu has no off-hand slot. Horizon only mapped 40 on the
inventory menu, so F in a chest predicted a move the server ignored
and left a ghost item.
Fixes#4887
Keeps both sides: main's owner updates, the "listed by its repository"
refusal and the label route next to the native turn and the retry. A mod
turning native is never an owner update (its kind changes, which the
owner-update workflow doesn't review), and the test checks for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An update that stopped between copying the new binary and moving it into
place left <name>.update-<pid> in the install folder, and the start-up
clean-up only matched .old- files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A reader that returns a Result fails with an error; the best-effort port-pack scans, which already skip entries they can't read, skip it. Also ports the readers main added since (resourcepacks.rs, upgrade/mod.rs) to the 9.x API.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A headless client (#4951) has no display, so it cannot run winit's event loop. The game's
methods used winit's ActiveEventLoop only to stop the loop and to say when to wake next:
they now take `&dyn EventLoopCtl` (game/event_loop.rs), which winit's loop implements.
window_event and about_to_wait keep their bodies, now the inherent handle_window_event and
idle, and the ApplicationHandler methods forward to them. The control socket's and the pad
menu's synthetic input call handle_window_event. Nothing changes for a window's game.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pufferfish.addAdditionalSaveData writes PuffState and reads it back clamped
to two; Enderman writes carriedBlockState with the block state codec, only
while it carries one, and an air state reads back as no block. Horizon
saved neither, so a puffed fish came back small and an enderman forgot its
block across a reload.
The block state codec moves from the save path into entity_nbt so both
sides share it, and the registry it needs is threaded to the two save
functions.
#4231 (on main) already returns the replaced piece in creative and
discards it there when the inventory is full. What this branch still
adds: survival throws the piece that no longer fits like Player.drop
(stack, false) instead of spawning it motionless at the eyes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main split open_device into open_with_retry and open_backend (#4064). The
CPU-adapter check now sits right after request_adapter and fails the
backend, so the non-conformant retry doesn't pick the same adapter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MushroomCow.get answers with minecraft:mooshroom/variant, red or brown, and
applyImplicitComponents reads it back, so a mob spawned from an egg keeps
its colour. finalize's species_variant_component listed wolf, cat, frog,
pig, cow, chicken and zombie nautilus but not the mooshroom: the component
was never reported, variant_name fell through to the frog's table and
variant_index could not recognise "brown".
The save format is untouched: a mooshroom still stores Type, not variant.
The ticks == 0 guard never let a dropped item reach ItemEntity::tick, the
only place ticks counts up, so every drop froze: no gravity, pickup delay,
merging or despawn. An item now waits one tick only when its id is past
the last one the tick saw after its functions ran.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Game release notes open and close with the warning to get the launcher and not run the files; the closing one sits above the disclaimer. Rebuilt on main's release notes.
Co-Authored-By: Codex (GPT-6.1) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The release switch picks Release; Fast still falls back to release for a
revision without [profile.fast]. The log filter now strips only the progress
pieces of a line, so an error after a redraw reaches build.log.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Main's OIT_ENTITY shares the layer's high bits with the HDR tier: the overlay
consumers mask 0xffff, vs_lightning masks the tier, and a test pins that a tier
keeps the material.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- options_ui: Space/Enter on FocusId::Cycle now emits Event::ButtonClick
before cycling (was silent before)
- options_ui: wheel on a keyboard-focused slider steps it; wheel on a
keyboard-focused cycle button cycles it (Horizon improvement; Vanilla 26.3
does not intercept the wheel for sliders at all)
- graphics_ui: same wheel improvement for the graphics settings page — when
can_change_value is set, wheel steps the focused slider or cycles the focused
cycle item instead of scrolling the list
- title.rs: confirm_key_size returns (Action, bool); the bool is true for any
enabled focused button press, false for disabled buttons or no-ops
- game.rs: title keyboard confirm plays the click sound from the bool, not just
on Action::Play
- startup_menu.rs: extract .0 from confirm_key_size return
- Tests: options_keyboard_space_enter_emits_button_click_for_cycle_and_default
and title_screen_keyboard_navigation_and_activation updated and passing
Co-Authored-By: Antigravity <[email protected]>
passenger_attachment multiplies the base attachment by the entity scale; the bounding-box fallback now reads height() already scaled after getDimensions learned minecraft:scale, which double-applied it for players and mannequins.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When landing a melee attack while sprinting with attack strength scale > 0.9,
vanilla Minecraft 26.3 executes a sprint-knockback hit:
- Plays minecraft:entity.player.attack.knockback
- Multiplies attacker horizontal velocity by 0.6 (ATTACKER_SLOWDOWN)
- Cancels sprint state and syncs ServerboundPlayerCommandPacket(STOP_SPRINTING)
- Excludes spears from sprint-knockback attacks
Fixes#3736
Co-Authored-By: Antigravity <[email protected]>
Secrets were split into ~1 KB pieces on every OS for Windows Credential
Manager's 2560-byte item limit. Each piece is its own keychain item and
asks consent for a launcher signature it doesn't know, so macOS players
got a prompt per piece after every ad hoc self-update (#4912). On macOS a
secret is now one item; split pieces an older launcher left fold into it
on first read, and a merged item reads fine as "piece 0" to launchers
that still split. has_owner also checks the selected account first, so
the owner gate touches one item when the player owns the game.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Each content block gets a minecraft:block_entity_type id after vanilla's,
named after the block. The chunk encoder sends a mod block's block entity
with that type and the tag native_block_entity_update_tag gives
(getUpdateTag), and leaves out a mod block that has none; BlockEntityData
on a block change uses the same hook. The client makes its copy through
native_block_entity and merges the tag into its nbt. NATIVE_HOOKS_API 23.
Refs #4615
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- post_builds: only a missing native-post-build.json means no steps; any other read error, or
a mod id that isn't one, stops the build (arismoko 3, dimaniojk 1, small point).
- native_data::built and native_built_data return None when the game's path is unknown,
never a folder relative to the working directory (arismoko 5, dimaniojk 2).
- .hnmod: data is only a bundle's data/ or a crate's top-level data/ (arismoko 4); README*,
LICENSE*, COPYING*, NOTICE* and .gitignore are accepted in the bundle or a crate folder
(reservedbytes 3).
- Post-build steps get HORIZON_NATIVE_BUNDLE_DATA_DIR, the bundle's staged data/ (reservedbytes 4).
- "Building horizon-mixins (once)" shows only when it is built; the crate check runs only when
a mod has a crate; building a post-build step has its own label (reservedbytes 5).
- launcher/Cargo.toml: the libc block above the keychain comment (DUzzL 2).
- docs/native-mods.md: the "A bundle" section (crates, data files, post-build steps), lost in
an earlier merge, is back, with the .hnmod limits, run-time data and why Horizon's own
crates are refused for now (#3897; arismoko 1, dimaniojk 3).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
stack_to_network writes a fixed list of components, and single player's
window gets its stacks through it from the integrated server, as LAN
guests do. custom_model_data and item_model were not on it, so the
client's item model never saw them: custom_model_data tints stayed on
their default, selects fell back, and an item_model override was ignored.
Write both: item_model as its id, custom_model_data from its saved form
(CustomModelData.CODEC, colours as ints or float triples) into its stream
codec.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Graphics: HDR gets a calibration for Black Level, Peak Brightness and Paper White:
each step a test picture drawn by the HDR pass straight in nits, with a slider
and one line of help. Horizon's logo disappears on black, then into a patch of 10%
of the screen; Paper White is a white card, by eye. Save writes the three, Cancel
keeps them, a step left untouched keeps Auto. Paper White goes down to 10 for
dark rooms.
Where the system reports the display's values the button reads Calibrate Anyway...
with "Already set up for your display", and Use Automatic Setup puts changed
values back; where nothing is reported it asks the player to calibrate.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Predict craftable PlaceRecipe placements on the remote client, look up
ghost recipes by display id, and clear the ghost when the server updates
the open menu so red ghost slots do not stick after a successful place.
Fixes#4549
Co-Authored-By: Composer <[email protected]>
Co-authored-by: Cursor <[email protected]>
handleLogin prepares the key pair only when the login packet says the server
is in online mode, and tick asks shouldRefreshKeyPair only once there is a
chat session. sendCommand sends ServerboundChatCommandSignedPacket whenever
the command has signable arguments, with no signatures when there is no
session (ArgumentSignatures.signCommand drops null signatures).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
StructureCheck.tryLoadFromStorage and onStructureLoad take the references of
a chunk's saved starts as known, so a world saved by vanilla keeps the
starts its maps already pointed at. SaveDir::start_references reads them;
the sidecar file still holds the ones maps add here.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A lobby's giant fake player (minecraft:player or mannequin with a
minecraft:scale attribute) rendered huge but kept a 0.6x1.8 hitbox at its
feet, so right-clicking it did nothing while vanilla's scaled box answers
the click. mob_hit also skipped non-alive mobs although CAN_BE_PICKED is
isPickable = !isRemoved, hiding health-0 NPCs from clicks and the entities
command, and entity reach was hardcoded instead of the
entity_interaction_range/block_interaction_range attributes.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Keep both sides in boats.rs: upstream/main's AbstractBoat.remove lead drop
(boat_removed) and this branch's chest-content spill (unconditional) plus the
entity_drops-gated boat item drop.
Co-Authored-By: DeepSeek Harness (deepseek-flash) <[email protected]>
A crash mid-write left truncated JSON, which loads as no references and
made maps repeat structures.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The F3 overlay is built on the window thread every frame; locking the
shared query worker froze the frame behind /locate biome or /place jigsaw.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- Pre-zero reused thread-local scratch density buffer in fill_noise before density sampling.
- Track heightmap bounds dynamically with re-queried first_available across downward scans and non-opaque blocks.
- Add regression unit tests for poisoned scratch density buffer reuse and top-down surface heightmaps.
Co-Authored-By: Antigravity <[email protected]>
Drain hub player packets before scheduled block ticks on the server thread so a second sand place in the same tick as the first's fall schedule still sees the support, matching vanilla's packet-before-block order.
Co-Authored-By: Composer <[email protected]>
Co-authored-by: Cursor <[email protected]>
Single-player often had a skin but no cape because the client only filled missing skins from the services, not missing capes. Ask GET /minecraft/profile once after join and merge ACTIVE skin and cape into local textures.
Co-Authored-By: Cursor <[email protected]>
- Re-add clippy::clone_on_copy allow for UniformLocation in command.rs.
- Format indirect_execution expression in adapter.rs to satisfy rustfmt.
- Note in gfx.rs comment that multi_draw requires indirect first instance
support (OpenGL 4.2 + GL_ARB_shader_draw_parameters) in addition to 4.0 indirect draws.
Co-Authored-By: Antigravity <[email protected]>
Forgetting the only profile on the last page left the page empty until
Previous was pressed: the page is now kept to the pages the list has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An --output naming root/.local itself took that folder's lock a second
time through another handle, and waited for its own process forever.
Java found through a relative JAVA_HOME, --java-home or PATH entry is now
absolute, since the exporters run in .local.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CombatTracker only tracked mob attackers, now we track players so we dont
print '%1$s was slain by %2$s'. pr covers melee hits, sweep attacks,
arrows, thorns.
The client uses #4214's SignatureCache and its handling of a bad packed id, instead of its
own cache. markMessageAsProcessed runs where the chat takes a message, past any Chat Delay,
with whether it was shown; handleDeleteChat now calls LastSeenTracker::ignore_pending.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Identical stills share a layer under several names, so sprite_tags gave
the generated switch duplicate cases (naga: ConflictingSwitchCase) and the
pack failed to load. Their tags are now merged per layer. Reported with
the fix by DUzzL's agent on #4186.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QS9x9HatX1QRXMqxeb
Right-clicking a chest boat, a chest raft, a chest minecart or a hopper
minecart did nothing: the server-side container path (Game::lan_open_container,
shared by LAN guests and single-player players) only knew block containers and
silently dropped the vehicle's placeholder key.
- give a container vehicle its own menu and title: ChestMenu.threeRows (9x3,
27 slots) for chest boats, rafts and chest minecarts, HopperMenu (5 slots)
for hopper minecarts, titled with Entity.getDisplayName's name key, as
AbstractChestBoat.createMenu / MinecartChest.createMenu /
MinecartHopper.createMenu / MenuProvider.getDisplayName do;
- fire CONTAINER_OPEN with the player and PiglinAi.angerNearbyPiglins on a
chest boat and a chest minecart (MinecartChest overrides interact; a hopper
minecart does not), and CONTAINER_CLOSE on their close (MinecartChest
overrides stopOpen; a hopper minecart inherits the no-op);
- send OPEN_INVENTORY while riding a chest boat (HasCustomInventoryScreen ->
ServerGamePacketListenerImpl.handlePlayerCommand), so E opens the chest
instead of the player's inventory;
- spill a chest boat's contents on every destroying removal, as
AbstractChestBoat.remove does (a creative hit discards, DISCARDED
shouldDestroy is true, and Containers.dropContents has no entity_drops
gate; the boat item stays on the entity_drops death path);
- drop the chest minecart's open/close chest sounds: vanilla plays none.
Tests: tools/test_vehicle_container_parity.py (new, 13/13 menu cases against
the real 26.3 jars), live before/after on the server thread, and two-client LAN
item-loss/duplication runs.
Refs #4370
Co-Authored-By: DeepSeek Harness (deepseek-flash) <[email protected]>
- AbstractSliderButton / OptionInstanceSliderButton parity:
- ArrowLeft / ArrowRight adjust slider values when in can_change_value mode
- Space / Enter toggles can_change_value focus state
- Escape cancels can_change_value before closing the options screen
- Mouse wheel scrolling over a slider steps its value
- Render state parity: track highlighted only when focused and !can_change_value,
handle highlighted when hovered or can_change_value
- Dialog sliders: support ArrowLeft / ArrowRight nudge and mouse wheel scrolling
- General UI keyboard navigation: Tab/Shift-Tab cycle and 2D spatial Arrow navigation
Co-Authored-By: Antigravity <[email protected]>
fast (incremental, no LTO) is the default Cargo profile of native builds; a
setting, native-build --profile and 'settings native-profile' pick release.
The profile is in the key and has its own target folder. CARGO_INCREMENTAL is
removed from the build's environment. The build says up to date / incremental
(what changed) / first build before it starts, shows crates done of the total,
the profile and the time while it compiles, and 'Built in 42 s (fast,
incremental)' after.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
OldMinecartBehavior.pushAndPickupEntities also picks up living entities
in the cart's box, 0.2 wider on each side. Horizon only pushed other
carts, so a cow shoved into a minecart stayed on the ground. Eligible
mobs now startRiding an empty cart on contact; players still click to
board, iron golems still push, and a cart that already has a rider
pushes instead. Entity.canRide refuses a warden and a boarding cooldown.
sync_riders treated only boats and mobs as vehicles, so a boarded cart
would drop its rider the next tick. Riders sit on the cart's passenger
attachment, get out when it breaks, and are saved under Passengers.
Vanilla: OldMinecartBehavior.pushAndPickupEntities, Entity.startRiding,
AbstractMinecart passenger attachment, Entity.positionRider.
Co-Authored-By: OhMyAgent (grok-4.6) <[email protected]>
Display.readAdditionalSaveData reads "transformation" with
Transformation.EXTENDED_CODEC (the four-part form or an ExtraCodecs.MATRIX4F
decomposed by MatrixUtil.svdDecompose) and falls back to
Transformation.IDENTITY. The comment above the read still described only
the four-part Transformation.CODEC; it now names the codec actually followed.
The MatrixUtil.svdDecompose test pins its expected bit patterns to u32 so the
0x8000_0000-style literals never fall back to i32.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Display.readAdditionalSaveData reads "transformation" with
Transformation.EXTENDED_CODEC: the translation/left_rotation/scale/
right_rotation record, or else ExtraCodecs.MATRIX4F (sixteen floats,
row-major), which new Transformation(matrix) splits on demand with
MatrixUtil.svdDecompose (eigenvalueJacobi on a^T*a for the right
rotation, then QR Givens rotations about z, y and x for the left rotation
and scale). Horizon left a matrix-form display at the identity.
- horizon-assets math: svd_decompose, following MatrixUtil.svdDecompose,
stepJacobi, approxGivensQuat, qrGivensQuat and GivensParameters with
JOML 1.10.9's Matrix3f.mul/rotate and Quaternionf.mul arithmetic; the
test checks the bits vanilla's own decomposition returns.
- text_display: the matrix alternative feeds DATA_TRANSLATION_ID to
DATA_RIGHT_ROTATION_ID. Axis-angle rotations now follow
new Quaternionf(AxisAngle4f) (the angle wrapped to [0, 2pi), the axis
not normalized) and component rotations Quaternionf.normalize, through
the existing horizon-assets helpers, instead of an approximation.
- entity_data: a test pins VECTOR3 and QUATERNION to
FriendlyByteBuf.writeVector3f / writeQuaternion's big-endian floats.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.getFluidInteractionBox asks the vehicle, and
AbstractBoat.modifyPassengerFluidInteractionBox keeps only the part of the
passenger's box above the boat's top, unchanged when the boat is underwater and
absent when the boat reaches the passenger's top. The rider's fluid interaction
now uses that box instead of clearing the eyes afterwards, so the body
immersion, in_water and the eyes all follow vanilla and a boat floating on water
no longer drowns its passenger (#4001).
Entity.updateFluidOnEyes finds no fluid on the eyes of a rider whose eyes
are inside a boat that is not underwater. The server-side tick of a LAN
guest ignored that, so a boat bobbing in water drowned its passenger.
RegistryDataLoader builds the synchronized registries from every enabled
pack, so a pack's data/<ns>/dialog/ files join minecraft:dialog in
Identifier order (path first, then namespace) and /dialog show can open
them. The data pack loader now reads dialog/ files, SyncData merges them
over the game's own and always sends their data (no client knows the
pack), and the commands' dialog ids follow SyncData so a parsed id sends
the index the clients hold.
Opening a world with data packs now drops the preloaded SyncData, which
was read without the world's packs and kept their recipes, advancements
and dialogs from the clients.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerPlayerGameMode.useItemOn fires DEFAULT_BLOCK_USE when the block's
useWithoutItem consumes the click, and handleUseItemOn fires ANY_BLOCK_USE
when the whole useItemOn does, with the hand's stack as the use left it.
Behaviours::use_item_on_branch says which of useItemOn and useWithoutItem
answered; the generic path now also fires ITEM_USED_ON_BLOCK for a
consuming useItemOn (a candle snuffed by hand). The jukebox eject, the
decorated pot wobble and the chiseled bookshelf removal fire
DEFAULT_BLOCK_USE on their useWithoutItem branch.
Fixes#4499
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MushroomCow.getEffectsFromItemStack reads the item's block, so a brown
adult takes a flower, stores its suspicious stew entry and hands it back in
the next bowl of stew as a suspicious stew carrying that effect. Horizon
had the component, the brown variant and the bowl branch, but no flower
branch and no stored effects, so a bowl always gave plain stew.
The effect belongs to the block, not the item, so the table lives beside
the other block facts: seventeen entries, cross-checked against the jar's
own suspicious_stew_from_* recipes. The two eyeblossoms and the wither rose
are holders too, which a count of FlowerBlock constructor sites misses.
- Precompute the 48-bit masked seed base in LegacyPositionalRandom to eliminate full multiplicative seeding on position and string queries.
- Reuse thread-local scratch density buffers in fill_noise to eliminate 393 KB heap allocations per chunk pass.
- Hoist column density slices in fill_noise to eliminate 98,304 redundant volume.index() lookups.
- Implement monotonic top-down heightmap termination in fill_noise, bypassing all subterranean heightmap checks once settled.
- Cache horizontal squared distances and cell coordinates in NoiseBasedAquifer across vertical slabs, reducing inner distance loop work to a single subtraction and addition.
- Early-return in apply_carving_mask when the carving mask is empty to avoid redundant MaterialConditions setup and graph compilation.
Co-Authored-By: Antigravity <[email protected]>
Review (rdbtCVS): the "Submitted by @<login> through the form." line is
plain text anyone who writes to the registry repository can copy, and a
commit's author can be set by anyone too. The form's commit now carries
a "Submission: <tag>" trailer, an HMAC of the branch and the login with
the service's key; a stale branch is started again only when it holds no
commit of its own or the one commit with this account's tag. The test's
someone-else branch now copies alice's exact words with a forged tag and
is refused without a reset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`MinecraftServer.tickChildren` runs `commandFunctions` before any
`ServerLevel.tick`, and `EntityTickList.ensureActiveIsNotIterated` gives
an entity added while the list iterates its first tick on the next tick.
Together they mean a tick function that polls for the drop a block just
made still finds it with `Age: 0` — the pattern data packs use to hook a
block break. Horizon ticked its functions inside the entity pass, after
the items: the drop had counted its age up already and the poll found
nothing.
The functions now run at the top of the tick, before the levels, with
the world's saved data read first (a load tag's `data modify storage`
must land after the save is restored, not before). An item entity added
this tick first ticks the next one.
`type_specific/player` also matches players only, as
`PlayerPredicate.matches` starts with `instanceof ServerPlayer`, and
honours `input` (`InputPredicate`; `input.sneak` is `Input.shift`, the
held key, not the sneaking pose).
Verified with the Vein Miner data pack: its tick-driven chain mining
takes both ores it is asked to, and its `veinminer:sneaking` predicate
reports false while the player is not sneaking.
Co-Authored-By: ZCode Agent <[email protected]>
publish() refused any kind change ("blockscope is a mod"), but the
registry's own review (horizon_mods::index::registry::review, run by
horizon-mods' CI) and docs/native-mods.md let a WebAssembly mod's entry
turn native-mod once, never back. The form now allows exactly that, by
an owner, and names the rule when it refuses any other kind change. The
pull request says the project turns native.
A submission GitHub broke off after making its submit/<id>-<version>
branch was refused as "already submitted" (409) on retry, with no pull
request. When the branch exists, the form now looks for an open pull
request (still 409, with its address) and at the branch's own commits:
if they are all this account's form commits (or there are none), it
resets the branch to main and submits again; anyone else's is 409.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main added refill_own_store, which called the launcher's lacks_languages,
gone since missing_extras became the one check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Show a dash when no scene-load measurements exist; preserve measured zero and the mean of available scenes.
Co-Authored-By: OpenAI Codex GPT-6.1 Sol <[email protected]>
Main took API 3 (world lifecycle) and 4-7 (arismoko's server tick, block reads,
neighbour and block-change observations). The data-file helpers (native_data!,
native_built_data) are API 8. MIXINS_REV moves to horizon-mixins 5140cdf
(#5, #7, #8 and #9: RETURN hooks on functions with closures and references).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Merges #3574 (and with it #3567 and #3551) for docs/api/launch.md.
- --java-home names a Java the launcher manages (Options::java_home was
there for horizon-launcher only).
- The import logs the client jar it read, which --client-jar may have
named outside the Minecraft folder.
- named_asset_store_serves_the_asset_commands: with an empty Minecraft
folder, an asset command fails without --assets-dir and copies from
the store it names.
- docs/api/launch.md and native-client.md: the named inputs, that an
earlier verified import is reused instead, the server jar rule (Horizon
never downloads it; a launcher only for an owner), --java-home.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- docs/api/launch.md gets a Preparing section: --output, --check (what it
tests and what it doesn't), --complete, and --progress json's rules:
one JSON object a line, the three keys, readers ignore what they don't
know, a new key is a patch of the Launch API.
- The changelog lists the preparer's options under 0.1.0, still
unreleased. The overview, docs/api/README.md and stability.md say the
Launch API covers the preparer now.
- progress_json_lines_keep_their_shape pins message, error and fraction
(a quote, a newline, three decimals); fraction_line is the one place
that formats a fraction.
- steps_open_every_stage reads lib.rs with either line end, so it passes
on a Windows checkout too.
This branch now holds #3551 as well, for docs/api/launch.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A preparation removes the stamp before it writes anything and stamps
again at the end, so --check calls a folder being prepared, or whose
preparation stopped, not current.
- Each preparation holds .horizon-prepare.lock in the resources and in
root's .local (add_assets in the resources); a second one says it is
waiting and waits. tools/prepare_output.py leaves the lock out of the
digest.
- missing_extras is the one completeness check: --check and the
launcher's COMPLETIONS both use it.
- The Java exporters and the worker run in root's .local, so vanilla's
logs/ stays in the scratch, not where the preparer was started.
- --check's help and docs/native-client.md say what it doesn't test
(contents) and that exit 1 is either gaps or "can't check".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack can let players turn its water reflections off: [reflection] option
names an option the mirror is drawn only while it is on (a bool on, a number
above 0); without it, the mirror is drawn whenever the material reads it.
The sample pack shows it.
While mirroring, solid quads were all drawn, since the mirror's eye sees the
undersides the camera does not. Sides::visible_from keeps the quads that face
any eye from the camera straight down to its mirror below the plane, which
differ only in y, for the sections and the distant tiles.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTrA2NFEcA5CdtUTS1eH
NoiseBasedChunkGenerator.addDebugScreenInfo: DebugEntryChunkGeneration
adds 'Density N: .. T: .. V: .. C: .. E: .. D: .. W: .. PV: .. PS: ..' (the
overworld's debug_functions, three decimals) at the feet where the game
runs the level's generator: a single-player world on its server thread.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MIXINS_REV moves to horizon-mixins d9c83d7 (#7 and #8 by reservedbytes):
- a crate dependency with default features (hashbrown = "0.15") builds: cargo adds the
lock links between crates Horizon already locks, any new crate is still refused;
- a post-build bin named like another mod's step bin or like horizon-client is refused
(E_POST_BUILD).
The launcher also copies the game and each step's program out of the shared target folder as
soon as it is built, so a bundle staged by an older horizon-mixins can't run another mod's
program or replace the game.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ChunkGenerator.findNearestMapStructure with createReference: a map with
skip_existing_chunks (the default, and every cartographer trade) points at
the nearest start no map pointed at yet and counts it as pointed at
(StructureStart.addReference, getMaxReferences 1), so two maps made in a
row no longer lead to the same structure. The starts are kept per
dimension in data/horizon_structure_references_<dim>.json.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Water can reflect only what is on screen, which loses the sky over a lake and
everything above the picture's top. Within API 0.1, additively, the material
stage gains reflection and reflection_sampler: the world as a camera mirrored
about the horizontal water plane around the camera sees it, sky and solid
terrain at half resolution, cut away below the plane. Frame gains
water_plane, has_reflection and reflection_resolution.
The mirror is drawn only for a pack whose material function reads
reflection (CheckedStage::reads), so other packs and vanilla pay nothing and
the game scans for the plane only then: the median standing-water surface of
a grid of columns around the camera, rescanned when the camera changes block
or each second. Sections are culled against the union of the camera's and
the mirror's frusta; while mirroring, solid quads are not left out by facing
and occlusion culling is off, since both follow the camera's eye.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTrA2NFEcA5CdtUTS1eH
Online servers with enforce-secure-profile=true (26.3's default) refused every chat
message and /msg from Horizon ("Chat disabled due to missing profile public key").
game/remote_chat.rs (ClientPacketListener):
- setKeyPair: after each login packet, and when the key pair is renewed, a chat session
(random id, chain from link 0) goes to the server as ServerboundChatSessionUpdatePacket,
only when the server plays the account under its own profile id: an offline-mode
server's offline id gets none, so chat stays unsigned there as before.
- sendChat / sendCommand: chat and each message argument of a command (SignableCommand.of
over the server's tree) are signed with the same time, salt and last seen, one chain
link each; every packet carries the last-seen update.
- handlePlayerChat / markMessageAsProcessed: other players' packed last seen through the
signature cache, and a ChatAck once more than 64 messages wait.
- sendUnattendedCommand: a clicked chat line, a dialog's button and a mod's command stay
unsigned (vanilla asks before signing one; there is no such screen here).
tools/secure_chat_client_e2e.py: vanilla 26.3's server and a mock of the services that
issues certificates; online with enforce-secure-profile (chat logged as secure, a /msg
whisper delivered, nothing refused) and offline (unsigned, as before). The net harness
takes its chat lines from HORIZON_NET_CHAT and reports whether it signed.
Fixes the LocalChatSession.createMessageEncoder, SignedMessageChain.encoder,
ClientPacketListener.setKeyPair and ClientPacketListener.sendCommand rows of
horizon-rust/horizon#3520 and horizon-rust/horizon#3518.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- auth.rs: the services' certificates endpoint (player/getCertificates, discovery), POSTed
with the account's access token: the PKCS#8 private key, the X.509 public key,
publicKeySignatureV2, expiresAt and refreshedAfter (AccountProfileKeyPairManager.
fetchProfileKeyPair). Fetched on a background thread at start and on each join, kept in
memory like the token (no profilekeys file), renewed past refreshedAfter (checked at
most hourly), and kept as it was when a fetch fails.
- horizon-command: ParseResults::signable_arguments (SignableCommand.of), moved out of
the LAN host's handleSignedChatCommand so a client can run it over a server's tree.
Nothing sends the key yet: the client's wiring follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The protocol pieces a client needs to sign its chat as vanilla 26.3's does, beside the
server's side that is already here:
- rsa.rs: PKCS#8 (and bare PKCS#1) RSA private keys, as the services hand out a player's
chat key, and RSASSA-PKCS1-v1_5 signatures (Signer.from), checked with the public key
before they leave. Signing is deterministic, so a test compares OpenSSL's bytes.
- secure_chat.rs: SignedMessageChain.Encoder (LocalChatSession.createMessageEncoder), the
client's LastSeenMessagesTracker, and MessageSignature.Packed.unpack against the cache.
Tests run what the encoder signs through the server's Chain, and the tracker's updates
through LastSeenMessagesValidator.
Nothing calls them yet: the account's key pair and the client's wiring follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Servers place HUDs with their pack's text.vsh (glyphs far right of a boss bar title, moved to the screen's corners) and colour effects in text.fsh. horizon-shaders translates the pack's GLSL (#include or #moj_import) to naga; the HUD draws GUI text runs with it.
A pack's material stage cannot tell water from glass, a fluid's top from its
side, or a lamp from stone, and has no surface frame for normal maps. Within
API 0.1, additively, MaterialInput gains face and fluid (0 none, 1 water,
2 lava), emission (the block's HDR tier as 0..1), and the geometric normal and
tangent from the derivatives of the position and uv, snapped to an axis on
block faces so distant faces do not shimmer.
The meshers mark fluid quads in the high nibble of the colour's alpha byte,
which terrain.wgsl does not read (it reads only the emission in the low
nibble); the distant LOD tiles get the same flags.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTrA2NFEcA5CdtUTS1eH
A temporal pass (TAA, denoising) needs its own output from the frame before.
Within API 0.1, additively, the post prelude gains history (group 3, binding 8):
after the passes have run, what the last pass read is copied into it, so a
temporal pass put second to last reads its previous result next frame. It is
black on the first frame and with a single pass. Binding 8 leaves 6 and 7 for
the shadow map.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTrA2NFEcA5CdtUTS1eH
ServerPlayer.isInvulnerableTo has three terms, and the third is
!connection.hasClientLoaded: from a death until the client reports the
world loaded, nothing hurts the player. That is what spares a respawned
player the void in the tick it is still standing where it died.
Horizon implemented all three terms, but the only production
implementation of the trait returned Ok(true) for this one, so it was
dead code. A guest now carries the flag: cleared by a respawn, set by
ServerboundPlayerLoadedPacket, copied onto the body each turn, and read by
the damage environment (including the starvation path, which builds one
too). A level change does not clear it, because vanilla resets the loaded
state on death and respawn only.
Temporal passes (reprojection, motion blur, accumulation) need where a point was
last frame; packs that adapt exposure or fade underwater fog need the eye's light
and medium without their steps. Within API 0.1, additively, Frame gains
previous_view_projection, camera_motion and delta_time, filled by NativeFrame from
the frame before, and eye_light_smooth, in_water and in_lava, eased with
half-lives of 2 s and a quarter second. The frame stays a multiple of 16 bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTrA2NFEcA5CdtUTS1eH
The material stage could shade water and glass only from their own texture: it
had no way to know what was behind them, so depth-tinted water and refraction
were impossible. Within API 0.1, additively:
- MaterialInput.screen: the point's place on the screen, 0..1.
- opaque_depth (binding 6) and opaque_color (binding 11): the world's depth and
colour as the solid terrain and entities left them. The world pass ends before
translucent terrain, the targets are copied, and a pass that loads them goes on;
the depth is stored for that while a material stage runs. The colour can be
copied from the scaled world target only: drawing straight to the window, it
is a black 1x1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTrA2NFEcA5CdtUTS1eH
Basic indirect drawing (glDrawArraysIndirect, glDrawElementsIndirect) is
standard in Desktop OpenGL 4.0+ and GLES 3.1+.
1. In wgpu-hal GLES adapter: decouple DownlevelFlags::INDIRECT_EXECUTION
from compute shaders, enabling indirect execution on Desktop GL 4.0+
(and GLES 3.1+), which unlocks multi_draw in horizon-client-render on
OpenGL 4.2 hardware (e.g. Intel HD Graphics 4000).
2. In wgpu-hal GLES command and queue: avoid pushing draw_count separate
Command variants into the command buffer. Push a single DrawIndirect /
DrawIndexedIndirect command with draw_count. In queue execution, bind
DRAW_INDIRECT_BUFFER once and loop glDraw*Indirect across offsets on the
CPU, eliminating command buffer allocation churn and repeated buffer
re-bindings.
Co-Authored-By: Antigravity <[email protected]>
The villager changes (crossed arms item, held offers, equipment sync) now
live in their own branch fix/split-villager-trades; this one keeps the map
banner and frame markers.
Keep main's held-stack API (set_main_hand, main_hand_stack) and bone meal
prediction; the villager's shown item now lives in the main hand stack, and
the equipment sync sends that stack.
Packs declare [[tags]] of block sprites (glass, say) and read them as
MaterialInput.tags; the frame gains precipitation, temperature, downfall
and wetness (rain smoothed as the shaders/ format's).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QS9x9HatX1QRXMqxeb
Accent carried one of seven names; it carries a hue now, and none for the
violet, whose tokens stay the palette's. Settings shows a strip of hues
beside the swatches: a click or a drag picks any colour, the swatches stay
as shortcuts, settings.json keeps "violet"/"blue" for those and writes a
picked hue in degrees. The contrast test runs over hues nobody preset too.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
Review on #3777: worlds are small ids now (Mixer::world_id), so the callback compares and
copies integers; its camera list is a buffer the mixer keeps; a sound only looks for another
player's copy (and takes the lock for it) while another player shares the output.
Measured with 32 playing sounds and one player, 512-frame buffers (release, M-series Mac):
upstream main 21.9-22.5 us per buffer, this part before 21.3-21.8 us, after 21.3-21.6 us:
the same within noise; the time goes to mixing the samples.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Upstream moved page titles and tooltips to translation keys: the profile page gets its own
(horizon.pages.split_profiles).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Light that isn't a block gets HDR output's tier too (entity.wgsl fs_hdr_mark):
- The additive pipelines marked nothing: lightning and the dragon's eyes and death
rays get mark passes of their own (vs_lightning passes the tier on as vs_entity
does). Lightning and the death rays very bright, the dragon's eyes bright.
- End crystals, their healing beam and beacon beams bright. The mark pass now wraps
a beam's v in its sprite as fs_entity does, or it sampled transparent texels.
- Blazes bright, magma cubes and glow squid low, entity flames incandescent.
- Fireworks (sparks and the burst's flash) bright: translucent-layer particles
carry a tier and get a mark pass after their draw.
- Experience orbs very bright, glow item frames incandescent (the item in one stays
only lit).
- The overlay is the layer's low 16 bits everywhere (the entity batch's materials,
the shader packs' plain-run and hurt-colour checks), so a tier never changes how
anything draws, with HDR on or off.
Tuned in game on an AW3423DW (Senjin). HDR off: nothing changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A bundle's [[crate]] (staged by horizon-mixins 0.6.0 as native-crates/<mod>/<crate>/)
may use only the registry crates Horizon's Cargo.lock has: the build checks the staged
lock's downloaded packages against Horizon's and fetches with --locked, saying plainly
when a feature would add a crate. After the game compiles, each [[post_build]] bin is
built with the same profile and run in the new version folder with
HORIZON_NATIVE_{MOD,VERSION_DIR,DATA_DIR,CACHE_DIR}; a failure fails the build.
Progress steps are translated. MIXINS_REV moves to horizon-mixins#5's head.
A test bundle (launcher/tests/fixtures/native-crate-demo) uses all of it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_data!("<mod>", "file") compiles a file of a bundle's data/ folder
(staged as native-data/<mod>/ by horizon-mixins) into the game;
native_built_data is the folder a mod's post-build step wrote into. A
.hnmod may carry a crate's own data/ folder too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A filled map's tooltip showed none of its lines. MapId.addToTooltip gives Unknown Map when the client has no data for it, else the id line (not for a named map or one a cartography table is locking or scaling), Locked, and with advanced tooltips the scale and level, a scaling copy counting one more up to 4 (MapId.java:27). The lines sit between the instrument and bees lines as in ItemStack.addDetailsToTooltipComponents.
Taking a map out of an item frame, breaking the frame, a blast or /kill left the frame's marker and saved entry on the map. ItemFrame.removeFramedMap calls MapItemSavedData.removedFromFrame(pos, entityId) on every path that empties the frame (dropItem unless the drops are off and something caused it, onKilled), which removes the marker and the frame entry (MapItemSavedData.java:421, ItemFrame.java:247).
A map hanging in an item frame never showed the frame's marker, and a player who saw it framed without carrying it kept a marker. ServerEntity.sendChanges ticks every 10 ticks tickCarriedBy(player, stack, frame) for each player: one carrying no copy loses their marker, and with position tracking the frame's marker is added under its entity and the frame saved with its block, rotation (2D direction times 90) and entity, replacing an older entity's marker at the same block (MapItemSavedData.java:159).
A player wearing a carved pumpkin was still drawn on other players' maps. MapItemSavedData.tickCarriedBy removes the marker of every other holder for whom hasMapInvisibilityItemEquipped finds a piece of #map_invisibility_equipment in an armor slot (MapItemSavedData.java:225). Each holder keeps what it wore on its own tick.
A banner marker stayed on the map after its banner was broken or changed. MapItem.update calls MapItemSavedData.checkBanners for every column it reads: a marker in that column whose banner is no longer the saved one (colour, name or the banner itself) loses its decoration and its entry (MapItemSavedData.java:401).
A map used on a placed banner did nothing. MapItem.useOn toggles that banner's marker through toggleBanner: SUCCESS swings and counts as a use, FAIL (the map refuses) ends the use quietly, and the item is never placed or used otherwise (MapItem.java:336).
A map could not add or drop a banner marker on request. MapItemSavedData.toggleBanner works only for a banner inside the map: one whose marker stands as it is loses it, otherwise the marker is added (renewed when the banner changed) unless the map already tracks more than 256 markers (MapItemSavedData.java:371, MapDecorationType.trackCount).
A map had no way to ask what banner stands at a position. MapBanner.fromWorld returns the banner block entity's base colour (the block's dye colour) and custom name, or nothing when no banner is there (MapBanner.java:25).
A map kept its banners and frames lists as raw tags, so the markers never showed. MapItemSavedData's constructor adds a banner marker in the banner's dye colour (MapBanner.getDecoration, the 16 banner decoration types) and a frame marker for each saved entry, without a level, and the lists are written back (MapBanner.CODEC, MapFrame.CODEC, MapItemSavedData.java:100). A locked copy keeps the banners and the decorations (MapItemSavedData.locked).
A map kept one marker per player and rebuilt the item ones, with no place for a banner or frame marker. MapItemSavedData.addDecoration files each MapDecoration under a key (player, banner, frame), keeps its place in the drawing order, clamps it to the map, swaps a player's arrow for the off-map ones and drops what falls outside (MapItemSavedData.java:253). Markers now carry their name and key, and the update packet sends the name.
With two or more matching offers a villager swapped nothing: the held item never changed. Vanilla counts 40 ticks, then holds up the next display item and wraps to the first (ShowTradesToPlayer.displayCyclingItems); start resets the counter and index.
A villager looking at a player who holds a trade's cost now collects the offers in stock that take that item, holds up the first and keeps looking for 900 ticks instead of 40 (ShowTradesToPlayer.findItemsToDisplay, updateDisplayItems, displayFirstItem). The held stack keeps its count and enchantments and never drops (displayAsHeldItem). Offers not made yet are rolled by the level first.
ShowTradesToPlayer clears the main hand every tick it has nothing to show and again when it stops, restoring the default 0.085 drop chance (ShowTradesToPlayer.clearHeldItem). Horizon left whatever the villager held in place.
A brain tick holds only the mob, but AbstractVillager.getOffers makes the trades the first time they are asked for and that needs the level's trade data and random sequences. VillagerOffers has the game run ensure_offers for the villager, as the trading screen does.
SetEquipment carried only the main hand's item and count, so an enchanted item a mob holds (the offer a villager shows) never glinted on a client. Vanilla sends the whole stack (ClientboundSetEquipmentPacket); the server now adds the held enchantments from the gear table and the client keeps them there.
A villager's held item was never drawn: its model has no hand arms, so ItemInHandLayer found no hand. Vanilla's VillagerRenderer adds CrossedArmsItemLayer, which puts the main hand's item on the arms and shows it in the GROUND display context (CrossedArmsItemLayer.java), as the witch already does.
Part of the splitscreen series (#3541). The page a joining splitscreen player picks its
profile on, in horizon-client-render with its own tests, and the profile store it reads:
names kept in the game folder (splitscreen/profiles/<name>/), pages of them, New Profile...
(a player name: 3 to 16 letters, digits or underscores), forgetting one (hidden, its settings
kept), Cancel. Options pages it opens from grey Language and Resource Packs for another
player (the whole process's). Nothing opens it yet: the game side joins next.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AMD FidelityFX SDK 1.1.4's frame interpolation (MIT): dilated depth and camera
motion vectors, the estimated depths, the game and optical flow vector fields,
the disocclusion mask, the interpolation and the inpainting, as WGSL compute
passes. Not used by the game yet; the setting and the present thread follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Select the nearest listener in each world, suppress duplicate world sounds, and clean up shared listeners and channels. Inaudible sources continue advancing. Part 2 of the splitscreen series; game integration follows separately.
Co-Authored-By: Codex <[email protected]>
Linux releases ship horizon-launcher-linux-x86_64.AppImage beside the
.tar.gz: one file to download, make executable and run, with the
libraries GPUI needs beside it. glibc, the graphics drivers and the
session's D-Bus stay the machine's (a driver cannot be packaged), so the
image needs glibc 2.35 or newer, like the .tar.gz. appimagetool 1.9.1
is pinned by SHA-256 and run extracted, so building needs no FUSE; the
image itself needs none either, as its runtime mounts it where FUSE
works and unpacks it where it does not.
Inside an AppImage, current_exe names a copy in the read-only mount, so
a launcher in one updates by replacing the file $APPIMAGE names, and
the menu entry it writes starts that file rather than the mount's copy.
The .tar.gz stays what every other launcher updates itself from.
Closes#2110.
Co-Authored-By: DeepSeek V4.1 Flash 1017 (River) <[email protected]>
--version-json, --client-jar, --libraries-dir and --assets-dir name
the files a launcher keeps in a layout of its own (instance folders,
shared libraries and assets), each replacing only what --minecraft-dir
would give; every file is still checked against research-lock.json's
pins. GameFiles gains the libraries and asset store folders, and
Options the Inputs (Default in the launcher, so it is unchanged).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--complete prepares, copies the sounds, then fails unless --check would
pass: one command for resources with nothing missing (without the sounds
step the game is silent, which is easy to miss).
--progress json prints one JSON object a line: {"message": ...} for each
log line, {"fraction": F} as each of the preparation's 18 stages begins
and at the end, {"error": ...} on failure. prepare_with_progress takes
the fraction; prepare keeps its signature, and a test keeps STAGES equal
to the stages steps() opens.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--output DIR puts the prepared resources elsewhere than
<root>/.local/horizon-client/<version>, for preparing and for the asset
commands (sounds, panorama, fonts, packs, languages, now one add_assets
behind them); the exporters' scratch files stay in <root>/.local.
--check exits 0 when the resources are complete and current for this
preparer and 1 otherwise, printing each gap with the command that fills
it: the manifest's schema and Minecraft version, the OUTPUT_VERSION that
made them, and the extras the launcher completes (packs, panorama,
Unifont, languages, sounds). A preparation now stamps its resources with
its OUTPUT_VERSION (horizon-prepare.json), which the output digest
leaves out, so the stamp needs no new OUTPUT_VERSION.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
One Hide, shown after the same three minutes on screen, now sits on the Servers
banner as well as the sidebar card, and hiding either hides both for good.
The on-screen timer counts on every page, so a player who lives in the Servers tab
reaches the Hide too.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The LOD Distance button becomes a slider from 64 chunks to 4194304 (the whole world). Far terrain plans up to 22 levels with larger cells past 1024 chunks, meshes tiles from level 6 up as heightmaps, makes a very wide face one scaled quad instead of thousands, and bounds the clip subdivision of coarse tiles around full-detail chunks.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The next start removes horizon-launcher.exe.old-* beside it. The launcher
that just restarted into the new binary is still exiting, so it keeps
trying for a few seconds in the background; a file still in use (another
launcher window) is left for a later start.
self_replace copied the launcher under a random hidden name
(.horizon-launcher.<random>.__relocated__.exe), started that copy to wait
for the launcher to exit and delete it, and deleted the copy through a
hidden cmd.exe. Microsoft Defender quarantines that self-deleting pattern
as Trojan:Script/Wacatac.H!ml (#3175).
Windows lets a running binary be renamed, so the running launcher now
steps aside to horizon-launcher.exe.old-<pid>-<n> in its own folder and
the new binary takes its path. No copy is started, and the crate (with
its start-up hook that ran in every launch) is gone from the build.
2026-10-28 13:57:08 +01:00
811 changed files with 177443 additions and 4964 deletions
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.'
Thanks for helping. Horizon is Minecraft: Java Edition 26.3 rewritten in Rust, and the one rule that shapes
everything is that **it plays exactly like vanilla**. This page is the short version of how we work; questions are
welcome on [Discord](https://discord.gg/HcTeMZxe5E).
welcome on [Discord](https://discord.gg/playhorizon).
This repository holds the game and the launcher, plus the tools, tests and docs that build, check and release them.
The website (playhorizon.dev) is built in a separate repository from this one's README, `docs/api/` and roadmap. Anything else (videos, Discord and other community material) lives elsewhere.
@@ -113,6 +113,10 @@ it (edited on every push, never repeated): talk to its author in the thread befo
can see.
- **Tick "Allow edits by maintainers".** When something small is left (a lint, a rebase, a nit) we push the fixup
ourselves instead of sending it back to you; only design questions come back.
- **Once we've pushed to your branch, merge main into it; don't rebase or force-push.** A rebase or force-push
replaces our commits with yours and erases the fixes we added (it happened on #5188 and #5203). Tell your agent
too: `git merge origin/main`, never `git rebase` or `git push --force` on a branch a maintainer has touched. If
our fixes get erased anyway, we finish the work in our own pull request that keeps your commits and credits you.
- **Tick its kind** in the template: `parity` (Horizon now does what vanilla does), `bug` (a crash or breakage),
`perf`, `feature` (new for players), `extra` (Horizon-only, off by default), `tooling` or `docs`. A workflow sets
that label, and area labels (`launcher`, `mobs`, `worldgen`, `rendering`...) from the paths you change.
@@ -136,5 +140,5 @@ new pushes, we add it again.
## Talk to us
[Discord](https://discord.gg/HcTeMZxe5E) for questions and ideas, [issues](https://github.com/horizon-rust/horizon/issues)
[Discord](https://discord.gg/playhorizon) for questions and ideas, [issues](https://github.com/horizon-rust/horizon/issues)
for bugs. To take something on, `/claim` its issue ([above](#claim-before-you-start)).
@@ -111,7 +111,7 @@ What is in progress, what comes next and where to help: [ROADMAP.md](ROADMAP.md)
## Contributing
Issues and pull requests are welcome: read [CONTRIBUTING.md](CONTRIBUTING.md) (vanilla first, small pull requests). Questions: [Discord](https://discord.gg/HcTeMZxe5E). 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). Questions: [Discord](https://discord.gg/playhorizon). Before a pull request, run the self-check and paste its output:
@@ -742,13 +742,13 @@ Open: the GUI Scale is clamped to 2 after a restart under Hyprland, while XWayla
</details>
#### AppImage and a Nix flake
#### A Nix flake
Install Horizon on any Linux distribution as an AppImage, and on NixOS through a Nix flake.
Install Horizon on NixOS through a Nix flake.
<details><summary>Technical</summary>
A player's idea (#horizon-ideas). An AppImage of the launcher and game from the release workflow, and a flake.nix packaging the release binaries with their runtime libraries (Vulkan loader, X11/Wayland, ALSA), the way horizon-parity's NixOS setup runs them today.
A player's idea (#horizon-ideas). The launcher's AppImage ships with each launcher release ([packaging/linux/appimage.sh](packaging/linux/appimage.sh)): one file with the libraries GPUI needs, which replaces itself when it updates. A flake.nix packaging the release binaries with their runtime libraries (Vulkan loader, X11/Wayland, ALSA) is still missing, the way horizon-parity's NixOS setup runs them today.
"horizon.pages.settings.tooltip":"Horizon's own settings: graphics, controllers, extras and mods.",
"horizon.pages.graphics.tooltip":"Graphics, simply: each setting says what it costs.",
"horizon.pages.graphics_advanced.tooltip":"Every graphics setting, Horizon's included, with what each costs.",
"horizon.pages.controller.tooltip":"Controllers: their buttons, sticks, vibration and menu navigation.",
"horizon.pages.extras.tooltip":"Optional additions, all off by default: the game stays vanilla until you turn one on.",
"horizon.pages.agent.tooltip":"Let your own AI agent play: horizon-ctl and MCP clients on this computer.",
"horizon.pages.split_profiles.tooltip":"Who plays: a profile kept in this game folder.",
"horizon.pages.shader_packs.tooltip":"Shader packs from the shaderpacks folder: Horizon's own and those in the shaders/ format. One runs at a time.",
"horizon.pages.mods.tooltip":"Mods made for Horizon, from the horizon-mods folder. Each runs in a sandbox and stays off until you turn it on.",
"horizon.pages.open.graphics":"Graphics...",
@@ -196,5 +198,8 @@
"Threads that generate, light and mesh chunks. Next start.":"Threads that generate, light and mesh chunks. Next start.",
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.":"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.",
"Mesh data sent to the GPU per frame. Lower is smoother while loading.":"Mesh data sent to the GPU per frame. Lower is smoother while loading.",
"Frame rate and frame times over the game, with a graph.":"Frame rate and frame times over the game, with a graph."
"Frame rate and frame times over the game, with a graph.":"Frame rate and frame times over the game, with a graph.",
"horizon.multiplayer.finding_friend":"Finding your friend's world...",
"horizon.options.unavailable":"Esta función aún no está disponible en Horizon.",
"horizon.options.search":"Buscar ajustes...",
"horizon.pages.mods":"Mods",
"horizon.pages.shader_packs":"Paquetes de shaders",
"horizon.pages.controller":"Ajustes del mando",
"horizon.pages.controller_binds":"Asignaciones del mando",
"horizon.pages.extras":"Extras de Horizon",
"horizon.pages.settings":"Ajustes de Horizon",
"horizon.pages.agent":"Agente de IA",
"horizon.pages.settings.tooltip":"Los ajustes propios de Horizon: gráficos, mandos, extras y mods.",
"horizon.pages.graphics.tooltip":"Gráficos, sin complicaciones: cada ajuste dice lo que cuesta.",
"horizon.pages.graphics_advanced.tooltip":"Todos los ajustes gráficos, incluidos los de Horizon, con lo que cuesta cada uno.",
"horizon.pages.controller.tooltip":"Mandos: sus botones, palancas, vibración y navegación de menús.",
"horizon.pages.extras.tooltip":"Añadidos opcionales, todos desactivados por defecto: el juego sigue siendo vanilla hasta que actives uno.",
"horizon.pages.agent.tooltip":"Deja que tu propio agente de IA juegue: horizon-ctl y clientes MCP en este equipo.",
"horizon.pages.shader_packs.tooltip":"Paquetes de shaders de la carpeta shaderpacks: los propios de Horizon y los del formato shaders/. Se ejecuta uno a la vez.",
"horizon.pages.mods.tooltip":"Mods hechos para Horizon, de la carpeta horizon-mods. Cada uno se ejecuta en un entorno aislado y sigue apagado hasta que lo actives.",
"horizon.multiplayer.join_friend":"Unirse a un amigo",
"horizon.multiplayer.invite_code":"Código de invitación",
"horizon.multiplayer.relay":"Relay (opcional)",
"Preset":"Preajuste",
"LOD Distance":"Distancia LOD",
"Far Detail":"Detalle lejano",
"Cave Culling":"Oclusión de cuevas",
"Entity Culling":"Oclusión de entidades",
"Chunk Updates":"Actualizaciones de chunks",
"Upload Budget":"Presupuesto de subida",
"Sky":"Cielo",
"Fog":"Niebla",
"Weather":"Clima",
"Animated Textures":"Texturas animadas",
"Present Mode":"Modo de presentación",
"Frame Pacing":"Ritmo de fotogramas",
"Worker Threads":"Hilos de trabajo",
"Resolution Scale":"Escala de resolución",
"Anti-aliasing":"Antialiasing",
"Upscaling":"Reescalado",
"HDR Settings...":"Ajustes HDR...",
"Paper White":"Blanco de papel",
"Peak Brightness":"Brillo máximo",
"Black Level":"Nivel de negro",
"UI Brightness":"Brillo de la interfaz",
"Battery Mode":"Modo batería",
"Graphics API":"API de gráficos",
"Performance Overlay":"Superposición de rendimiento",
"Full Detail Distance":"Distancia de detalle completo",
"On":"Activado",
"Off":"Desactivado",
"Unlimited":"Ilimitado",
"Auto":"Automático",
"Custom":"Personalizado",
"Performance":"Rendimiento",
"Balanced":"Equilibrado",
"Quality":"Calidad",
"Ultra":"Ultra",
"Ultra Performance":"Rendimiento ultra",
"Native":"Nativo",
"Full":"Completo",
"Reduced":"Reducido",
"Efficient":"Eficiente",
"Precise":"Preciso",
"Auto (VSync)":"Automático (VSync)",
"FIFO":"FIFO",
"Mailbox":"Mailbox",
"Immediate":"Inmediato",
"Vulkan":"Vulkan",
"DirectX 12":"DirectX 12",
"OpenGL":"OpenGL",
"FPS and Graph":"FPS y gráfico",
"FPS":"FPS",
"TAA":"TAA",
"FXAA":"FXAA",
"No cost":"Sin coste",
"Low cost":"Coste bajo",
"Medium cost":"Coste medio",
"High cost":"Coste alto",
"Saves time":"Ahorra tiempo",
"Rendering":"Renderizado",
"Display":"Pantalla",
"Preferences":"Preferencias",
"Overlay":"Superposición",
"HDR":"HDR",
"Graphics":"Gráficos",
"Graphics: Advanced":"Gráficos: avanzados",
"Graphics: HDR":"Gráficos: HDR",
"Advanced...":"Avanzados...",
"Simple...":"Simples...",
"Done":"Hecho",
"This feature is not yet available in Horizon.":"Esta función aún no está disponible en Horizon.",
"Your display or system doesn't offer HDR output.":"Tu pantalla o sistema no ofrece salida HDR.",
"Turn HDR on to use it.":"Activa el HDR para usarlo.",
"%s chunks":"%s chunks",
"%s per frame":"%s por fotograma",
"%s MiB/frame":"%s MiB/fotograma",
"%s KiB/frame":"%s KiB/fotograma",
"%s nits":"%s nits",
"Auto (%s)":"Automático (%s)",
"Auto (%s nits)":"Automático (%s nits)",
"%.2f nits":"%.2f nits",
"Full-detail terrain range. LOD Distance can extend local worlds farther.":"Alcance del terreno con todo el detalle. La distancia LOD puede extender más los mundos locales.",
"Coarse distant terrain in local worlds. Integrated GPUs stop at 128 chunks.":"Terreno lejano de baja resolución en mundos locales. Las GPU integradas se limitan a 128 chunks.",
"Reduced draws far terrain coarser: faster on this integrated GPU.":"Reducido dibuja el terreno lejano con menos detalle: más rápido en esta GPU integrada.",
"Reduced draws far terrain coarser, for GPUs it slows down.":"Reducido dibuja el terreno lejano con menos detalle, para GPU a las que ralentiza.",
"Field of view. Wider shows more terrain at once.":"Campo de visión. Más amplio muestra más terreno a la vez.",
"Caps frames per second; a cap saves power and heat.":"Limita los fotogramas por segundo; un límite ahorra energía y calor.",
"Waits for the display's refresh: no tearing, a little latency.":"Espera al refresco de la pantalla: sin desgarro, algo de latencia.",
"Size of menus and the HUD.":"Tamaño de los menús y del HUD.",
"Sets the options below together. Changing one makes it Custom.":"Ajusta juntas las opciones siguientes. Cambiar una lo pasa a Personalizado.",
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.":"La lava, el fuego y la luz del sol brillan más que el blanco. Requiere una pantalla HDR.",
"Your display's darkest black. 0 for OLED; LCDs glow a little.":"El negro más oscuro de tu pantalla. 0 para OLED; las LCD brillan un poco.",
"How bright highlights get. Auto: the peak your system reports.":"Cuánto brillan los reflejos. Automático: el pico que informa tu sistema.",
"How bright white paper and menus look. Auto: your desktop's white.":"Qué tan brillante se ve el blanco del papel y los menús. Automático: el blanco de tu escritorio.",
"How bright the HUD and menus are, against paper white.":"Qué tan brillantes son el HUD y los menús respecto al blanco de papel.",
"How far mobs, crops and machines keep working. CPU time.":"Hasta dónde siguen funcionando mobs, cultivos y máquinas. Tiempo de CPU.",
"How far away mobs and items are drawn, as a share of normal.":"A qué distancia se dibujan mobs y objetos, como proporción de lo normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.":"Omite el terreno oculto tras secciones cerradas. Desactivarlo cuesta mucho.",
"Hides models behind opaque blocks. Entities update. Off with custom packs.":"Oculta modelos tras bloques opacos. Las entidades se actualizan. Desactivado con paquetes personalizados.",
"Meshed sections uploaded per frame. Lower is smoother while loading.":"Secciones malladas subidas por fotograma. Menos es más fluido durante la carga.",
"Soft shadows in corners. Costs chunk building time only.":"Sombras suaves en las esquinas. Solo cuesta tiempo de construcción de chunks.",
"Smaller copies of block textures for distant terrain: less shimmer.":"Copias más pequeñas de las texturas de bloques para el terreno lejano: menos parpadeo.",
"Blends grass and water colours across biomes when building chunks.":"Mezcla los colores del césped y el agua entre biomas al construir chunks.",
"Cloud layer: off, flat (fast) or 3D (fancy).":"Capa de nubes: desactivada, plana (rápida) o 3D (detallada).",
"How far clouds reach, in chunks.":"Hasta dónde llegan las nubes, en chunks.",
"Draws the sky, sun, moon and stars.":"Dibuja el cielo, el sol, la luna y las estrellas.",
"Distance fog. Off shows the edge of the loaded world.":"Niebla de distancia. Desactivada muestra el borde del mundo cargado.",
"Draws rain and snow.":"Dibuja la lluvia y la nieve.",
"How far rain and snow fall around you.":"Hasta dónde caen la lluvia y la nieve a tu alrededor.",
"How many particles effects spawn.":"Cuántas partículas generan los efectos.",
"Round shadows under mobs and items.":"Sombras redondas bajo mobs y objetos.",
"Leaves with see-through gaps. Off draws them solid.":"Hojas con huecos transparentes. Desactivado las dibuja sólidas.",
"Sorts water and glass more exactly behind each other.":"Ordena el agua y el cristal con más precisión unos tras otros.",
"Which chunk rebuilds wait for the frame: none, nearby or yours.":"Qué reconstrucciones de chunks esperan al fotograma: ninguna, cercanas o la tuya.",
"Animates water, lava, fire and other moving textures.":"Anima el agua, la lava, el fuego y otras texturas en movimiento.",
"World pixels per screen pixel. Below 100% is faster on the GPU.":"Píxeles del mundo por píxel de pantalla. Por debajo del 100 % es más rápido en la GPU.",
"Smooths jagged edges. FXAA: softer. TAA: smoothest, moving things may trail.":"Suaviza los bordes dentados. FXAA: más suave. TAA: el más suave, lo que se mueve puede dejar estela.",
"Renders the world smaller and rebuilds it with TAA. Faster, softer.":"Renderiza el mundo más pequeño y lo reconstruye con TAA. Más rápido y más suave.",
"HDR output on an HDR display: lava, fire and sunlight shine brighter.":"Salida HDR en una pantalla HDR: la lava, el fuego y la luz del sol brillan más.",
"How frames reach the screen while VSync is off.":"Cómo llegan los fotogramas a la pantalla con VSync desactivado.",
"Efficient sleeps until the next frame; Precise spins, like vanilla.":"Eficiente duerme hasta el siguiente fotograma; Preciso gira, como en vanilla.",
"Fills the screen (borderless).":"Llena la pantalla (sin bordes).",
"Brightens dark places.":"Ilumina los lugares oscuros.",
"Lowers the frame rate when minimised, or idle too.":"Reduce los fotogramas por segundo al minimizar o al estar inactivo.",
"Sways the view while walking.":"Balancea la vista al caminar.",
"Darkens the screen's edges.":"Oscurece los bordes de la pantalla.",
"Shows when the world saves.":"Muestra cuándo se guarda el mundo.",
"Shows attack cooldown.":"Muestra el enfriamiento del ataque.",
"60 FPS cap (30 in menus or unfocused), half the workers: less power.":"Límite de 60 FPS (30 en menús o sin foco), la mitad de los hilos: menos consumo.",
"Threads that generate, light and mesh chunks. Next start.":"Hilos que generan, iluminan y mallan chunks. Próximo inicio.",
"Vulkan, then DirectX 12 (Windows) or OpenGL (Linux) where it fails. Next start.":"Vulkan y, donde falla, DirectX 12 (Windows) u OpenGL (Linux). Próximo inicio.",
"Mesh data sent to the GPU per frame. Lower is smoother while loading.":"Datos de malla enviados a la GPU por fotograma. Menos es más fluido durante la carga.",
"Frame rate and frame times over the game, with a graph.":"Fotogramas por segundo y tiempos de fotograma sobre el juego, con un gráfico.",
"horizon.multiplayer.finding_friend":"Buscando el mundo de tu amigo…",
"horizon.loading.opening_world":"Abriendo el mundo",
"horizon.loading.cancelling_world":"Cancelando"
}
Loaded 100 of 811 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.