🤖 Agent: queued at 73047c1810. The import refuses anything that isn't a native executable carrying the mod API mark (`read_mark`). It builds the version in `versions/.importing/<tag>-<pid>-<n>` and renames it into place in one step, so a version is never half made. Deletions stay inside the launcher's own `versions` folder. The game files come from an installed version, so the owner gate still applies.
🤖 Agent: queued at 857b399103. The three MCP servers now share one `horizon-mcp` loop: `2026-07-28` stateless (version on every request, `resultType`, `ttlMs`/`cacheScope` on lists) and `2025-06-18` for clients that still open with `initialize`. An unknown version gets `-32022` instead of an echo. Each server keeps only its name, its instructions and its two tool methods.
🤖 Agent: queued at 3ce797f378. `separateAo` keeps the tint without AO in RGB and puts AO in the vertex alpha, only while the pack asks for it. Off, it costs one branch per vertex. `depthtex2` now leaves the hand out, and packs without PBR maps get flat normals and black specular. I dropped the textual end-of-function return in favour of #6821's `ZeroValue`, which also covers struct results.
🤖 Agent: queued at 09199b88ab. I checked every row's target on main: all 3 files and functions exist. For example, `ChatTypeDecoration.decorate` points to `crates/horizon-client/src/game/remote.rs::chat_type_decorate`.
The text rewrite appended `return T(0);` to every value-returning
function, which doesn't compile for a struct type (`S(0)`). Main's #6821
gives naga's added end returns the result type's ZeroValue, structs
included; this PR's early-return test passes on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 55e1564629. I checked every row's target on main: all 2 files and functions exist. For example, `InputPredicate.matches` points to `crates/horizon-client/src/advancements.rs::input`.
🤖 Agent: requeued at e7226b798 after merging main. #6821's zero-return test and this PR's two prototype tests now sit side by side; the horizon-shaders translate tests (64) and clippy pass.
🤖 Agent: queued at 7ba83315a8. Agent-made window events are left out of the trace, and the socket line is recorded instead, so an agent's input is never counted as a person's. A person's intrusion is announced once per quiet window. I added a gate so the trace runs only while the control socket, the repro keys or a recording is active. I also merged main, keeping both the `reproKeys` and the splitscreen `extras` keys.
🤖 Agent: queued at 4cc1135dc1. The client now shows the server's result slot (container slot 0 of the open menu) instead of computing it from its own recipes, so a world data pack's recipe shows. A result the click engine predicts is kept, because `click_agreed` sends none back. The new `datapack-recipe` lan_e2e check covers both the host window and a guest.
🤖 Agent: queued at f487821eff. Enter on the server list now goes through `entry_address`, like a click, so a selected LAN game joins with `lan: true` too. With nothing selected it does nothing. The test steps with the arrows and joins each server.
🤖 Agent: queued at e77404a1f0. The test reads `tickCount` (`m.age`), not `AgeableMob.age`, which stays at 0 for an adult, so the 2400 rule now applies to adults as in vanilla. The cat joins the ocelot with `!isTame()`, and other animals keep `Animal.removeWhenFarAway`'s false.
🤖 Agent: queued at b61060e387. I checked every row's target on main: all 1 files and functions exist. For example, `EntitySelectorParser.createRotationPredicate` points to `crates/horizon-client/src/game/commands.rs::rotation_in`.
🤖 Agent: queued at 5cecb571ac. I checked every row's target on main: all 11 files and functions exist. For example, `InteractWithDoor.create` points to `crates/horizon-client/src/mobs/animal_brain.rs::B::trigger,`.
🤖 Agent: queued at 7aefc5f447. I checked every row's target on main: all 1 files and functions exist. For example, `PlayerAdvancements.reload` points to `crates/horizon-client/src/lan/advancements.rs::ServerAdvancements::reload`.
🤖 Agent: queued at 4e673a629e. I checked every row's target on main: all 2 files and functions exist. For example, `CompilableString.CommandParserHelper.apply` points to `crates/horizon-client/src/game/commands/text_commands.rs::invalid_selector`.
🤖 Agent: queued at 80f5297499. I checked every row's target on main: all 1 files and functions exist. For example, `PlayerDataStorage.backup` points to `crates/horizon-client/src/save.rs::back_up_unreadable_player`.
🤖 Agent: queued at 17a6c9527d. I checked every row's target on main: all 8 files and functions exist. For example, `ServerGamePacketListenerImpl.getMaximumFlyingTicks` points to `crates/horizon-client/src/lan/hub.rs::maximum_flying_ticks`.
🤖 Agent: queued at f0649477b6. I checked every row's target on main: all 1 files and functions exist. For example, `StructureBlock.setPlacedBy` points to `crates/horizon-client/src/game/structure_blocks.rs::structure_block_placed`.
🤖 Agent: queued at 5ec019ddeb. I checked every row's target on main: all 3 files and functions exist. For example, `PotentSulfurBlock.triggerEvent` points to `crates/horizon-world/src/level/pistons.rs::Sim::trigger_event`.
🤖 Agent: queued at df471629cf. I checked every row's target on main: all 17 files and functions exist. For example, `AbstractHorse.causeFallDamage` points to `crates/horizon-client/src/mobs/living.rs::Mob::cause_fall_damage_at`.
🤖 Agent: queued at 1c70bc70b8. I checked every row's target on main: all 9 files and functions exist. For example, `PotentSulfurBlockEntity.findNoxiousGasSourceBlock` points to `crates/horizon-client-render/src/potent_sulfur.rs::find_noxious_gas_source_block`.
🤖 Agent: queued at c6c858aadb. I checked every row's target on main: all 5 files and functions exist. For example, `MapBanner.getId` points to `crates/horizon-client/src/game/maps.rs::toggle_banner`.
note_input ran on every key and every grabbed mouse motion, agent control
off included. It now returns at once unless the control socket is up, the
repro shortcuts are on, or a recording runs, so players who use none of
them pay nothing per input event.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at ab8be4adc3. The two layout choices and Names are P1's settings, saved in its options file during its own turn (`self.turn(0)`), and loading clamps out-of-range values to 0..=2. Every label goes through `horizon.splitscreen.setup.*` keys. One leftover: `extras::SPLIT_TWO` and `SPLIT_THREE` are English arrays that nothing reads, so they can go in a follow-up.
🤖 Agent: queued at 4d0c0fb6d1. Manual mutes reuse the filter's store, with optional `by` and `reason`, and `until: null` means permanent, so old `chat-filter-mutes.json` files still load. A filter mute never shortens an operator's. One point for a follow-up: the replies (`Muted {name} …`, `There are N mute(s)`) are English literals, as `/unmute`'s were. The game's rule is translation keys, so a later pass should move all four commands to `lang` keys.
🤖 Agent: queued at 955a5059e9. This now follows `BucketItem.emptyContents`: on a refusal it retries `pos.relative(direction)` with no hit result, where Shift no longer counts. `bucket_target` also asks `canPlaceLiquid`, so kelp, seagrass, a double slab and a survival barrier aim beside the block from the start, and a fish never spawns inside a double slab. The client's dry run now judges "server-side only" on where the water actually went.
🤖 Agent: queued at 3c9f09475e. The fallback now names `gpui_platform/runtime_shaders`, the feature of `launcher/Cargo.toml`'s `gpui_platform` rename (`gpui-fast` has none). The test runs `pr_ready`'s real shell snippet with fake `uname`, `xcrun` and `cargo`, so it checks what cargo receives, not the string.
🤖 Agent: queued at 2b5e91b348. Answering no now returns to the book or dialog the click left, as `openSendConfirmationWindow`'s `currentScreen` does. A lectern's book isn't kept, because it goes with its menu. The prompt frees the pointer and takes Tab, the arrows, Enter and Space. The capture test covers the release.
🤖 Agent: queued at 81569ba550. These tests pin `CurrentAccumulator.applyTo`: a mob takes the normalised direction (two samples push as hard as one), a player the average, and the 0.0045 lift applies only to a body nearly still on x and z. Each expected value is worked out by hand from vanilla's formula.
🤖 Agent: queued at 06582aea2b. The launch world's preload now starts once the block registry and the biomes are in, about 250 ms earlier. It runs only when `root == vanilla_root`, so a failed pack that restarts core from the game's own resources doesn't preload twice. `biomes` becomes an `Arc` so both share it.
🤖 Agent: queued at 5299166ad0. The two expected radii come from the renderers, not from the code: `StriderRenderer` halves 0.5 × 0.5 again (0.125), and `TurtleRenderer` takes 0.83 of 0.7 × 0.3.
🤖 Agent: queued at 8324aabac2. I checked every row's target on main: all 11 files and functions exist. For example, `BlockStateModelWrapper.updateTints` points to `crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices`.
🤖 Agent: queued at d1a5b12246. I checked every row's target on main: all 6 files and functions exist. For example, `ServerPlayer.saveParentVehicle` points to `crates/horizon-client/src/save.rs::Level::player_tag`.
🤖 Agent: queued at be131063a0. I checked every row's target on main: all 9 files and functions exist. For example, `ClientBundleTooltip.extractSelectedItemTooltip` points to `crates/horizon-client/src/game/stations.rs::Game::draw_bundle_selected_name`.
🤖 Agent: queued at 40c20bf016. LOD tiles now carry the section mesher's HDR tier (`hdr_tier`), so a light source keeps shining past the LOD boundary. The conduit's shine skips its crumbling overlay, and the warden's layers shine by their faded alpha (`* in.color.a`). All of it is per-vertex data set at mesh time, with no extra pass.
🤖 Agent: queued at a7125b5ace. Water gets HDR lighting from a second, alpha-only pass (`ALPHA_ONLY`: colour kept, alpha replaced) over the translucent draws. `fs_translucent_mark` discards texels below 0.6 alpha, so clear glass doesn't hide light behind it. It costs one more translucent draw list per frame, and only while HDR lighting is on (off by default). `mark_translucent_terrain` returns before any draw when it is off.
🤖 Agent: queued at 997f9477e3. The fan is now one function, `breath_puffs`, used by `remote_dragon_phase_tick`, and the `flameTicks` advance stays in the client copy. I removed the host-side call: it drew from the server's `world.random` on the server thread. Single player already breathes through the remote path.
🤖 Agent: queued at 99deb65138. `PlayerList.broadcast` leaves out a player exactly on the radius (`d < r²`), and the test pins 16.0 vs 15.99. It only changes `<=` to `<` on a path that runs once per sound per guest.
🤖 Agent: queued at 4a5a130c2a. `harm_amount` is `(int)(0.5 * (6 << amp) + 0.5)`, the cloud's scale passed to `HealOrHarmMobEffect`: 3 from the flame cloud and 6 from a fireball's, where Horizon dealt 6 and 12. The 0.5-tall box, with the radius check on the position, replaces the `|dy| < 1` test. The fireball's cloud now snaps to a player within 4 blocks. Mobs are not snap targets yet, as the comment says.
🤖 Agent: queued at 36a4682121. A creative or spectator player is dropped as a target at the start of the tick, and an angry zombified piglin or iron golem loses its anger too. The test ticks 40 times to check the piglin doesn't pick the player back up through its anger.
🤖 Agent: queued at cbaf3287f6. States with neither the axis nor the facing family skip all six clones. Within a family, a rotation that sets the value the state already has skips its clone too. The test pins the observer's six facings with `powered` kept, and the hopper's five-way facing left alone. Load time only.
🤖 Agent: queued at 3df1f09d35. Only a `Return { value: None }` that naga's `ensure_block_returns` put at a reachable end is rewritten to `ZeroValue`, in functions with a result type. A function's own returns are untouched, and the test checks that with `own`.
🤖 Agent: queued at cb38e971c6. The credits vignette now samples `credits_vignette.png` bilinearly, through its own `fs_vignette` and a linear sampler at binding 5, which honours the `.mcmeta`'s `blur: true`. The other HUD pipelines keep their nearest sampler. The extra binding costs nothing per frame.
🤖 Agent: queued at 228889f046. The two Y faces now carry `CULL_BACK`, so each side draws only from its front, as `rendertype_end_portal` does. The test checks the winding of both faces, not only the flag.
🤖 Agent: queued at 9460d096a8. A primed TNT is now pickable by the hook (`PrimedTnt.isPickable`), followed at 0.8 of its height, and pulled. The `t.id != 0` guards keep unassigned TNTs out. The `fishing-tnt` lan_e2e check reads the pull through `/data`.
tick_dragon runs on the server thread (single player's default since #1514,
LAN, dedicated): drawing the puffs there took nextGaussian()s from the
server's world random, which vanilla's server never does, and spawned
particles nobody sees. Every client, single player's included, already
breathes the fan in remote_dragon_phase_tick, now through breath_puffs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 57627e6fce. `cart_push_from` follows `AbstractMinecart.push`: the 1e-4 floor, `pow` capped at 1, 0.1f × 0.5, and a quarter back to the mob. It runs once per tick after `push_carts`, which returns at once with no carts, so worlds without carts pay nothing. With carts it costs mobs × carts box tests.
🤖 Agent: queued at 191cc8cdbc. `passenger_attachment_at` follows `Camel.getPassengerAttachmentPoint`: the driver sits 0.5 ahead, the second seat 0.7 behind, and an `Animal` sits 0.2 further ahead. The seat index scans `mobs` for each camel rider each tick, which is negligible since only camel riders take that path.
🤖 Agent: queued at 6330a192d6. An angry iron golem (`extra.anger > 0`) now counts as targeting, so it keeps the guest it is angry at after the 100-tick `getLastHurtByMob` window. Only golems take that branch. The test puts a bystander nearer to prove the angry target wins.
🤖 Agent: queued at f8741e7eee. The four `getLeashOffset` overrides match vanilla: iron golem 0.875, snow golem 0.75, copper golem 0.75 on the centre (no forward shift), fox 0.55. The `0.875_f32 * eye as f32` keeps vanilla's float maths.
🤖 Agent: queued at 0fa27d49b7. `Fox.applyImplicitComponent` and `Frog.applyImplicitComponent` now set the variant from the egg's component. `variant_index` gains the fox's `["red", "snow"]`, so the frog's list no longer serves foxes by accident.
🤖 Agent: queued at cc99f96033. Both sites now read loot through `world_items!`, so a world data pack overriding a block or shearing table is honoured when the table has no random sequence. Before, they read the built-in tables from `res.items()`.
🤖 Agent: queued at 0d15238cc3. A click now focuses its widget only for the highlight (`focus_from_keyboard = false`). The wheel scrolls the list, as `AbstractScrollArea.mouseScrolled` does, instead of toggling the clicked option from anywhere. Tab, the arrows and a pad keep the documented Horizon divergence, and the test covers both cases.
🤖 Agent: verify green; pr_ready --quick plus a 3-round A/B on playhorizon (−118 MB RSS at RD 32; 97 of 116 MB of the window's sections shared with the server thread).
🤖 Agent: verify green; pr_ready --quick plus a 3-round A/B on playhorizon (−102 MB RSS at RD 32, light per section −49 %); the rebase's mesher change is checked, clippy-clean and tested on the Mac.
🤖 Agent: queued at 513cd005e5. A spectator returns before `water_splash` makes any random draw, as `Player.doWaterSplashEffect` does, so a spectator player and a normal player now consume the RNG differently. The test checks both modes.
🤖 Agent: queued at 247ff24c46. Only `JoinError::Unavailable` (no answer) is retried once after 500 ms; a refusal from the service (invalid session, banned, 429) is not. The tests cover both. Logging the transport error makes the "service unavailable" reports diagnosable.
🤖 Agent: queued at a8ec40548d. The fall now accumulates in the move, `(float)` cast included, the way `Entity.checkFallDamage` does. A landing of 3 or more blocks explodes with `(fall / 10)²` as the speed squared (`MinecartTNT.causeFallDamage`). Before, the code primed a zero fuse and threw the cart up instead.
🤖 Agent: queued at 1e1c6d5699. Rockets, eyes and NBT-loaded tridents now take ids through `take_entity_id`. The trident used to go through `assign_entity_id`, which only assigns when the id is 0, and `from_nbt` leaves it at 0, so the result is the same.
🤖 Agent: queued at d89499c0ae. `frame_drawn` is only false after `render` returns `Ok(false)` (the surface was occluded, lost or timed out). While frames reach the screen the loop still polls, so pacing is unchanged. A hidden Wayland window now waits 16 ms instead of pinning a core.
🤖 Agent: queued at 189e128d00. Its own thread now reads `sync_data` beside the worldgen and loot preload, and the preload joins it, so it still ends when its thread does. If the thread cannot start, the data is read inline as before. The launch-world path does not call `behind_server`, which is right, since the opening waits on it.
🤖 Agent: queued at 8fb8106da9. Both fixes are for Windows checkouts. The `include_str!` test normalizes CRLF. The capture fixture expects one warp, which matches `capture` on main since #6463 removed the warp after the grab.
🤖 Agent: queued at a357026d64. While the view is still arriving, frames upload meshes for 6 ms instead of 2. `arriving` already holds the far terrain back in the same function, so play and edits keep the 2 ms slice. The table shows −75 ms to `world.view_full`, over 6 alternating runs on an RTX 3070.
🤖 Agent: queued at ce08e86518. I checked every row's target on main: all 1 files and functions exist. For example, `Raider.readAdditionalSaveData` points to `crates/horizon-client/src/mobs/raider.rs::read_nbt`.
🤖 Agent: queued at 2aaac835bb. I checked every row's target on main: all 1 files and functions exist. For example, `WorldUpgrader.verifyChunkPosAndEraseCache` points to `crates/horizon-world/src/datafix/world_upgrader.rs::verify_chunk_pos_and_erase_cache`.
🤖 Agent: queued at 631afe689c. I checked every row's target on main: all 6 files and functions exist. For example, `Bucketable.loadDefaultDataFromBucketTag` points to `crates/horizon-client/src/entity_nbt.rs::apply_item_components`.
🤖 Agent: queued at b31fcfad5d. I checked every row's target on main: all 3 files and functions exist. For example, `DedicatedServer.readCodeOfConducts` points to `crates/horizon-client/src/lan/code_of_conduct.rs::read`.
🤖 Agent: queued at 41d82fca71. I checked every row's target on main: all 8 files and functions exist. For example, `NowPlayingToast.extractToast` points to `crates/horizon-client/src/toasts.rs::draw_now_playing`.
🤖 Agent: queued at 7676138acf. I checked every row's target on main: all 7 files and functions exist. For example, `FetchProfileCommand.resolveName` points to `crates/horizon-client/src/game/commands/extra.rs::Game::cmd_fetchprofile`.
🤖 Agent: queued at 83c0f67e9e. I checked every row's target on main: all 27 files and functions exist. For example, `MultilineTextField.selectWordAtCursor` points to `crates/horizon-client-render/src/ui.rs::EditBox::select_word_at_cursor`.
🤖 Agent: queued at 80cc16e412. I checked every row's target on main: all 5 files and functions exist. For example, `DebugLevelSource` points to `crates/horizon-client/src/debugworld.rs`.
🤖 Agent: queued at 1166472c8e. I checked every row's target on main: all 6 files and functions exist. For example, `BanIpCommands.register` points to `crates/horizon-client/src/game/commands.rs::Game::execute_command`.
🤖 Agent: queued at 30d5b97529. I checked every row's target on main: all 7 files and functions exist. For example, `ClientboundBossEventPacket.createUpdateProgressPacket` points to `crates/horizon-client/src/game/bossbars.rs::Game::diff_boss_events`.
🤖 Agent: queued at fc27217c05. I checked every row's target on main: all 12 files and functions exist. For example, `ServerStatusPinger.pingLegacyServer` points to `crates/horizon-client/src/net.rs::ping_legacy`.
🤖 Agent: queued at 001ed6987e. I checked every row's target on main: all 8 files and functions exist. For example, `IronGolemRenderer.setupRotations` points to `crates/horizon-client/src/mobs/render.rs::base_transform_at`.
🤖 Agent: queued at eefd16a005. I checked every row's target on main: all 5 files and functions exist. For example, `KeyboardHandler.textEditing` points to `crates/horizon-client/src/game.rs::ime_preedit`.
🤖 Agent: queued at e038e8d568. I checked every row's target on main: all 8 files and functions exist. For example, `CreateWorldScreen.getOrCreateTempDataPackDir` points to `crates/horizon-client/src/title.rs:971`. I moved `copyBetweenDirs` to `horizon-client-render/src/datapack.rs`, where `copy_pack` lives.
🤖 Agent: queued at 6e2790907c. I checked every row's target on main: all 8 files and functions exist. For example, `Fox.canHoldItem` points to `crates/horizon-client/src/mobs/fox.rs::can_hold_item`.
🤖 Agent: queued at 35409ecb35. I checked every row's target on main: all 6 files and functions exist. For example, `ItemFrame.canHurtWhenFixed` points to `crates/horizon-client/src/game/frames.rs::attack_frame`.
🤖 Agent: queued. Keeping `performance` and `dev` in one `native/target/fast/` and passing the opt-level through `--config` rather than `[profile.fast]` is right: the workspace's own fast builds stay untouched. One point to watch: the build record is keyed by folder, so `performance` and `dev` share it, and only the build key keeps a dev binary from passing as a performance one after a switch. A test that builds dev, switches to performance and expects a rebuild would pin that.
🤖 Agent: queued at db02e3d8d3. I checked every row's target on main: all 4 files and functions exist. For example, `ClientboundAddTransientBlockPacket.write` points to `crates/horizon-client/src/game/remote.rs:`.
🤖 Agent: queued at 815e81e027. I checked every row's target on main: all 7 files and functions exist. For example, `ContainerEventHandler.preeditUpdated` points to `crates/horizon-client/src/game.rs::ime_preedit`.
🤖 Agent: queued at 7bc5a58464. I checked every row's target on main: all 6 files and functions exist. For example, `MinecartCommandBlock.defineSynchedData` points to `crates/horizon-client/src/game/minecarts.rs::command_due`. I moved `setRecipeUsed` to `horizon-client-render/src/containers.rs`, where `recipe_used_key` lives.
🤖 Agent: queued at ab9cdd096e. I checked every row's target on main: all 5 files and functions exist. For example, `RemotePlayer.shouldRenderAtSqrDistance` points to `crates/horizon-client/src/game/entity_culling.rs::entity_view_scale`.
🤖 Agent: queued at a3afe6b586. I checked every row's target on main: all 10 files and functions exist. For example, `CloudRenderer.renderOit` points to `crates/horizon-client-render/src/gfx/oit.rs`. I moved `performTransientBlockRemovals` to `game/transient_blocks.rs`, where `add_transient_block` lives.
🤖 Agent: queued at 752aad9385. I checked every row's target on main: all 8 files and functions exist. For example, `UseBonemeal.checkExtraStartConditions` points to `crates/horizon-client/src/mobs/villager_brain.rs::B::try_start`.
🤖 Agent: queued at 263efa9370. I checked every row's target on main: all 10 files and functions exist. For example, `OptionInstance.SliderableEnum.next` points to `crates/horizon-client-render/src/options_ui.rs`.
Raider.readAdditionalSaveData still said missing, but mobs/raider.rs::read_nbt
reads PatrolLeader, Patrolling, Wave, CanJoinRaid and RaidId, the same fields
write_nbt saves. map-check on the file is clean.
- A cancelled book edit resends the server's book to the client.
- Snow filling a cauldron reports level 1, the level it places.
- fluid_level_change also sees (and can cancel) a flow drying up to 0.
- A cost changed by prepare_item_enchant shows that cost's clue.
- raid_spawn_wave's doc says it fires after the wave, without mounts.
- A cancelled item cooldown ends the guest's predicted cooldown.
- Anvil, grindstone and smithing hooks run only when a click changed
the inputs, the result or the cost.
- A crafting result set by prepare_item_craft is sent to the client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The per-chunk upgrade already checks the chunk's position and drops its
heightmaps, light flag and stored light. The row said the erase was not in
Horizon's datafix.
ConversionTracker.readAdditionalSaveData is in entity_nbt.rs::mob_from_nbt
(the affliction time and a conversion started from the saved time), and both
DropChances rows too: mob_to_nbt writes only the chances that differ from
0.085, mob_from_nbt reads them back by slot.
Bucketable.loadDefaultDataFromBucketTag cited entity_nbt.rs::load_bucket_data,
which is apply_item_components now, and the two Display inner classes were
down as a fn verdict a class row cannot take; they are home in
game/text_displays.rs. map-check is clean afterwards.
🤖 Agent: verified by pr_ready --quick on playhorizon (env-only failures explained in the body; render tests 985/985 single-threaded) and a 3-round A/B with byte-identical screenshots: the GUI atlas's 80 MB raw copy is kept lz4-compressed once uploaded (−47 MB RSS at the title).
A show_entity hover builds its lines in chat.rs: the name when it has one, the
entity type, then the uuid. The row said show_entity hovers are dropped. The
two rows pointing at a renamed function are corrected too.
x_rotation and y_rotation are applied through rotation_in, which wraps both the
angles and the range and runs through 180 when the minimum is above the maximum.
The player predicate's sneak input is read, and item_durability_changed fires
from an enchantment's loss and a mount's riding wear. The row that pinned the
input gap now says what is still missing instead.
Eight rows still said missing for behaviour main has: the four
ShowTradesToPlayer methods (the villager holds up the offer the player's item
pays for and cycles every 40 ticks), the three PlayTagWithOtherKids methods
(a kid joins a chase of one with 1 to 5 chasers, and a chased kid runs inside
the village), all in villager_brain.rs.
Three rows cited functions that are gone or were not functions at all
(B::InteractWithDoor, LongJump::start's parenthetical, LaySpawn), so
map-check is clean afterwards.
/reload re-reads every loaded guest's advancement progress against the new
tree and resets its visible set before the tick advancements and reward recipes
are added. The row said the tree was kept for the session; it now names the
function and the half that is still not covered.
A selector or nbt component is parsed when the component is decoded
(game/commands/text_commands.rs), and a world another copy has open is flagged
through its session.lock (save.rs::SessionLock::is_locked, used by title.rs).
game2-023.tsv said a player file that fails to load is left in place, but
save.rs copies it to <uuid>_corrupted_<time>.dat first, as
PlayerDataStorage.backup does.
The window showed the result of its own recipe table, which has no world data pack. It shows the result slot the server sets (CraftingMenu.slotChangedCraftingGrid, ClientboundContainerSetSlotPacket slot 0) now.
SET_COMMAND_MINECART, SET_STRUCTURE_BLOCK, TEST_INSTANCE_BLOCK_ACTION and
SPECTATOR_ACTION have handlers, chat spam is throttled, the flying kick's tick
limit is computed, and the build-limit trigger and its air test are there. The
eight rows that said they were missing now name them.
Generated mod API code is also compiled by xtask's test, which cannot see
horizon_command; mod_api now re-exports Suggestion and the test stubs it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's ServerSelectionList.OnlineServerEntry.keyPressed joins the selected
entry on a selection key; Horizon's confirm only handled the direct-connect
address box, so Enter on the list did nothing.
🤖 Agent: a call timeout now logs the server's state and no longer reads as a stop; the stall didn't reproduce (27k summons, slowest 0.32 s); fmt and clippy clean.
game2-015.tsv said the scaffolding block has no tick arm, that snow layers
have no shade brightness lookup, and that geysers never erupt. All three are
in the tree.
Ten rows still said missing for behaviour main has: the skeleton horse's
water jump sound (game/mounts.rs), the two ocelot goals (mobs/tame.rs), the
five schooling-fish methods and the fish tick (mobs/aquatic.rs), and the
pufferfish's PuffState read (entity_nbt.rs).
Seven rows pointed at entity_nbt.rs::load_bucket_data and
Mob::cause_fall_damage, neither of which exists: the bucket components are
apply_item_components and bucket_components now, the horse and llama fall
damage is Mob::cause_fall_damage_at. map-check is clean afterwards.
IntegratedServer::call waits 10 s for the server thread's answer, and both
a stopped server and a slow one gave "the integrated server stopped". A
perf_bench run failed with it in the middle of 2000 summons and the log
could not say which. A timeout now logs a warning with the server's state
(running, autosaving, smoothed tick time), and the callers' error says the
server stopped or did not answer within 10 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
classes-04.tsv called seven PotentSulfurBlockEntity helpers missing ("helper
of the geyser gas, not ported", "the eruption timer is not loaded") and said
a chest item neither hands its loot table to the block nor takes it back.
The tree ports all nine.
Cat.removeWhenFarAway is `!isTame() && tickCount > 2400` and
Ocelot.removeWhenFarAway the same on isTrusting. Horizon kept every animal
(Animal.removeWhenFarAway returns false) and read the ocelot's rule off
AgeableMob.age, which an adult keeps at 0, so neither feline ever went for
distance.
feline_removed_when_far holds the two overrides and check_despawn reads it on
Entity.tickCount.
The integrated server and its window each held every column both see:
the server its level, the window what the chunk packets decoded to, with
the same blocks, biomes and light. Sending a column on its memory
connection, the server offers the column's sections as Weak references
(lan/handoff.rs); the window's decoder takes each one equal to what it
decoded and drops its own copy. Edits on either side copy the section for
that side (Arc::make_mut). PalettedContainer equality compares the layout
first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section's packed light was one value or 4,096 bytes. Most lit sections
have no block light (above ground) or no sky light (below), so Light gets
Sky and Block forms that keep that layer's nibbles (2,048 bytes, as a
vanilla DataLayer) and one value of the other; compact() picks the form.
The server's chunk encoder writes its DataLayers straight from a
section's light instead of caching two 2,048-byte layers per section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify and launcher green on this exact head; recipe/loot harnesses 8/8 on playhorizon. One risk checked: loot::load_dir now reads a whole folder before parsing; json_folder takes no lock during the read, and server.open_world did not slow in interleaved runs.
Vanilla files a map's banner marker under `MapBanner.getId()`'s key, reads a
world's `DataPacks` and `enabled_features` back with the level, and builds a
world-list row's status text, playback gate and backup status from the
level summary. classes-08.tsv called all five missing or pointed two of them
at the wrong function.
The server reads the codeofconduct folder and tells operators about low disk
space, and a dialog slider's text is its label_format with the value. The three
rows that said these were missing now name them.
The golem's walk tilt, the pufferfish bob and the salmon, tropical fish and squid body rotations
are in mobs/render.rs::base_transform_at and mobs/aquatic.rs::rotations.
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls. The two world
border hooks take the placeholder body the generator fills.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The resident set and mimalloc's committed bytes, the client's and the
integrated server's columns by part (blocks, biomes, light, heightmaps,
records; shared sections once), the renderer's per-section bookkeeping and
the GPU allocator's resources by label. A system-allocator feature routes
Rust's allocations to malloc for heaptrack and massif.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The op and pardon arms live in game/commands/profiles.rs and /fetchprofile in
game/commands/extra.rs, all reached from the dispatcher in game/commands.rs.
The seven rows that said they were missing now name them.
Twenty-seven rows still said missing: word selection on a book page, list
arrow keys, the title's splash and notice buttons, and the F3 biome, chunk,
light, heightmap, looking-at, speed, effects, sounds and spawn lines.
levelgen-1.tsv called DebugLevelSource, FlatLevelSource.createState,
getBaseHeight and getBaseColumn missing, and said specialWorldProperty had
no debug world to report. The tree ports all of them: debugworld.rs is the
debug grid, flatgen.rs reads a flat world's layers and structure sets,
generator.rs answers the base height and column from those layers, and
save.rs reads the preset back.
/ban, /ban-ip, /banlist, /pardon, /deop, /op and /pardon-ip are dispatched to
game/commands/profiles.rs on a dedicated server, as vanilla's DEDICATED
selection does, and /datapack create writes pack.mcmeta, data/ and the pack
folder. The six rows that said they were missing now name them.
Eleven rows still said missing: block-model tints, a special block model,
bounding-box and invisible-block markers, the brushable block's item, the shelf
items, and a test instance's error marker.
DragonLandingPhase.doClientTick and DragonSittingFlamingPhase.doClientTick
drew their fans only on a server's client (remote_dragon_phase_tick). The
host's own fight never ran the phase's client tick, so a single player saw
no breath at all while the dragon came down or flamed: the port map still
had both rows down as missing.
breath_puffs is the fan both paths now share: eight puffs a tick at the head
(getY(0.5)) jittered by nextGaussian() / 2 during the landing, and on flame
ticks 2, 4, 6 and 8 eight starts of six puffs down the look line, the
vertical speed -look.y * 0.6F once. Vanilla turns the look vector by a
quarter and throws the result away, so every puff leaves along the same line.
The host draws its jitter from the world's random: the dragon's own stream is
the fight's, and vanilla's client copy never draws on the server copy's.
🤖 Agent: queued at 821b9c45e. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
Twelve rows still said missing: legacy server pings, the chat trust levels and
their tags and icon, the cod's swim and flop rotations, and block and item
display states and geometry.
🤖 Agent: queued at 42b2cd1d3. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
🤖 Agent: queued at 7b0c5e3bf. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
🤖 Agent: queued at 91acd7626. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
Each of these `tools/port_map/game-002.tsv` rows said a `ServerPlayer` method
was missing while current main has it; the row now names the function and what
it does.
🤖 Agent: queued at 532339112. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
🤖 Agent: queued at 59823928d. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
🤖 Agent: queued at 6d9f9189a. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
🤖 Agent: queued at 509a5e82c. I stacked Detimes' hook PRs in landing order (this branch holds the ones before it), added the mod_api entries main needs since #6253 and ran `cargo run -p xtask -- mod-api`. On the Mac: clippy -D warnings, xtask mod_api tests, native_hooks tests and Python tests pass.
Six rows still said missing: the bundle tooltip's selected-item name box, the
server list's F5 refresh, an entry's narration, Ctrl+wheel on the Video page,
the data pack folder watcher and a dropped pack file.
PlayerList.broadcast compares the squared distances with `<`, so vanilla's sound
packet does not reach a player standing exactly at SoundEvent.getRange(volume).
Hub::broadcast_near used `<=`; the boundary is now the same strict test, in a
helper the unit test can call.
The rest-020 row for SoundEvent.getRange said missing: it is folded into that
broadcast, and no 26.3 sound event has a fixed range, so 16 * max(volume, 1) is
the whole formula.
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
preload_launch_world needs the block registry and the biomes, but it
started after the whole core stage: the block models and sprites and the
block facts, ~250 ms later. The world's open then waited for the preload
(the worldgen bundle and the decoration). With the game's own resources
(no resource pack, which a failure would reload core without), the
preload now starts on core's biomes worker as soon as they are read.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main requires a mod_api entry for every pending fragment since #6253;
cargo run -p xtask -- mod-api filled the dispatch calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mob.getTarget/setTarget run the target through asValidTarget, so a creative
or spectator player reads as no target however it was set (an alert from
another zombified piglin included). Horizon read ai.target directly and
kept chasing. Drop it at the start of the mob's tick; a zombified piglin or
iron golem angry at that player stops being angry (NeutralMob).
Part of #6790
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
StateProviderBindings::rotations' table (a coral feature's decode builds
it, while the world's decoration is prepared) cloned each of the
registry's ~30k states once per direction and looked each clone up by
hash, under a BTreeMap keyed by cloned block names: ~230 ms of the world
data preload, on the path to the world's open. A state with no full axis
or facing family now rotates to itself without a clone, as does a
rotation that sets the value the state already has; the map borrows its
keys. The table is the same.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
tick_furnace cloned the fuel, input and result stacks of every furnace on
every tick, with no mod loaded (their components' Box included). It now keeps
the fuel's id only while the furnace is out, the input's id and the result
count, and hands the hooks the ids as they are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 6ce752baa8: verify green, pr_ready green (render tests single-threaded on llvmpipe). One point worth a look: Chunk::light now goes through section_at, whose Err carries the out-of-column light (0 below, 0xF0 above), and the new padded_fill test checks every padded cell against Chunk::get/light for those edges.
The translucent pass blends its alpha as coverage, which diluted a lake's light beside the
lit shore. A second pass over translucent terrain writes it for mostly opaque faces; clear
glass leaves what's behind it alone. Only with HDR lighting on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: ready. The benchmark's 2 s wait before each flight now times the render passes, and the timing is off again before the flight, so FPS and gpu_p50 stay comparable with older reports. Metal is skipped because its passes overlap. pr_ready green on playhorizon (1377 client-lib tests); e2e report checked on an M4 Pro.
A Windows user name with a space (C:\Users\Jane Doe\AppData\Roaming)
broke every native mod build: windres passes its include folders to the
preprocessor unquoted, which splits them at the space, and
horizon-client's build script panicked on the failed resource compile.
- launcher: build_dir moves a path with a space to the ProgramData build
folder, as it already does for a non-ASCII one (#5189, #6009).
- horizon-client build.rs: outside CI, a failed icon embed is a warning,
so players on a launcher without the fix can still build. Release
builds run in CI, where the icon stays required.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GLSL lets a function be declared any number of times (GLSL 4.60 6.1); naga's
frontend refuses a second prototype with "Prototype already defined", which
skipped Bloop's deferred10 and its entity and water programs (the same
helper's prototype comes in from two included files). The prototype naga
refuses turns to spaces, lines kept, and the source is parsed again, like the
other refusals the translator rewrites.
Fixes#6810
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GLSL lets a function that returns a value reach its closing brace without a
`return` (the value is undefined, GLSL 4.60 6.1). naga's frontend ends such a
body with a `return` of no value, which its validator refuses, so the program
was skipped (Lux's composite5, RedHat's composite). Those returns now give the
result type's zero.
Fixes#6809
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: stress scenes for perf_bench (Horizon and vanilla); Horizon beats vanilla x2.2 (armor stands) to x10.8 (signs) on the M4 Pro; Python tests 607 ok, the bench mod compiles against 26.3.
The credits vignette (misc/credits_vignette) specifies blur in its mcmeta
and receives a wrapped border when packed into HudAtlas. Previously, the
HUD vignette pipeline reused generic fs_main with the nearest-neighbor
atlas sampler, causing subpixel rasterization jitter and flickering
during credits and attribution screens.
Add linear_sampler to the HUD bind group and a dedicated fs_vignette
fragment shader entry point to bilinearly sample the vignette texture.
Co-Authored-By: Antigravity <gemini-2.5-pro> <[email protected]>
examples/gen_light_bench.rs runs the columns around a point through the
whole streaming pipeline (proto, decoration, final column, light) once per
worker count, and prints columns finished and lit per second, CPU per
column, the coordinator's CPU, and the workers' CPU by job kind
(streaming::job_stats, off unless enabled). The shared worker pool is sized
once per process, so each worker count runs in a child process with
HORIZON_JOB_THREADS set. A checksum of blocks and light shows that builds
and worker counts make the same world.
--simulate PROTO,DECORATE,FINISH swaps in a generator whose jobs sleep:
what is left is the pipeline itself, which shows its scaling to many
workers even on a busy machine.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: perf-gen-light verified this head: pr_ready --quick green, chunk_generation/decoration/server_world_gen/worldgen pass on playhorizon, generation order unchanged (differential test); coordinator CPU -30% at RD 32.
player_for kept a mob on its bound guest only while it targeted them or
was hurt by them in the last 100 ticks. A golem's anger lasts 400-780
ticks, so once the attacker ran off, the golem fell back to the nearest
player and its anger goal attacked that bystander.
Fixes#6781
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
`IronGolem`, `SnowGolem`, `CopperGolem` and `Fox.getLeashOffset` use their
own height, not the default: 0.875, 0.75, 0.75 and 0.55 of the eye height,
and the copper golem's rope hangs on the centre rather than `bbWidth / 2`
in front. `leash::leash_offset` knew the llama, wolf, allay, camel and bee
and gave these four the default, so their leads were drawn too high.
Corrects the four ledger rows in game-012.tsv.
The Data Packs screen, its temp folder, the chosen packs reaching worldgen and Re-Create
reopening them are all in Horizon; the rows said the button was disabled and there was no picker.
A canceled Nether portal trip kept its finished process and asked the hook again every tick; the process is cleared now.
Written by Codex (cancel audit), reviewed by Claude.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The fox pickup (fox.rs::can_hold_item, game/foxes.rs::fox_pick_up), the
frog's long jump and frogspawn goals (frog.rs::register,
frog.rs::acceptable_landing) and the tadpole's bucket load
(entity_nbt.rs::apply_item_components) are in the tree, but their rows
still said missing; the tadpole row also named a function that does not
exist, which `map-check` reported as a dangling row.
- Add separate_ao detection from shaders.properties in shader_packs.rs
- Wire separate_ao through MeshSettings, Streaming, and Mesher
- When separate_ao is enabled, pass un-occluded pure tint in vertex RGB
and pack ambient occlusion factor into vertex color alpha
- Pass vertex alpha through to hz_Color.a in PackAtlas when separate_ao is enabled
- Add unit tests verifying separate_ao vertex color packing and atlas inputs
`BucketItem.use` targets the clicked block only when it is a liquid
container that `canPlaceLiquid`. A double slab, kelp, seagrass or a
survival barrier send the bucket to the block beside from the start, so
a fish bucket's mob spawns in the poured water and kelp no longer
swallows the click. `canPlaceLiquid` moves to `Behaviours` so the game
can ask it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review round 1 on #6783:
- `Behaviours::bucket_empty` takes `beside: Option<BlockPos>` and returns
where the contents went; Shift only counts with a hit result, as in
vanilla's `!shiftKeyDown || hitResult == null`.
- The client leaves waterlogging to the server wherever the water lands,
not only on the clicked block.
- `Hit::beside` replaces three copies of `pos + face`.
- The click cases get their own test, with a case where both blocks refuse.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ground friction decays the pull by the time the server answers, so its size
varies run to run; an unhooked TNT reads exactly 0.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
`Fox.applyImplicitComponent` and `Frog.applyImplicitComponent` set the
variant from `minecraft:fox/variant` and `minecraft:frog/variant`.
`entity_nbt::apply_item_components` knew the variant components of
chickens, cows, pigs, zombie nautiluses, mooshrooms, sheep, axolotls,
salmon and tropical fish, but not those two, so the component was dropped
and the mob kept the variant its spawn picked.
`finalize::variant_index` also had no fox entry, so `minecraft:snow` fell
through the frog name list and read as red.
Corrects the two ledger rows these gaps sat on (game-012.tsv).
🤖 Agent: verified by perf-net-client: main merged in, pr_ready --quick on playhorizon all green, CI verify green, and the Mac runs in the body used this branch end to end (Horizon, vanilla and vanilla-mods clients on a local Horizon server and on a vanilla 26.3 server.jar, with and without 100 bots). Tooling only (skip-release). Merging.
The fixed item frame's hit rule, the enderman's carried block, primed TNT's fuse, the creeper's
fall distance and the monster idle timer are all in Horizon; the rows still said missing.
🤖 Agent: verified by perf-net-client: main merged in, pr_ready --quick on playhorizon all green (horizon-bots + horizon-protocol 173 tests), CI verify and parity green. Tooling only (skip-release). Merging.
AbstractScrollArea.mouseScrolled scrolls the options list; a hovered cycle button
is the only widget that keeps the wheel (CycleButton.mouseScrolled), and vanilla
never routes the wheel through the focused widget. A click focuses a button for its
highlight only.
OptionsUi kept one focus for both jobs, and the click handler set it. The wheel
treated any Slider or Cycle focus as keyboard focus and stepped it wherever the
cursor was, returning before scroll_at: after clicking one option, every later
wheel event toggled it, so a trackpad's stream of small events made it flip
repeatedly and the list never scrolled.
focus_from_keyboard now records how the focus was taken: Tab, the arrow keys and
the pad's focus steps set it, a mouse click clears it. The wheel's focus branch
requires it, so the written keyboard stepping stays and a click no longer hands the
wheel a widget to toggle. The hovered cycle branch is unchanged: it is vanilla's
CycleButton.mouseScrolled.
A window the compositor stops showing gets no RedrawRequested, and the event loop kept
polling and asking for another redraw every pass, pinning a core with nothing to draw.
Once a frame fails to reach the swap chain the loop now waits for the next event.
Game::block_loot_at rolled the game's own tables when the block's table has a
random_sequence, so a world data pack replacing blocks/stone was ignored.
Game::sequence_loot did the same for a table without a sequence (dispenser and
player shearing, brushing). Both now roll world_items!, as vanilla's
reloadableRegistries().getLootTable does.
Fixes#6777
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Casts a rod at a lit TNT and reels in: the TNT's x motion must turn toward
the player (0.0 without the fix).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The trait covers every hook in the catalog and the pending fragments, and "Adding a hook" says how a new one gets its trait method: its mod_api entry, the placeholder the generator fills, and the free-function hooks wired through installed callbacks.
A new `Game` hook can start with the placeholder body
`let _ = (every, parameter);`. Given its mod_api entry,
`cargo run -p xtask -- mod-api` replaces the placeholder with the
`mod_api::dispatch_<hook>` call, laid out as `cargo fmt` would; `--check`
reports it instead. A body that is neither the placeholder nor already
calls the dispatch is an error, named and never changed. Free-function
hooks stay wired by hand. Generated code is formatted for the
workspace's edition.
`menu_slot_rules`, `living_breathe` and `ingredient` are free functions in
horizon-client-lib, which can't see the mod list. One `NativeClientHooks`
struct of callbacks, installed once from the executable's entry point as
horizon-world's `NativeLoadHooks` is, carries the three to their dispatch.
The generator resolves `horizon_client_lib::` targets (and the older
`horizon_client::menu` and `breathing` names) to the client source.
With these, every hook has a mod_api entry, and the check now fails
naming any hook without one.
Every hook in native-hooks.json and the pending fragments gets a mod_api
contract, so trait mods can answer each through `Hooks`. The generator
reads the fragments too, rejects duplicate names once, and the game's
hook bodies call their dispatch in one short form. Event types the
generated trait names are re-exported from `mod_api`, and horizon-world's
`NativeLoadHooks` gains loot functions and conditions.
Block entity updates, team changes and transient blocks do reach the client or the guests;
the LookAt and boss event senders really are missing from the LAN host.
AbstractEndPortalRenderer.submitCube draws the unit cube's faces that
TheEndPortalBlockEntity.shouldRenderFace keeps, which is the Y axis: the top
and the bottom face. RenderPipelines.END_PORTAL never asks for withCull(false)
(only the *_no_cull pipelines do), so the render type culls back faces, and
each face is wound outward: from above only the top face is drawn, from below
only the bottom one.
portal_cube emitted those same two faces with normal [0; 4], which entity.wgsl
reads as "do not cull", so both faces drew from both sides and the portal
showed two stacked surfaces at the top and the bottom of the block. Every
portal vertex now carries CULL_BACK, the marker an entitySolid render type
already uses (the decorated pot), which drops back-facing pixels without
changing the baked colour.
ContainerEventHandler's focus path, tab order and arrow navigation are in
game/key_focus.rs, and IME preedit reaches the focused field; the rows still said missing.
The creative hotbar hint, the entity draw-distance scale, the singleplayer owner and the
low-disk-space reports are all in Horizon; the rows still said missing.
The seven UseBonemeal rows and the getWorkPackage note still said the farmer's
bone meal behaviour never starts. It is in villager_brain.rs: B::try_start
starts it every 10 ticks (160 after a session) with bone meal and a growing crop
in the 3x3x3, B::tick_or_stop grows the crop with level event 1505 and picks the
next one 40 ticks later for up to 80 cycles, and the work package registers it at
vanilla's farmer priorities. Point each row at the function that ports it.
FishingHook only checked mobs, players and dropped items, so a lit TNT could
never be hooked. Add self.tnts to the hit, hooked-follow and reel-in pull.
Fixes#6601
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The jeb_ sheep's colour cycle, the quit shortcut option, anisotropic filtering, the IME preedit,
the skin layer toggles and the graphics preset stepping are all in Horizon; the graphics and
input rows still said missing.
Every frame the CPU posed each mob's model parts and pushed 44-byte vertices
for every face (mobs::render::draw_normals): with 1000 mobs in view that was
most of the frame's CPU time. mobs/skin.rs now flattens each model once for
the renderer's store (gfx/entity_skin.rs, the previous commit) and, per
frame, walks the poses like draw_normals but sends one matrix pair per drawn
part and one job per contiguous run; the compute pass writes the vertices.
Culling uses the posed parts' bounds; a culled mob's jobs are taken back.
The vertices are bit for bit the CPU path's: a test poses every mob type at
several ages and yaws both ways and compares them. A mob takes the GPU path
only when all its draws can, so the draw order stays: no custom (CEM)
models, translucency, full-bright layers, energy swirl, armour extras,
outlines, and not players. HORIZON_NO_ENTITY_SKIN=1 turns it off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first half of posing mobs on the GPU (the game side follows): the
renderer keeps a store of model-space vertices (position / 16, UV, axis
normal, part index) that grows as models are handed over, and each frame
runs gfx/entity_skin.wgsl over the frame's jobs (one run of a model's
vertices with its parts' matrices, texture rect, scroll, colour, light and
overlay). The shader writes mesher::Vertex words into the entity buffer
ahead of the CPU-built entities, so every pass that reads the buffer (OIT,
shadows, HDR marks) sees the same vertices as before.
`expand` is the CPU reference: glam's transform order, axis normals taken as
exact normal-matrix columns and `as i8` normal packing, so the shader's
output is bit for bit the CPU path's (test on a real device). The pass needs
compute and four storage buffers, and stays off with a shader pack, a native
pack, frame generation or HORIZON_NO_ENTITY_SKIN=1. Nothing sends frames yet
(`Frame::skin` is None).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_ingredient_stays_hookable normalises CRLF as the tests beside it
do. test_capture_lifecycle expected two warps on a Windows grab, from
before #6463 made it one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified: verify green; pr_ready --quick green (horizon-threads 16 tests, 602 Python tests); the pool has no user yet (skip-release). First of the job-system stack (#6721, #6722).
json_folder moves to horizon-world so loot's load_dir reads through it; the
LAN recipe sync reads through it too. The server-data preload and
SyncData::load hold the recipe folder, so the loot item tables, the recipe
sync and the recipe book's index read each recipe file once, not three times.
Part of #1961.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without the player's Chunk Updates or Upload Budget, a frame uploads
meshes for AUTO_UPLOAD_TIME (2 ms). While a view's terrain is still
arriving (ViewArrival, a world's open, a teleport) the meshes waiting are
that view, and up to ~1300 of them queued at the end of a world's open,
holding the full view back. Those frames now give them 6 ms.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The Mods page's picker already offers the dev build in the window, so the
hidden developer-options switch goes. The picker's dev caption and the
docs say how much faster and slower it is relative to performance, and
the docs describe `fast`'s LTO as Cargo's thin-local one.
origin/main refactored agent_status_lines into status_lines and translated
its keys; the branch's recording line is appended after it. The language
file keeps both sides' keys. #6202 (the collision scan) came in too.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
AGENTS.md, the native mods docs and the CLI's "Build it next" hint say
how to opt in to `dev` while developing a mod, and the launcher's hidden
developer options get a "Faster mod builds" switch bound to the same
setting. Players and releases keep `performance`; nothing is guessed.
Address the review of #6712:
- The two debug keys leave MAPPINGS (restoring the vanilla key table and
options parity); F3+R/F3+E are fixed Horizon keys, off by default behind a
new "Recording shortcuts" extras switch on the AI Agent page. The page's
buttons use translation keys and say start/stop; the page shows the live
recording/replay line.
- Bundles no longer carry secrets: typed key text is dropped, chat/command/
type/signedit/connect/book/publish lines keep only "<redacted>", the zip's
text goes through horizon-telemetry's Scrubber (user folder, IPs, UUIDs,
world/server names, player names), status.json loses server/name tags/
sidebar/clipboard, only a crash from this recording is included, and the
keyframes lose the absolute recording path.
- A replay only runs an allow-list of input and movement commands, never
world, network or account ones; it stops when the world closes, releases
every key/button it held, times out from the command rather than the world
load, and removes its saves/ copy unless --keep-world.
- record-start saves and copies with the integrated server paused (no torn
regions, no 60 s stop block); read-only polls and session commands are not
traced; a deferred ui command is traced once; events.jsonl works without a
socket; an intrusion counts while an agent is connected or waiting.
- Smaller fixes: end_tick is optional and a torn input line is tolerated,
--no-world skips the world directory, exports are written to a temporary
name and renamed (unique when taken), the replay timeout is an integer,
input-log --origin is an enum, repro-export paths resolve from ctl's
directory, recording is refused in the deprecated in-frame mode, the
bundle extractor caps entries and bytes, and old recordings/bundles are
pruned (the newest ten are kept).
- The e2e scenario now asserts the replay's done/skipped counts, a
deterministic turn (not just terrain-dependent walking), a bounded input
log, and keyframes the tick wrote.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Two profiles for playing and developing with native mods, plus the
release's own. `performance`, the default, is the workspace's `fast`, the
launcher's build until now, with the same key, record and folder, so warm
builds and installed versions stay valid. `dev` builds in the same folder
with only `horizon-client` at opt-level 0, so every other crate is shared
and switching between the two recompiles only the game crate.
The LTO-off default and the experimental profiles are removed.
- Padded::fill finds a column's section, or the light outside it, through
the new Chunk::section_at, which Chunk::light now uses too, so the two
can't disagree.
- MeshScratch::opaque and opaque_rows become solid_rows: they hold
isSolidRender, not the opaque render layer.
- Docs of sides::sort and SortScratch describe the integer keys.
- A fill test with every neighbour loaded, per-block light, and sections
at both ends of the column; get_run is checked against the stored values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Joining sometimes stops with "authentication servers not reachable" and
works when the player joins again. The cause was dropped, so log it, and
repeat the join once when there was no answer. An answer from the service
stands. Vanilla leaves the retry to the player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passes on 835f2b522; parity 171 passed on it merged into main 0454e97e3. The 5 failures aren't this change's. The budget only ever grows (max of the caller's 1,000,000 and the sweep's cells), so behaviour changes only where the old scan aborted. world_spawn#3 (seed -7565038591901794094, y 115 vs 108) searches with no_collision_or_liquid, not a movement, and its y follows chunk decoration order. sculk, first_person, server_player_targets (600 vs 599) and pack_screen are runner timing and environment. One cost to know about: scan_cells(sweep) adds a few multiplications to every movement, negligible next to the scan. Queued by pr-sweep-M.
🤖 Agent: recipe folder read once per load (json_folder), rebased on main after #6720. Verified: CI verify green on this head; recipe opens 28,882 → 8,172 at start (strace).
Every streamer (the client's, and one per level on the server thread)
started N-1 worker threads of its own: a single player world ran two to
four such pools at once. They now queue their jobs on the process's
pool (horizon_threads::jobs), sized from the workers setting before any
world opens. Saves and freed meshes go first, then jobs by column
distance, as before; per-worker generator state, face cache and mesh
scratch move into the streamer's per-worker slots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pool of worker threads sized once for the machine, which every source
of background work can share instead of starting its own threads: each
source keeps its queue (so it can rank its jobs again and drop the ones
that left the view) and a free worker runs the most urgent queued job
of any source, by class (urgent, near, far) then the source's key. Far
work never holds every worker. A notify is two atomic operations while
the workers are busy, so the thread queuing work (a frame, a tick) never
waits behind a worker's choice. Per-worker scratch belongs to the
source; dropping a registration waits for its running jobs; idle
workers free their buffers. No user yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On WARP or llvmpipe the menu blur took ~250 ms of CPU per frame at 1440x900:
every blurred menu (options, world list, first-launch screen) ran at 3 FPS.
On a software adapter it now runs its six passes at half the width and height
with half the radius, the same blur over a quarter of the pixels. GPUs blur as
before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An Intel HD 2500 on Windows has DirectX 12 only through WARP and its GPU through
OpenGL. With DirectX 12 chosen (or set by the launcher), Horizon rendered the
title and every menu on the CPU: 90% CPU and minutes to load (#5185). Auto
already tried every backend on a GPU first (#3985); a chosen API now does too
before it falls back to a software adapter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Lang files: both sides' new strings kept; for keys main also added to
fr_fr.json (the Splitscreen Setup page), this branch's wording stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The world data preload read the registries, tags, recipes and advancements
the server sends (lan::sync_data) after the worldgen bundle and the loot
tables. A launch into a world reached the server thread's own preload
before that, so the server started a second read of the same data and
then waited on SYNC's lock while the preload read it again: ~100 ms on
the server thread before settle_spawn (server.sync_adopt). The preload
now reads it on a thread of its own from the start.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The crafting tables, the stations, the recipe book and brewing each listed and
read every file under data/minecraft/recipe again: a start to the title opened
each recipe file 7-8 times (14,581 opens for 2,043 files). json_folder::read
lists and reads a folder once and shares the bytes with any reader while one
holds them; the rest loader and /reload hold the recipe folder across the
tables, so it is read once, and nothing is kept after. Each table still parses
its own copy, with the same order, conditions and errors as before. Brewing
alone (a server join) still reads only recipe/brewing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's xtask check (mod_api::tests::generated_output_is_current) fails: the
hooks catalog's native_state_block mention landed without the regenerated file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The launcher now builds horizon-mcp in: a change only there needs the
launcher job and ships a launcher release (some-du6e's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BucketItem.emptyContents retries at the block beside the clicked face
when the clicked block refuses the fluid. Without it, a water bucket on
a double slab did nothing, and a sneak-click on a waterloggable block
failed instead of pouring next to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityFluidInteraction.CurrentAccumulator.applyTo gives a body that is not a player the
direction of the current it stands in and a player the average, lifts a weak flow to 0.0045
for a body that is still on x and z, and does nothing under a squared length of 1e-5. None of
that had a test.
🤖 Agent: read the change: HandSway::tick is vanilla's again (unwrapped yaw), and tick_wrapped unwraps the game's -180..180 yaw by short steps first, so #6451's boat-turn fix stays (HandSway::rotation already wraps view - bob). It fixes main's first_person failure since #6451 (yaw off by exactly 360°: seen on #6503's CI parity). Verify green; CI parity runs first_person on this head. Thanks!
- No import starts while a removal runs: it could take the folder of the
same program imported again.
- `import-build --version` on a program imported before on other game files
says so, instead of selecting the old version as if it were made anew.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Windows refuses to rename a folder while a scan holds the program just
written in it: the move and the cleanup are retried for a second, as
backend::remove is. A folder at the version's place without the installed
mark (a removal that stopped at the running game) is cleared instead of
failing every import of that build. The .importing folder stays, so two
imports never remove it under each other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MinecartTNT.causeFallDamage explodes with (fall/10)^2 as the speed; Horizon set
a zero fuse instead (power 4, one tick late) and counted only the fall before
the last move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The copy is made under versions/.importing, where no version is listed,
with its installed mark already in it, and renamed into place: no other
import (or the window's list) ever sees it half made, and none deletes a
version another one finished. A damaged zip says so instead of "not a
Horizon game", and an artifact holding a .tgz is read too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by perf-net-client: CI verify green, pr_ready on playhorizon (only failure the known llvmpipe SIGSEGV in horizon-client-render GPU tests, untouched crate), horizon-protocol tests 160/160 on the Mac, and the branch's horizon-bots joined a real vanilla 26.3 server.jar and a Horizon dedicated server (20/20 bots, 14952 and 17682 chunks, no disconnects). Merging.
🤖 Agent: perf-gen-light verified this head: pr_ready --quick green, the 10 generation/light harnesses pass on playhorizon, worldgen output unchanged; -2.8% CPU per proto chunk.
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on d1ee2d879 after the rebase onto #6698 (clippy, 2917 tests of horizon-client/-lib/-render, 600 Python tests). Tooling only (skip-release): pacing_bench.py, horizon-ctl stats raw/reset, spans::laps.
- An Actions artifact is a zip around the release's archive: the game is
found in a zip or .tar.gz inside it, and in a picked .tar.gz.
- Two imports of one program at once (window and CLI) each copy into their
own folder, and the second leaves the first's version as it is.
- A second file picker's pick starts no second import.
- A native build for an instance on an imported build starts from the
imported build's base, not from a tag GitHub doesn't know.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An agent driving the control socket could not tell its own input from a
person's, and a player had no way to hand over a session that reproduces
a bug. Both are now in the agent surface:
- Every input event carries an origin: user (the window's own keyboard,
mouse or pad), agent (the control socket) or game (input the game made
up). state and status gain an input section; input-log returns the
newest 512 events; a person's input during an agent's run logs once and
pushes a user-input event, so wait-for event user-input catches it.
- record-start/stop/status/list write inputs, second-by-second state,
events and, in single player, a copy of the world. repro-export zips
that with logs/latest.log, the newest crash report and a status.
record-replay loads the world, restores the start state and feeds the
recorded keys, mouse, wheel, look, pad buttons and control lines back
at their ticks, best effort. F3+R and F3+E, and the AI Agent page,
give a player the same without horizon-ctl.
The commands come from the one command table, so horizon-ctl and MCP get
them together (also on the pending shared horizon-mcp crate).
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
NeoForge's ItemFishedEvent fires later, in FishingHook.retrieve once the
loot is rolled, and carries the drops and rod damage: not this hook's
contract (Dyna's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The module is build_import, apart from `import` (dropped files and packs).
- `import-build --version`, as native-build and prepare name the installed
version a command works on; its help says a Nightly works too.
- launcher/README.md: the Versions page's import, the CLI line, and that an
imported build stays selected through updates.
- fr uses "compilation" as the table does, tr "yükleyin", ja its half-width
colons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rust 1.99.1 toolchains fail the launcher checks because the fallback names
`gpui/runtime_shaders`, a feature that no longer exists: launcher/Cargo.toml
renames gpui-fast as `gpui` and gpui-fast-platform as `gpui_platform`, and
`runtime_shaders` lives on the latter. Fix the flag in pr_ready's launcher
snippet, the launcher.yml Metal fallback, and the mod browser harness doc,
and add a regression test that runs the snippet through fake uname/xcrun/
pkg-config/cargo to pin what the fallback asks for on Mac with and without
Metal, and on Linux.
Co-Authored-By: GLM Flash (T3 Code) <[email protected]>
- native_mods::copy_base is the base-folder copy both a native build and an
import start from; carried_over stays private.
- is_game (platform magic and the mod API mark) and base_for (with --base)
are pure and tested: a program without the mark, a --base that is not an
installed release or Nightly, and a failed import that must leave nothing.
- One reader for a plain file and a zip entry, refused past 1 GiB.
- install_as runs the same after-install step as an import.
- The Versions page draws native and imported builds with one helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Post::prepare built the scaled world targets while the old ones were still
alive and without catching a failed allocation, so dragging the Resolution
Scale slider could run a small GPU out of memory and wgpu's fatal default
handler panicked (#6607).
Drop the old target first, build the new one inside an out-of-memory and
validation error scope (a failed texture is invalid, so its view fails
validation too), and on failure step the scale down by a quarter, to a floor
of 0.25, then draw at the window's size.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The window's 4096x5120 GUI atlas stayed in memory as built (80 MB of
RGBA) for the whole session, though only a renderer being made again
reads it. Once a renderer exists, the pixels are lz4-compressed on a
thread of their own and the raw copy is dropped; a renderer rebuilt
later (graphics API switch, lost device) decompresses them. The
enchanting table's Standard Galactic text read glyph widths from the
raw pixels each frame: they are measured once when the atlas is built.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The new field clashed with `importing` (import_page): it is
`importing_build` now.
- The partner banner's 10 s timer is back; a formatting revert had put a
second copy of the drain loop in its place.
- A picked file must be named as the game is (the launcher and horizon-ctl
carry the mod API mark too), and no larger than 1 GiB, checked before it
is read; a zip entry past that is refused instead of cut off.
- The same program imported on another day selects the version made then.
- A failed last step removes the version folder it had renamed into place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As openSendConfirmationWindow's currentScreen, a book or dialog a clicked
command left comes back on no, a dialog with what was typed in it; a
lectern's screen goes with its menu, as in vanilla. Tab, the arrows, Enter
and Space work its buttons. The pointer is now free while it is open: from
a book or a closing dialog it stayed captured, so its buttons couldn't be
clicked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first layer of joining older servers: a ServerVersion type and, per
connection state, how a 26.2 server numbers each packet, generated from
both releases' packets reports. Nothing calls it yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
perf_bench measures throughput on short flights; pacing_bench records every
frame of the sessions where players see hitches (64 blocks/s flight, eight
long teleports, Nether trips, inventory and pause menu spam) and reports p50,
p99, p99.9, max, 1% and 0.1% lows and frames over 33 ms, with the timed spans
of the slowest frames (HORIZON_SPIKES) beside them.
- horizon-ctl `stats raw`: every frame time since the last reset, as `fly ...
raw` gives for a flight.
- spans::laps: a function's consecutive steps as spans, nothing read while
spans are off. Renderer::draw_frame and Game::entity_geometry time their
steps with it; they found the lead knot's model parse (41 ms) and the
arena page frees (8-14 ms).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A horizon-client built on a fork's Actions can't start on its own: it has
none of the prepared game files. The Versions page gets "Import a game
build…", which takes the program (or a zip holding it), checks that it is a
Horizon game for this platform, and installs it as imported-<date>-<sha>: a
copy of the selected (or newest) release or Nightly's folder with the picked
program in place of the game, so it plays with that version's prepared
files. It is listed apart as unsigned, can be used and removed like any
version, and no update replaces it, prunes it or moves the selection off it.
The CLI gets `import-build <path> --trust-unsigned`, kept out of MCP: running
a program nothing signed is the player's decision.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main lists monitors once a second (#6620); keep that beside this branch's
split_hud.menu in about_to_wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on 08508f5d8 (clippy, horizon-client-render tests, 598 Python tests); CI parity decides the affected harnesses. Linux only: submit-thread queue waits now count in last_wait, so frame CPU, the frame graph and HORIZON_SPIKES stop reporting GPU waits as CPU.
🤖 Agent: the title's first frame before the world renderer's build. Verified: CI verify green on this head (incl. the new handoff-wait test); A/B in the PR (title median 3510 → 2482 ms).
🤖 Agent: rest loader starts before the handoff's CPU work. Verified: CI verify green on this head; A/B in the PR (rest ready −216/−233/−316 ms, 3/3 pairs).
🤖 Agent: renderer pipeline reuse across the loading/title/world renderers on one device. Verified: CI verify green on this head (fmt, clippy -D warnings, workspace tests), A/B in the PR (title artwork step median 4.8 → 1.5 s on llvmpipe).
The original uniform-name control already fails on unchanged upstream in the generated uniform block. Exercise the existing local identifier rewrite without expanding this SSBO-keyword fix into uniform code generation. The frozen upstream GPU fixture accepts this corrected control.
Co-Authored-By: Codex GPT-6 <[email protected]>
🤖 Agent: verified: CI green on this head, A/B on the Mac M4 Pro at RD 32 shows 0.1-0.25 ms less main-thread CPU per frame (with the sibling PR), nothing drawn changes. Merging.
🤖 Agent: verified: CI green on this head, A/B on the Mac M4 Pro at RD 32 shows 0.1-0.25 ms less main-thread CPU per frame (with the sibling PR), nothing drawn changes. Merging.
6aec3da41 wrapped the yBob easing inside HandSway::tick, so a turn of more than 180
degrees in one tick (a teleport, a scripted rotation) eased the short way where vanilla's
unwrapped yaw eases the long way. tools/parity.py first_person went from 0 to 2,973
differing cases, all in yBob. tick is vanilla again; the game's wrapped yaw goes through
tick_wrapped, which unwraps it by its short steps first, so the boat case keeps its fix.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on 873ff80cd (clippy, 989 horizon-client-render tests, 598 Python tests); CI parity decides the 132 affected harnesses. Emptied arena pages are freed 4 frames later on a helper thread instead of on the render thread.
The server's copy of a guest never travels with the client's own
velocity: handleMovePlayer moves its body, and server_travel starts from
whatever delta movement the copy last had, a few hundredths while
gliding. LivingEntity.handleFallFlyingCollisions reads that as the speed
the glide lost (moveHorLength), so (moveHorLength - new) * 10 - 3 stayed
negative and flying into a wall never hurt.
Carry the tick's movement into the copy's delta movement before the
travel, as handleMovePlayer does, and guard the rule with a test: a glide
into a wall at 1.2 blocks a tick hurts by minecraft:fly_into_wall.
Fixes#6494
🤖 Agent: bench-only (chunk_load_bench measures the workers' mesh_reusing + sort_solid path, percentiles, --mesh-threads, --json). Verify green on this head (rebased on main db34ce484d); pr_ready block in the comments (its one failure, armor_stand_motion_tests, also fails in other agents' runs on playhorizon). skip-release.
Surfaces at the strongest block and sky light rise above paper white, sky light by how a face
meets the sun, and the whole of it eases into the peak. Part of the frame's alpha carries it
to the last pass, where it amounts to vanilla's lightmap going past 1. Off while a shader
pack's post or material stage runs. Fog and water take it away as they should.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on 33e2c32d3 (clippy, 593 horizon-client tests, 598 Python tests); CI parity decides the 60 affected harnesses. Moves six entity models' bake to the startup worker: removes a 41 ms render-thread hitch on first sight.
Review:
- getHitEntitiesAlong returns the block it clipped when no entity is in the
attack range, and filterHitResult keeps a block within reach, so only a
miss falls back to the plain pick (a mob inside a spear's minimum reach
with the floor behind it is not the target).
- Minecraft.startAttack attacks an entity only inside the main-hand item's
attack_range.
- handleKeybinds takes the number keys before swap hands, drop, inventory,
chat and pick-block, which act on the key here: they select the queued
keys first.
- A remote client sends the new slot in the tick only when that tick acts
(ensureHasSentCarriedItem); else the next tick's sync does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Native mods compiled with the workspace's `fast` profile, whose
`lto = false` still runs thin LTO over each crate's codegen units. The
default profile now sets `lto = "off"` over `fast` through
`cargo --config`, so the workspace's own `fast` builds stay as they
are: a mod edit compiles faster for about a fifth more tick time.
The player picks default (the everyday build for playing with mods),
dev (horizon-client unoptimized: the quickest rebuild while developing
a mod, for a game about twice as slow) or release (unchanged) on the
Mods page, with `settings native-profile`, `native-build --profile` or
HORIZON_LAUNCHER_NATIVE_PROFILE. An older settings file's release
switch still picks release. Three experimental profiles, named only by
the environment or --profile, keep other settings measurable:
performance (the old `fast` build), balanced and fast-rebuild.
Each profile's name and settings are in the build's key, and each has
its own folder under native/target/.
🤖 Agent: verify passes, and parity passes on 5fe8c6def merged into main 4bc8e359c (server_knockback PASS, with the new pvp-shield-partial and zombie-shield-partial cases). The code fix came in through #6381, so this PR adds the two harness cases that pin it. Queued by pr-sweep-M.
A light source's mark ignored fog: fully fogged, it stood out, through water since #6471.
A sprite that shines all over took a threshold from one frame of its animation, so other
frames' pixels dropped out. The vignette's darkest corners lost the lift at once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A native mod (a Dynamic FPS port) needs to cap the frame rate while the
window is unfocused or minimised. Game::framerate_limit now asks
native_framerate_limit(focused, minimized, in_world, limit) after the
inactivity throttle and Battery Mode, and keeps the lower of its own limit
and the mod's; values below 1 are ignored. The API number comes from the
framerate_limit fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Minecraft.tick picks the crosshair target (raycastHitResult: the held
item's attack_range, a spear's 4.5 blocks) before handleKeybinds takes the
number keys and then the attack clicks. Horizon changed the slot when the
key came in and picked the attacked mob with the plain 3 blocks at the
click, so a spear-to-mace attribute swap swung at nothing (#6376).
The number keys now wait for the tick: its pick (block and mob, with the
active item's attack_range) runs first, then the keys select, then the
clicks act; a remote client sends the new carried slot before them.
tools/attack_swap_e2e.py drives the client against a vanilla server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An ingredient that names a type in a namespace outside minecraft: used
to fail the recipe file whole. It now reaches native_ingredient(kind,
data, items), a free function in crafting.rs which mixin bundles hook:
fill the item list with what the ingredient matches, as a tag is. The
list holds item ids only (components are not checked; a match-time
hook follows separately). Duplicates are removed keeping first-seen
order. The hook is called once per ingredient by each copy that loads
the recipes — the crafting tables', the network copy's — so at least
twice per load, and the answer must be the same. An unclaimed kind
leaves the list empty and the recipe fails, as an unknown kind does; a
bare type name is minecraft's own.
The catalog target names horizon_client_lib, the package crafting.rs
compiles into since #5446's split: horizon-mixins resolves the crate
by its package, and refuses horizon_client::crafting::... (proven on a
staged build; the same split left menu's and breathing's catalog
targets on the old crate name — their bundles would fail the same way,
left to their own fix).
Part of #5367: predicates over components need a match-time hook, not
this load-time list, and follow separately.
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: verify passes on 3740468b5; parity 45 passed. The one failure is a timing flake: server_player_targets' console effect is 599 ticks against vanilla's 600, a tick off on a loaded runner, in a step with no firework rocket (this change only adds rockets to selectors). Job rerun. Queued by pr-sweep-M.
posed_rider kept the id of the last mount, so boarding the same boat, cart or animal again
compared equal and the camera swept to the seat instead of cutting to it. Not riding clears
it, so every mount is a new seat.
Arena::trim dropped an emptied page's buffer (up to 256 MB) on the render
thread; giving it back to the driver took that frame 8-14 ms of CPU on Mesa's
lavapipe (spans, RD 12 flights). A dimension change (clear_sections) dropped
every page at once the same way.
Arena::trim and Arena::clear now give the buffers back; the renderer keeps them
in a Graveyard for four frames, so the GPU is done with them and the bind
groups naming them (flat draws' page tables) are gone, then drops them on a
short-lived thread, as chunk::free_columns does for unloaded chunks. The
driver frees them as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Linux the frame's commands are submitted and presented on a submit thread
(#5779); every use of the queue first waits for the previous frame there. In a
GPU-bound view that wait (uploads in update_world, the frame's own writes, the
next submit_later, offscreen pacing in land_frame) was counted as the frame's
CPU time in FrameStats, F3, the frame graph, HORIZON_SPIKES and perf_bench's
cpu_ms: on lavapipe 15-150 ms "CPU" frames whose thread worked 0.1-0.5 ms.
Queue now sums the time spent blocked on it, and the renderer adds it to
last_wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The lead knot, wind charge, shulker bullet, wither skull, evoker fangs and a
raid captain's banner loaded their model on first draw, each by parsing the
whole entity-models.json (2.4 MB) on the render thread. The lead knot's ran on
the first frame of every world, with no lead in sight: 41 ms of CPU and
125-230 ms of wall time in that frame. They now come from the document
GameCpuResources already parses once at startup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tridents and rockets ignored nether and End portals. They are now swept for
portal blocks, saved with the existing writers, re-spawned in the other
dimension and keep their portal cooldown on arrival. A rocket boosting a
player and a stuck trident that did not move stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
wind_knockback skipped both; vanilla's hurtEntities pushes every entity that
does not ignoreExplosion. Adds lan_e2e --check wind-remaining and lists
rockets and trident motion in simulation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Record early failures and errors selecting or exporting a compiled build,
without dropping compiler diagnostics or changing the returned build result.
Refresh the generated native-build CLI reference.
Co-Authored-By: GLM 5.3 <[email protected]>
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
🤖 Agent: server-only export endpoints (off without a token); fmt, clippy -D warnings and the full test suite pass inside crates/horizon-telemetry-server, CI green on this head.
Firework rockets (launched, angled, raid fireworks, NBT load) and tridents
loaded from NBT were pushed with id 0 and got one only when a LAN guest was
connected. They now draw it from the one counter when created, as vanilla's
Entity constructor does. Primed TNT, falling blocks and projectiles already
did at every creation site. Part of #6537.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Entity.handleOnAboveBubbleColumn ends in sendBubbleColumnParticles for every
entity at the top of a column, and PrimedTnt does not override it. The TNT
was pushed by the column but showed nothing at its surface. PrimedTnt::tick
now reports the column blocks it is at the top of (bubble_column_push_over,
added for mobs and items in #6474) and tick_tnt draws them with
Game::bubble_column_particles, which also sends them to the LAN guests.
Fixes#6626
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
#5317 was merged with three deviations from vanilla 26.3 in the `ArmorHurt`
path that its review had already named (Joansitoh's agent:
`LivingEntity.java:1887`, `:1907-1909`, and `doHurtEquipment`'s amount):
- the gate was `drops::head_is_body` minus Wolf, which also matches donkey,
mule, skeleton and zombie horse, camel, llama, nautilus, happy ghast and
sulfur cube. `hurtArmor` is overridden by `Horse` alone — the base one is
empty (`LivingEntity.java:1887`), `Wolf` and `Player` have their own rules —
so those species wore a body item vanilla never touches;
- nothing tested `#bypasses_armor`, so a fall, drowning, starvation, the void
or a magic hit wore the piece too (`hurtArmor` is called inside that gate,
`LivingEntity.java:1907-1909`);
- the amount was `dealt`, computed after the armour, toughness and
enchantment reductions and after the invulnerability window's subtraction,
where vanilla hands `hurtArmor` the damage `getDamageAfterArmorAbsorb`
received: 8 raw damage is 8/4 = 2 durability, not 1.
`Horse.hurtArmor` also asks `doHurtEquipment` for `EquipmentSlot.BODY` alone,
so the four armour slots are not this path's to wear; `hurt_mob_armor` now
wears the body piece only, which drops the slot loop with it.
`doHurtEquipment` also starts from the item's `equippable` component
(`Equippable equippable = item.get(DataComponents.EQUIPPABLE)`), which
`wearable` did not check: `equip_damage_on_hurt` defaults to true when the
component is absent (`horizon-items/src/items.rs`), so a damageable item
carrying no `equippable` — a sword put in a horse's body slot by
`/item replace` — wore out.
Tests: three cases in `mobs::armor_hurt_tests` (a donkey with a body item, a
fall on a horse, the amount before the reduction); each one fails on the
merged code and passes here. The `equippable` gate needs a live `Game`, so it
is the one part without a unit test.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Draw the active book without the retained inventory/container screen or its
native screen render hook, and hide that menu's slots from the agent UI.
When the book closes, leave the pointer available to a retained menu.
Add a standalone OpenBook packet proxy and book_menu check to the vanilla
server e2e harness. The check covers exclusive book widgets, page navigation,
return to the retained menu and mouse capture. It requires no container-close
fix and introduces no inventory packet queue.
This recreates the previously stacked book correction on current main,
preserving the gameplay change while isolating its network test setup.
Co-Authored-By: Codex <[email protected]>
GET /v1/export/benchmarks (raw reports, paginated by id) and
GET /v1/export/hardware (play sessions per machine description with CPU
threads and cores, paginated by offset). Both need a bearer token compared
in constant time (TELEMETRY_EXPORT_TOKEN), answer 404 while it is unset,
401 without it, gzip on request, and never carry an install ID. Wrong
tokens take from the address's rate limit; right ones do not.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The enums and the structs a mod is handed (errors, holders, phases,
panel events, the lifecycle context, the render frame) can gain variants
and fields without breaking a mod. Flow and the structs mods build
(NativeZoom, NativeScreen, AttachmentType...) stay open.
🤖 Agent: startup trace (HORIZON_STARTUP_TRACE + tools/startup_trace.py), skip-release. Verified: pr_ready on playhorizon (render lib tests --test-threads=1), CI verify green on this head after the rebase.
When every quart cell the section's blend squares can zoom to holds one
biome, each block's blended tint is that biome's colour (the mean of equal
colours), so Tints::get returns it without the per-block zoom and the
(2r+1)^2 sum. Swamp grass, whose colour follows noise, keeps the blend.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A face whose neighbour shows a full occlusion face toward it is hidden:
should_render_face's first test, read from a per-state bit table without
the visibility lookups.
- round_to_u16 and gray compute f32::round/floor's results without the
roundf/floorf calls x86-64's baseline makes of them (tested against them).
- Padded::fill finds each chunk column's section once per layer and copies
fixed-length runs.
- VisGraph's flood fill works on bit rows: each row filled along its open
runs at once (an occluded fill), regions grown by sweeps over the rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured with chunk_load_bench (seed 1234 village, 4,440 sections, 1,598
with geometry): 265 -> 210 us of CPU per section with geometry, p99 670 ->
530 us, with the same vertices and visibility (checksum afdcd05aec013a23).
- Padded::fill copies the section row by row and reads each border run
from a section found once (PalettedContainer::get_run), rather than a
chunk, section and palette lookup per cell.
- Hidden blocks (all six neighbours isSolidRender) are found from an
opacity bit row per padded row, read from a compact per-state table,
instead of six lookups into the per-state Info.
- Biome tints keep each cell's colour once computed (a cell is in the blend
square of up to 25 columns, and swamp grass samples noise), and mix the
biome zoom's corner jitter once per quart cell (BiomeZoom::corner_jitter
and select_corner, as generation already does).
- sides::sort sorts one integer key per quad (bucket, plane, index), which
orders exactly as the stable sort did, in buffers each thread keeps; the
sorted copy no longer replaces the mesh's recycled buffer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Meshes through Mesher::mesh_reusing with a recycled mesh and scratch, then
sort_solid, as the workers do; reports each section's best-of-rounds CPU time
(p50/p90/p99/max), the vertex bytes, the wall time of N threads meshing at
once, and optionally a JSON summary for A/B scripts. The checksum now covers
the visibility graph too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The loading screen built the world renderer on the main thread in the same
pass that swapped in the title artwork, so whenever the world tables were
ready by then the title waited for that build. The handoff now waits for one
presented title frame (or a second, for a window never asked to redraw), and
not at all when a world is waiting to open.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- is_blocking, is_using_item: the synced living entity flags (and a player's
blocking item), as the client keeps them
- is_swimming, is_crawling: the swimming pose (a player's or mannequin's
avatar pose, another mob's DATA_POSE, a drowned's swimming flag), crawling
out of water
- is_holding_item_right/left: the held items on each arm, by the main arm
- is_swinging_right_arm/left_arm: the current swing's arm, the main arm when
the swing names no hand
- is_ridden: the rider SetPassengers names
- is_sitting: foxes, pandas and camels from their own flags
- anger_time, height_above_ground, fluid_depth_up and is_wet's rain stay as
they were, with comments naming what the client lacks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Block overlays come before tiles under the shared cap, so a pack's many
tiles can't crowd out vanilla blocks'; any tile named by its file pairs,
not only those under ctm/; the doc comment is back on load_optifine.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
update_recipes applied the data's recipe filters (#5910) to its own
copy, and RecipeIndex to its index, so a removed recipe never reached
the book - but book_entries wrote from the network copy, which the
filters never touched: a replace_input or replace_output showed
unswapped in the book's displays, while the property sets and the
stonecutter carried the swapped recipe.
network_recipes now runs the filters once, over its files, before the
ingredient normalization - a swapped name resolves, and every copy
agrees on the swapped recipe. update_recipes parses the already
filtered copy without running them again, so one swap cannot feed a
later one twice.
Fixes#6595.
Co-Authored-By: ZCode AI agent (GLM-5.3)
A client looked a mob up by a linear search of its mob list for every
movement, data, motion, attribute, equipment and event packet. A Mob is
~3.3 KB, so every compared id is its own cache line: in a 3000-mob farm,
the ~1000 movement packets of one tick took 5.5 ms of searching, 0.11 ms
with 1000 mobs. MobIndex keeps id -> position in a map and checks each hit
against the list; a miss falls back to the linear search and rebuilds the
map, so the list keeps changing freely in its ~35 places and the answer is
always the linear search's (ids are unique: spawn_entity drops the old mob
first).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A tile with <name><suffix>.png beside it (0.png and 0_e.png) pairs with
its overlay as block sprites do, so emissive glass, lamps and ores keep
glowing when connected textures replace their sprite.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main now saves rockets (firework_rocket_to_nbt over RocketSave, Rocket::to_nbt):
drop this branch's own writer and use main's for /data get. A rocket has its id
from the start here, so its chunk save reads its UUID and kept compound by id,
as primed TNT's does, instead of by position.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passes, and parity run 41356682661 passes inline_object (555 s) on 7aeddbcf4 merged into main 8d93d68f1. One-line harness fix, skip-release. Queued by pr-sweep-M.
The menu bundle the title waits for built the GUI atlas, then decoded the
panorama's six faces after it. They now load side by side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The world tables (items, recipes, behaviours, mobs...) waited for the game's
own CPU tables and the world renderer's textures, which ran one after the
other on the resource loader. The tail now starts first, and the two
handoff jobs run side by side; a failure still waits for the tail.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
to_title_ms was the moment the menu bundle was ready, which can be seconds
before the renderer that draws it exists (its pipelines compile on the main
thread first): on llvmpipe it read 0.7-1.6 s for a title drawn after 11-15 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The start builds three renderers on one device (the boot screen's, the title
artwork's, the world's), and resource reloads and graphics options build more.
Each compiled every shader module and pipeline again though most come out the
same. gfx/reuse.rs keeps them under their descriptors' Debug text and hands the
same object back; what the latest renderer did not ask for is let go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every named step of the start (resource tables, the renderer's device and
pipelines, the world's open on the server thread) and its marks (title drawn,
first frame in a world, the view filled, a dimension change) go to a file as
Chrome trace events when HORIZON_STARTUP_TRACE names one. tools/startup_trace.py
starts the game as a player does and prints the timeline.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The piglin brute's two activity sounds, the three ChatListener lines behind Chat Delay, the
angry enderman's render offset and the tipped arrow's potion colour are all in Horizon; the
rows still said missing.
A plant's 45° plane snapped its texture's right along the face normal;
directions now need to lie within about 25° of a block axis.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
[[particle_lights]] names particles, with a colour and reach, that join the frame's coloured lights
at their position, with their alpha as intensity. MaterialInput.colored_light gives those lights at
a point to the material stage, which could not read them before.
Fixes#6584
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Fireball.addAdditionalSaveData always writes Item, a fire charge by default,
and readAdditionalSaveData reads it back through setItem, which refuses an
empty stack and keeps a single one. Horizon wrote only the flight, so a
saved fireball or a /data get entity had no Item and a loaded one was lost.
fireball_to_nbt now writes it for the large and the small fireball (not the
wind charges, which are no Fireball), from the loaded item when there is one;
the projectile's loaded Item is kept beside its Tags. docs/server.md names
the control request RCON uses since #6443.
Fixes#6594
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
anim::Poses keyed every part by an owned String, so each mob drew a new
String per model part for its body and for every layer (wool, clothes,
armour, eyes) every frame: tens of thousands of small allocations a frame
in a mob farm. It now keys parts by the Fx hash of their name (Fx maps
and sets), with the same lookups and first-name-wins rule; a test checks
that no two part names of any vanilla model share a key.
An ignored benchmark poses the stress_mobs farm (1000 mobs of 8 kinds)
through render_with and hashes the vertices: the same hash before and
after, 6.93 -> 5.73 ms of thread CPU per frame, the body's poses
1.28 -> 0.65 ms.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Panes, whose tiles draw with the cube's UVs, read those as their texture;
plain overlay keeps the standard directions (Continuity's orient leaves it
out); Axes::texture refuses NaN and quads turned off the block's axes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SpriteIconButton.TextAndIcon.extractContents gives the label left = x + 2,
right = x + width - spriteWidth - 4 and centerX = x + width / 2, and
ActiveTextCollector.defaultScrollingHelper centres a label that fits on
Mth.clamp(centerX, left + lineWidth / 2, right - lineWidth / 2). Title's
Button::draw_label used x + 2 .. x + width - 2 for every button, so on the
first-launch Accessibility screen a label that fits (fil_ph "Settings sa
Aksesibilidad...", about 30 languages) ended under the icon and a wider one
(fr_ca, about 35 languages) slid under it.
ui::scrolling_text_around_at takes the centre; scrolling_text_at is the
default centre (left + right) / 2 and draws as before. Button::draw_label_at
passes the icon's room and the button's middle.
MenuTabBar.MenuTabButton.renderLabel hands the tab's label to
acceptScrollingWithDefaultCenter with a 1 px margin, its box 3 px lower on an
unselected tab, gray only on an inactive tab (WithInactiveMessage). The Create
World tabs drew it unclipped, so a label wider than its tab ran into the next
one; the Statistics tabs did too, drew every label at the selected tab's
height and grayed the active unselected ones. Both draw through
ui::menu_tab_label now.
The port ledger rows of acceptScrolling, RenderingTextCollector.acceptScrolling,
acceptScrollingWithDefaultCenter, defaultScrollingHelper,
extractScrollingStringOverContents, the slider's extractWidgetRenderState,
TextAndIcon.extractContents (it has a caller: the Accessibility screen) and
MenuTabButton.renderLabel say what is drawn now.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Vanilla screens already read the selected language (creative inventory with every tab, its
search, survival and hotbar tabs and the Destroy Item tooltip; pause, chat, death, crafting,
furnace, anvil, enchanting, brewing, trading, advancements, statistics, world select and
Create World were checked in de_de, pl_pl and ja_jp). What was left:
- Sign edit's Done button was a hard-coded "Done": now `gui.done` (also for its narration).
- The AI Agent page's four status lines were English sentences built in code: now
`horizon.agent.*` keys, with the connection count and the time since the last command as
arguments (`status_lines`, a function of the socket's state, so a test reaches it).
- Leaf Culling, Frame Generation, Shadow Cache, Screenshot Quality, Distant Plant Dither, the
Culling header, the Synchronous/Asynchronous and Leaf Culling values, their descriptions and
the controller binds' "Hotbar: Previous/Next Slot" were English keys that `en_us.json` did not
have, so every language showed English. They are in the table now. The Entity Culling
description's old key had drifted from the code; it is replaced. Translucent Batching, its
description and the GPU reason that blocks it get their translations too.
- 17 Extras and Splitscreen keys added to en_us.json after de/pl/uk/fr were written had no
translation there: added (Particles, Hide block-break particles, Smooth chat, Message
indicator, Keep channels for UI sounds, Splitscreen).
- zh_cn gets the new keys too. es_es, ru_ru and tr_tr still lack about 130 keys each.
`lang::scoped` runs a closure with a language that only that thread sees and returns the keys
it asked for and the language did not have, so tests render in another language without
touching the process-wide one. The tests: every text of the three graphics pages is a key of
the table; the Horizon options pages in de_de and pl_pl; the agent page and the Done button in
de_de, pl_pl, fr_fr and en_us.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
The server made a new body for a player from the defaults when they died and
respawned or went to another level, so /attribute @s minecraft:scale base set
0.1 was 1.0 again (box, eye height and reach as well), and so was every other
attribute a command or a datapack had set. Vanilla keeps the same ServerPlayer
through teleport, and ServerPlayer.restoreFrom copies the base values to the
body after a death (assignBaseValues). The old body's attributes now go to the
next one (with their permanent modifiers for a level change, base values only
after a death), and a player saved between two bodies writes them to the file.
restoreFrom then gives the respawned player its max health
(setHealth(getMaxHealth())): a max health base of 40 comes back with 40, not
with the fixed 20 the respawn set.
Also: a glide the client announced before the body's own tick used a
scale-1 box for that tick (Guest::dimensions); it takes the body's scale. The
port map's LivingEntity.getScale row still said there was no scale attribute.
Tests: the respawn health, the glide box, the carried attributes, a save
between bodies; lan_e2e check scale-body (host and guest: the Nether and back,
a death and respawn, the health after it).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
A conduit.jem that reshapes the eye alone no longer paints the shell,
cage and wind, whose vanilla geometry keeps its texture's layout. The
part-name test gives every entry a box of its own, so swapped names show.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve the maintainer merge and current main while adding macOS native
microphone purpose metadata as a separate follow-up.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Localize and XML-escape the microphone purpose string without requesting
permission or opening a device. Reuse the existing XML escape helper.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
Co-Authored-By: OpenCode Nemotron 3.5 Super VL <[email protected]>
A rule with orient=texture finds a face's left, right, up and down from
the quad's own texture (Axes::texture: the UV gradients of its corners,
snapped to the block's axes), so turned or mirrored models connect along
their texture. OptiFine's defaults are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The three MCP servers carried their own copy of the version negotiation and
the JSON-RPC line loop. They now share crates/horizon-mcp, so what a client is
told and what it gets cannot drift between horizon-ctl, horizon-server and the
launcher.
The crate serves two revisions. 2026-07-28 is stateless: every request carries
its version and capabilities in _meta, server/discover reports the versions,
capabilities and identity, every result says resultType, tools/list carries
ttlMs and cacheScope, and each result names the server in _meta. 2025-06-18
stays for the clients that open with initialize, which includes the launcher's
game_* tools talking to whichever horizon-ctl the player has installed. A
request with no _meta is answered in the handshake revision's shapes.
A version we do not serve is refused with UnsupportedProtocolVersion (-32022)
listing the ones we do, a modern request missing its capabilities is malformed
(-32602), and ping is answered for the handshake revision only, since
2026-07-28 removed it.
Also regenerates docs/launcher-commands.md, which #6462 left stale (its own
up-to-date test fails on main).
Fixes#5673
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
#6381 landed the same order (the shield's push before the hit's) in creatures.rs:
main's version is kept; this branch keeps its two server_knockback cases, which
pin that order for a half-blocking shield.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While a world loads, Streaming::update ran on one thread and at render
distance 32 took most of it: DecorationOrder::blocked asked waits_for
(36 lookups) for 24 neighbours of every wanted chunk on every update, in a
SipHash map, and schedule_generation scanned every column of the area each
time a worker finished.
- Decoration ranks use the Fx hasher; waits_for answers are kept for the
pass (nothing it reads changes in it but decorated, which updates them).
- GenerationScan keeps the sorted candidates of a full scan and takes the
next batches from them while the ranges stay the same and no column left;
a removal, a dropped proto chunk or a range change scans again.
The decoration and generation order are unchanged (differential test
against the full scan through jobs finishing, columns and protos leaving
and the centre moving).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Heightmap::update returns at once for a block two or more below the
column's first available height, but set_block_worldgen first checked the
state, the chunk bounds and went through Results for each heightmap of
each write. The surface rule rewrites most stone under y 8 to deepslate:
that cost about 14% of a proto chunk. The skipped path reads the packed
height directly and still rejects unknown states. Same heightmaps
(test: random writes against updating every map each write).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each trait hook now takes its host, where it has one, and one
generated #[non_exhaustive] <Hook>Event holding the hook point's
arguments, as NeoForge's events do: a later version can add a field
without breaking any mod. A number or bool a mod may change is a plain
field, written back once every mod has run, even after a cancel; other
mutable arguments stay &mut fields. One event goes through every mod,
so each sees the changes before it. Cancel and replace outputs stay the
return value. Dispatch signatures and the native_* hook points are
unchanged. Trait injections get the same pattern, their bindings
becoming the event's fields.
boat_server_passes runs inside the loop now, and lava breaks a boat
there: a for loop over the indexes skipped the boat that moved into the
broken one's place. The next boat is found after this one by its id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main brings the dedicated server export (#6462). Trait mods build for
the server target too: the generated game package owns horizon-client's
default features, so the server build's --no-default-features reaches
it, and no mod's API package turns them back on. The build key keeps
hashing the mods in bundle order, which decides trait dispatch order.
The launcher reference gains main's --server-output.
Vanilla puts a rider on its seat with Entity.moveTo, which sets the old position as well, so
the camera cuts to the cushion, boat or cart. Horizon kept the position the player stood at
as the previous one, so the first ridden tick drew the camera flying from there to the seat.
Only rules before the one that picked were tried on the face's sprite;
later ones naming the picked tile chain as any other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Trapped and ender chests read only their own files (cem_model.txt lists them with no
fallback); copper chests have no CEM name and always draw vanilla's chest.
- An entry whose name isn't OptiFine's for the model reaches the vanilla part of that name,
as mobs do; it is skipped only when neither exists.
- Tests: every OptiFine part name of the block entity tables lands on its vanilla part,
book.jem reaches the lectern's book, a copper chest draws vanilla's geometry and UVs beside
a chest.jem, and only entries no model has a part for are warned about.
- jem_files' precedence test moves next to it in horizon-optifine; read_small is private.
- docs/resource-packs.md names both test modules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Recognize farmland and crop templates through their retained class chains and plant loaded tagged block items in inventory order. Add vanilla-oracle classification and real content-root regressions.
Co-Authored-By: GPT-6.1 Sol <[email protected]>
perf_bench reports for Horizon main 1bd24e4042, vanilla 26.3 and vanilla with
Sodium, Lithium, FerriteCore and ImmediatelyFast at render distances 8, 16 and
32, 1920x1080, three games each, with a page on why they are not player
numbers: the Mac ran at load 15-114, every game ran offscreen, and vanilla's
window was shrunk to 1920x930 by the screen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second pass skips rules tried on the face's own sprite (an overlay
naming both drew twice), and doesn't run when the picked tile is that
sprite itself (tiles=stone).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by perf-kacper-1: requeued after merging main (lan/entity.rs conflict: contains → contains_key for main's tracker map). clippy and the lan tests pass on the merged tree. Server bench before: no MSPT regression (50 bots 6.11 → 6.18, 100 bots 9.88 → 10.27, noise). Parity passed on the previous head.
Main's #6231 work turned the tracker's seen set into a map: the chunk-index
loop now asks contains_key.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ctm.properties: "Tiles output by CTM rules can also be matched by another
rule" (matchTiles=optifine/ctm/mygrass/1.png). After a rule picks a tile,
the tile rules naming that tile (and this block, if they name blocks) are
tried once more; <default> there keeps the first pick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by perf-kacper-1: verify green, pr_ready --quick green (979 render tests), retargeted to main after #6499, mergeable. A/B on the Mac mini M4 Pro at RD 32 + far 256 (100 fps cap): GPU 5.66 → 4.21 ms (pre-#6005: 4.77), CPU +0.19 ms; RD 12: GPU 1.25 → 0.97 ms, CPU 0.65 → 0.63 ms. Screenshots identical to pre-#6005 apart from clouds, mobs, water and crops moving. Metal only; other backends keep one solid pass.
These kinds were pushed with id 0 and got one only when a LAN guest was
connected. They now draw it from the one counter when created, as vanilla's
Entity constructor does. Part of #6537.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: verified by perf-kacper-1: verify green, pr_ready --quick green (975 render tests), A/B on the Mac mini M4 Pro at RD 32 + far 256: CPU 2.91 → 2.88 ms, GPU 4.45 → 4.39 ms; RD 32 without far terrain CPU 1.61 → 1.54 ms. No visible change (the fade work it skips is a no-op while nothing fades).
MinecraftServer.setDifficulty is the one door /difficulty, a dedicated
server's management and a test environment's difficulty all go through, and
nothing told a mod about it. native_difficulty_changed(from, to) now runs
there, after the change, with the two difficulty names, and only when the
difficulty is another one.
Every client of the run (Horizon, vanilla, vanilla with the performance
mods) joins the same fresh local server instead of creating a world:
seed 1234, creative, offline, the bench player an operator so the scenes'
/tp and /time work. A new server world gets an unmeasured warm-up join, so
measured joins compare the clients, not the server's generation.
startup.join_ms is connect to loaded. The vanilla driver connects with
ConnectScreen.startConnecting when its script names a server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractFurnaceBlockEntity.consumeFuel takes the fuel item's own crafting
remainder: it takes the fuel slot once the stack is used up, and is dropped
at the furnace while more of the fuel is left. furnace.rs only knew the lava,
water and milk buckets by name, so a content mod's fuel with a
crafting_remainder burnt without leaving it. The remainder now comes from the
items table (the buckets carry theirs there), and Furnace::tick hands the
dropped ones to its caller, which drops them with Containers.dropItemStack's
helper.
Fixes#6524
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
entity_nbt_in_level stamped UUID from level.player_uuid, the world owner's,
on a guest's turn too: on the server thread every player is a guest, so
/data get entity @s UUID gave every player offline_uuid("Player"). Use the
acting guest's UUID, as acting_uuid does. lan_e2e data_uuid checks the
guest and the host.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ConnReader read 16 KB at a time into the stack, appended it, copied each
frame out, drained the rest of the buffer to the front for every packet,
and copied the body again; each compressed packet built a new inflate
state. Reads now go straight into the buffer behind what is pending, a
frame is consumed by moving an offset, its payload is copied once, and
one flate2 Decompress is reset per packet. The in-memory pipe (single
player) copies with VecDeque's slices instead of byte by byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ResultSlot.onTake is where vanilla awards a craft, so the crafting grid's take
now tells native mods: native_item_crafted(player, recipe, stack) runs with the
player's uuid, the recipe the grid matched and the stack taken, on the
integrated server and a dedicated server, guests included. The menu records the
recipe and stack for a guest's click; the host's click fires it directly, once
per craft a shift-click takes.
claim_for_lost runs first in a world, so a controller that comes back
still takes back its own player (or the one waiting longest) when P1
waits too; only what it passes on, with P1 the one waiting, goes to P1.
The extras doc names Y (play as P1).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lightning bolt does not override Entity.isIgnoringBlockTriggers (bytecode: only Marker, OminousItemSpawner, Display, ArmorStand, Interaction and Bat do), so BasePressurePlateBlock.getEntityCount and TripWireBlock.checkPressed count it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: merging "A player's thrown trident fires the projectile impact hook" (head 05c7ec158) after review by Ludovic's AI agent; thank you, @breachato.
It set the launcher's process-wide language (Russian, Spanish) while other
launcher tests run in parallel and read tr(), and it checked a copy of
the view's match rather than the view.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passes on 5107acc19; parity 39 passed. The 3 failures are not this PR's: lunge_motion (known flake), inline_object (main's harness still builds client_probe with -p horizon-client since the client-lib split) and pack_screen (oracle errors: lang files missing from the runner's prepared resources). Queued by pr-sweep-M.
The hook now sees the raw strength: a 0.5 resistance hands it 0.4, and a
full resistance stops whatever push the hook leaves, as NeoForge's
LivingKnockBackEvent does. API 125's paragraph and the hook's doc say so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The items engine (block drops, entity drops, loot_table, interact_loot)
answered a mod's loot condition but had no arm for a mod's loot function, so a
block's own table could not carry one. Its modifiers now build the stack as the
main loot engine's LootStack, call native_loot_function, and write back only
what the hook changed: item, count, damage, enchantments, potion, ominous
bottle amplifier, stew effects, names and the components the engine keeps as
saved.
🤖 Agent: perf-net-client. Hardware AES for the connection cipher; online-mode RD 32 join receive CPU 8-9.5 s -> 0.5-0.6 s (EPYC replay), decrypt x240 on M4. pr_ready on playhorizon, ARM path tested on the Mac, CI verify+launcher green.
ServerGamePacketListenerImpl.handlePaddleBoat applies a paddle packet only
when the sender is the vehicle's controlling passenger, and Entity.addPassenger
seats the first player in front. The server now numbers the riders of a vehicle
as they get in and takes paddle packets from the first one only.
tools/test_boat_paddle_reset_parity.py gains a boat with two players: the
second rider's paddle packets must leave the metadata alone, the first rider's
still change it, and once the first rider gets out the second one drives.
Since the client library split the example belongs to horizon-client-lib, so
`cargo build -p horizon-client --example client_probe` exits 101 and the harness
fails on every PR that selects it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`loot_place_at` reported the shared weather levels in every dimension.
Vanilla's `Level.isRaining` and `isThundering` also need
`canHaveWeather()`, false with a ceiling (the Nether) and in the End, so a
mod's weather condition saw rain there while the Overworld had it.
Fixes#6486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/api/agent-control.md listed "A version" as not there yet, and
stability.md planned a hello reply. horizon-ctl hello (and its MCP tool)
now replies {protocol, release, features}: protocol is the control
protocol's own version (0.1.0, the first numbered line), release the game's
version (the string MCP serverInfo carries, which both pages still called
planned), features the command groups the game serves.
Fixes#6484
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Far terrain carries the same tier as the section mesher writes, so a light source keeps
shining across the LOD boundary. The warden's layers shine by how far they've faded in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.knockback scales strength by KNOCKBACK_RESISTANCE inside the method, and NeoForge's
LivingKnockBackEvent fires at its start, so a mod's change to the strength lands on the raw
value. Horizon ran the hook after the resistance had scaled it, for mobs and for the player.
The hook now sees the raw strength and the resistance scales what it leaves.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A dedicated server now times its spans every tick and writes a tick slower
than 250 ms to the log with where its time went (HORIZON_SPIKES's line;
the variable still sets another threshold). A server more than two seconds
behind, which already skipped the ticks it missed, now says so as vanilla's
MinecraftServer.runServer does ("Can't keep up! Is the server overloaded?
Running Nms or N ticks behind"), at most every 15 seconds.
The official server shows 10-13 s freezes (bursts of 'moved too quickly'
from 15+ players at once) with nothing in its log to say where they went.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "HDR: glowing items shine, dropped, framed and held, the hand's too" (head e03324cd4) after review by Ludovic's AI agent; thank you, @senjinthedragon.
🤖 Agent: merging "Native mods: a mod loot kind sees where the roll happens (origin, block entity, level)" (head 26103c104) after review by Ludovic's AI agent; thank you, @ErikKalikas.
🤖 Agent: merging "OptiFine packs: CTM overlays face the right way on rotated blocks" (head 9fbd459b6) after review by Ludovic's AI agent; thank you, @some-du6e.
🤖 Agent: merging "A boat's entity tick Post comes after the rest of its tick" (head ae295952c) after review by Ludovic's AI agent; thank you, @breachato.
#6005 drew every solid list twice on Metal, once per pass, and each pass
clipped the other's quads in the vertex shader: at render distance 32 with
far terrain the cutout pass cost 2.2 ms of GPU, most of it shading opaque
vertices it then dropped.
Sorting a mesh's solid quads by facing side now keeps its opaque and cutout
quads apart (mesher::Part), and Sides draws either part. Where the renderer
draws two passes (Metal), meshes put all their opaque buckets first, so each
part is one or two runs; elsewhere each bucket holds its opaque quads, then its
cutout ones, and one pass draws the same runs as before. Phase 1 lists every
mesh's opaque runs, then its cutout runs (a fading mesh's quads all go to the
cutout pass), and flat draws start a call where the passes split.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Save and reload keeps primed TNT, rockets and thrown tridents" (head fd93627b1) after review by Ludovic's AI agent; thank you, @HrafnaValkyrja.
route_pads handed the first controller pressed in a world to P1 whenever no player had joined
yet, whatever P1 was playing with, so with P1 on the keyboard and mouse the only controller took
P1's controls instead of offering to join.
A pad is P1's without asking only in a menu, where it drives the menus, or while P1's own
controller is gone and its view offers "Use another". Anywhere else in a world the press starts
the join offer, where A joins as the next player and Y gives the pad to P1.
🤖 Agent: merging "A player who dies with a container open closes it at death, not at respawn" (head c7d2b1199) after review by Ludovic's AI agent; thank you, @xFedeT.
🤖 Agent: merging "Pistons and shulker lids push falling blocks, XP orbs and projectiles" (head 6578a0abf) after review by Ludovic's AI agent; thank you, @HrafnaValkyrja.
🤖 Agent: merging "Title panorama: read the faces through the resource layers" (head bbec3f1a4) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "parity.py runs harnesses on Windows: file locks, process trees, no peak or shim there" (head 5ebf42f7b) after review by Ludovic's AI agent; thank you, @Ragbie.
Content::read and Content::declare accept the mod's blocks that have an item as a crafting
remainder; drop_dangling_remainders now counts them too, so a valid mod is not left out.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Add the two missing rows of the tooltip_display visibility table to
tools/test_tooltip_display_parity.py: hide_tooltip with hidden components and
a non-creative flag, and a creative flag with no hidden components.
Add three lore stacks to tools/test_lore_tooltip_parity.py: a line with no
text of its own whose explicit italic:0b reaches its children, a plain-string
child next to an object child with bold:0b, and a packed #rrggbb colour with a
strikethrough a child inherits.
These kinds applied their own gravity and ignored NoGravity:1b, so a
summoned one fell (Entity.isNoGravity / getGravity is 0 in vanilla).
Arrows, orbs and fireballs also dropped NoGravity on save.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The sliders' "nits" were real nits only where the compositor passes PQ through, not on
KWin or macOS, and the game can't tell which: the values stay, the unit goes. Black Level
0 is now Auto, what the system reports, rounded down so an OLED stays at true black. The
HDR shortcut no longer reaches the title screen through the mouse or control socket.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The file check now accepts a remainder naming a code-declared item before that declaration is
validated. If accept_declared then refuses the declaration, the file item's remainder dangles
and the overlay failed for every content mod. A mod with an item whose remainder it does not
have is left out alone, with a warning.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ItemEntity keeps Entity.shouldRenderAtSqrDistance: 64 times its 0.25 box,
times the view scale, so 24 blocks at render distance 12, 32 at 16 and 40
from 20 on, measured from its current position. Horizon drew them up to 64
blocks, which posed and uploaded every item of a big storage or farm area
that vanilla leaves out. Item pickup particles keep their 64 blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stage wrote alpha 1, so with a pack's material no light source on terrain shone.
It now writes the mark as fs_solid does, from the sprite before the pack's material.
Its emission also read a tier that shines all over as the top tier.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/stress_scenes.py builds nine scenes above the spawn with vanilla
commands (1000 mobs, 2000 dropped items, 512 item frames, 500 armor
stands, 800 containers, 687 signs, 300 banners, steady particles from
redstone-powered command blocks, and an empty baseline) and removes each
afterwards. The same lines run in Horizon (horizon-ctl command) and in
vanilla (the driver mod's new per-scene setup and cleanup).
perf_bench --scenes stress holds each scene still and reports frame CPU,
client tick time (vanilla: Minecraft.tick through a new mixin) and
resident memory per scene.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Content::read checked remainders against the mod's files only, before the native mods'
declarations were collected, so a remainder naming an item the same mod declares in code refused
the whole content mod. read_declaring takes the ids native_items declares in the mod's
namespace, and the remainder check accepts them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
holder_set writes any JSON object as an empty list (varint 1), so a
recipe whose ingredient is one of NeoForge's standard shapes —
neoforge:compound, intersection, difference — reaches a connected
client with an empty ingredient slot. The crafting copy resolves these
shapes to flat item lists, but the network copy loaded its recipes from
disk separately, and the resolved lists never reached it: neither the
book's displays nor UpdateRecipes' property sets and stonecutter.
The network copy now loads through one place, network_recipes: every
recipe file and overlay recipe with each ingredient object — NeoForge's
shapes, a brewing slot's item predicate — replaced by its flat item
list, covering the shaped key's values, ingredients, ingredient, the
smithing trio and the brewing pair. A recipe that keeps an ingredient
object no loader can parse (a mod's own kind) is dropped: the server
does not load it, and an empty ingredient slot in its place misleads
the client. Only vanilla recipe types are normalized; a mod's own type
keeps its JSON whole. A missing item catalog leaves the shapes
unresolved with a warning instead of silently.
An item the registries do not know is left out of the ingredients it
was named in, not sent as id 0.
The lan_bot test drives the whole path live: a server over the vanilla
data with a pack of compound recipes, two guests joining, each reading
its own UpdateRecipes and RecipeBookAdd.
Fixes#6368.
Co-Authored-By: ZCode AI agent (GLM-5.3)
Entity.handleOnAboveBubbleColumn ends in sendBubbleColumnParticles: two SPLASH
and two BUBBLE particles over the column block, sent by the server to the
players around. Only the player and arrows showed them. A mob (tick
applyEffectsFromBlocks) and a dropped item now ask for the same particles
through MobEvent::BubbleColumnParticles and the item tick loop, drawn and
sent to the LAN guests by one Game::bubble_column_particles, which the
arrow event uses too (its particles reach the guests now).
Refs #5768
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
comeOffTrack keeps the delta its move resolved, so a cart an obstacle stopped there is
standing still as far as MinecartTNT.tick is concerned; only the rail paths re-apply the
track's velocity and detonate.
Round 2 review fixes: load_optifine stops at 96 pairs so native mods' 32 always fit,
and table no longer cuts; it runs after the connected-texture tiles load, so a tile
identical to a block makes its layer shared (no overlay) instead of glowing; the 32-cap
test uses 40 distinct bases; insert keeps its doc; the docs list the limits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlayerList.respawn builds a new ServerPlayer whose lastEquipmentItems starts empty, so its first
detectEquipmentUpdates reports every worn item. The last-heard equipment is cleared at the
respawn.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Round 2 review: the trident glow moves to trident_geometry (tridents the server sends,
and those flying to whoever picks them up); the in-frame projectile branch it was in is
deprecated. A dropped variant beside its texture also takes its blink frames and their
overlays out of the atlas.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Launcher: version picker overflow and low-contrast text/buttons" (head b2d84a7f9) after review by Ludovic's AI agent; thank you, @Brineexee.
heal_hold_latches releases sneak and sprint when the OS says Shift or Ctrl is up,
to recover a keyboard release that never arrived. A controller's stick click acts
as that key without setting the modifier, so the heal released it the next frame,
before a tick could start the sprint: left stick click stopped sprinting. Skip a
mapping the pad holds (or whose release waits for a tick), as modifiers::reconcile
already does.
Fixes#6377
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Players on a server keep their tags: @a[tag=...] and /tag work for guests" (head 66359b492) after review by Ludovic's AI agent; thank you, @xFedeT.
🤖 Agent: merging "Native mods: a player changing dimension (native_player_changed_dimension, part of #5816)" (head 0e9f118ca) after review by Ludovic's AI agent; thank you, @Detimes.
Experience orbs, snowballs, eggs, potions and fireballs ignored nether and
End portals; vanilla sends them through like items. They are now swept for
portal blocks, saved and re-spawned in the other dimension, and keep their
portal cooldown on arrival so they do not go straight back.
Tridents and firework rockets still stay behind (no save/load writer yet).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Tags given to a summoned experience orb, arrow, snowball, egg, ender
pearl, fireball, small fireball, wind charge, End crystal or Ender Dragon
were dropped: @e[tag=] found nothing and /tag wrote nothing. They now keep
their Tags by entity id, and /data get writes them back. /data get entity
on a wind charge answered "no entity"; it is written like the other
fireballs now. lan_e2e --check selector-tags covers it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
LivingEntity.collectEquipmentChanges compares every living entity's slots with the last seen
stacks, players included, so a hotbar selection that moves the main hand or an item replaced in
place fires the event with the previous stack. Horizon compared mobs only. Each guest's main
hand, off hand and armor are compared the same way, per tick and only while a mod listens.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review fixes:
- native_emissive::table builds the frame's overlay table: native mods' pairs first
(their own 32), then the packs' OptiFine pairs flagged to draw cutout (opaque where
alpha >= 0.1, as OptiFine draws SOLID textures' overlays), sorted so the terrain
shader's per-vertex search stops at the first larger base layer
- an overlay that doesn't load is skipped (Sprites::try_insert), not drawn as the
missing texture; blocks sharing a layer (identical pixels) get no overlay; pairs past
the cap and oversized files are logged
- tests for the table and for the broken and shared cases
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The underwater overlay raised the frame's alpha, which HDR output reads as something
covering the world, so nothing shone with the eye in water. It tints the whole view,
so with HDR on it now leaves that alpha alone and tints the kept world too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review: the texture's overlay is mostly its glowing open eyes, which painted over the
shut ones and hid the blink. Frames pair with their own _blink_e (tested through the
renderer and the atlas); the docs list where overlays live, and that trims aren't covered.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reuse the launcher native build pipeline for a headless server target,
export an independent runtime, and document manual server selection.
Keep server cache entries outside playable versions and preserve export
permissions, resources, and post-build data.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
Co-Authored-By: OpenCode Nemotron 3.5 Super VL <[email protected]>
LocalPlayer's constructor calls KeyMapping.resetToggleKeys, so a toggled sneak or sprint (the
sitting and swimming poses the report sees) is not still on in the next world. close_world now
puts every toggle mapping up and releases the latched sneak and sprint keys.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A pack connecting glass with matchTiles=glass retiled panes but left their
seams; a pane counts as connected when any rule names it or one of its
sides' sprites.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Items draw from the HUD atlas, so their overlays would only take table slots; the docs
list placed blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
For each loaded block or item sprite whose <name><suffix>.png sits beside it, the
overlay is loaded into the sprite array and paired with it in the terrain shader's
overlay table, which draws it full bright over the base in the same fragment (the
native-mod emissive path). The table holds 128 pairs (native mods keep their 32);
horizon_optifine::emissive::suffix is shared with the entity overlays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- the armour stand's own body glows, and its overlays are read per lookup, not held while
wings_geometry reads the same lock
- the thrown trident glows (ETF's guide names it)
- a random variant's blink frame pairs with its own _e overlay, which the atlas packs; a
blink frame without one keeps its texture's glow instead of going dark every blink
- blinkLength near u32::MAX no longer overflows
- blink frames come after every variant in the atlas budget, and a dropped variant beside
its texture takes its overlay out of the atlas too
- docs/resource-packs.md lists random, emissive and blinking textures
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.baseTick immerses a boat and adds the flow of the water or lava it sits in to its
velocity, before AbstractBoat.floatBoat scales that velocity. Horizon's boat tick never ran
the fluid step, so a boat in flowing water moved as if the water were still.
With prepared resources, options.on/off made it "Splitscreen: OFF", unlike
the label before localization and what the live splitscreen tests expect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LocalPlayer's yaw runs on without wrapping, so yRot - yBob is always the short way. Ours is kept
in -180..180 and a boat turns its rider through the wrap: the difference jumped by 360 degrees
for a tick, swinging the hands 36 degrees (a tenth of it) and then easing back by halves, like
jelly. The difference is wrapped, both when yBob eases and when the sway is read.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla's pane models mirror some faces' textures, which put a connected
tile's borders on the wrong side; OptiFine's pane models don't, so tiles
on panes take the cube's own UVs. Panes with connected textures also hide
the top and bottom strips they share with the same pane stacked on them
(Continuity's Glass Pane Culling Fix), where the other pane has that part.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 2 review fixes: the armour-stand armour loop, the elytra (when no cape is drawn on
it) and the ender dragon draw their texture's _e overlay full bright; render::FULL_BRIGHT
is shared; tests for the extra layers' glow flag and for overlays coming after every
variant in the atlas budget; the suffix test pins a lone backslash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Graphics: Advanced, Performance gets a Translucent Batching switch after
Graphics API ("Saves time"; it applies at the next start, as Graphics API
does). Where the device can't batch (translucent_batching_offered) it is
greyed and reads Off while the saved value waits, and neither a click, the
wheel nor click-then-Enter changes it; on macOS, where Metal never offers
it, it is hidden beside Graphics API.
The setting is now on by default, under a new key, batchTranslucent: every
save wrote translucentBatching:false while the setting was off by default
and had no switch, so under that key the new default would have reached no
one who had saved an option since. The old key stays ignored, as shaderPack
is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without mods the round doesn't listen and the Posts send nothing, so the
per-tick list of ticked boats (an entry and a kind per boat) isn't built.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Draw an arrow between the rotations of the two ticks it turns over" (head c77c94cb2) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Foxes take their food's consume effects and survive on a totem" (head 6def6595b) after review by Ludovic's AI agent; thank you, @JustKarkat.
MultiPlayerGameMode.useItem runs ItemStack.use on the client and swings
the hand on a shouldSwing() result (SUCCESS); CONSUME ends the click
without one. The remote path sent the packet and predicted everything
else but never swung, so a thrown pearl, splash potion, wind charge,
snowball or experience bottle, a bucket fill/empty, a spawn egg, a
writable book and equipping armor from the hand only did the itemUsed
dip on a server (#6425, #6409). The useItemOn half already swung.
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
world_runs is TickRateManager.tick: called a second time for the own
rockets, it spent two of a /tick step's ticks each client tick. The
test also runs the real rocket_spark now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Push the shield before the hit that it only partly blocked" (head c30dc0feb) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Button, slider and title-screen labels wider than the widget scroll, as vanilla does" (head 26e6743fb) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Buttons scroll a label wider than they are, as vanilla does, instead of panicking (Resource Packs screen in 8 languages)" (head 76e2ffdb8) after review by Ludovic's AI agent; thank you, @mariuslozo-boop.
🤖 Agent: merging "Translucent batching (3/4): the large pages shrink once the view has settled" (head b91226cac) after review by Ludovic's AI agent; thank you, @NerfPhaselock.
🤖 Agent: merging "Shader packs: a parameter or local named like a bool uniform hides it" (head 9696cd39e) after review by Ludovic's AI agent; thank you, @Joansitoh.
🤖 Agent: merging "docs(hosting): Horizon players join online-mode servers and sign their chat" (head a0f851b33) after review by Ludovic's AI agent; thank you, @OsamahDev1.
🤖 Agent: merging "Remote: handle arrow hit, pufferfish sting and limited crafting in GameEvent" (head f983b543a) after review by Ludovic's AI agent; thank you, @DmytroChup.
🤖 Agent: merging "Send an iron golem to a spot near its target, not straight at it" (head 367515af3) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Let Tab reach the world list so a keyboard player can join" (head 0de0b1d1b) after review by Ludovic's AI agent; thank you, @MSedgeMC.
Overlays took the face's model UVs, so on dirt's, sand's and grass's
randomly turned tops a transition picked for the north side drew on
another side; they now take the cube's own UVs, as Continuity's spec says
overlays do. Over a ctm_compact face, an overlay is cut into the same
pieces, so it keeps the face's depths and translucent sort order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Play with friends: the host attaches from any port when its direct port times out" (head f3de190aa) after review by Ludovic's AI agent; thank you, @Ragbie.
🤖 Agent: merging "Render tests: no OpenGL instance on Windows, where WGL hung them" (head 2ca08a955) after review by Ludovic's AI agent; thank you, @Ragbie.
MinecartTNT.tick detonates when the cart is in a horizontal collision and its delta
horizontally is still at least 0.1 a tick - on a rail the track re-applies the velocity
after the move, so a cart driven into a block keeps it. Horizon zeroes a cart's velocity
where the collision is resolved, so the check always read zero and the cart just stopped.
Completes the previous commit: the API number goes back to main's (the
stamp tool assigns it after merge), the hooks are listed in
tools/native-hook-fragments/experience_orb_pickup.json, and a guest is
swapped in for the orb pickup hook only when a mod sets listened in
native_experience_orb_pickup_listened, so a server without mods pays
nothing more per pickup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hooks go through tools/native-hook-fragments/ instead of a hand-set
API 154 (the number is stamped after merge); a guest is swapped in for the
orb pickup hook only when a mod listens (native_experience_orb_pickup_listened),
so a server without mods pays nothing more per pickup; the Python test that
compiled extracted source text is dropped (it pinned the code's wording).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
getHandHoldingItemAngle follows the player's main arm, so a left-handed
player's rocket sits on the left. The rockets boosting this player now
tick after the whole player tick, so they keep following it while it
waits for chunks. A rocket used on a block passes on cooldown, and in
adventure mode only off its can_place_on, as ItemStack.useOn does.
The boost and the hand offset are one helper shared with the in-frame
tick, and the test runs the real hand angle against vanilla's view
vector.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mount hook that removes mobs shifts self.mobs, so the indices collected
before the loop could name another mob, which could then be picked up or
pushed without having touched the vehicle (the id check only guarded the
mount write). Both loops now carry the mob ids and look each one up.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An RCON command used to run as the server console, so the operators saw nothing of it and its
`say` read `[Server]`. It now goes through its own control request (`rcon <line>`): the same
console at level 4, with the source named `Rcon` as RconConsoleSource.createCommandSourceStack
names it. Its feedback reaches the operators in game as `[Rcon: ...]` while
`broadcast-rcon-to-ops` is on (CommandSource.shouldInformAdmins), its `say` reads `[Rcon] ...`
and leaves the reply empty, and the console and an RCON client take success feedback whatever
`send_command_feedback` says (CommandSource.acceptsSuccess), as a player's source does not.
Fixes#6393
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The six CreateBuffetWorldScreen rows said missing; they now name the screen's
functions in title/buffet.rs, and the two CreateWorldScreen rows that mention
the Customize button name both editors PresetEditor.EDITORS has.
🤖 Agent: merging "Native mods: a player starting or stopping flight" (head 7673b4511, main merged in, both hooks kept) after review by Ludovic's AI agent; thank you, @Detimes.
🤖 Agent: Opened and checked by Ludovic's AI agent (Claude): JSON-only; stamp_native_hooks.py stamps all 72 pending fragments on this head, tests.test_stamp_native_hooks OK; unblocks the native hook stamp workflow on main.
Layout icons and the Auto tooltip now come from the draft's layout index, not
from parsing the English label, so translated labels keep their glyphs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PresetEditor.EDITORS has a screen for the flat preset
(CreateFlatWorldScreen) and for the single-biome preset
(CreateBuffetWorldScreen); the other presets have none. Horizon enabled
Customize for flat worlds only, and its single-biome overworld always fixed
minecraft:plains, so the preset could not be edited at all.
The button now opens CreateBuffetWorldScreen for the single-biome preset: the
biome registry of the prepared resources, sorted by the name each is shown
under and narrowed by a search box that matches the name case-folded, with
Done and Cancel in the footer. Done hands the biome back to Create New World
as WorldCreationUiState.updateDimensions does with
fixedBiomeConfigurator: NoiseBasedChunkGenerator over a FixedBiomeSource with
the overworld's noise settings, so the chosen biome becomes the overworld's
fixed biome source. Re-Create starts the screen on the world's own biome, and
changing the world type starts it over at plains, as a new preset's
dimensions do.
🤖 Agent: merging "A respawn into another level of the same dimension type makes a new level" (head 0ccddf82b) after review by Ludovic's AI agent; thank you, @breachato.
🤖 Agent: merging "The client level unloads when the server sends it back to configuration" (head 8b728305b) after review by Ludovic's AI agent; thank you, @breachato.
🤖 Agent: merging "ender dragon: the dying model dissolves through dragon_exploding's mask instead of fading" (head 7458a6ca1) after review by Ludovic's AI agent; thank you, @iLostName.
🤖 Agent: merging "camera fluid: lava fog starts when a corner of the near plane dips under the lava" (head 30715a97d) after review by Ludovic's AI agent; thank you, @iLostName.
🤖 Agent: verify passes on 0d3eb998a (rebased on main after #6189); silverfish back into infested stone, checked against the vanilla bytecode. Queued by pr-sweep-M.
🤖 Agent: merging "HDR: a light source lifts by its texel's own brightness, not the lit pixel's" (head 3451589c3) after review by Ludovic's AI agent; thank you, @senjinthedragon.
🤖 Agent: merging "Far terrain: finishes loading in about a minute instead of seven on 4-thread machines" (head 4d53099db) after review by Ludovic's AI agent; thank you, @some-du6e.
🤖 Agent: merging "Riders get off a mount that can't swim with one when their eyes are in water" (head fd43c4620) after review by Ludovic's AI agent; thank you, @Akirabane.
🤖 Agent: merging "Type keyless characters (Windows Alt codes) into the focused text field" (head 76b26c980) after review by Ludovic's AI agent; thank you, @xFedeT.
🤖 Agent: merging "Baby happy ghasts and nautiluses look like babies on a server" (head 02be02992) after review by Ludovic's AI agent; thank you, @some-du6e.
entity_chunks wrote none of them, so a world save or chunk unload dropped
them; tridents also had no load arm. Keys follow PrimedTnt,
FireworkRocketEntity and AbstractArrow/ThrownTrident addAdditionalSaveData.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
AbstractContainerScreen.tick closes the container as soon as the player is no longer alive, so
the server's doCloseContainer runs at death. Horizon closed a dead guest's container only in
guest_respawn, so the lid stayed up and PlayerContainerEvent.Close reached mods only after the
Respawn button. tick_player_menus now closes the menu (and drops the lid) for a dead guest and
counts a dead guest's other menus as closed.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ServerLevel.tickNonPassenger runs AbstractBoat.tick in full (its lava
damage, the ejection of riders under water too long, the bubble column and
what it picks up or pushes) before EntityTickEvent.Post. tick_boats sent
Post right after tick_boat, before those passes, so a mod reading a boat
there saw it before them, and a boat it moved was then pushed and picked up
from there in the same tick. The ticked boats' Posts now go out, in order,
after the passes.
Fixes#6388
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemStack.getRarity reads the stack's minecraft:rarity, which is the item's
default unless a command or a server set it, and then upgrades it for
enchantments. Horizon's item_tooltip::rarity started from the item's default
only, so /give stick[rarity=epic] showed a white (or, enchanted, aqua) name
instead of a magenta one. The rarity component, which both the command and
the network paths keep among the stack's components, now comes first.
Part of #6405 (the rarity; the missing lore is not covered here).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolve labels, status text, instructions and warnings from the language table with parameterized messages and bundled English defaults. Add French translations and a process-isolated locale/pack-override regression that preserves draft state and literal player names.
Co-Authored-By: OpenAI Codex <[email protected]>
Vanilla registers the six CubeMapTexture faces with the texture manager, so
they resolve through the resource manager's pack stack: the topmost pack that
has a face supplies it, and the vanilla pack is the bottom layer. Horizon
flattens the stack into one overlay root and read the panorama from that root
alone, without the game's own resources the atlas build falls back to, so a
face missing from the overlay turned the whole cube into the grey placeholder
even when the game's resources had every face. The warning also dropped the
path it tried, leaving only the io error.
Each face is now taken from the first layer that has it, with the game's
resources behind the pack overlay, and a cube no layer completes falls back to
the placeholder with every path it looked in, plus the horizon-prepare hint
the sounds warning already gives.
Vanilla's grab outlives the window's focus: MouseHandler.grabMouse sets
mouseGrabbed and the disabled cursor mode, nothing releases either when the
focus goes, and the mode is re-applied when the window comes back, so the
pointer is locked again without a click (and put back at the window's
centre). Horizon released the pointer on every focus loss and never took it
back, and winit only applies a cursor clip while the window is focused and
does not re-apply one on WM_SETFOCUS, so the grab was gone: the pointer
stayed where it was, often outside the window, and clicks went to whatever
was under it.
The window now remembers whether it held the pointer when the focus went and
takes it again on the focus return, through the same capture(true) the rest of
the game uses. A screen that is open keeps the pointer free, and the existing
press-to-grab path stays as vanilla's fallback.
🤖 Agent: verify passes on 8512214d0 (main merged, with #6246's cem_texture/paints_with_jem kept). OptiFine ETF fixes, base of the #6217/#6219/#6220 stack; render-only. Queued by pr-sweep-M.
The same Windows checkout problem as the loot test: the breathing hook test
(new on main) looks for "\n}\n" in breathing.rs, which a core.autocrlf
checkout has in CRLF. It normalises the line endings first too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
block_entity_energy, data_map_types and item_fluid_tank listed files without
their @API marker, so stamp_native_hooks.py stopped on main and the native
hook stamp workflow has failed since #6291's merge. The hooks' own markers
are in native_hooks.rs, which the tool always stamps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Finish guest actions before collecting block and entity updates. Drain packets before this snapshot even when world ticking is frozen. Add a two-client regression that queues an instant Survival break behind a server barrier at one TPS.
Co-Authored-By: Codex (GPT-6) <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ModelLibrary::overlaid / resolve_with_textures / bake_resolved for CIT model and texture swaps (unused until the next PR; tests); carries #6232 (CIT textures into the GUI atlas, load time only); main merged by me (lib split, sprites prefetch). verify green on 17e6fd55b.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ModBlockEntity revision behind Deref/DerefMut: kept mod block entity geometry checked by two integers instead of a deep compare (11 ms → 0.04 ms/frame at 4096 large machines); update tags merge without bumping when unchanged; main merged (ModEnergyBuffer imports). verify green on 978591ea1.
parity.py started on Windows since #3428 but stopped before any harness
ran: machine_slot locked its slots with fcntl, which Windows lacks. The
rest of the run path was POSIX too: a timed-out harness was killed with
os.killpg, trace_main read the peak memory with resource, and the oracle
shim went on PATH as a `java` symlink to a Python script.
- tools/file_lock.py: an exclusive lock, flock or msvcrt's byte lock; used
by machine_slot, integration_env.suite_lock and test_datafix_parity.
- kill_tree: the process group where there is one, taskkill /T on Windows.
- Without resource, the trace has no peak_bytes and the scheduler assumes
DEFAULT_MEMORY, as for a harness that never ran.
- No oracle shim on Windows: a program started without a shell is found
there only as an .exe, and the shim's execv returns before the program.
Harnesses run their oracles fresh.
Fixes#6391
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): native_sign_changed after every updateSignText check, through as_guest_messaged; cancel keeps the text and resends the block entity; no cost without a sign update. Union of 6247+6260+6270+6279 with main: Python tests 594 OK, stamp_native_hooks OK; verify green on 6b6b55279.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): WitherSkull.getBlockExplosionResistance cap; main merged (kept #6262's test beside this one). verify green on ea514b4c6.
CreateWorldScreen.keyPressed, KeyMapping.setAll and EnchantmentHelper.createBook are all
implemented on main; the rows were written before the code that covers them, so they now
point at the wrong work.
- block_entity_cem.rs applies a pack's .jem to the block-entity models: chest(_left/_right),
trapped_chest(_left/_right), ender_chest, shulker_box, banner / banner_base / banner_flag,
head_*, bell, book / enchanting_book / lectern_book, conduit and decorated_pot, with
OptiFine's part names mapped to the vanilla ones (knob -> lock, stand -> pole, slate -> flag...)
- Entries replace or join the vanilla part's boxes and take their scale; animations come later
- The .jem's texture paints its model when the atlas has it; trapped and ender chests borrow
the chest's geometry without its texture, copper chests take the geometry only
- Closed-chest meshes are baked from the reshaped models, per chest model
- BlockEntityShapes follow pack reloads (built on the reload worker, swapped on apply); without
.jem files the renderer keeps sharing vanilla's shapes
- The .jem discovery and reading move from mobs/cem.rs to horizon_optifine::cem::files
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): content block states laid out in StateDefinition order (names sorted, first slowest) instead of refusing a property that sorts first; ids unchanged for blocks that already loaded (test); load-time only. Union of 6247+6260+6270+6279 with main: Python tests 594 OK, stamp_native_hooks OK; verify green on b40d434d4.
Silverfish.getWalkTargetValue gives a position standing on an infested host block a value
of 10, so a silverfish wanders back toward stone rather than away from it. Horizon had no
silverfish case, so stone scored the same as any other floor and the goal was missing.
An item takes the tier of its block, through the same table as dynamic lights.
World items are marked as other entities are. The hand stays at the world's
brightness: its own pass writes the kept world's alpha, 1 where the hand drew and
the mark where it holds a light source, and HDR output reads that for hand pixels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A guest's mount events carried entity id 0: native_acting_entity looked the
acting guest up through the hub, which is often out of `lan` while the
guest acts. The guest's entity id now travels with the action
(lan_acting_entity), like its name, UUID and permission.
- A mob a boat or minecart picks up now asks native_mob_may_ride first, as
vanilla's pickups go through Entity.startRiding; a canceled one stays off.
- Boarding a cushion no longer seats a player whose dismount from the old
vehicle a mod canceled: startRiding stops when stopRiding left the
passenger seated.
Fixes#6365
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CTM overlays (OptiFine/Continuity docs only); my commit b41a21a6b skips the overlay pass for faces without overlays, so meshing without CTM costs the same as before. verify green on b41a21a6b.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CEM animation fixes, re-read at the new head (test tweak + main merge only); no-pack draw path unchanged. verify green on bf724b491.
Dolphin.tick hurts a dolphin whose moistness ran out with damageSources().dryOut(), so the
death message says it dried out; Horizon used the drown source, which reads as drowning.
native_projectile_impact's doc comment lists tridents among the projectiles
it covers, but a trident a player throws flies in tick_tridents, not
tick_projectiles, and never called the hook. tick_tridents now tells the
mods about its mob and block hits (the face from the block clip, which
clip_block_stop now returns), and a canceled one does nothing: no damage,
sound or deflection on a mob, and on a block the trident stops where it would
have hit without sticking, as the doc comment says of arrows.
Part of #6366 (the trident; the canceled arrow that still sticks needs a
decision on where a canceled arrow goes).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's PistonMovingBlockEntity.moveCollidedEntities (and
ShulkerBoxBlockEntity.moveCollidedEntities) move every entity from
getEntities except PushReaction.IGNORE_ENTITY, which only a marker
armor stand returns. push_targets covered players, mobs, items, boats,
carts, stands and TNT only.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Title::draw's button loop drew every title-screen label centred over its own
full width, so a label wider than the button ran over the buttons beside it
(Create World's 590 px World Type label in so_so, Multiplayer's Join Server in
hi_in). AbstractButton.extractDefaultLabel hands the label to
extractScrollingStringOverContents with a 2 px margin, and vanilla slides it
inside the button; the loop now draws through ui::scrolling_text, which is
that helper.
Dolphin.addAdditionalSaveData writes GotFish and Moistness; Horizon kept both in the mob
but wrote neither, so a fed dolphin forgot it was fed and a wet one came back from a reload
dry and drowned.
NativeLootContext gains `place`: the roll's origin (LootContextParams.ORIGIN),
the broken block's entity as it was (BLOCK_ENTITY, saved with its id,
position and Items) and the level (dimension, game time, time of day, rain,
thunder), for conditions like vanilla's time_check and weather_check. The
game sets it with `with_native_loot_place` around a player's and an
explosion's block drops and a mob's death loot; both loot engines put it in
the context. Other rolls carry None. API fragment `loot_context_place`.
Fixes#6200
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A submission or a rating is taken only from the site's own pages: its Origin
header, then the session and its CSRF token. The registry's README says the
request must come from REGISTRY_PUBLIC_URL's origin, and that is what a
browser sends: a scheme, a host and a port, never a path.
The gate compared the Origin to REGISTRY_PUBLIC_URL as whole strings, so a
public URL that carries a path (the form is a page below the site's root) or
another case in its host name turned the site's own page away with "send it
from the registry's pages" (403). The origin is now taken from the public
URL, case folded as a host name is, and the environment value is trimmed of
whitespace before it is used, as a pasted deploy variable may carry it.
Fixes#6159
hurtServer runs applyItemBlocking (the shield's knockback) before dealDefaultKnockback
(the hit's own), and knockback halves the old motion, so the order decides the direction.
Horizon made the hit's push first and the shield's second, which sent a half-blocked
defender toward the attacker at twice the speed.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Sprites::prefetch decodes block and CTM textures on every thread, inserts keep their order (test: same layers/pixels with and without); load-time only, no per-frame cost; includes #6254. verify green on 5543bffed.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): overlay build walks each folder's run of the sorted map and inflates zip entries on every thread (1686→691 ms cold); test checks every file of a 3-pack stack; pack-bench.yml is ubuntu-latest, contents: read, manual/push only. verify green on e0eb57399.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Villager.getAmbientSound sleeping/trading branches; main merged, test modules kept whole. verify green on 736fe7c62.
Distinguish a visible composited view from a hidden offscreen run. Preserve capture when only the viewport changes, and deliver raw mouse motion only to the keyboard/mouse owner. Add behavioral regressions for capture, real mouse rotation and native-window changes.
Co-Authored-By: OpenAI Codex <[email protected]>
The test looks for "\n}\n" to find the end of each loot hook's body in
loot.rs, read with include_str!. A Windows checkout with core.autocrlf has
the file in CRLF, so the search found nothing and the test panicked on every
branch there. It now normalises the line endings first, as the tests next to
it do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`/tag` reads a target's `Tags` out of `persist.raw` for eight kinds of target
(`tags_of`), and `raw_mut` -- the only writer, reached from `set_entity_tags`
-- spelled seven of them: `Target::Tnt` fell through to `_ => None`, so
`/tag add` and `/tag remove` on a primed TNT printed success and stored
nothing.
Vanilla has no such split: `TagCommand.addTag` calls `Entity.addTag` and
`removeTag` calls `Entity.removeTag` on whatever the selector returned.
Measured in a running client (creative world, `horizon-ctl`): before, `add
boom` left `Primed TNT has 1 tag(s): seeded` and `remove seeded` left it there
too; with the arm, `boom, seeded` then `boom`, and
`@e[type=minecraft:tnt,tag=boom]` matches.
Co-Authored-By: Hermes Agent (deepseek-v4.1-flash) <[email protected]>
#6246 picks a .jem's texture through cem_texture and paints_with_jem:
render_visible takes main's choice, and drawn_textures follows it (the
.jem texture and the vanilla one it falls back to are both listed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tick_player_menus built the open menu's name as a String and cloned the
last heard one for every player with a menu open, every tick. It now
compares borrowed fields and builds the owned menu only on a change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Stop timing after readback failure and do not unmap an unreadable buffer. Cover normal readback and destruction after a successful map callback.
Co-Authored-By: Codex (GPT-6) <[email protected]>
remote_player_names() built every player's name as Strings for each
player drawn, every frame, and native_mob_layer posed every mob in view
with no mod to ask. A direct lookup (remote_player_name) replaces the
list, and native_player_render_listened gates both hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mesher runs this for every face of every section: work out the face's
translucency and walk the overlay slots only when a rule gave one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractArrow.tick turns its aim 20% of the way to the flight direction each
tick, and vanilla draws the model between the last two ticks' rotations
(Entity.setOldPosAndRot, then Mth.lerp(partialTick, yRotO, yRot)). Horizon
kept only a previous position, so a turning arrow was drawn in whole-tick
steps of up to 36 degrees, a tick ahead of the position it was drawn at.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CEM texture fixes: a .jem texture paints its own layer only, atlas-missing texture falls back to vanilla, EMF folder precedence; no-pack path does less work (no texture clone); docs-only sources. verify green on 8a7c9259f.
SelectWorldScreen's world list takes keyboard focus and its rows answer Enter and Space;
in Horizon Tab went from the search box straight to the buttons, so the list could only
ever be used with a mouse and a keyboard player had no way to open a world.
ui::button, ui::button_as, ui::slider and ui::slider_ext clipped a label
wider than the widget by characters, so its end was unreadable ("Graphics
API: Prefer Vulkan", "Simulation Distance: 32 Chunk", "> Left Contro").
Vanilla slides it instead: AbstractWidget.extractScrollingStringOverContents
hands the label to ActiveTextCollector.defaultScrollingHelper, which draws it
left-aligned at left - (int) pos inside a scissor of the widget's box, pos
sliding over the overflow with a sine period of max(overflow * 0.5, 3) s.
The two ui.rs widget paths now draw through scrolling_text, which is that
helper; centered_text keeps its clipped centring for titles and text lines,
which vanilla does not scroll.
The host attaches every relayed session from its direct port, so each
attach to a relay reuses one address pair. The relay closes a finished
session first and keeps that pair in TIME_WAIT; a relay on Windows then
ignores the host's next attach from the same port (Linux takes it), the
connect times out instead of reporting the address in use, and
connect_from's fallback never runs. The second guest within a couple of
minutes got HostUnreachable.
connect_relay now gives the relay's addresses that timed out from the
direct port one more try from any port, after every address had its try
from the direct port (a dead IPv6 route doesn't eat the relay's attach
window twice). An attach from another port no longer offers what the relay
saw of it as a direct candidate: nothing listens there. The direct race's
dials keep their port.
Fixes#6342
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): HDR screenshot gains an ICC v2 profile (BT.2020, PQ to 203 nits) beside cICP; screenshot path only, no per-frame cost; unit tests on the profile. verify green on 4fa1a55b0.
Review points from #6280 (arismoko): an item merged into an earlier one
never ticked, so it is taken out of the round; a minecart's Post now
comes once the ticked cart is written back, so a mod reads and changes
the cart as it is. Unit test for the round.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): three stale tests fixed (armor stand first tick, TNT water and up bubble column); test-only. verify green on 9cabc6278.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): AbstractBoat.tick non-authoritative branch zeroes motion and deltaRotation: the boat stops on dismount. verify green on 9425bb9ac.
native_standalone_models(models) is asked once at start for the ids of
models a block entity or a mob draws as parts. Every resource load, a pack
reload included, bakes them beside the block models (StateModels::
bake_standalone; one a state already bakes unturned is shared, one that
doesn't read is logged and left out). native_draw_model(draw, model, tint)
draws one under a block entity's or mob's pose, as native_draw_block draws
a block's model. API from a fragment.
Step 1 of #6303 (the issue stays open for steps 2 to 4).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ItemEntity.ignoreExplosion via ServerExplosion.shouldAffectBlocklikeEntities (javap checked: griefing off + KEEP ignores items; no wind charges reach server_explode). verify green on 37fc39e48.
ClientPacketListener.handleConfigurationStart calls
Minecraft.clearClientLevel, which drops the client level there. Horizon's
Reconfiguring cleared the level state but kept heard_level, so
native_client_level_unload waited for the next login (after the new level's
load) or a disconnect, and a mod kept the old level's state through the
configuration screen, as on a proxy's server switch. Reconfiguring now takes
heard_level and fires the unload.
Fixes#6317
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handleRespawn keeps its ClientLevel only when the
spawn's level key is the current level's, and makes a new one otherwise.
Horizon compared the dimension type's name, so moving between two levels
sharing a type (a data pack or mod dimension using the Overworld's) kept the
old level's chunks and entities, and fired neither native_client_level_load
nor native_client_level_unload. It now compares the level key the client
last entered (heard_level), so such a move clears the world and fires both
hooks, with the right level named on the next unload.
Fixes#6316
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On every server, each tick, the mob and item rounds cloned each ticking
mob's type into a list and each boat built its type name, with no mod to
tell. A mod that hooks native_entity_tick_pre/post now also hooks
native_entity_tick_listened and sets it; otherwise no round is kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_blocks(blocks) and native_items(items), asked once at start on every
side, take horizon_mods::content::Block and Item: the declarations a content
mod's data/<id>/horizon/block/ and item/ files hold. They join the content
plan, inside the content mod whose id is their namespace when it is on, else
as a content mod with no files of its own (Content::declared), and pass the
files' checks (Content::declare, shared check_block and check_item), so their
state and network ids, saves and the joining client's content check are the
files'. A namespace whose declarations are refused is logged and left out.
The content root's stamp counts the declarations. API from a fragment.
Step 1 of #6300 (the issue stays open for its later steps).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
render_with's extra layers carry whether they glow since the emissive
change; two tests still built 4-tuples.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The per-tick comparison cloned every ticking mob's equipment each tick in a
game without mods. A mod that hooks native_living_equipment_change now
also hooks native_living_equipment_listened and sets it; otherwise nothing
is read or kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MoveTowardsTargetGoal.canUse asks DefaultRandomPos.getPosTowards for a random spot within
16 blocks and 7 up or down, a quarter turn off the target, and start walks to that spot.
Horizon navigated straight at the target's own position, so a golem always drove into the
entity it was after.
"Before you start" still said Horizon's client can't sign in to a Microsoft
account, so Horizon players could join offline servers only, and that it has
no chat session. Both stopped being true: the launcher signs players in
(docs/microsoft-account.md), the game joins online-mode servers, and since
#4375, #4376 and #4377 (Nightly 2026-11-01) it signs its chat.
- Online mode: every player joins with their own account, vanilla and
Horizon players alike.
- Offline servers: the same safety advice, and only players under an
offline name are limited to them.
- Secure chat: Horizon's client signs its chat too.
Refs #3332 (the website half is horizon-rust/horizon-site#38)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LocalPlayer.aiStep gates flight toggling with Abilities.mayfly; it does not
clear a server-supplied flying state when that permission is false. Stop
clearing flight on every movement tick so zero-speed server-forced flight
holds the player in place, matching vanilla 26.3.
Add a local-input regression and extend the resident movement parity harness
with independent flight flags, zero/default speeds and explicit flight end.
Validation: 39 movement tests pass; eight flight scenes match both vanilla
jars; player_travel and living_motion parity pass; focused Clippy passes;
the fast-profile game binary builds. Contributor confirmed the correction
on benchmark.ogulniega.com.
Full resident scene parity also exposes an unrelated attribute-cache mismatch
for negative fall damage, reproducible without movement/abilities operations.
Full pr_ready --quick is blocked by missing ALSA development files and an
existing Python block-entity fixture missing Entities.has_mods.
Co-Authored-By: Codex <[email protected]>
horizon-ctl save / save-all on a dedicated server, and closing a world
through the staged save, wrote the forced chunks but not the level's
attachments.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): EatBlockGoal.tick: destroyBlock and grass->dirt behind MOB_GRIEFING, Mob.ate outside (javap checked); two goal-driven tests. verify green on 7d7a73e84.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CrossbowItem.onUseTick start/mid flags (reset under 0.2, each plays once): a charge jumped by a mod still plays both; unit test. verify green on 00c7de376.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Piglin.finishConversion runs PiglinAi.cancelAdmiring first: the admired off-hand item drops; same ADMIRING_ITEM test as the activity picker. verify green on 7671485c3.
native_hears_projectile_impacts (same fragment): without a mod setting it,
tick_projectiles keeps no copy of each flying projectile (an arrow's clone is
a Box, two item stacks and a Vec) and the impact hook is not called, so a
server without such a mod pays nothing per tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Setup's controls from the full feature (#3541), on the Setup screen:
- drag a player's tile onto a window, a place among its players, or a monitor; dropped where
it was, nothing changes;
- while a window has two or three players, a square picks how they share it (saved in P1's
options: splitscreenTwo, splitscreenThree; the views follow it);
- Names (splitscreenNames, on by default): each name stays in its view's corner;
- Identify: every view says its window; tooltips on every button.
13 unit tests and the live scenario 'controls'. Controller pick-up comes next.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
46 of 78 named native_hooks while the method lives in another game
module; the generator found methods by name anywhere and checked the
target only against two module names. It now records each method's
module and requires the target to match it.
`Fox.aiStep` finishes the item in the mouth with `ItemStack.finishUsingItem`,
whose `Consumable.onConsume` runs the item's `on_consume_effects`: a chorus
fruit now teleports the fox. The chase is `TeleportRandomlyConsumeEffect`
over `Entity.randomTeleport`: 16 draws in a cube of the item's diameter, the
drawn height clamped to the level, ground found by dropping to the first
motion-blocking block, free space without liquid, and not a
`#consumable_does_not_teleport_to` block; then the fox's own teleport sound,
the portal trail (level event 2017) and a reset fall distance.
`LivingEntity.checkTotemDeathProtection` was ported for players only, so a
fox holding a totem of undying died with it. `Mob::damage` now runs the same
rule for every mob: the first hand with the item, the item used up, health
set to 1, `DeathProtection.TOTEM_EFFECTS` applied and entity event 35
broadcast for the clients' totem particles and sound. A blow in
`#bypasses_invulnerability` (`/kill`, the void) still ignores it.
Fixes#5839
Co-Authored-By: DeepSeek v4.1 Flash <[email protected]>
mobs/ai.rs: #6218 and #6228 landed test modules at the same place; each
module kept whole beside main's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): drowned trident uncertainty from difficulty (RangedAttackMob; Drowned field is -1). verify green on 3217c6759.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Creeper.getMaxFallDistance health-1 branch (javap checked); test skips without vanilla data. verify green on 4d31db99b.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): SpawnEggItem.useOn: a clicked block without collision takes the mob (bytecode checked). verify green on cbb7efe89.
native_data_map_types registers a mod's data map types for items and blocks.
Their values come from data/<mod>/data_maps/<registry>/<name>.json in the
world's data packs and from files a mod pushes through native_data_pack_files,
merged each time the server's tables are built: replace, values (entries and
#tags) and remove, a newer value overriding an older. native_data_map and
native_data_map_entries read them. Fixes#6312.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Translucent Batching's vertex and sorted-index pages grow up to about 2 GiB
and never shrank, so a page kept its high-water size after the water that
filled it was gone (an ocean left behind, a lower render distance). Now a
page of 64 MiB or more that a right-sized page (grown(used)) would halve,
or make 256 MiB smaller, gets that page in video memory and is closed:
#3466's move_off_page empties it into the new page within the same copy
budget as the section arena's compaction (32 MiB a frame, 8 MiB for sorted
indices), and trim drops it. Each mesh's pointer switches in the step that
records its copy. With no room for the new page, or a range no mesh owns,
the page stays as it is and the next try waits 5 s.
A shrink starts only a second after terrain stops arriving, in a frame the
section arena's compaction copied nothing (ShrinkGate). The game reports
terrain arriving: the view's outermost columns not all there yet
(ViewArrival, capped at the server's view distance and given up 30 s after
the view last changed), events the server sent that wait to be handled, far
tiles being sampled, and, for those 30 s, re-meshes and pending uploads. The
renderer also waits while translucent sections or far tiles wait to upload.
A shrink under way finishes. move_off_page always moves the first range it
is given, so a range larger than the budget no longer keeps a page closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Heard when a creative player sets a slot (or throws a stack out) from the
creative screen, after vanilla's checks, cancellable, with the player's
state swapped in.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Heard when a server player is about to open one of the game's own menus
(chest, furnace, crafting table, villager, station, lectern, horse
inventory), cancellable, with the player's state swapped in.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Each GPU test made a wgpu instance with every backend, so up to one thread
per core opened Vulkan, DirectX 12 and WGL at once while finished tests'
threads exited. On Windows with an NVIDIA driver the render lib tests hung
most runs (8 of 10 here, RTX 4070; #5238). The tests now take their
instance from gfx::test_gpu, which on Windows opens Vulkan and DirectX 12
only, as the game prefers there; WGPU_BACKEND still overrides. Linux and
macOS are unchanged.
Fixes#5238
Co-Authored-By: Claude Opus 5.5 <[email protected]>
It returned the decode error and left the value in place, so a corrupt
saved value could never be removed. It is removed now, and the error
still returned.
While any entity had an attachment, prune_entity_memory ran every tick,
and it forgot the UUIDs of entities outside its list (projectiles, TNT,
orbs), which then got a new random UUID each tick. The tick now drops
only the attachments of entities that left, by the same check that
admits an entity as a holder.
Review fix: `Streaming::loading` is also the field that says the loading
screen is up; without the parentheses the far terrain would still compile
and follow the wrong one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): owner console reaches native registered commands; Python hook tests pass merged with main. verify green on c73e087d7.
LivingEntity.travelInFluid lifts a mount that carries a passenger, is in
minecraft:can_float_while_ridden and is deep enough in the water. Horizon's
mob travel skipped that step, so a horse with a player on it only sank with
its rider.
[mod] version = "1" (as the fixtures write it) failed the strict
semver parse when another mod depended on it; missing parts now count
as zero, as in Cargo's requirements.
It ran the check only for xtask/, native-hooks.json and native_hooks.rs,
while the generator also reads every game file, mod_api.rs, loot.rs,
item_defaults.rs and native_render.rs.
Guests' attachments lived in their hub records, which a guest's turn
can't reach (as_guest has the hub out of self.lan), so Holder::Player
for anyone but the acting player failed in player_tick, use_item_on,
interact_entity and the other guest-turn hooks. They now live on the
server Game by UUID from join to leave, and go back into the record
only for playerdata to save.
Review fixes: on the server thread the near terrain can start and stop
loading between chunk batches. Each start dropped the queue and rebuilt
the whole wanted heap; the dropped tiles now go back into it where they
were. A sample still running when the hold starts no longer lets the
next one start at once. The queue test no longer moves the list it
checks later.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every save wrote <dim>/data/minecraft/horizon_attachments.dat, with mod
data or without, and a malformed file stopped the world or dimension
from opening. The file is now data/horizon/attachments.dat, written
only once a level has attachments, and a file that doesn't read is
moved aside with a warning while the level opens without them.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): status and End flash uniforms for shader packs, from the public uniform reference. verify green on 7745fddd2.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): client LevelEvent hooks, empty bodies; Python hook tests pass merged with main. verify green on ba04f5032.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): four shader translator fixes (bool-shadowing part removed: LGPL source); horizon-shaders tests in verify. verify green on 6e554aaa1.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): worn dragon head jaw follows wornHeadAnimationPos incl. the vehicle's positionScale; supersedes #6166. verify green on f5161d9eb.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): beacon update tag keeps the client's beam (loadAdditional reads only powers); unit test. verify green on 2d5ee61a5.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ARM Linux CPU name from implementer/part; unit tests on real cpuinfo shapes. verify green on ce02c0329.
Heard once a tick for each boat or minecart that carries a server player
(and for ten seconds after one a player left stood still), after it
moved, with the rider's state swapped in.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): random_ticks for content blocks; registry lockfile refresh; hook/mod Python tests pass merged with main. verify green on 5d08602df.
NativeDispensed, NativeServerPlayer, NativeCommand, the lifecycle types,
NativeZoom, NativeLootDrops and NativeScreenView moved into mod_api and
were imported back privately, so mixin code naming them at their old
paths hit E0603. They are re-exported there with their old visibility.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): mod block state property sorting before the template's is refused with a clear error (message whitespace fixed). verify green on dc1597e11.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): server LevelEvent load/unload hooks, empty bodies, one unload per load; hook/mod Python tests pass on the head merged with main. verify green on c60b4c8d4.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Superflat grass icon uses the grass colormap (GrassEnv), as the inventory does. verify green on b3384f9bd.
main_with installed the mod list after logging, the crash handler and
the RAM limit, and an injection's helper indexed the list, so an
injection into any of those panicked in a release build. The list is
now installed first, and the helper does nothing when its mod is
absent.
Only horizon-mixins staged a bundle's data/ into native-data/<id>/, and
a bundle with no mixin content never reaches it, so native_data! did
not compile and native_built_data was empty. The launcher now stages
those bundles' data/ where horizon-mixins would.
The launcher's generated package owns the horizon-client binary but had
no build script, so on Windows a build with a trait mod fell back to a
1 MB main-thread stack and lost the icon. It is now staged beside
horizon-client and builds with that crate's build.rs, which finds
packaging/ where it looks.
Review fixes: the dropped tiles must come back best first, and larger
machines keep the first SAMPLE_JOBS of the queue. retiring_detail gets its
doc comment back, and docs/graphics.md says one sampling thread below 8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_item_fluid_tank makes a mod item a fluid container with a capacity;
its contents are kept in the stack's custom_data under horizon_fluid.
native_item_fluid reads one item of a stack, native_item_fluid_fill and
native_item_fluid_drain answer how much it takes or gives and the single item
it becomes. Vanilla's buckets are built in: empty, water and lava, a whole
bucket or nothing. Fixes#6278.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The inventory's own player in first person is drawn only by the preview,
so a preview stores when the world didn't draw the entity this frame or
the last. Shared variables are read as each entity's frame began, like
its own, so drawing it again shows the same value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BlockEntities::insert dropped the position's attachments on every
call, so a jukebox taking a disc, a lectern taking a book or
/data merge block lost them, where vanilla keeps the block entity.
A replacement of the same type (the same id for a mod's) now keeps
them; any other replacement, and a removal, still starts them over.
Alt + numeric keypad digits make Windows send a WM_CHAR after the Alt is released, which winit
turns into a key event with no physical key but with the character as text. Every screen's key
handler reads the key code first and returned, so the character was lost. GLFW's char callback
types it; the event now goes straight to the focused text field.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review fixes:
- When the near terrain starts loading again, the queued sample jobs
past the held budget are dropped (they come back once it is in): with
32 a thread queued, the hold otherwise waited for the whole queue.
- `Streaming::loading` counts neighbours' light views, as `busy` does.
- Without sampling threads, the far worker takes summaries and detail
requests before the queued samples.
- One filter for the columns that take generated detail; the status pair
is `detail_target_columns` / `refined_target_columns`.
- docs/graphics.md: the sampling and decoration threads and the hold as
they are now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stamp tool dropped a fragment hook's mod_api contract, and replaced
markers only in native_hooks.rs and a fragment's comments files, while
mod_api.rs and the generated files copy hook and helper comments with
their markers. After a stamp the generated files were stale and
xtask mod-api --check failed. Now every marker is also stamped in
mod_api.rs and the four generated files.
`EatBlockGoal.tick` gates both the `destroyBlock` of an edible block and the
grass block's `levelEvent(2001)` + `setBlock(DIRT)` behind `MOB_GRIEFING`, and
keeps `Mob.ate` outside that gate. Horizon pushed the two `MobEvent`s
unconditionally, so a sheep on a grass block with the rule off turned it to
dirt: measured in a running client, 0 -> 1 dirt blocks at 41.6 s of a 150 s
window.
`MobWorld.mob_griefing` already carries the rule (it gates an allay's pickup,
the dragon's block breaking, blasts and a mob's projectiles); this goal was the
outlier. The test world gains `with_built_test_world_rules` so the flag is
reachable from a test, and the new cases tick a sheep for 6000 ticks through
`mobs::ai::tick` with the rule on and off, one per half of the gate: dirt
events 4 on and 0 off with `EntityEvent` 10 in both, and the short grass under
its feet destroyed on, untouched off. Inverting the gate fails both.
Co-Authored-By: Hermes Agent (deepseek-v4.1-flash) <[email protected]>
native_living_breathe(entity, kind, can_breathe, consume, refill) is called
every tick for a player (on the server's copy) and for a mob with the
generic air logic. Horizon's own answers come in as the defaults and a mod
may change them; the air arithmetic moves to a new breathing module, which
is a free function hook like the loot and menu ones, because the player's
motion and the mob tick have no Game.
Fixes#6248.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
AgeableMob.isBaby is canBeABaby() && getAge() < 0, so a vanilla client
ignores DATA_BABY_ID on a frog, parrot, slime, magma cube, camel husk or
zombie nautilus. The client now does too (drops::can_be_baby). The test
gives each type a model, which MobTypes::load needs, and checks a parrot.
baby_index reuses doors::is_zombie.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tags_of answered nothing for a server's player (Target::Guest) and raw_mut had no compound to
write to, so tag <player> add <tag> was dropped and a tag= selector never matched. A guest's tags
live in the saved player compound its file is written from (Guest.saved).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
native_map_decorations(map, decorations) is asked each time a map is drawn
(held, in an item frame, on the cartography table's screen); a mod pushes
NativeMapDecoration icons that are drawn after the vanilla ones with the same
pose. Typed decorations saved in the map need code-side registration and are
the later step.
Fixes#6269.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
LivingEntity.baseTick stops a rider whose eyes are in water (not in a bubble column) from riding a
vehicle of the dismounts_underwater tag: camel, chicken, donkey, happy ghast, horse, llama, mule,
pig, ravager, spider, strider, trader llama and zombie horse. Horizon only did it for boats. The
window's player and a server's player (through the same tick as the mount's input) and the mobs
that ride a mob (a baby zombie on a chicken) now get off; a skeleton horse keeps its rider.
tools/test_mount_submerged_parity.py compares 14 mounts with a vanilla server, passenger packet by
passenger packet.
Fixes#6152
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
bounding_box_gizmos went through every loaded block entity each frame to find the structure
and test instance blocks whose box it may draw. BlockEntities now keeps their positions
(like the mod block entities), and the frame looks at those only. A sample of an RD 32
mountain flight on an M4 Pro spent 0.12 ms of every frame in that scan.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Split::about_to_wait listed the monitors every frame, splitscreen on or off. On macOS each
listing is a round trip to the window server (SLSGetActiveDisplayList): a sample of an RD 32
flight on an M4 Pro spent 0.1-0.3 ms of every frame there, 2-3 % of the main thread.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
baby_index listed the AgeableMob kinds by hand and missed eight of them,
the happy ghast and the nautilus among them. A server's client never read
their DATA_BABY_ID (16), and this game's own server never sent it, so a
hatched dried ghast was drawn and boxed as an adult (#6133). The index now
comes from finalize::ageable, which lists every vanilla AgeableMob, plus
the zombies, the zoglin and the piglin.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_block_entity_energy declares a mod block's energy buffer as
SimpleEnergyHandler is built: capacity, the most one insert and one extract
moves, and the faces a fill may come through and a drain may leave by. The
amount is kept in the block entity's saved compound as the int `energy`.
native_energy reads it through a side, native_energy_insert and
native_energy_extract move energy (with `simulate`), and native_move_energy
is EnergyHandlerUtil.move between two blocks. Fixes#6227.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
horizon-bots --capture FILE records the first bot's play packets; the
horizon-protocol example net_replay sends them through an in-memory
connection as single player, an offline server and an online-mode server
send them, and prints the CPU of ConnReader::recv, of decoding per packet
type and of parsing chunk sections.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Piglin.finishConversion calls PiglinAi.cancelAdmiring before it converts, which spawns the
off-hand item the piglin was admiring and clears the hand. Horizon copied the whole off hand
to the new zombified piglin, so the item that should have been on the ground travelled with it.
Add native_entity_mount, asked before a player or a mob gets on or off a
boat, minecart, cushion or mount (including /ride, stabs, bucks, teleports),
on the server thread and a dedicated server. A mod can cancel it.
Fixes#6273.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
AbstractBoat.tick drops a boat's cached motion on the side that does not drive
it, so the level takes a boat over at a standstill when its rider leaves.
Horizon kept the velocity of the ride, and the empty boat went on gliding.
Match vanilla 26.3 ClientPacketListener.handleGameEvent by playing the arrow hit player sound on event 6 at eye height, the pufferfish sting sound on event 9 at entity position, and updating do_limited_crafting on event 12.
Co-Authored-By: Antigravity <[email protected]>
- A layer takes its .jem's texture only when it is the first layer drawn on that model
(and not on the body's): the wolf armour does, the cracks over it on wolf_armor#main
keep theirs. One helper picks the .jem texture when the atlas has it, for the body
and the layers, without cloning a String first.
- Fallbacks follow chains up to three steps, babies included: cat_baby#collar takes
cat.jem's geometry (not its texture) through cat_collar.
- jem_files keeps every file of a name, EMF's first; each layer tries its candidates in
order (its own names by name, then its fallbacks, nearest first) and a file that fails
to read or parse gives way to the next. Each file is parsed at most once, and only
when a layer tries it.
- Warn once at load when a .jem's texture names a file missing from the pack root.
- Drop the `over` test wrapper and the bool of fallback_names (a fallback onto another
layer is geometry-only); LAYERS and FALLBACKS are slices; the "first by name" rule is
written once.
- Tests: a sheep's wool draws in its sheep_wool.jem texture; a wolf armour .jem paints
the armour but not its cracks; cat_baby#collar from cat.jem; a broken llama_decor.jem
lets trader_llama_decor.jem apply, a broken EMF file lets OptiFine's. The ignored
real-data test also checks both columns of FALLBACKS and every baby layer, and says
its part names are a sample.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An overlay over a face whose picked tile is translucent goes in the
translucent pass with it. The connectTiles test now resolves the sprite
name through build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_player_submit draws a player with a mod's own model (other players and
the window's player in third person); native_mob_layer adds geometry to every
mob and player after the vanilla model, in the same feet-and-body frame
native_mob_submit uses.
Fixes#6257.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Every received byte on an online-mode server went through a byte-wise
software AES (~450 ns a byte, about 2 MB/s for a whole core). CFB8 decryption
knows each byte's register up front (the ciphertext before it), so runs of
bytes are encrypted together: ARMv8 AES instructions on Apple Silicon (a small
allow(unsafe_code) module for the intrinsics), the RustCrypto aes crate
(MIT OR Apache-2.0, AES-NI picked at run time) elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 2 review: the first-sight and new-scan check clears an answered miss like the
regular check does; docs say a miss is answered once between checks; the test goes on
to the next regular check and a miss answered after it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proof: the official server's boat false positives (1357 claims at y 63.0857639 = a floating boat's top, isInferno banned twice) come from LanHost.states being the last level's entities when moves are read; states now park with their level. pr_ready --quick: fmt, clippy pass; the one lib test failure (armor stand, outside this change) passed in CI verify.
The registry is its own workspace, and its workflow runs on any change under
crates/horizon-mods. Its lockfile lacks png (and fdeflate, miniz_oxide), which
horizon-mods needs, so `cargo clippy --locked` refuses it and `check` fails on
every PR that touches horizon-mods.
Fixes#6196
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_entity_tick_pre and native_entity_tick_post for the mobs, item
entities, minecarts and boats that tick in an entity-ticking chunk, on a
server. Set canceled in the Pre and the entity does not tick this tick, and
its Post is not told. Fixes#6249.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- Within a frame every run starts from the frame's starting variables; only the
frame's first run outside a GUI preview stores them, and global_var /
global_varb are stored only by such a run.
- is_right_handed reads Mob::main_arm_left (the player's main hand option too).
- Variables refuse infinity as well as NaN and keep their old value.
- The saved table stays bounded: cleared if still at twice SAVED after eviction.
- is_in_gui is a thread-local set by in_gui() with a drop guard, no longer a
Frame field; parts write back only changed visibility and new skip_draws;
one lock() helper; one entry() per run for the entity's variables.
- Tests: non-finite variables keep 7, the client's synced flags and main arm,
the 4096-expression cap, and a preview not changing the next frame's value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
layer=cutout stays a cutout whatever the tile's alpha, but an overlay on a
translucent face goes in the translucent pass with it; connectBlocks and
connectTiles count only for method=overlay, as documented; a tintBlock the
game lacks tints nothing; connectBlocks looks states up by block. Also
fixes two compile errors the first overlay commit had.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_living_equipment_change for mobs and armor stands: each tick, a slot
that holds another stack than the mods last heard is told, with the slot's
name and the stacks before and after. Mob::equipment_stacks gives the slots
(and the equipment sent to clients now reads it). Fixes#6242.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ItemEntity.ignoreExplosion asks ServerExplosion.shouldAffectBlocklikeEntities, so with
mobGriefing off a blast that keeps blocks (a creeper's or a ghast's) never touches a
dropped item. Horizon hurt and pushed every item inside the blast whatever the rule.
CrossbowItem.onUseTick keeps a flag for the start and the middle sound and
plays each once the charge reaches its point, so a charge a mod's tick moved
past both points in one tick plays both. Horizon compared the charge with the
tick before it (`before < 0.2 && now >= 0.2`), so the sounds a jump passed
were skipped; only the end sound played.
The charge's two flags now live on the game beside `using` and are cleared
when a use starts and while the charge is under the first point, which is
vanilla's rule. `charge_sounds` holds it as a function of its own, with a test
for the jump, the ordinary crossing, a second charge and Quick Charge.
native_gui_layer_pre(layer, batch, canceled) and native_gui_layer_post(layer,
batch) fire around each vanilla HUD layer Horizon draws, so a mod draws below
or above a layer, or hides it. The jump/locator/experience bar is drawn as one
layer, and the mount's jump bar and the title and action bar are split out of
the passes that drew them together.
Fixes#6243.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The lift came from the finished pixel, after the block's light and face shading:
a soul lantern (light 10) lifted less of its glass than a lantern, and on two of
its sides less than on the other two. The shaders that mark the frame now work
the lift out from the texel before lighting, with the tier windows from the
globals, and write it into the mark's alpha: a band per tier, the amount within
it. HDR output reads it back and applies the gain to the lit pixel as before, so
a dimmer light still shows dimmer, but shines alike on every face.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
WitherSkull.getBlockExplosionResistance reports min(0.8, resistance) for a block
WitherBoss.canDestroy, so a blue skull's blast tears through blast-resistant blocks.
Horizon used each block's own resistance, so a blue skull left obsidian-like blocks standing.
ServerGamePacketListenerImpl's clientVehicleIsFloating and
aboveGroundVehicleTickCount: a vehicle move that doesn't go down, by a vehicle
that hadn't come down onto something, with nothing but air around it (water
holds a boat up), floats; a driver whose vehicle floats for more than
getMaximumFlyingTicks of the vehicle (160 ticks for a boat, 80 for a horse) is
kicked with "multiplayer.disconnect.flying", unless the server allows flight.
The server had no check on vehicle moves beyond moved too quickly and moved
wrongly, so a boat flying over land was never stopped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_player_container_open and native_player_container_close, per player
and per menu, with the menu type's name and a block menu's position. Any menu
a server player opens: containers, furnaces, crafting tables, trades,
workstations, lecterns, mounts, a mod's own. Fixes#6188.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
FireworkRocketItem.useOn and, while gliding, FireworkRocketItem.use
return SUCCESS (SwingSource.PREDICTED) on the client too: vanilla's
startUseItem swings the hand itself and stops, and a vanilla server
swings it for the other players only. A server's client never swung for
either use, and a rocket used on a block also sent UseItem and gave the
off hand its turn (a flint and steel there lit the block too, #2216).
The use on a block passes while gliding (the use boosts instead) and in
adventure mode (ItemStack.useOn).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A rocket attached to a server client's own player followed it and
boosted it in the entity tick, before the player's own move that tick,
so its spark spawned where the player (and the camera) had just been and
was never in view: an elytra boost left no trail. In vanilla the player
ticks first (ClientLevel.tickEntities); the rocket then boosts the glide,
sits at the rocket hand (getHandHoldingItemAngle, half a block out) and
adds its spark there. tick_own_rockets now does that after the player's
tick; rockets of other entities and free ones tick as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes: overlays count against the atlas budget only after every variant; the
suffix is read from the first file that sets it, Latin-1 too, and never holds a path
separator or ..; docs say what is paired and drawn; tests for a layer's glow, a
variant's own overlay (and none from the base), the atlas packing it, and where the
suffix is read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_player_start_tracking and native_player_stop_tracking: the hub's
per-client Tracker keeps what started and stopped, with the entity's ID, UUID
and type, from every place that changes the set (the tick's range checks, a
forgotten or cleared set, a leaving player), and the game tells the mods each
tick. Fixes#6231.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
CI's verify has no extracted data, so with_test_world doesn't run the
closure there; the other mob tests ignore its result the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_projectile_impact for each impact tick_projectiles finds, with the
projectile's id, type and owner and the entity or block it hit. A canceled
impact flies the tick again through what it hit (Projectile::tick_through,
arrows::tick_through): nothing of the hit happens and the projectile flies on.
Fixes#6203.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Cutting both axes at every crossing keeps the pieces on the quad's
diagonal square, so a split face shades exactly as the whole one does.
Tints::get and the biome rules share Tints::biome_id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Players' worn armour reaches render_visible as extra layers (rects, no names):
armor_layers now adds each layer's _e overlay as a full-bright extra layer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A .jem's texture paints only its own layer; overlays drawn on the body's
model (collars, eyes, villager type/profession, armour cracks) keep theirs.
- A .jem texture the atlas lacks falls back to the vanilla one instead of
hiding the mob; a texture outside assets/minecraft/textures/ is logged.
- emf/cem beats optifine/cem in both forms, the flat file beats the folder
form, a directory named x.jem is no file; scan is sorted.
- Two names of one layer (llama decor, undead horse armor): first by name.
- Fallbacks salmon_small/large -> salmon, cat_collar -> cat (geometry only),
player_slim -> player; nautilus_baby gets none.
- Small pufferfish fins and rabbit hind feet map to Horizon's part names.
- Files no layer uses aren't parsed; no CEM files: no vanilla map built.
- Reuse render::find_part; docs list custom entity models and mobs/cem.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block's states were its template's states times the declared combinations,
the template's properties varying slowest, but StateTable::new reads a state
table as vanilla's StateDefinition does: every property sorted by name, the
first name slowest. A declared name that sorts before one of the template's
(`bites` before `facing` on carved_pumpkin) stopped the game at load.
The states are now laid out in that order, with one lookup from a template
state and a declared combination to the state's row, used for the template
state's new id (the default state included), the collision shape fan-out and
the cached shapes. Declared names that sort after the template's lay out as
before, so those blocks' ids do not move. The refusal this replaces is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: server-tps-1: creaking heart scan follows chunk arrivals; tick_block_entities −0.45 ms/tick on the 50-bot bench (A/B alternated), test passes; merging once CI is green.
RecipeBookComponent puts the magnifier icon's clickable rectangle from eight pixels past the
book's left edge to the search box's own left edge, 25 pixels wide. Horizon used 17, so the
last eight pixels of the magnifier did not focus the search box.
native_interaction_key(button, key_mapping, hand, swing_hand, canceled) fires
before the attack, use and pick-block keys act on the crosshair, for a single
player's window and a server's player. A cancel skips the action; a cancelled
attack or use still swings unless the mod clears swing_hand.
Fixes#6213.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MerchantScreen.mouseScrolled moves scrollOff by (int)(scrollOff - scrollY), so one event
with a two-notch delta moves two rows. Horizon moved exactly one row per event whatever
the delta was.
CreateFlatWorldScreen.DetailsList.deleteLayer selects the row at the same visual position
after the removal, or the last row when the last one went. Horizon deselected everything,
so deleting a layer left the screen with no row highlighted.
Review fixes: the atlas budget counts variants beside their texture too; the body's
custom-model key is one helper for render_visible and drawn_textures; a texture that
keeps missing makes the mob choose at most once between regular checks; select's doc
points to matching; tests for the budget, a layer following the body's miss, and a
render assert that can't pass on nothing drawn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
They only run with prepared resources, so they went stale unseen. The armor stand test expects a fall on tick 1, where a summoned stand starts moving on tick 2; the two TNT tests set up the wrong scene.
Fixes#6235
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
- A part's visible / visible_boxes start from what vanilla set and are written back
only when an animation changes them; visible_boxes 1 keeps vanilla's skipDraw.
- One const Frame default (first person); mob_geometry fills paused, first and
third person; GUI previews (inventory player, mount box) set is_in_gui.
- A second draw of an entity in one frame starts from that frame's starting
variables; a full table forgets only entities not drawn for 600 frames.
- global_var / global_varb live in one process-wide registry shared by all entities.
- is_aggressive, is_right_handed, is_sitting, is_tamed, is_sprinting, is_sneaking
read the synced mob fields.
- Non-finite part values keep the vanilla pose; NaN never lands in a variable.
- At most 4096 expressions per model; FRAME is read once per run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cut a ctm_compact face where its UVs cross 0.5, not at its middle, so a
dirt path's side or a slab's gets its quarters in the right place (a slab
side isn't cut across at all); no allocation per face. The blend test now
uses corners only the triangle split gets right.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
screen_draw::button now goes extractDefaultLabel ->
scrolling_string_over_contents (AbstractWidget.extractScrollingStringOverContents,
margin 2) -> scrolling_with_default_center
(ActiveTextCollector.acceptScrollingWithDefaultCenter) -> scrolling_text
(defaultScrollingHelper), as vanilla does. Same draws; it closes the
callee gap the port ledger opened on button (extractDefaultLabel without
a port of extractScrollingStringOverContents). Left open: the callee gap
on AbstractWidget.getWidth, a trivial accessor passed in as `w`.
the_offset_follows_the_vanilla_period_and_easing ended with a hash over
2.4M offsets from a Java transcription kept outside the repository: nobody
could regenerate it, and it checked a copy of the formula, not vanilla's
code. The pack screen harness now runs vanilla's own defaultScrollingHelper
at fixed clocks instead; the hand-checkable spot values stay.
scrolling_text says it must not run inside another Draw::Scissor (paint
replaces scissors instead of intersecting them; no caller does today), and
the clippy too_many_arguments allows go: the workspace allows it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
It came from reading an LGPL pack's source (Pixel Perfect), which
CONTRIBUTING puts off limits even to read. The other four fixes rest on
the GLSL specification and an MIT pack, and stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fifth of the cases now run in another language (the eight whose "Open
Pack Folder" is wider than its button, plus fr_fr and de_de, which fit)
at a fixed Util.getMillis. The oracle injects the language (en_us, then
the case's file from the prepared assets), sets Util's time source to the
case's clock, and records a text's own scissor (defaultScrollingHelper's
withScissor) as a scissor around it, the way Horizon draws it. The fixture
loads the same language before it lays the screen out.
3000/3000 match, 427 of them a scrolling footer label (offsets 0 to -25
px). With the old clamp in screen_draw::button the fixture panics
("min > max"), so the harness now catches #6129. The outputs are read as
UTF-8: the Macedonian labels broke cp1252 decoding on Windows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Witch.registerGoals adds HurtByTargetGoal(this, Raider.class), so a raider that damages a
witch is never taken as its target; Horizon used a plain HurtByTarget, so raid friendly fire
made the witch fight the raid.
Render inline atlas sprites and player heads in the text shadow pass instead
of skipping them. Use the text tint, shadow colour and one-pixel offset,
vanilla's sprite baseline, and foreground depth for world text.
Reference: Minecraft 26.3 Font.PreparedTextBuilder, PlainTextRenderable.render
and AtlasGlyphProvider.Instance.renderSprite, inspected from the pinned jar.
Add a vanilla oracle and Rust probe covering 96 sprite geometry/colour cases,
plus regression coverage for inline player head shadows.
Validation: inline_object parity passes three consecutive runs; 27 HUD tests
pass; renderer/client probe Clippy passes with -D warnings and no default
features; rustfmt and public-tree checks pass.
Full pr_ready --quick remains blocked by missing ALSA development libraries,
mixed system Rust/Clippy toolchains, and an existing Python fixture missing
Entities.has_mods (test_mod_block_entity_any_template).
Co-Authored-By: Codex <[email protected]>
The message carried the source's indentation between "the" and "game";
the test now checks the block's id and the single spaces.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The owner console (horizon-ctl command, the MCP command tool) ran every
line through the vanilla dispatcher, so a command a native mod registered
answered "Unknown or incomplete command". It now offers a registered name
to native_perform_command first, as the owner at level 4, the way the
owner's own chat does. The dedicated console was done in #5815.
Fixes#5682.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Endermite.registerGoals is the silverfish-style monster list: powder-snow climbing, a
melee attack, a stroll at 1.0, and a HurtByTargetGoal that alerts the others. Horizon
never put the endermite in the hostile family, so it fell back to the generic monster
block: stroll at 0.8, no powder-snow climb, and a hurt-by-target goal that never alerts.
#6126 warps the pointer before the Locked grab on Windows (#6076), so a
focused grab warps twice there. The fixture still expected one warp and
failed on Windows (CI's Linux run kept the old count). It now expects two
on Windows, one elsewhere, which also pins #6126's warp.
Fixes#6214
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A movement scan walks every cell of the box the movement expands to, so a
diagonal move covers about N x N of them. The client passes a 1,000,000
candidate budget, which a legal `movement_speed` of 400 passes within a
second of running: the scan then ended in `VisitBudget` and the game
aborted with "collision scan candidate budget exhausted" (#6124).
Vanilla walks the same box with no counter at all, so the movement is slow
there, never fatal. The budget now only has to cover the cells the scan can
visit (the sweep, and the step's own box raised by max_step), which leaves
every ordinary movement identical and makes the pathological one stall
instead of exiting.
`scan_cells` shares the cursor arithmetic with `BlockCollisionScan::new`
rather than repeating it.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Entity.processFlappingMovement emits a flap game event when the entity flaps over air;
Bee and Bat each override isFlapping, a bee every other tick while flying and a bat every
tenth tick while it is not resting. Horizon only modelled the bee, so a bat flying around
was silent to sculk sensors and a warden.
native_client_level_load when the client makes a level (joining a server, a
dimension change), native_client_level_unload when it drops one (replaced,
told after the new one's load as Minecraft.setLevel does, or leaving the
server). The client half of #6154.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
old_world_e2e asked horizon-client-lib for the horizon-client binary, which
only horizon-client has; three e2e docstrings, the agent-control guide and
xtask's comment still built horizon-ctl from horizon-client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nautilus.canBeLeashed and ZombieNautilus.canBeLeashed return false while
AbstractNautilus.isAggravated, which is the brain holding ANGRY_AT or ATTACK_TARGET.
Horizon let a nautilus be leashed at any time, so a lead could drag off a nautilus in
the middle of a charge.
The registry is its own workspace, and its workflow runs on any change under
crates/horizon-mods. Its lockfile on main lacks png (and fdeflate, miniz_oxide),
which horizon-mods now needs, so `cargo clippy --locked` refuses it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ports main's lib.rs (block_edit, cit, lod_net, ram_limit, social_screen) into
client_lib.rs, main.rs's --player-dir logging and ram_limit into entry.rs, and
the horizon-optifine dependency into horizon-client-lib.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: test-only fix for command_exec on main: the boxed-in orb case (#5646) depended on vanilla's unseeded orb random; a stone ring keeps the top the only free side. verify + parity (command_exec PASS on the merge) green.
WanderingTrader.addAdditionalSaveData writes DespawnDelay and the wander target; Horizon
wrote neither, so a trader reloaded mid-visit forgot its countdown and started over, and
came back with nothing to walk to.
Villager.getAmbientSound returns null while the villager sleeps and the trade sound
while a player has its menu open. Horizon always played the plain ambient, so a sleeping
villager talked in its bed and a villager in a trade screen never made the trade sound.
🤖 Agent: fixes#6009, native builds use a real ASCII folder in ProgramData instead of the 8.3 short name that rustc expands back to the Cyrillic path; Cyrillic unit test, clippy for x86_64-pc-windows-gnu and pr_ready --quick green. Not run on a real Windows machine.
A test that fire in the air beside a block stays while its ignite odds are
above zero and that burn odds of zero never destroy it (round-1 review:
the fire test only reached burning out, and a swapped tuple went unseen).
native_fire_odds says to answer only for your own blocks and to keep the
odds within vanilla's 0 to 100; the fragment's summary names the campfire.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes found reviewing #5744 after it merged:
- no rule matching, and ETF's 0, draw the texture itself, not <name>1.png
- choose walks the textures render_visible draws: a .jem's own texture and the
warden's glowing layers with their own rules get picks
- rechecks count this window's ticks, so a frozen world (game_time stopped) still
rechecks every mob once a second instead of one in 20 every tick
- a texture drawn with rules and no pick (a wolf just tamed) chooses on the next tick
- a scan's generation replaces the stale flag: every splitscreen player's mobs choose
again after a reload
- variant images past a 4096x2048-texel budget stay out of the atlas instead of
failing the whole pack set
- tests for layers with their own rules, the villager exemption, the facts, the render
lookup and its fallback, and the atlas naming
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test blocked in a read with no timeout and waited for the deadline's
shutdown to wake it. Linux wakes a blocked reader on shutdown; Windows does
not, so the test never finished there (#6183). It now reads once the limit
has passed, and a new test checks the case the deadline exists for: a peer
that sends one byte at a time, under the read timeout the callers set.
Fixes#6183
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EvokerAttackSpellGoal.createSpellEntity walks down from the top of the target until it
finds a block whose up face is sturdy, which is BlockState.isFaceSturdy(.., UP) and not
"is a full cube". Horizon required a full collision shape, so on a soul sand, mud or
full snow layer floor the fang search skipped the block under the evoker and stood the
fang on that block's own collision top, two sixteenths inside it.
depthtex0 at the screen's centre, smoothed with OptiFine's default centerDepthHalflife (1 tick):
a depth texture can only be copied whole, so a one-pixel pass loads the texel into a 1x1
R32Float target, copied into one of three small buffers and read back frames later without
waiting for the GPU (as timing.rs reads its timestamps). Packs focus depth of field with it;
it was zero.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- currentPlayerHealth/Hunger/Air/Armor (fractions of their maximum) and maxPlayerHealth/
Hunger/Air/Armor, -1 in creative and spectator where the shaders.properties reference says
so; isSpectator, isRiding, feetInWater, inSwimmingAnimation, is_burning, is_hurt,
is_invisible (spectators too), is_sneaking, is_sprinting, read as the HUD reads them.
- endFlashIntensity and endFlashPosition (view space, length 100): the End flash state that
draws the flash sprite, 0 outside the End.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sky program that reads the pack's SSBOs (Noble's gbuffers_textured, the fallback of
gbuffers_skytextured) was drawn without bind group 4: wgpu refused the frame ("expects a
BindGroup to be set at index 4") and the game panicked. The sky pass now binds them as the
other passes do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FoodOnAStickItem.use passes on a client, and the server answers SUCCESS_SERVER (which swings the
hand) only when the mount can be boosted. The client counted both sticks as items whose use ends
the click, so every right click swung the hand, boost cooling down or not.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The update tag's carry-over of client state (a vault's and a trial
spawner's spin, a beacon's beam) is now keep_client_state, tested with a
real decoded beacon update tag: the beam stays, the powers and levels are
the tag's. Beacon::keep_beam and its test go.
The world border filter on the beacon tick goes to its own change: the
other remote block entity ticks skip the border too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LanHost.states, the entities a level's moves are checked against before its
tick describes them again (the anticheat's ground check, move collisions, the
prediction's excuses), was one buffer that every level pass refilled. On a
server with players in several dimensions, the Overworld's moves met the
Nether's or the End's entities: a player standing on a boat at sea had no boat
under them, and the ground check kicked and banned players for it on the
official server (claims at y 63.086, a floating boat's top).
The states now park with their level (Parked.lan_states, swapped in
enter_level), like the level's other entity state.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player.damageStatsAndHearts sends DAMAGE_INDICATOR particles to the level. Player.attack's path
also queued them for the served players (the window of the server thread, LAN guests); the
stab's own copy only drew them on the server's side, so the window never saw them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Keeps the branch mergeable into main. Conflicts:
- lib.rs and main.rs keep the split's layout. Main's new block_edit
re-export and ram_limit module go into client_lib.rs, and ram_limit::start
moves to entry.rs beside crash::install.
- block_entities.rs keeps both the attachment maps and main's per-chunk
index of mod block entities.
The generated trait picks up the updated block_entity docs.
world_drawn and render_needs dispatch through an ordered RenderHooks
trait, which a mod returns from Hooks::render. The renderer list is
installed once beside the mod list and called on the render thread
while the frame is built. block_entity_submit and the existing
block-entity drawing helpers come through the generated trait and
Host, so a trait mod can draw its block entities without a mixin.
A trait mod can depend on another mod's [api] crate. Required and
optional semver requirements resolve against the installed bundles.
Missing dependencies, version mismatches and cycles are rejected, and
hooks dispatch in a stable dependency order. Optional features turn on
only when that mod is installed, so mods call each other through
ordinary Rust functions rather than a runtime registry.
Mods can attach typed data to players, entities, block entities,
chunks and levels through AttachmentType<T> and a Holder. Values
decode lazily, unknown and undecodable entries round-trip untouched,
persistent values encode when they change and memory-only values go
away on unload. Opted-in player values survive respawn.
Entity values load and save at the entity compound boundary, with
passengers and portal arrivals. Player values follow guest and
playerdata state. Block-entity values use a per-dimension side table,
chunk values live in chunk extras, and level values live in the
dimension's data/minecraft/horizon_attachments.dat. Errors are typed.
There is no client sync.
AbstractButton.extractDefaultLabel hands the label to ActiveTextCollector
.defaultScrollingHelper: a message wider than right - left is not centred but
drawn left-aligned, scissored to the button, and slid back and forth with
Util.getMillis (period max(overflow * 0.5, 3) s, sine easing, (int) cast);
only a message that fits takes Mth.clamp(centerX, left + w / 2, right - w / 2).
screen_draw::button always clamped, so a label wider than its button made
i32::clamp panic (min > max). In the Resource Packs footer that is "Open Pack
Folder" in af_za, ca_es, io_en, isv, mk_mk, oc_fr, tl_ph and vp_vl (150 px
button, label 150 to 171 px), which crashed the client when the screen opened.
screen_draw::scrolling_text ports the helper and scroll_offset its formula
(checked against a Java run of the same formula); button takes the clock as
now_ms, PackScreen::draw passes it on (OptionsUi uses hud_overlays::now_ms,
the chat's Leave Bed button too), so tests and the parity fixture stay
deterministic. The footer is drawn by draw_footer, which takes its labels, so
a test can use a language's strings without changing the global language.
ui::centered_text (HudBatch buttons and sliders) still clips by characters; it
cannot panic, and is left as a gap in tools/port_map.
Fixes#6129
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
ZombieNautilus.applyImplicitComponents applies the zombie_nautilus/variant component,
so a spawn egg or a bucket can pick the coral variant. Horizon applied the chicken, cow
and pig variants from the same components but had no arm for the nautilus, so the egg
always left the biome's temperate variant in place.
- textureGatherOffsets (GLSL 4.60, 8.9.4), which naga lacks: a prelude helper gathers each
footprint at P moved by its offset over the texture size, for float, int and uint samplers,
a component and the depth compare form (Mellow's deferred1).
- dot(float, float) and normalize(float), GLSL genType overloads naga's vector-only built-ins
lack: scalar overloads in the prelude unless the pack defines its own.
- discard and return guarded in braces, `{ if (true) discard; }`: the old `if (true) discard`
took the pack's `else` (`if (c) discard; else x = 1.0;` lost its else). A return with a value
followed by code in its block is guarded too (Miniature's hand program failed validation).
- A returned value converts to the function's type (`return 1;` in a float function), as GLSL
does and naga does not: 73 more composite programs translate over 40 packs.
- A parameter or local named like a bool uniform hides it in its function instead of becoming
`(hz_bool_name != 0)` in a declaration (Pixel Perfect's fogify).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RangedAttackMob.rangedAttackUncertainty is 14 - difficulty.getId() * 4, so a drowned
throws straighter the harder the world is. Horizon hard-coded the normal-difficulty 6,
which made every drowned as accurate as a hard-mode one on easy and peaceful.
Creeper.getMaxFallDistance is
getTarget() == null ? getComfortableFallDistance(0) : getComfortableFallDistance(getHealth() - 1),
so a creeper that has a target accepts a drop as deep as its health allows. Horizon only
had Mob's shared health-minus-a-third formula, so a creeper would not path down a drop
that vanilla walks off.
The same helper is now used for the base case too: getComfortableFallDistance is
Mth.floor(f + 3.0F), which is what the hard-coded "+ 3" was.
An arm64 kernel prints no model name in /proc/cpuinfo, only CPU implementer and CPU part, so
telemetry sent an empty cpu, the benchmark report had no hardware.cpu (and the server then
refused it), and the crash report said aarch64. linux_cpu_name reads model name, else maps the
implementer and part as lscpu does (ARM Neoverse-N1, Apple, Ampere...), else the board's Model.
The three readers share it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A host sends a beacon's update tag when a block there changes or its
powers are set, and the client replaced the whole block entity with each
one: its beam sections were empty until the next tick walked them again,
so the beam blinked out on every update. BeaconBlockEntity.loadAdditional
reads only the powers; the beam the client walked now stays, as a
vault's spin does.
The client's beacon tick also follows LevelChunk.isTicking: only inside
the world border.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Players behind a shared address spend GitHub's 60 anonymous API calls an
hour together and then can't install or update ("GitHub's rate limit was
reached"). The launcher's API reads (release list, launcher tags, commits)
now go through launcher/src/github.rs:
- each answer is saved in <data>/cache/github/ and used again for 5 minutes
without asking (three starts in a row spend one call, measured), then
revalidated with its ETag (an anonymous 304 still counts, measured; it
saves the download);
- a 429, or a 403 with GitHub's rate-limit headers, or no connection: Horizon's
own repository is read through Kira's read-only mirror
(mcp.playhorizon.dev/github/public, kira#109);
- both failing: the last saved answer, else an error naming both.
A plain 403 is no longer called a rate limit. Release files still come from
their browser_download_url (github.com -> release-assets.githubusercontent.com),
which the API limit doesn't cover.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content block's states are its template's states times the declared
combinations, the template's properties varying slowest. StateTable::new
reads a state table as vanilla's StateDefinition does, sorted by property
name with the first name slowest, so a declared name that sorts before one of
the template's (`bites` before `facing` on carved_pumpkin) stopped the game at
load with "registry state order differs from vanilla's state definition".
Such a block is now refused when the plan is built, with its id and the
property to rename, so the mod's row says why and the game starts.
Fixes#6139
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two hooks on an authoritative server's level, for #5499:
- native_fire_odds answers FireBlock's flammability table
(FireBlock.setFlammable's igniteOdds and burnOdds) before vanilla's
table, so fire spreads to, burns and stands beside a mod block.
- native_smoke_source answers CampfireBlock.isSmokeSource for the block
under a campfire, when the campfire is placed and when that block
changes, so a mod block makes a signal fire as a hay bale does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractHorse keeps a pending jump until the mount is on the ground (and not still jumping); only
Camel.onPlayerJump refuses it at the start (cooling down or off the ground). Horizon cleared the
rider's pending jump on every tick, dash or not, so a release on a tick the camel was not on
the ground (a bounce after landing, a slope) dashed sometimes and not other times.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A mod block only got random ticks when its template ticks, so a crop,
sapling or soil on a template that never ticks (dirt, stone) never grew.
`properties.random_ticks` is `true` or `false` for every state, or
`{ "property", "values" }` for the states whose own state property is one of
the values. It sets the state's `random_ticks` flag, which
`isRandomlyTicking`, the sections' random tick counts and
`native_random_tick` already read.
Fixes#6138
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A 4-thread machine had one far decoration thread, which left idle time
while the far trees and structures came in. The threads run at idle
priority, behind the near chunks, the ticks and the frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`lod.detail_target_columns`: the drawn far columns that take their chunk's
generated detail (trees, structures), and `lod.detailed_target_columns`:
how many have it, as `target_tiles` and `sampled_target_tiles` do for
sampling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A trait mod can declare HEAD injections that the launcher turns into a
generated trait method plus an ordinary horizon-mixins helper, so the
injected code calls the mod's own Hooks object. Listen and cancellable
unit HEAD injections are supported. Other kinds, and targets outside
horizon-client, are rejected with a clear error.
Patched game sources are cached by Horizon and mixins revisions, Rust
version, profile, features, bundle order and mixin inputs, so editing
a trait mod's own crate rebuilds without another mixins pass. Only
bundles with mixin content are staged for horizon-mixins: before, a
trait-only bundle installed beside a mixin bundle failed with E_MANIFEST.
Trait mods had no way to call the game back. Host is generated from an
explicit `helpers` allowlist in native-hooks.json: each listed public
native_* helper becomes an object-safe method with the helper's
receiver and docs, implemented by forwarding to Game. Helpers whose
signatures use gameplay-private types or generics stay out, and the
docs list them with the reason.
Native mods could only reach the game through mixins, so every build
patched the game's source. A mod can now implement
horizon_client::mod_api::Hooks instead.
The trait is generated from native-hooks.json. Each catalog hook gets a
mod_api contract (listen, cancel, replace or collect), and
`cargo run -p xtask -- mod-api` writes the trait methods and their
dispatch from the hook functions' own signatures and docs. Hooks keep
their exact receivers. Level hooks get read-only or mutable level hosts,
and library hooks (loot tables, default components) dispatch through a
set-once table that entry::main_with installs. Types that mods need move
out of the game into mod_api, which re-exports library types rather
than copying them.
The launcher links a bundle's [api] crate into a generated executable
that calls entry::main_with with every trait mod, in bundle order. A
build with only trait mods skips horizon-mixins. pr_ready runs
`xtask mod-api --check` when the catalog, the hooks or xtask change.
native_level_load when a level opens, before its chunks; native_level_unload
once it has closed, after its last save: a level idle for 300 ticks, and every
level still open when the server stops or fails. Fixes#6154.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
0.2.33 handed MinGW-w64 the data folder's short (8.3) name when the user
name is Cyrillic, and linking still failed: "No such file or directory" on
C:/Users/\xca\xe8... A short name is only another spelling of the same
folder, and rustc canonicalizes its own path to find its sysroot, which
spells the long name again for the linker.
build_dir now uses a real folder of ProgramData's
(ProgramData\horizon-launcher-<id>, one per data folder) whenever the data
folder's path isn't ASCII; the short name is kept only as the fallback when
that folder can't be created. A unit test covers a Cyrillic data folder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These screens asked self.keys for Ctrl and Shift, which only gameplay and the chat fill, so
Ctrl+arrows (word by word), Ctrl+Backspace and Shift+arrows (selection) did nothing in them.
They read InputWithModifiers.hasControlDown / hasShiftDown now, as dialogs and chat do (Cmd
instead of Ctrl on macOS, hasControlDownWithQuirk).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings mod-api-rework up to the main this API was built and tested on. The only conflict is crates/horizon-client/src/lib.rs: it keeps the split's layout, and main's new library modules move into client_lib.rs.
LivingEntity.hurtServer runs applyItemBlocking (blockedByItem's push) before
dealDefaultKnockback; hurt_player_from ran them the other way round, so a
partly blocking shield pushed its holder the wrong way. Adds two partial-shield
cases to the server knockback harness. Fixes#6109.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The "experience orb stuck" case (#5646) assumed the push out of a block only
changed how many ticks the escape takes. It does not: once the orb's middle
rises past y 216 with its box still in the 3x3 block, Entity.moveTowardsClosestSpace
finds north, south, west and east free with the same 0.5 distance, picks north
(the first), and depending on the draws the orb slides off the block (z 23.25,
22.x, or down to y 215 on the floor). Vanilla's orb random is unseeded
(RandomSource.create()), so both sides land on different outcomes from run to
run: the harness failed on main with an orb at y 215 against vanilla's 216.
A ring of stone one block higher keeps the top the only free side all the way
out; the case still pins the push (without it the orb stays at y 215).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gliding asks canGlideUsing for every slot: a stack's own glider and equippable components (or a server's patch) count; main's wear kept; no allocation for stacks without components.
command_export kept the parsed command-registries.json (4.2 MB of
serde_json Values) for the whole run, though the registries take only
part of it. Cache Registries::from_export, the fields fill_from_export
fills (2.2 MB), and fill from it with fill_from_exported. A dedicated
server opens its world once: it keeps nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
has_mod_block_entity asks BlockEntities::has_mods first; the fixture's stub
lacked it, so the Python test (and verify on every PR) failed on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dedicated server's ItemRenderer kept what only drawing reads: the
~1,500 client item definitions (items/**/*.json), the special
renderers' entity models and the grass colormap, 1.4 MB of the 7.3 MB.
ItemRenderer::load_for_server keeps the prototypes (the stacks' default
components, which gameplay reads) and the item ids.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Block model baking and connected textures added their textures one at a time,
reading and decoding each PNG in turn. They now hand the whole list to
Sprites::prefetch first, which decodes it with par_map, then insert in their
own order as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handleTakeItemEntity plays entity.experience_orb.pickup
at the orb for every orb taken; the server sends no sound (ExperienceOrb
.playerTouch plays none). On a server Horizon played nothing, expecting the
server's sound, so collecting a furnace's experience was silent. In a local
world it played once at the eyes, and not at all when mending took the
points. Each orb taken now sounds at the orb in both.
Fixes#6122
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerAdvancements kept all ~2,000 recipe files parsed (4.6 MB of
serde_json Values) for the server's whole run, only to build recipe book
entries when a player joins or unlocks a recipe. Keep each file as its
compact JSON text (0.57 MB) and parse the one an entry needs: parsing
every recipe takes 1.2 ms, the worst case of a full book on join.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SulfurCube.hurtServer answers true for the blows it only knocks about, so Player.attack goes on
with causeExtraKnockback (sprint, the Knockback enchantment), which reaches the cube's own
knockback with comesFromEffect: the launch scaled by power * 0.25 on top of sqrt(damage).
Horizon answered false, so attack_mob skipped the whole hit's aftermath, and the extra push
went through the generic Mob::knockback. The answer is now true for the tag, and the player's
extra push goes through Mob::knockback_effect.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The overlay walk looked at every overridden file once per folder it visited, and
inflated and wrote a zip pack's files one at a time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: MOTD also stored in server.properties and server.toml as vanilla's DedicatedServer.setMotd; main merged (difficulty kept); fmt, clippy, horizon-server tests, management_protocol + dedicated_server harnesses pass at 87e8e3122.
SpawnEggItem.useOn spawns in the clicked block itself when its collision
shape is empty (vines, grass, flowers) and only otherwise in the block on the
clicked face. Horizon always took the face's block, so an egg used on vines
put the mob a block out in the air, where it fell instead of climbing.
Fixes#6125
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Commands the player didn't type go through ClientPacketListener.sendUnattendedCommand's checks: confirm on parse errors or elevated permissions, never send a message argument unseen.
🤖 Agent: Fire and lava hit mobs after baseTick's timers (applyEffectsFromBlocks in aiStep), effects tick after them, and wither roses use the world difficulty.
scan_for_hearts read every resident chunk each tick (is it lit, was it
scanned) and then checked every scanned key against the chunk map: with
the 17,000 chunks of a 54-player server that is two hash lookups and a
cache miss per chunk, every tick. The scan now follows the chunk map's
membership changes: a chunk that arrives waits in a small set until it
is lit and scanned, one that leaves is forgotten. A chunk gone and back
between two ticks stays scanned, as before; a map that can't replay its
changes (cleared, or too many of them) is rebuilt from its keys. Ready
chunks are scanned in key order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The supervisor serves vanilla's GS4 query as QueryThreadGs4 does: the handshake (type 9, a token
below 2^24 as text), then the basic and the full stat to a request that carries the token, each
byte for byte what a vanilla 26.3 server sends for the same settings (checked on the wire). The
players come from the game's server-status, read at most once a second; the full stat is kept
5 seconds as in vanilla. Off by default; an invalid port leaves it off with vanilla's warning.
Refs #6108
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Mob.doHurtTarget calls causeExtraKnockback(target, getKnockback): (attack_knockback attribute plus
the Knockback enchantment) / 2, pushed along the attacker's yaw through LivingEntity.knockback,
which scales it by 1 - the target's knockback resistance, and slows the attacker by 0.6. Horizon
added a fixed velocity along the line to the target, with no resistance, no slowdown and no
attack_knockback. The attribute is now read (type default, or the mob's own from NBT).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A stack's minecraft:lore (as loaded, or as a server mod wrote it into
StackData.other) is now sent in its component patch, so a native mod's
menu items show lines under their name (prices, changes) in a stock
client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BlockEntities keeps the positions of its Mod block entities by chunk, so the
renderer reads only those within view distance instead of scanning every
loaded block entity each frame.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
wgpu clears what a vertex or index binding covers that nothing has written
yet, on the GPU, before the pass runs. Draws bound Translucent Batching's
large pages whole, so a page that had just grown had its new tail, up to
256 MiB, cleared in the next frame. Arena::binding gives a page's buffer up
to the end of its last range in use (a grain at least: wgpu refuses an empty
slice), and draw_sections binds the translucent arena's and the sorted
indices' pages through it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A container screen still checks its hotbar keys after Tab or an arrow moves the focus (AbstractContainerScreen.keyPressed), so Tab bound to the off hand swaps.
The splitscreen, friends, extras, HDR calibration, on-screen keyboard and
splitscreen-profile keys landed in en_us after zh_cn's last pass; a
Chinese player saw English on those pages. The bundle is complete again
(308 keys of 308), with the terms the earlier translations set: 分屏,
档案 for a profile, 与好友一起玩 for Play with Friends.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
winit's Locked on Windows is ClipCursor to a 1x1 rectangle at the pointer's current spot, and
SetCursorPos stays inside the clip, so the warp that followed the grab could not move it: the
pointer froze wherever it was (over another window or monitor). InputConstants.grabMouse warps to
the centre first; macOS already did, and now Windows does too.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: main merged again (submit thread, leaf and entity culling). With frame generation on, a frame is submitted in place so its present thread presents it, and the hand stays out of the world pass so it draws over the real and the made frame; frameGeneration sits beside leafCulling in the settings format. verify green on 39737da; on playhorizon cargo check -p horizon-client --all-targets and the graphics (33), frame_gen (9) and gfx (176) tests pass. Thanks @spidercraft.
🤖 Agent: Command block and command minecart admin lines go to operators through broadcastToAdmins (host and console included); Done's confirmation reaches only the editing player.
🤖 Agent: A world without singleplayer_uuid gives its owner their own (or a new) player file, never the newest other player's (PlayerList.loadPlayerData).
🤖 Agent: Native hooks experience_orb_can_merge / experience_orb_merge around ExperienceOrb.scanForMerges and the spawn merge; no-op stubs without native mods.
🤖 Agent: Review by Ludovic's AI agent (Claude). Extra atlas folders from blocks.json are packed (one walked list, 42e6769). Verify green. The parity failure was the runner's dead .horizon-prepare.lock link (2 s, before any harness), not this change; parity rerun.
🤖 Agent: Review by Ludovic's AI agent (Claude). Native column and surface-height reads on #5735, bounded by MAX_READ. Verify green on the whole stack. The parity failure was the runner's dead .horizon-prepare.lock link (2 s, before any harness), not this change; parity rerun.
🤖 Agent: Review by Ludovic's AI agent (Claude). Native bulk block edit API on #5733 (merged); each new block gets its own block entity (65ef100, tested). Verify green. The parity failure was the runner's dead .horizon-prepare.lock link (2 s, before any harness), not this change; parity rerun.
AbstractArrow.onHitEntity: the charge cannot be hurt, so the arrow takes
deflect(REVERSE, 0.2) and falls. The harness now also checks the arrow is
still in the world a second after the shot.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
new_block_entity no longer needs the block's template to have a block entity.
The level asks the game whether a mod block entity lives at a position
(LevelAccess::has_mod_block_entity) to remove it with its block and to keep
pistons from moving it, and the renderer lists mod block entities the
template's flag leaves out.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Results are taken and jobs started once a frame. With four jobs in flight
and tiles taking milliseconds, the sampling threads sat idle for most of a
slow frame. Queued tiles the view leaves are dropped at the replan.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On machines with four threads or fewer, far terrain samples one tile per
500 ms and meshes nothing while the near terrain loads. It asked
`Streaming::busy`, which counts every section being remeshed: block,
light and fluid updates near the player kept it held back long after the
near chunks were in. `Streaming::loading` counts chunks being generated,
decorated, finished or lit and sections meshing for the first time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A cart with any passenger (mob, guest or the player) uses the occupied slowdown and speed scale (AbstractMinecart.getSlowdownFactor keys on isVehicle).
🤖 Agent: Review by Ludovic's AI agent (Claude). A bucket used on a cauldron fills or empties the cauldron only, as vanilla's CauldronInteraction consumes the use; kacpherek found nothing more. Main merged in (lan_e2e check list: both sides kept, cauldron included). Verify green.
🤖 Agent: #5644 merged in. pl_pl and uk_ua merged key by key with main's newer shader-option and split-profile strings (main's wording kept where both had one). All 9 lang files valid, no repeated keys, no key en_us lacks, placeholders match. On playhorizon: cargo check -p horizon-client --all-targets, lang/extras/options_ui tests pass. verify green on 8726619. Thanks @mariuslozo-boop.
🤖 Agent: main merged again: its new Chat rows in Extras (Smooth chat, Message indicator and their header) read the language table too, en_us keys added. verify green on 79b92a3.
🤖 Agent: Review by Ludovic's AI agent (Claude). Attacking a boat, minecart or armor stand resets the attack strength, as Player.attack does for every entity. Main merged in; verify green.
native_inventory(uuid, kind) reads a player's inventory (Player: 41 slots,
main + armour + off hand; Ender: 27), live when they are on the server,
else from players/data/<uuid>.dat; never while they log in, are dead or
swapped in. native_move_stack moves one stack, checked against what the
mod showed, where it fits whole, or nothing moves; at most one side is a
file, written before the live side changes. native_send_map sends a
128x128 map picture to a player. For the official server's /invsee,
/endersee and the market's price charts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`key esc` went past it and closed the chat behind, unlike the game's own
key handling, where Escape answers no.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Level::read took the newest file of players/data when level.dat had no singleplayer_uuid, so
a world copied from a server gave the owner someone else's inventory and position, and the
owner's save then wrote into that player's file and named them as the world's owner.
PlayerList.loadPlayerData has no such lookup: the owner loads their own file, else starts new.
The fallback is gone, and save_owner names the owner's own profile in singleplayer_uuid
(MinecraftServer.saveAllChunks writes the integrated server's profile).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: Mod container viewers count the host's own player and copper golems too (ContainerOpenersCounter counts any ContainerUser), every tick from tick_world; spectators excluded.
🤖 Agent: Enchantment levels outside 1..=255 drop that entry and keep the rest (ItemEnchantments codec); levels past X keep their untranslated key unless a language names them.
A command block's or minecart's `chat.type.admin` line went to the server game's own chat, so no
operator saw it; it now joins the broadcastToAdmins queue (to every operator when
send_command_feedback, the world's host too, the console when log_admin_commands). A command that
sends its success unbroadcast (`sendSuccess(.., false)`) no longer reaches the operators either.
The confirmation of Done on a command block or a command minecart (advMode.setCommand.*) is sent
to the editing player only, through as_guest_messaged.
Fixes#6066Fixes#6067
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: IME: the options searches, game rule boxes and block editor fields allow the input method, and the chat TextField reports its caret so the composition and candidate window show (TextInputManager.setTextInputArea).
🤖 Agent: Review by Ludovic's AI agent (Claude). Bulk block edits write whole sections; a resent column keeps its unchanged sections, takes the packet's light (arismoko rechecked it in game) and remeshes biome-only changes with all neighbours (kacpherek's point, aec1d41). Main merged in. Verify green.
A command the player didn't type went straight to the server. Now
verifyCommand decides: a parse error or a restricted command asks before
it goes, and one with a message argument only reaches the chat box, or
the clipboard over a screen, for the player to send. Refs #6080.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_experience_orb_can_merge and native_experience_orb_merge (cancellable)
at spawn and on the tick scan, plus native_set_experience_orb for value,
count and age.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A loot table's function or condition that names a type in a namespace
outside minecraft: used to be silently skipped (functions) or failed
(conditions) at the roll. It now reaches free functions in loot.rs —
native_loot_function changes the stack, native_loot_condition says
whether it holds — which mixin bundles hook, as they do the game's own
hook points. The context brings the entity, the damage, the player who
killed it, the tool, the block, the luck, the biome and a seed from the
roll's random, drawn only when a mod's kind is in the table.
The tables the items engine rolls (block drops, entity drops,
loot_table, interact_loot) answer the condition hook with the tool and
block in the context; the function hook there follows when the stack
types converge. A bare type name is minecraft's own; an unclaimed kind
leaves the stack or fails the condition, as unknown kinds do.
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: Review by Ludovic's AI agent (Claude). Fox and dolphin item pickup follows vanilla (canTakeItem/pickUpItem rules); server-side only, nothing on the frame path. Main merged in; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). Arrows travel through nether and End portals as the vanilla Entity.handlePortal path does. Main merged in; verify green.
🤖 Agent: /tick step broadcasts the command's count before the tick's block events, joins still get the remaining ticks (ServerTickRateManager.updateStepTicks / updateJoiningPlayer).
🤖 Agent: Device loss recovery; main merged in and the rebuilt renderer now gets the Translucent Batching arguments build_renderer takes since #5984 (the LAND-STOP).
🤖 Agent: tools/build_times.py run: cold, no-op and incremental build times per crate in its own target folder, leaving the tree as it found it (tested); tooling only, skip-release.
🤖 Agent: A frozen level ticks no client block entities (Level.tickBlockEntities behind runsNormally): a /tick step advances a moving piston once; e2e against a vanilla server.
🤖 Agent: Windows CPU brand from the registry (ProcessorNameString) for bench reports and telemetry, cached once, reg run with CREATE_NO_WINDOW; one reader shared with crash reports.
🤖 Agent: Primed TNT runs applyEffectsFromBlocks' bubble column push after its move and before drag, as items and mobs already do; test with a soul sand column.
🤖 Agent: Powder snow snowflake now emitted on the server copy and sent to nearby players (PowderSnowBlock.entityInside, count 1, ALTERNATIVE randomization), the client copy no longer double-spawns; I merged main and dropped this branch's handleParticleEvent copy in favour of main's spread(); verify green.
🤖 Agent: Shulker bullet END_ROD trail on the client (ShulkerBullet.tick: two in the 0.3125 box), client visual only; I merged main (kept main's remote_projectile_power beside it), verify green on the merge.
🤖 Agent: verify and parity are green on levzzz5154's main merge, levzzz5154 approved it, and arismoko's in-game sample showed the hook changing and canceling the push. Queued for merge, thanks @mariuslozo-boop.
The constructor ran once per block type and every position took a clone, so a
mod's per-instance state (a serial, a seed) was shared. Vanilla calls
EntityBlock.newBlockEntity for each position; a block that makes none is still
asked once (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block's tint can sample the biome quart of the section next to it, so a
biome-only resend of one section must remesh its vertical neighbours, as a
light update does (kacpherek's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ModBlockEntity` keeps its fields (`ModBlockEntityFields`) behind
`Deref`/`DerefMut`, and each mutable access counts its revision on.
A new or cloned entity takes a fresh one, so one replaced in place never
matches. `KeptGeometry` keeps the revision it drew instead of a clone of
the entity and compares that each frame, O(1) whatever the entity holds.
The client merges an update tag field by field, leaving the revision
alone when the server sends the same tag again. Equality, `Debug` and
the save format are the fields' alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Review by Ludovic's AI agent (Claude). Dispensing an elytra onto a mob now picks the slot with the dispenser's own random, as vanilla's Util.getRandom does; the level random was used before (fixed on the branch). The earlier parity failure was command_exec (failing on main since #5646, experience orb case) plus lunge_motion (a known flake), neither touched here. Verify green on this head.
🤖 Agent: Explosions break and push boats and push falling blocks (VehicleEntity.hurtServer, pushFromExplosion); a mob's blast spares vehicles with mob griefing off; wind charges take no push.
TurtleEggBlock.decreaseEggs plays block.turtle_egg.break at the block and
sends level event 2001 for the eggs left; the last egg goes through
Level.destroyBlock, which sends 2001 too. The player's landing events reached
the game, but its block sounds were dropped (only the two fall sounds were
played) and its level events ignored, so a trampled egg vanished silently.
Block sounds (the egg, a shelf mushroom's bounce) now play at the block for
the local player and LAN guests, and the level events go out.
Part of #5858 (the player trampling eggs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.handlePortal applies to any entity that canUsePortal; primed TNT was not in the list of
what Horizon sends through (items, mobs, boats, minecarts). It now is: swept through the
portal box (0.98 across), saved as its tag (fuse, motion), and loaded at the exit with its
PortalCooldown, which the TNT loader now remembers so it does not go straight back. A TNT that
carries a mob stays.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Frames with frame generation are submitted in place (its present thread
presents them), and the hand stays out of the world pass so it draws over
the real and the made frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FeatureBlockBindings::bind_tags built every block tag's state set up
front: vanilla's 300 block tags hold 406,779 states (blocks_motion and
mineable/pickaxe ~30,000 each), and world generation reads a few dozen of
them. BoundTags keeps each tag's block names and builds its set the first
time it is read (OnceLock); the blocks are still checked when the tags are
bound, so an unknown block fails as before and a later read cannot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.canGlide checks every equipment slot with canGlideUsing: the
stack has glider, its equippable slot is the one it is in, and its next damage
won't break it. Horizon only looked at the chest slot and the item's own
components, so a chestplate a server (or /give) gave glider and an equippable
couldn't glide. The client and the LAN host's replay now share one check that
reads the saved components and the server's patch, which can add or remove
either one; the patch's equippable isn't kept otherwise, so its slot is read
from the patch.
Fixes#5773
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On NixOS pkg-config only sees packages listed by a shell's setup hooks, so
installing them in environment.systemPackages leaves the check failing, and
the APT advice it fell through to did not apply. Point at the launcher's own
shell.nix, which now carries those packages.
Co-Authored-By: horizon-agent (GLM 5.2) <[email protected]>
The shell provided GPUI's libraries but not the two native mods' build looks up
with pkg-config, and the client it produces needs libudev at runtime.
Fixes#5248
Co-Authored-By: horizon-agent (GLM 5.2) <[email protected]>
Upstream added --player-dir handling to the startup code in main.rs. With the
library split that code lives in entry.rs, so the change is applied there and
main.rs stays the thin launcher.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Screen.keyPressed moves the focus on Tab or an arrow and returns false, so
AbstractContainerScreen.keyPressed goes on to checkHotbarKeyPressed: with
key.swapOffhand bound to Tab, vanilla both focuses the recipe book button and
swaps the hovered slot with the off hand. Horizon's key focus kept the key,
so the inventory never saw it. In container screens, Tab and the arrows now
move the focus and pass the key on.
Fixes#6028
Co-Authored-By: Claude Opus 5.5 <[email protected]>
build_renderer takes the Translucent Batching setting and the picked pack's
flat drawing since #5984.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#4347 let the input method in, but Game::text_field_focused left four fields
out of the window's gate, so the OS was never asked for an input method while
they had the focus: the language list's search, the settings search, the
resource pack screen's search and a game master block's boxes. The title
screens' game-rule boxes were out too: focused_field is None while the rules
screen is open, and has_caret_field asks it.
The chat box — where the report typed — is a screen_draw::TextField, and that
box never reported its caret, so flush_ime had no area: the composition was
not drawn and set_ime_cursor_area was never called, which leaves typing blind
until the commit even with #4347 merged. TextField now reports the caret where
it draws the cursor, as EditBox and MultiLineEditBox already do.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Match AbstractBoat.tick by resetting velocity and angular velocity on non-authoritative client copies. Cover dismount/reboard ownership transitions with a real vanilla tick oracle and the actual headless Game ticker, plus ice reboarding for host and guest.
Co-Authored-By: Codex GPT-6 <[email protected]>
PostChain's textures/effect inputs had their own png::Decoder with CRC
checks on (kacpherek's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main.rs moved to entry.rs on this branch; carry main's --player-dir
log/crash folder handling over to entry.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Combined staging branch: the game library split (#5446) and the single mod
file format (#5909) together, as agreed to land in one release. No conflicts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
windows_cpu ran `reg` on every call: twice per Hardware::detect (its own
cpu() and System::detect's), and again for every telemetry item. The name
does not change while the game runs, so it is read once, as the module
says of its other command probes.
System's cpu() no longer runs `reg` on systems that are neither Linux,
macOS nor Windows.
The Windows test compared the name only against the family/model format,
so a parser that kept only part of the name would have passed. It now
checks the exact value against the registry as PowerShell reads it, and
expects PROCESSOR_IDENTIFIER where the registry has no name (some VMs),
instead of failing there. Its comment no longer says a CI runner runs it:
no Windows job tests this crate. The parser test gains tab columns and a
name with runs of inner spaces; a comment that said padding is kept now
says it is trimmed.
docs: benchmark-report.md and telemetry.md say when cpu is
PROCESSOR_IDENTIFIER.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
PrimedTnt.tick updated its fluid immersion but never ran
Entity.applyEffectsFromBlocks, so BubbleColumnBlock.entityInside never pushed
it (handleOnInsideBubbleColumn) and a primed TNT dropped into a soul sand
column sank.
The tick now asks motion::bubble_column_push for the columns its box touches,
after the move and before the drag, as items, mobs and armor stands already do.
Closes#6008.
Co-Authored-By: OhMyAgent (grok-4.6) <[email protected]>
- Rename "FOLLOWS UPDATES" to "auto-update" so it fits the version picker bar
- Make data/resource/shader pack titles and "None yet" lines readable over the background
- Give Add pack, Open Folder, Add server and Show all the same style as Add mods
Gate client block-entity tickers on runsNormally, keeping moving pistons alive until subsequent server steps. Verify18 snapshots against an actual vanilla server, including extension, retraction and unfreeze controls. Part of #5977; server notification flags remain a separate follow-up.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
Upstream added `pub mod lod_net;` to the old lib.rs module list, which this PR
moved to client_lib.rs: add it there. Upstream's new horizon-optifine
dependency is used by mobs/cem.rs, a reusable-client module, so it goes in
horizon-client-lib's manifest instead of horizon-client's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PrimedTnt.tick updated its fluid immersion but never ran
Entity.applyEffectsFromBlocks, so a bubble column's entityInside push never
reached it. A primed TNT dropped into a soul sand column sank.
The tick now asks motion::bubble_column_push for the columns its box touches
after the move and before the drag, as items and mobs already do.
Closes#6008.
Co-Authored-By: OhMyAgent (grok-4.6) <[email protected]>
hardware.cpu in every benchmark report and the cpu of telemetry items read
PROCESSOR_IDENTIFIER on Windows, which always holds family, model and stepping
(AMD64 Family 25 Model 97 Stepping 2, AuthenticAMD), never the name. The
telemetry server groups its benchmark summary by that string, so chips of one
family would have shared a row.
horizon_telemetry::system::windows_cpu reads ProcessorNameString from the
registry (reg query, hidden window, trimmed) and falls back to
PROCESSOR_IDENTIFIER. horizon-bench, horizon-telemetry and the crash report
use it; the crash report's own copy of the parser is gone and its test moved
next to the shared one, with more cases (CRLF, padding, missing, garbage).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
The dragon was drawn and ticked only in the End (its fight), so /summon ender_dragon in the
overworld put an invisible, motionless dragon there that only made sounds. EnderDragon.aiStep
does not depend on a fight (ServerLevel.getDragonFight is null outside the End): draw the
level's dragon in any dimension, and tick it outside the End without the fight.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Util.getRandom(slotsWithGliders, this.random) draws from the entity's random,
so the pick must not take a draw from the level's stream.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Certain server resource packs (notably on SMP servers like HugoSMP using custom font and GUI sheets) contain PNG textures with stripped or corrupted CRC-32 chunk checksums.
Vanilla Java Edition (26.3) reads images via STB Image (NativeImage.read), which does not enforce or fail on PNG chunk CRC-32 errors. Setting decoder.ignore_checksums(true) matches vanilla behavior and allows custom font sheets, emojis, scoreboard icons and sprites to decode properly.
Fixes#5979.
keep_unchanged_sections kept the old section whole when its blocks matched, so a
server that resent a column with new light and no separate light update left a
resident client with stale light (arismoko's review on #5733). Vanilla's
handleLevelChunkWithLight always applies the packet's light. Such a section now
takes the packet's light and remeshes with its neighbours (light_updated); a
section whose light is unchanged is still shared with the old column.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's spread() already applies RandomizationType ALTERNATIVE in
handleParticleEvent, so this branch's copy of it is dropped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Read and set the dedicated server message through vanilla management
requests, updating the value used by server-list pings immediately.
Part of #5147
Co-Authored-By: Codex (GPT-6) <[email protected]>
PowderSnowBlock.entityInside's SNOWFLAKE particles only ran on the
in-frame Game. The server copy skipped them, and guest ticks dropped
the event, so the window never drew them.
Emit them on server_block_damage, send ALTERNATIVE packets to nearby
players, and skip the client-side copy.
Fixes#5836
Co-Authored-By: Grok 4.6 <[email protected]>
fn vertex(input: VertexInput) -> vec3<f32> moves terrain in the world and the shadow map, for
waving plants and rolling water. It needs a [material] and shares its module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Review on #5909: native versions published as .hmod would reach
launchers that drop their content or refuse them. The registry keeps
.hnmod until launchers and the game read native .hmods.
docs/api/mod-files.md holds every rule of a .hmod/.hnmod in one
place: zip and path rules, top-level entries, the horizon-native.json
schema, what mixins/<id>/ may hold and the size limits.
The registry's Cargo.lock catches up with horizon-mods' png
dependency so its --locked check passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An id can name blocks as well as sprites, and can move its sprites into the translucent pass. A
sprite pattern beats a block on the same sprite. Sections mesh again when the list changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A client that moved, or whose own entity changed section, had every
entity of the level checked against it each tick: players times
entities. sees caps the range at the client's view distance, so only
entities in chunks that close can be seen. The tick now keeps each
chunk's entities and walks only the chunks within reach; the flags,
pairs and their order are what checking every entity gave.
loot_table_load_stays_hookable searched for "\n}\n", which a CRLF checkout
(core.autocrlf=true on Windows) never contains. Normalize as the other
source-reading test in this file does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .hmod's contents decide what it is: content only, native code
(horizon-native.json + mixins/<id>/), or both in one file. Native
bundle reading skips a .hmod's content entries, checks both
manifests name one mod, and refuses mod.wasm with a clear error.
The registry publishes native-mod versions as .hmod and still
accepts .hnmod; .hnmod keeps loading unchanged.
Launcher install, game content loading of native .hmods and the
GitHub install path are not done yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Heard when a server player takes an item from an armor stand or puts one
on it, after the stand's disabled-slot checks and before anything moves.
Cancellable.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla ShulkerBullet.tick draws two END_ROD particles in its 0.3125
box every client tick. Horizon moved the bullet and drew the model,
but never spawned the trail, so the sparkle behind it was missing.
Fixes#5842
Co-Authored-By: Grok 4.6 <[email protected]>
Keep native hook targets and source paths stable. Run gameplay tests in the gameplay library, and build ctl/probes from horizon-client-lib without gameplay. Update build/test selection and the fixed mixin-tool pin.
Co-Authored-By: Codex <[email protected]>
`native_surface_heights` and `native_read_columns` return `Result<_, String>` as `native_edit_blocks` does, so a mod
tells "wrong level" from "ask for less". A read is at most 1,048,576 blocks (was 16,777,216: it runs on the server
tick), a height query at most 65,536 columns, and an empty span (`below + above` of 0) is refused. The docs say the
tints are the biome's own at the column's top, not blended as the game draws them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HudAtlas walked only a fixed set of texture folders, then skipped
minecraft: file sources whose sprite name matched the texture path,
assuming the walk had packed them. Pack folders such as custom/ never
got a sprite, so item models that used them were invisible.
Keep the have.contains skip, and only treat matching File origins as
already packed when they sit under a walked folder.
Fixes#5720
Co-Authored-By: Grok 4.6 <[email protected]>
Fox: FoxSearchForItemsGoal walks to the first dropped item within 8 blocks while
the mouth is empty (goal 11, after the stroll), and Fox.pickUpItem takes it:
the extra items of the stack drop at its feet, what it held is spat out ahead
(entity.fox.spit, pickup delay 40, thrown by the fox), it holds one item and its
eating clock restarts. Fox.canHoldItem swaps non-food for food once it has eaten.
Dolphin: MoveToItemGoal (priority 7) swims to an item floating in the water within
8 blocks with entity.dolphin.play, PlayWithItemsGoal (8) throws what it holds, and
Dolphin.pickUpItem takes a whole stack into an empty mouth. ItemGoal.drop throws
from the eye, forward at 0.3, with a pickup delay of 40.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
`below + above` could overflow and `columns.len() * per` could ask for
gigabytes: a read past the level's height or 16,777,216 blocks now answers
None, reading nothing. Blocks past the top of the column read as air from
the chunk's own layout rather than the vanilla -64..320, the always-0 `tag`
and the unused `NativeSurfaceHeights` (leftovers of an async API) are gone,
the reads are tested through free functions on `World`, fork names are out
of the docs, and docs/native-mods.md describes the helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A run naming a palette entry or filter past the end panicked mid-edit, and a
filter index past u16::MAX wrapped: every run is now checked before anything
changes, and the edit answers Err. Palette entries and filters with NBT are
refused rather than silently dropped. New blocks get their block entity from
`new_block_entity`, so a mod block's own (API 15) is made, as
`native_set_block` does. The heightmap walk stops at the chunk's own bottom,
`Game::block_matches` calls `block_input_matches` instead of repeating it,
fork names are out of the docs, and docs/native-mods.md describes the helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`edit_runs` clamped runs to the vanilla column (-64..320), so a data pack's
taller dimension lost the blocks above or below it: each chunk's own
`min_y`/sections now bound its runs. `replace_remote` kept an unchanged
section whole, biomes included, so a resend with new biomes under the same
blocks kept the old ones: those sections now take the new biomes and
remesh. `edit_runs`'s doc lists what it does not record per block (block
entities, watched writes, block_log, path_writes, light_edits).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client of the server (single player's window, a LAN guest, a dedicated
server's player) asked only horizon-world's useItemOn whether the clicked
block takes the item, and that has no cauldrons. So after UseItemOn the
click went on to UseItem: the client predicted the bucket's own pour beside
the cauldron (a water or lava block that showed until the server answered,
and the empty sound a second time), and in creative the server poured the
bucket a second time beside the cauldron, where it stayed.
Like vanilla's client (AbstractCauldronBlock.useItemOn, CauldronInteractions,
which act only server side), the client now answers the cauldron's
interaction without acting, ends the click on SUCCESS or CONSUME and swings
on SUCCESS. A sneak with an item in hand skips the cauldron on both sides
(Player.isSecondaryUseActive). lan_e2e gets a cauldron check, which fails on
upstream/main and passes with this.
Fixes#5294.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Skeleton arrows passed through portal blocks and kept flying. The portal
sweep in find_entity_portals covered only items, mobs, empty boats and
minecarts, so an arrow never got a portal state and never changed
dimension.
Vanilla does not exempt projectiles: Entity.canUsePortal is true for a
living arrow that is not a passenger, so NetherPortalBlock.entityInside
and EndPortalBlock.entityInside (NetherPortalBlock.java,
EndPortalBlock.java) call setAsInsidePortal on it. AbstractArrow.tick
(AbstractArrow.java) then hands it to handlePortal, and the transition
time is 0 for anything that is not a player
(NetherPortalBlock.getPortalTransitionTime). A stuck arrow skips its move
while in the ground, so it enters no portal.
Arrows now join the portal sweep from where they stood at the start of
their tick, which also covers an arrow shot this tick. An arrow that went
through is saved with arrow_to_nbt as a waiting arrival, like an item or
mob. On load it restores its portal cooldown, so it does not go straight
back through the portal it came out of. Tridents are unchanged.
Horizon let a player glide only when the chest slot held a glider from
its own item definition. A glider worn in the helmet, leggings or boots
did not count, and neither did a glider component a server patched onto
the stack, which Paper plugins do. The glide's wear also always hit the
chest piece, so a helmet glider would wear down the chestplate.
Vanilla's LivingEntity.canGlide checks every slot: canGlideUsing needs
the item to have DataComponents.GLIDER, the slot its EQUIPPABLE names
to be that slot, and a next point of wear that won't break it. The
server's updateFallFlying then picks one such piece at random with
Util.getRandom and runs ItemStack.hurtAndBreak(1) on it.
Horizon now checks each worn piece this way (gliding_pieces, also used
for the server's copy of a guest), and the wear goes to a random glider
piece (wear_glider), for the local player and the guest path alike.
pl_pl, uk_ua, de_de and fr_fr use the renamed horizon.splitscreen.profiles.*
keys. Main now keys the two "Connected to your friend" lines as
horizon.multiplayer.connected_friend_*, so de_de and fr_fr use those names,
and they gain main's two new keys (not_signed_in, invalid_address).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Review of the bundled de, fr and uk strings:
- de_ch read de_de's file with its "ß"; vanilla's de_ch writes "ss" (1
"ß" in its file, 312 in de_de's), so Bundled serves de_ch as de_de with
"ß" spelled "ss".
- de "Shortcut: %s" was "Kurzanwahl" (a phone's speed dial): "Tastenkürzel".
- de: the calibration's white step hint trailed off ("..., bei deinem
Licht."); fr: "one test picture at a time" had become "image test".
- uk: the Extras caption "Коліщатко: перенос" put a second colon on its
button ("...: перенос: Увімк."): "Перенос коліщатком".
- The lang test checks one German and one French string and that de_at,
fr_ca and fr_ch read the same file, instead of pinning two strings in six
languages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
de_de.json and fr_fr.json cover all 289 keys of en_us.json. de_at, de_ch,
fr_ca and fr_ch read the same files, as the es_* variants read es_es. AI
translation (Claude), not reviewed by a native speaker. Terms follow vanilla's
de_de/fr_fr (Optionen/Einstellungen, Fertig, Mehrspieler; Options, Terminé,
Multijoueur, "Oui/Non" for On/Off, a space before ":" and "?"). German uses
"du", French "vous", as vanilla does. The lang test now also loads the title
footer and the Graphics button in de, fr, pl and uk.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
pl_pl.json and uk_ua.json had 16 keys each; they now have all 289 keys of
en_us.json. AI translation (Claude), not reviewed by a native speaker. Terms
follow vanilla's own pl_pl/uk_ua files (Ustawienia/Налаштування, Paczki/
Пакети, Gotowe/Готово, byty/сутності, ...), and the ellipsis is the single
character vanilla uses in those languages. Placeholders (%s, %.2f) are kept.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
More of Horizon's own screens drew English literals (or looked up keys that
en_us.json did not have, which also shows English). They now go through the
language table:
- HDR calibration screen: title, the three step names and hints, Cancel/Back/
Save/Next (vanilla gui.cancel, gui.back, selectWorld.edit.save), and the
"Shortcut:" row; the Options > Graphics HDR descriptions and the Calibrate
buttons that had no en_us entry.
- Splitscreen profile page (split_profiles.rs): every label and line.
- Play with Friends screen, its chat notices and the two "Connected to your
friend" loading lines.
- The on-screen keyboard's key labels and button hints.
English text stays the fallback (lang::get_or) so nothing changes without a
translation. en_us.json gains 55 keys.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Review of the bundled lang file test:
- `every_horizon_key_the_code_names_is_in_en_us` skipped a key whose group
had no other entry in en_us, so a new screen's keys were never checked
(deleting `horizon.title.not_affiliated` passed). A key is now any
`horizon.<group>.<name>` of lower-case letters, digits and `_`, with the
one server address that looks like one listed.
- The CRLF assertion failed on a Git for Windows checkout
(core.autocrlf=true) although CRLF is harmless to the parser; it goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every bundled horizon lang file must be served by Bundled::read (a file with
no match arm is never loaded), parse as a flat map without a repeated key,
have no key en_us lacks (stale or renamed), and keep the placeholders of the
English text. Every `horizon.<group>.<name>` key the client sources name must
exist in en_us.json. A language does not have to be complete: a missing key
falls back to English.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The title screen's "Not affiliated with Mojang or Microsoft" and the whole
Horizon Extras page (section headers, the eleven captions, their tooltips and
the On/Off values) were English literals that never reached the language table,
so they stayed English in every language.
They now read `horizon.title.not_affiliated` and `horizon.extras.*` from the
bundled `horizon` namespace (lang::get_or, with the English text as fallback,
as the multiplayer buttons do). en_us.json gains the 27 keys; other languages
fall back to English until they are translated.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Chests (closed ones' baked geometry too), shulker boxes, bells, banners, decorated pots,
books and the other block entities draw each part again with their texture's _e
overlay at full brightness, shaded alike. The emissive overlays are shared by the
atlas and the mobs (an Arc), so the pack reload no longer moves them out of the atlas.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
overlay draws the 17 transition tiles over a face toward the blocks or
sprites it names (connectBlocks, connectTiles); overlay_ctm, _random,
_repeat, _fixed and Continuity's _horizontal/_vertical forms pick a tile
as their method does. Overlays come before the rule that picks the face's
own sprite, draw after it at the same depth with an alpha cutout (or
translucent with layer=translucent), and are tinted by tintIndex and
tintBlock.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::blink finds each entity texture's <name>_blink.png (and _blink2.png,
half shut) beside it, a random variant's beside the variant, with blinkLength and
blinkFrequency from <name>_blink.properties. The entity atlas packs the variants'
frames; render_visible draws the frame instead of the texture while a mob blinks,
each mob at its own phase, and shut while a villager sleeps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mesher asks ConnectedTextures for each face's sprite: a tile swaps the
quad's layer (in the pass its alpha needs), and ctm_compact's quarters
split the quad in four, its light and colour blended across them. Rules
that test biomes get the block's biome. Nothing changes without CTM files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::emissive reads the pack's suffix (optifine/emissive.properties,
ETF's etf/ and textures/ copies, _e without one) and finds each entity texture's
overlay beside it, a random variant's beside the variant. The entity atlas packs the
variants' overlays; render_visible draws the body and each layer again with its drawn
sprite's overlay, full bright with the model's shading (OptiFine's look), armour
included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_surface_heights(dimension, columns, heightmap)` and `native_read_columns(dimension, columns, heightmap, (below, above))` for server mods, in the level being ticked: a column's heightmap height, or the blocks round it as a palette and indexes with the biome's grass, foliage and water tints, thousands of columns a call. Ported from Stink-O/horizon cbceb3f0e, a3ba97c94 and 20a8a553c.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_edit_blocks(dimension, edit)` for server mods: a palette of block states and filters read as commands read them, runs up columns from a height or a heightmap's, made at once in the level being ticked through `World::edit_runs`, with what `fill ... strict` does for block entities. Millions of blocks a second where `native_set_block` does one: for world generators, terraformers, schematics. Ported from Stink-O/horizon ee55a05e6.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`World::edit_runs` (block_edit.rs): runs of blocks up columns written straight into chunk sections, a section replaced at once (`Chunk::replace_blocks`); the chunks changed that way go whole to the players who see them (`World.bulk_log`). A column a server sends again while it is resident keeps its light and is relit and remeshed only round the cells that changed (`Streaming::replace_remote`, `PalettedContainer::same_storage`, `LightChanges::classes_differing`), which also makes any resent column cheaper. Tests for the runs, the storage comparison and the sections dirtied. Ported from Stink-O/horizon ee55a05e6 and 8820a77ca.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player.attack calls onAttack, which resets the attack strength ticker, for
every entity that passes cannotAttack. Horizon did it for mobs, players and
the dragon, but not for boats, minecarts and armor stands, so on a server
only the client's swing packet reset it: a client that skipped the swing
hit vehicles at full strength every time. A fixed item frame refuses the
hit of a player who isn't in creative, and a cushion that of a player who
may not build, so skipAttackInteraction is false there and they reset too.
server_packet_rules_e2e checks it with two bots that hit a boat each in one
go without swinging: eight hits leave one standing (it breaks on the fifth
without the reset), 24 still break the other. horizon-bots' --attack takes
a UUID, so a bot can hit an entity summoned with one, and --attack-rounds
stops it after N rounds. The harness turns the anticheat off, which would
drop those attacks, and force-loads the boats' chunks.
Fixes#5631
Written with Claude Opus 5.5 in OpenCode.
Options > Graphics > Display: Frame Generation, Off or FSR 3.1 (off by default,
frameGeneration in horizon-graphics.txt). The world is kept at the screen's size
without the hand and HUD, the optical flow and interpolation make the frame
halfway (with mobs' and dropped items' own motion vectors), the hand (at its
halfway pose), outlines, post effects, screen effects and HUD are drawn over it,
and a present thread shows it half a real frame before the real one. Off under
shader packs, HDR, the title screen and blurred menus.
When the present thread gets anything but a frame from the surface (outdated,
lost, occluded, timeout, validation, suboptimal) or dies, the renderer stops it
and acquires the next frame itself, so gfx.rs's normal path handles the surface;
frame generation resumes once that path has a frame again. The F3 screen's first
line adds the frames reaching the screen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use the effective split-menu flag consistently for drawing, hit testing and keyboard navigation. Setup fixtures enable the saved switch explicitly while keeping HORIZON_SPLIT transient.
Co-Authored-By: OpenAI Codex <[email protected]>
Combine draft arrangements and monitor-map projection with the usable Setup screen. Preserve current pause dialogs, HDR settings and keyboard navigation. Mouse dragging and controller pickup follow separately.
Co-Authored-By: OpenAI Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_living_knockback (NeoForge's LivingKnockBackEvent) runs in
LivingEntity.knockback once the knockback resistance took its share and
before the motion changes, with the pushed entity's id and type, the
strength, the direction (x, z) and a cancel flag; a non-finite value a mod
leaves is put back. In 26.3 every knockback has a DamageSource, so the hook
names it (damage type, causing and direct entity) as
native_living_incoming_damage names the same hit (`Hurting`,
`hurt_server_args`). The API number comes from the fragment
(tools/native-hook-fragments).
It is reached by every LivingEntity.knockback Horizon makes on players
(including LAN guests and a dedicated server's) and mobs: a hit's own push
(dealDefaultKnockback), a sprinting or Knockback hit and a spear
(causeExtraKnockback), a sweep, a goat's ram (RamTarget, `mobAttack`) and a
mob's spear push, which now carry their source in MobEvent::Knockback, and
LivingEntity.blockedByItem's push on a shield's holder, which was
open-coded past it (and past the knockback resistance) in shield.rs and is
now knock_back_player(0.5, ...). A sulfur cube with a block hit by an entity
doesn't reach it (SulfurCube.knockback doesn't call super there). The docs
list the pushes Horizon still makes another way: a mob's melee push from its
attack knockback or a Knockback weapon, an armor stand's, the ender
dragon's.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clicking a cauldron predicted nothing: the remote useItemOn dispatch had no
cauldron arm, so the click fell through to Item.use while the hand still held
the water bucket. BucketItem.use then emptied the bucket into the face that was
aimed at, which is the water block that showed for a couple of frames before
the server's cauldron state arrived, and the water left in the air when a full
cauldron was emptied into the bucket.
remote_cauldron_reacts now runs AbstractCauldronBlock.useItemOn/CauldronInteractions
on this client's level through a dry copy of the hand, like the host's
use_on_cauldron: the cauldron's new state and the sound it made are predicted
when the item is one the cauldron takes, and a Pass still leaves Item.use its
turn. Cauldrons are also hand operated blocks, so a guest's predicted cauldron
use forwards its sound to the other players the way doors and levers do.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
docs/native-mods.md keeps main's API 58 notes, then the API mods section.
With the game module in the horizon-client library, its unit tests ran in the
library's test process and shared process-wide state with the library's tests
(health::HURT_SERVER, the sulfur cube archetypes): six tests failed. The game
module now builds into the library only outside tests; its unit tests stay in the
executable's test build, as before the move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: game.rs keeps the api_mods export beside main's narrator module;
horizon-threads takes main's windows-sys features (the same Windows fix);
native-mods.md keeps main's API 47-57 notes, then the API mods section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A native bundle with [api] (crate and entry) and no [[injection]] is an API mod: its
crate implements horizon_mod_api::Mod and the game asks it right after the item and
block hook points it is named after, once the mixin mods there are done.
- horizon-mod-api: the Mod trait (use_item, use_item_on, right_click_block,
left_click_block, destroy_block, place_block, tooltip), Action and Host.
- horizon-client: the game module moves into the library and the executable only adds
the allocator, so a build can link the game from another package; game/api_mods.rs
dispatches, and a mod turned off in Options > Mods is skipped with no build.
- launcher: API mods skip horizon-mixins; their crate and a generated native-game
package build the executable, so horizon-client never depends on a mod.
- horizon-mods: the manifest's optional api field and its checks.
- docs: native-mods.md "API mods (no mixins)", stability.md; example in
launcher/tests/fixtures/native-api-demo.
boot_paint.rs draws the boot screen with GDI (Win32_Graphics_Gdi) and reads the
window's size (Win32_UI_WindowsAndMessaging), which horizon-threads didn't turn on.
2026-10-31 19:47:15 +01:00
1025 changed files with 110827 additions and 8705 deletions
- Contributors first: `gh pr list --state open --search <area>` before you start; never duplicate theirs.
- **No hardcoded user-facing strings.** Keep UI translation-ready from the start. In the launcher (`launcher/`), wrap visible text with `tr("...")` or `trf("... {arg} ...", &[("arg", &val)])` from `crate::i18n` instead of raw string literals or `format!`. In the game, use translation keys (`Text::tr`, `lang::get`), never hardcoded English strings.
- **Claim before you start**: comment `/claim` on the issue (no issue yet: open one, then claim it). An assigned
issue is taken: pick another. `/unclaim` when you stop; a claim with no open PR linking it is released after 5 days.
@@ -39,7 +40,8 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| Crate | Owns |
|---|---|
| `horizon-client` | The game: a lib (`src/lib.rs`, ~100 flat modules: gameplay, networking, title screens) and the bin (`src/main.rs`→`game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) |
| `horizon-client` | Gameplay library (`src/lib.rs`: `game.rs`+`game/`, `entry.rs`) and thin executable (`src/main.rs`). Also the dedicated server (`--dedicated`). Re-exports `horizon-client-lib` for existing mod paths. |
| `horizon-client-lib` | Reusable client systems, `horizon-ctl`, probes and their tests. Its manifest points to the existing sources under `crates/horizon-client/`; the library root is `src/client_lib.rs`. It never depends on gameplay. |
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
@@ -84,8 +86,8 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
```sh
python3 tools/pr_ready.py # the self-check: fmt, clippy -D warnings, touched crates' tests, harnesses; paste its block in the PR
python3 tools/pr_ready.py --quick # same without harnesses
cargo test -p horizon-client --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo test -p horizon-client -p horizon-client-lib --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client-lib --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo build --profile fast -p horizon-client # optimized, quick to link: use it to play or run lan_e2e
python3 tools/parity.py --changed origin/main # the harnesses your branch can affect (CI's parity job)
python3 tools/parity.py --changed-files paths.txt --list # which harnesses cover these files, without running them
@@ -101,9 +103,16 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
+ a Rust probe: `crates/<crate>/examples/<name>_{probe,fixture}.rs` (horizon-world 175, horizon-client 104),
or the game binary with a fixture env var (`game/dispense_fixture.rs`, `game/sculk_fixture.rs`,
`game/command_harness.rs`, `game/net_harness.rs`). Copy a nearby harness for a new one.
- Developing a native mod? Run `horizon-launcher settings native-profile dev` once. `dev` builds the game crate
unoptimized: a mod edit rebuilds about 6× faster than with `performance`, while game logic runs up to about twice as
slow. Use it while developing, not for playing; `performance` stays the default for players and releases
([docs/native-mods.md](docs/native-mods.md)).
- Probes build into `.local/parity-target/`; harnesses find binaries via `lab.cargo_target()`, never `target/`.
-What a change reruns (of 329 harnesses, measured 2026-10-23): a file of the game binary (`game.rs`, `game/`)
~25, a file of the clientlib ~103, of `horizon-world` ~103–109, of `horizon-core` ~119. Plan for it.
-Harness selection uses Cargo dep-info and reachable-code narrowing (`tools/parity_reach.py`).
`horizon-client-lib` builds the control tool and client probes without gameplay; their dep-info
excludes `game.rs` and `game/`, so gameplay edits do not rebuild or select those probes.
Source paths remain in `crates/horizon-client/`. Measure actual harness selection on refreshed
records with `tools/parity.py --changed-files paths.txt --list`; historical counts are not a guarantee.
@@ -160,7 +172,7 @@ the same `horizon_client::<module>` paths), `G:` = bin (`crates/horizon-client/s
- `struct Game` (game.rs, ~290 fields) holds everything. Its methods are spread over ~180 files: game.rs keeps
the core (tick, render, input, inventory, world open/close) and each `game/<feature>.rs` adds an
`impl Game<'_>` block for one feature, with its own state types. Fields are private to the bin.
`impl Game<'_>` block for one feature, with its own state types. Fields stay private to the gameplay module and its descendants. Gameplay tests run in the gameplay library, separate from reusable-library tests.
description="The client's world data, rendering and screens: chunks, meshing, light, streaming, the renderer, the HUD, menus and settings; no gameplay"
[dependencies]
# The GUI atlas's pixels, kept compressed once uploaded (hud.rs `pack_pixels`).
/// Turning it on makes frames faster (culling, caching, batching).
Saves,
}
implCost{
Loaded 100 of 1025 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.