🤖 Agent: the client keeps only the server's attribute modifiers, so Speed now ends one tick after RemoveMobEffect as in vanilla (ServerPlayer.doTick runs after sendDirtyEntityData); Paper+GrimAC bench 7-17 Simulation flags at expiry -> 0, 8 selected harnesses pass.
🤖 Agent: verifies the per-mod-set build signature with its own Ed25519 key (separate from the release key) before the launcher runs a prebuilt binary; independent review found it sound; opt-in stays off until the rollout steps.
🤖 Agent: the default build resolves the same features as before (390 third-party units with identical fingerprints); every new gate is cfg!(), so CI's existing --no-default-features clippy already compiles the no-extras side and no stub can drift.
with_vault built the list of every player (lan_present: eye heights, effects,
omens) for every loaded vault every tick. VaultBlockEntity.Server.tick reads the
players only when the vault's state may change (stateUpdatingResumesAt, once a
second), so the list is now built at the first ask, from the same game state.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: item components read by reference instead of cloned per frame (frame prepare 217→193 µs); earlier parity failures were harness bugs since fixed on main; parity-main covers it after merge.
🤖 Agent: HUD glint sprite looked up once per item instead of per quad (HUD 84→72 µs); render-only, packet harness 16/16; parity-main covers it after merge.
A server's client applied its effects' attribute modifiers itself, so it
slowed down as soon as ClientboundRemoveMobEffectPacket arrived. Vanilla's
server removes a Speed that runs out in ServerPlayer.doTick, after the
tick's sendDirtyEntityData, so the movement speed without the effect's
modifier reaches the client a tick later and vanilla's client runs fast for
that tick. GrimAC's Simulation flagged the Horizon client on that tick
(0.051 off with Speed II, decaying over the next ticks).
LivingEntity.onEffectAdded/onEffectUpdated/onEffectsRemoved change
attributes on a server only; a server's client now keeps the modifiers
ClientboundUpdateAttributesPacket sends.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: stamps the existing chunk_tickets fragments (API 412/413, NATIVE_HOOKS_API 413); tests.test_stamp_native_hooks 10 OK, verify green, and this exact head built into the SMP engine on playhorizon (rtp2), whose /rtp proof passes with no slow tick. The SMP 0.35.1 PR in horizon-mods needs it.
🤖 Agent: notices list the three cases (Iris, OpenJDK math, OpenJDK HashMap); both clean-room rewrites (#11944, #12043) are merged, so the notice now describes main accurately.
🤖 Agent: clean-room java_hash_map (CLRS tree, slab); java_hash_map and java_identity_map oracles identical over 1.18M lines plus 3,028 edge fixtures; 11/12 caller harnesses pass (structure_layout lacks oracle data on the desktop).
🤖 Agent: clean-room jmath; JVM-identical on ~2.5M inputs per function (log/log10/cbrt), 5.9M hypot pairs, 3.05M sin/cos, all 2^32 floats for round_f32; parity-main covers the ~420 dependent harnesses after merge.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 512 lines (Loading, App, run_event_loop, the start checks) to game/loading_screen.rs; the Windows-only hwnd stays in game.rs and stays visible to the moved code.
🤖 Agent: pure move of 506 lines (keys and clicks over a container screen) to game/inventory_input.rs; click_inventory stays because an open draft changes it.
python3 tools/stamp_native_hooks.py --only chunk_tickets chunk_tickets_in_level.
The SMP's /rtp moves from /forceload (which waits on the server thread
until its chunk is generated) to native_force_chunk_in, so it needs a
number to declare.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The same check for #6741 found that java_hash_map.rs was translated from
OpenJDK's HashMap.java. List it with the other two, and say so in
how-horizon-is-built.md.
Refs #6741
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of cauldron_outcome and use_on_cauldron (96 lines) to game/cauldrons.rs; cauldron_at and cauldron_extinguish stay because an open draft changes them.
🤖 Agent: pure move of 117 lines (GLFW key/button codes, follow_named_keymap, the key routing rules) to game/key_codes.rs; game.rs re-imports the names other game modules reach through super::.
A blended draw over a surface of the other kind leaves one mark for both, so a frame that mixes
marked and written light would clip the written light and lift it again, or leave the marked part
unlifted. Step 4 now goes path by path: the frame-case rebuild first, then every marking shader on
the default path in one pull request, then Improved Transparency and the visibility buffer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: moves 575 lines of game.rs's inline tests to game/core_tests.rs with unchanged bodies; the horizon-client test count stays 1159, and the tests open PRs touch stay put.
🤖 Agent: pure move of spawn_player (117 lines) to game/player_spawn.rs; test_world_input_capture's loading-completion check reads it from the new file and still sees the player set before the mouse capture.
The previous implementation was translated from OpenJDK's
java/util/HashMap.java (GPL-2.0), which Horizon's MIT licence cannot
carry. This one was written in a two-person clean room: the implementer
worked from a behaviour spec, the Java SE API documentation, CLRS
chapter 13 (red-black trees) and runs on a real JVM, and never read
OpenJDK's source or the old file.
Entries live in a slab of nodes addressed by u32 index. A bucket holds
the first node of its bin's list and, for a tree bin, the root of a
red-black tree over the same nodes, whose links sit in a side table the
map allocates with its first tree bin. The public API is unchanged.
Unit tests pin iteration orders printed by java.util.HashMap on OpenJDK
25: a doubling from 16 to 32 buckets, a tree bin made at 64 buckets
after two doublings below 64, a tree-bin insertion after its tree
parent, movable and cursor removals from a tree bin, copies, and the
cursor's error order.
Refs #6741
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 190 lines (startUseItem, interactOn, the placement/bucket cancel hooks) to game/item_actions.rs; the Player actions docs rows now point fn use_item and fn attack at their files.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 90 lines (autosave interval, tick-rate change, player autosaves, level.dat metadata) to game/autosave.rs; save_game_with and save_level stay because open PRs change them.
🤖 Agent: pure move of 109 lines (renderer_for, apply_settings, translucent layers, far terrain distances) to game/render_settings.rs; prepare_lod and all_sections_changed stay because an open draft changes them.
🤖 Agent: pure move of 234 lines (title actions, open_world, load_world, save_for_exit, close_world) to game/world_session.rs; finish_open_world and finish_close_world stay because an open draft changes them.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): the tests no open PR changes go to their own test module; the ones open PRs touch (the module header, the scaffolding fall test, the integrated save test) and the tests next to them stay. Test bodies are unchanged; rustfmt re-indented them one level.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 110 lines (block face and particle sprites, honey particles, destroy particles) to game/block_particles.rs; particle_sprite and crack_particle stay because open PRs change them.
🤖 Agent: pure move of 186 lines (attack strength, crits, sweeps, Player.attack) to game/melee.rs; melee_damage and attack_mob stay because open PRs change them.
🤖 Agent: pure move of the game window's ApplicationHandler (295 lines) to game/window_handler.rs; the input tests read window_event/device_event from the new file first, since Loading and App have handlers with the same names.
🤖 Agent: pure move of 359 lines (slot layout, hit test, item and decoration drawing) to game/inventory_slots.rs; slots/slot_count/draw_inventory stay because open PRs change them.
One Cargo feature, on by default, holds the entry points of Horizon's own
extras so a fork can build the game without them
(`--no-default-features --features audio,controllers`) or delete them:
cosmetics, emotes and the "Horizon player" presence (one gate,
game::cosmetics::enabled), the official and partner servers added to
servers.dat, the Options screen's Horizon Settings and Mods buttons, and
the existing discord, friends and heartbeat features it now turns on.
The default build resolves the same features as before and every gate is
cfg!(), so both sides compile in every build. A build without the extras
names the official build on Credits & Attribution. docs/forking.md lists
the extras, their files and their gates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 237 lines (an item in use, tick by tick) to game/using_item.rs; apply_consume_effect and drop_bundle_content stay in game.rs because an open draft changes them.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: requeued after merging main: the only conflict was the module list in game.rs (`pointer_capture` from main beside `portal_ticking_tests`), resolved by keeping both; no code change since the checks.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued: spans only, no behaviour change; pr_ready --quick green. Point: the guards wrap the mods' hooks outside every level span, so the SMP's 1-2 s ticks get a name in horizon-report.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 390 lines (toggled keys, mapped keys, hotbar wheel, play_mouse) to game/play_input.rs; the two hook tests that compile mapped_key_event and the wheel branch read the new file.
🤖 Agent: pure move of 170 lines (title panorama, menu blur, border wall, the screens' render-post hooks) to game/screen_backgrounds.rs; regenerated on main after #11939 since the blocks were adjacent.
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dedicated server's slow tick lines on the SMP show 1-2 s ticks whose
time is in no span: it is spent outside every level, where only the mods'
ServerTickStart/End hooks, the config poll and the idle-level close run.
Each gets a span, so a slow tick line and horizon-report name it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of 415 lines (first renderer, start_game) to game/loading_window.rs; the Windows-only hwnd stays in game.rs, so no cfg'd re-import is needed.
Entity.handlePortal runs in the entity's own tick, and ServerLevel.tick
ticks only the entities in entity-ticking chunks. handle_portals and
find_portals walked every loaded entity, so a mob in a nether portal past
the simulation distance went through it and its cooldown counted down.
They now skip entities outside entity-ticking chunks and keep the portal
state of the ones waiting there.
Fixes#11960
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857). Private methods and items become pub(super), the same reach they had in game.rs (the game module and its children). Port map rows, source-reading tests and the docs tables follow the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pure move of the world entry impl block (367 lines) to game/world_entry.rs; the world_items!/world_recipes! macros stay defined before the mod list, so the moved code still sees them.
🤖 Agent: merging #11948 (smp-200). The one risk was the filter cutting block contact for entities that do tick: it keys on the same entity_ticking_area test the mob and item ticks use. The new test shows a zombie inside simulation distance 4 still presses its plate while one past it doesn't (and fails without the change). server_entities, mob_ai, cauldron, raid and dedicated_server harnesses pass locally; CI parity covers the rest.
🤖 Agent: pure move of 324 lines to game/pointer_capture.rs; test_world_input_capture's method() now also finds pub(super) methods, so the capture fixture still compiles the real code.
JavaHashMap reproduced java.util.HashMap iteration order by translating
OpenJDK's java/util/HashMap.java (GPL-2.0 with the Classpath Exception):
its tree-bin routines, their signatures and their quirks (#6741). This
keeps the public API and removes the body, which panics with todo!() until
the next commit writes it again from a behaviour description, the Javadoc
and the real JVM's results, by an agent that has not read either source.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: kacpherek's Dir point is fixed (from3DDataValue wrap, Steps unclamped, tested); on top of MSedgeMC's main merges I only moved the no-target doc comment back onto its test; shulker_space and the save harnesses pass on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
🤖 Agent: port_ledger map-check on the four changed TSVs reads 519 rows, no problems, and kacpherek's PackSelectionScreen.Watcher row is restored (rdbtCVS's fix); TSV-only, no game code.
jmath.rs held ports of OpenJDK code: fdlibm::{log, log10, cbrt, hypot}
from OpenJDK's FdLibm.java (GPLv2 with the Classpath Exception),
aarch64::{sin, cos} from HotSpot's AArch64 trig intrinsics (GPLv2), and
round_f32 from OpenJDK's Math.round(float). Horizon is MIT.
Rewritten in a two-person clean room from Sun's fdlibm 5.3 C sources, a
behaviour-only description of the AArch64 sin/cos, and the Java SE API
documentation:
- jmath/fdlibm.rs (new, Sun's notice at the top): log, log10, cbrt and
hypot ported from e_log.c, e_log10.c, s_cbrt.c and e_hypot.c; sin_fma
and cos_fma, fdlibm's sin and cos (s_sin.c, s_cos.c, k_sin.c, k_cos.c,
e_rem_pio2.c) with fused multiply-adds, as Math.sin/cos on AArch64.
- jmath.rs: round_f32 from the Java SE specification of
Math.round(float); the module doc says what each platform computes;
jmath::fdlibm and jmath::aarch64::{sin, cos} keep their paths.
- geometry/trig.rs: Sun's notice above its fdlibm asin/atan/atan2 ports.
- THIRD_PARTY_NOTICES.md: an fdlibm section.
Bit for bit against JDK 25: StrictMath log, log10, cbrt and hypot on
2.5M, 2.5M and 2.7M inputs and 5.9M pairs; Math.sin and Math.cos on an
Apple M4 on 3.05M arguments, compiled and -Xint; Math.round(float) on
all 2^32 floats.
Refs #6741
Co-Authored-By: Claude Opus 5.5 <[email protected]>
THIRD_PARTY_NOTICES.md gets a "Code removed for its license" section for
anyone holding an older version: the shader pack code written from Iris's
source (LGPL-3.0, removed in #1561) and the math functions translated from
OpenJDK and HotSpot (GPL-2.0, #6741). In those versions that code is not
covered by Horizon's MIT license.
CONTRIBUTING.md says OpenJDK's own source is GPL too, so it is off limits
like any GPL project; Java's behaviour comes from its documentation, a JVM's
results, or a permissive original such as Sun's fdlibm.
docs/how-horizon-is-built.md no longer says Horizon holds no Minecraft data
and never downloads it: the datafix tables come from vanilla's code, and the
launcher downloads 26.3 from Mojang for a signed-in account that owns it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without a skin the launcher can read (signed out, offline), the Shop's pictures
and its 3D view drew the plain grey figure. They now use the stand-in the
website's shop draws (its standInSkin, original art painted from rows, not
Mojang's Steve or Alex), so both shops show the same player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In a frozen level, water a command placed in newly forceloaded chunks
before the next world tick never flowed: the chunks' tick containers
were only registered by tick_blocks (a running world tick) or by
postprocessing, so the water's flow tick found no container and was
dropped. Vanilla registers them when the chunk becomes a full chunk
(LevelChunk.registerTickContainerInLevel), before any command sees it.
update_world now runs the existing sync_tick_chunks once the streamed
chunks are resident and the unloaded ones are saved with their ticks,
so registration comes before the tick's commands and world tick. Only
an authoritative game (not a server's client) registers.
Fixes#11873
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block entity's faces were baked with `minecraft_mix_light` and their
normal left zero. A shader pack's `gbuffers_block` (or its fallback)
receives the unshaded colour and the face normal from Iris, then applies
its own lighting, so Horizon's baked shade was applied a second time:
chests, shulkers, banners and the rest came out darker than the terrain
around them (#8390).
`draw_part` now emits the tint alone and the face's world normal
(`normal.w` = 127), and the entity shader applies `minecraft_mix_light`
from it, as it already does for mobs. Lights that are not the level's (an
item preview's `Lighting.Entry`, which the entity shader cannot know)
stay baked into the colour with `normal.w` 0, which is the path
`game/item_draw` gives block-entity items in inventories and hands.
The parity harness compares the tint and the face normal; the loom's GUI
preview keeps its baked `ITEMS_FLAT` colour. The chest cache test now
checks the nether flag reaches the shader instead of changing a baked
colour, so the cached geometry serves both dimensions.
Fixes#8390
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
🤖 Agent: pictures are drawn on background threads and cached by item hash and screen scale, so the Shop never renders on the UI thread; prices and the checkout call are the same rules as before (two live checkouts cancelled via Paddle).
Entity.applyEffectsFromBlocks runs in the entity's own tick, and
ServerLevel.tick ticks only entities in entity-ticking chunks, so a mob or
item past the simulation distance presses no plate and is not hurt by a
cactus. tick_blocks swept every loaded entity: 2.5 ms a tick on the SMP,
where ~2,900 of 4,600 overworld mobs sit outside entity-ticking chunks.
Fixes#11881
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Carry the already selected mob through an optional creation-only level hook. Preserve existing entries and the generation initializer and RNG order.
Co-Authored-By: Codex <[email protected]>
A pack that adds font providers over the game's own can leave the overlay without the vanilla font sheets its merged font/default.json names. Every glyph then became the missing box. Read a bitmap sheet, TrueType file or Unihex file the overlay lacks from the game's own root, as vanilla's resource stack keeps the game below every pack. Font definitions stay the overlay's, so a pack filter can still hide one.
Fixes#8875
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A pack text shader may declare colour outputs past location 0. The HUD's
pipelines have one target, so they went nowhere, but the coverage wrapper kept
them, and one at location 1 collided with the coverage and dropped a shader
that works today.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ludovic (2026-11-07): "In the launcher you just don't see the items." The Shop
now opens on the season pack across the top (its looks drawn side by side, name,
item count, price, Buy), then rows of big tiles: Looks, each collection's items,
the founder set and the Supporter badge. A tile is the item drawn, its name and its
price; it lifts under the pointer. A tile opens it big on the account's own player
in 3D (an emote plays), with a look's items, Buy and Gift.
- Pictures: the Wardrobe's CPU renderer, framed on the item (preview::fitted_view),
at device pixels supersampled 2x, on background threads, kept by item hashes.
- Buy and Gift are one checkout of that offer through the same web checkout;
prices still come from the catalogue and the service's set rules.
- Cut: the page's line, the basket and its notes; three new strings (Buy, Gift,
Looks) in all 86 tables, nine unused ones removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
items.rs tests: keeps this branch's tool-rules test and main's command
loot zero-count test, which both landed at the same place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
end.rs: keeps this branch's live gateway items; main's only change there
(item_entity_to_nbt_with_items) was inside the saved-item path this
branch removes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: opens the code page in the default browser's private window (flag per browser, pure-function tests); no Azure change, Safari/unknown get Copy link + hint
Pure move (#11857): defer_world_entry to join_world_rest (the deferred world entry, world creation and rest polling, the world's item/recipe/station tables). Private methods become pub(super), the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): the creative screen's tabs, pages, tooltips, trash, search and clicks. Private methods become pub(super), the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move (#11857): look_options to regrab_on_refocus (mouse look, pause when inactive, held keys, the pointer grab and cursor icon). Private methods become pub(super), the same reach they had in game.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A bitmap provider's file may be any texture, and HUD packs keep sheets outside textures/font. HudAtlas already received the sheet from the font commit, but only if the texture was packed: WALKED_TEXTURE_FOLDERS did not include custom paths such as textures/lumiere/hud, so those sheets still fell back to per-glyph run-time copies. Collect every bitmap provider's file while building the atlas, deduplicated against the walked textures, so its glyphs use the sheet's atlas rect.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A bitmap provider may name any texture, and server HUD packs keep their glyph sheets under their own namespace or a path such as textures/lumiere/hud. The HUD atlas already holds those textures, but Font only looked up sheets under font/, so every glyph was decoded from disk and copied into the 512-row run-time region one by one. When that region filled, a glyph became the 6-pixel missing glyph, shifting the rest of the HUD line.
Look the sheet up by its atlas name for every namespace and keep every atlas sprite available to the font. Glyphs now use the sheet's atlas rect, so their advances come from the original texels and the run-time region is not involved.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
In PQ output the debug view mixed toward PQ (1, 0, 1), so any covered pixel
blew out. Mix toward BT.709 magenta at the paper white instead, encoded like
the picture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon-release-sign gets `native_build`: a build's request (target, base commit, each mod's
id, version and registry file SHA-256, sorted) and its key, the release and asset names in
horizon-mods, and the signed message `horizon native build <key> <target>\n<sha256> <asset>\n`,
which no release or Nightly signature can pass for. The keys are their own
(native-build-keys.txt): horizon-mods' `native-builds` environment holds the private one,
because the registry compiles other people's code there.
tools/ci/sign_native_build.sh signs with OpenSSL as sign_release.sh does, refuses a key whose
public half isn't listed, and checks its own signature; tests sign with it and verify as the
launcher will.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The device code page goes ahead with the account the browser is signed in to.
Open it in the default browser's private window (Windows ProgId, macOS
LaunchServices, Linux xdg-settings), or give the link to copy, or sign the
browser out of Microsoft first. No Azure app change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/place feature refused every inline feature value with "An unexpected
error occurred", although vanilla 26.3 places it like a named one
(PlaceCommand.placeFeature over a ResourceOrHolderArgument value) and
reports commands.place.feature.success.inline.
The inline value is read with the existing SNBT-to-JSON pair, decoded
by the same feature codec as a named feature (feature_with_blocks,
uncached), and placed through the same Feature.place path with the
level random. A feature that does not place reports
commands.place.feature.failed; a value that does not decode keeps the
plain failure.
Fixes#11843
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HORIZON_COSMETICS_CATALOGUE=<file of GET /v1/catalogue> gives the development preview
real prices, sets and collections, so Shop screenshots show what players see.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With HORIZON_HDR_FRAME=float, HDR on and no shader pack, on devices with independent blending, each
HUD and menu pipeline gets a variant with a second output into an R8 target: how much of the world
behind it the draw hides (docs/design/hdr-linear-light.md, The HUD and menus). The fragment shader
is wrapped as it is, so the primary colour, its blend and alpha are unchanged, and the coverage
follows the draw's final colour and blend: layering by its alpha, additive draws not at all,
darkening ones by how much they darken, inverting and replacing ones fully. HORIZON_HDR_DEBUG=coverage
tints it magenta in the last pass; nothing else reads it yet. A resource pack text shader without a
colour output is dropped as an unusable one is, instead of panicking.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PvP & HUD, Replay Studio, the music page, Browse Mods, the resource pack
browser, cosmetics and emotes, moderation and tempban, the controller page,
graphics tooltips and recipe transfer. Placeholders, leading and trailing
spaces match en_us; durations use abbreviations (хв, год, дн.) so one form
reads right for every number.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: requeued at 8cd24bc4e5: the merger stopped on a main conflict, and the author's main merge resolves it. verify is green on this head; the run-parity label reruns parity on it before the merge.
The GL backend reported MAX_TEXTURE_SLOTS (16, WebGL2's minimum) as max_sampled_textures_per_shader_stage even on desktop GL, where drivers usually offer 32. A native shader pack's sky pipeline layout declares the terrain's 10 sampled textures and the frame's 9, so it was refused at 16 although the driver accepts it (#10095).
Query GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS and GL_MAX_TEXTURE_IMAGE_UNITS, take the lower, and cap at the arrays' new 32 slots (MAX_SAMPLERS likewise). On WebGL2 the query still yields 16.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A layout or pipeline the backend cannot take (a stage's bindings past its sampled-texture limit, say) reaches the device's error scope. A native pack built its frame, stages and passes outside one, so wgpu's validation error hit the default handler and crashed the game (#10095). Build the pack in a scope and return the first error, which the shader pack screen already shows as a refusal.
The test asks for OpenGL's minimum 16 sampled textures per stage and builds a sky-only pack whose layout needs 19: the pack is an error, not a validation panic.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Glow ink text was full-bright in the lightmap only: HDR output and shader packs that light
with their own GI (and read the HDR tier, horizonTier) showed it as dark as plain text.
Its glyphs now carry the low HDR tier, like other things that glow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CI's stable Rust deprecates AtomicU64::fetch_update in favour of
try_update, which the workspace's minimum Rust (1.95) doesn't have, so
clippy -D warnings failed. A compare_exchange_weak loop does the same
on every version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
🤖 Agent: test-only: Flame is Ignite(constant(100)) seconds, so 2000 ticks; a_flame_crossbow_shoots_burning_arrows passes with prepared resources on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
🤖 Agent: a re-angered suspect keeps its saved anger as increaseAnger does (test now expects 54); carries #10290 for the shared corpus; sculk, wolf_anger_writes and the save harnesses pass on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
🤖 Agent: added the hit's Glowing from the arrow's own Duration (SpectralArrow.doPostHurtEffects reads this.duration; Horizon used a fixed 200); carries #10344 since both extend the command corpus; spectral_duration and command_exec pass on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
🤖 Agent: merged main; the Health doc line now sits on item_entity_from_nbt next to main's numeric-UUID line instead of above the UUID helper; item_health_load and item_numeric_uuid pass on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
🤖 Agent: merged main, keeping main's arrow_owner_tests next to this PR's saved_tag_tests in data.rs; saved_empty_tags, arrow_owner and the explicit-empty ignored test pass on the union of this gate's PRs on main 1d63e0a5a (playhorizon: fmt, clippy -D warnings, client + client-lib tests, 26 harnesses all pass).
AbsorptionMobEffect.applyEffectTick, asked on every tick, answers false
when the absorption amount is 0, so MobEffectInstance.tickServer drops
the effect before its duration counts down, finite or infinite. A mob's
effect tick had no Absorption case: a used-up Absorption stayed until it
expired. It now ends through the existing removal path; other effects,
infinite ones included, are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Older packs write expressions in the legacy syntax: uppercase variables
(AGE, DISTANCE_SQUARED, RAIN...), pi and e, and bare rand(), gaussian()
and noise(). Each variable now runs as the scripting call the wiki maps
it to, and a legacy expression keeps the old argument order of lerp,
smoothstep and color. The color functions (red, color, withAlpha,
lerpColor, hsb, lerpHsb...) follow vanilla's ARGB channel rules, and
dist/distSquare join the math table. All expressions of Visual
Effects+ now compile.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: F1 stays the vanilla two-state toggle and resets the mod's hand override; set_hud_mode is a no-op on servers and during a guest's borrowed turn; hud_mode helper fragment passes tests.test_stamp_native_hooks with main merged in (union of 74 heads on main: fmt, clippy -D warnings, tests green).
Expressions such as `o.age() * 0.1`, ternaries, `&&`/`||`, strings,
local variables, `if` blocks and calls on context objects are parsed
once when the pack loads, with every name resolved, so a typo is a load
error. A Host answers the context objects (o, p, c, g, r, v), their
methods and the game's global functions; the math functions follow
Java's Math. 1461 of the 1520 distinct expressions of Visual Effects+
compile; the rest use the legacy uppercase names, which come next.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An attributes_modifiers object with a group (`base`...) no longer refuses
the file over a key that is none of the modifier's groups: it is left
alone, so a pack Polytone loads isn't rejected over an entry neither reads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mob loaded or summoned with FallFlying:1b kept the flag even when it
could not glide, and a gliding mob's elytra never wore. The flag was
only the raw tag's value: the loader did not read it, the save only
supplied a default, and the mob tick had no updateFallFlying step.
- mob_from_nbt reads FallFlying into the mob, and mob_to_nbt writes the
mob's current flag (LivingEntity.read/addAdditionalSaveData).
- The mob's aiStep, with or without AI, runs updateFallFlying while the
flag is set: canGlide (ground, vehicle, levitation, a glider whose
next damage does not break it) clears the flag; every 20 glide ticks
Util.getRandom picks a glider from the mob's own random.
- The game applies that wear with the existing durability path
(Unbreaking from the level's random) to the mob's kept stack.
- LivingEntity.tick counts fallFlyTicks after aiStep.
Glide travel, checkFallDistanceAccumulation and the ELYTRA_GLIDE game
event are not part of this change.
Fixes#11809
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/parity.py runs every harness with no arguments, so the harness's
required --work-dir made it fail on every CI run before reaching the
oracle. Without the flag it now captures into a fresh
.local/function-tags-parity; an explicit --work-dir must still be new.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
hand_geometry: keeps this branch's ui.hands_drawn (native HUD modes) and
main's rule that a studio replay never draws the hands.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
game.rs: keeps this branch's wax_on_sound (honeycomb) beside main's
transform_copper_chest_neighbour (axe/hoe/shovel transformers, #33ec06ab12).
test_use_item_on_parity.py: keeps both the honeycomb double-chest cases
and main's block transformer cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
view_distance and chunk_center come straight from Login,
SetChunkCacheRadius and SetChunkCacheCenter, any i32 a server sends.
remote_chunk_in_range added 3 to the radius and subtracted the centre,
and the streaming radius and sent_radius added 1: a server sending
i32::MAX or i32::MIN panicked a debug build, and in release (no
overflow checks) the wrapped range dropped every loaded chunk.
The storage range is now in_storage_range: the same ClientChunkCache
rule (radius at least 2, plus 3), with a saturating add and abs_diff,
so nothing changes for any radius a real server sends. The +1s
saturate too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Keeps both imports in command_exec_corpus.groups(): this branch's
player_food_predicate_corpus and main's treasure_map_copies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each distinct pack selection gets its own overlay folder under
.horizon-cache/resourcepacks, holding a full unpacked copy of its zip
packs, and nothing removed old ones. Every server pack, and every new
version of one, left a permanent copy the player can't see: ten
servers with 100 MiB packs kept about a gigabyte.
resource_root now marks the overlay it returns as used by this process
(a shared lock on <overlay>.overlay-cache.use, held until the process
exits, whose time is the last use), then keeps the 8 most recently used
overlays in the folder and deletes the rest. An overlay another
process uses or is building is skipped, so two games sharing a game
folder never pull one out from under each other; the marker goes before
the folder, so a half-deleted overlay is never reused. The lock files
stay, as build() already requires.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
After the encrypted join, the guest offered the session on a random
127.0.0.1 port for 30 s and handed it to the first loopback connection.
Any other local user or process that found the port first got the
authenticated session to the host's server, and a web page aiming
fetch at local ports could send HTTP into the tunnel and make the
player's own join fail.
serve_loopback now returns a Loopback with the port, a random 16-byte
token and the thread. A connection must send the token within a second
of connecting (compared in constant time); anything else is dropped and
the port keeps waiting until its deadline. The client's join_friend
passes the token on, and run() writes it raw before the handshake.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Light written into the frame can't be taken back out from under the HUD, and without the tone curve it clips at
the peak, so step 3 now holds the HUD's own layer and the tone curve, and step 4 the shader groups. The look
target names the differences allowed (rounding, light behaving as light under glass, water and anti-aliasing)
and the artifacts that aren't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SpectralArrow.doPostHurtEffects builds the effect from this.duration, which this PR now loads and saves; the hit still used a fixed 200.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts kept from both sides: crates/horizon-client/src/entity_nbt.rs tools/command_exec_corpus.py tools/test_command_exec_parity.py
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parity.py and parity-main call tools/test_*_parity.py without --binary or --work-dir, so the harness failed in 0 s there. Use harness_args.ensure_work_dir, as a98ba0273 did for the dolphin and spell-ticks harnesses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In PCP and NAT-PMP a lifetime of 0 means the mapping is deleted, but a
success reply carrying 0 was taken as a mapping. Mapping::due() was
then always true, so the host renewed on every 100 ms wake-up of its
loop (a UDP exchange each time, under the reach mutex) while
advertising a direct address that no longer existed.
pcp_map and natpmp_map now refuse a zero lifetime when they asked for
one (a delete, which asks for zero, still expects zero), and due()
never fires sooner than 30 s after the last renewal, so a router that
grants a few seconds isn't asked again ten times a second either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Applying packs builds an on-disk overlay: every file under assets/ in
every zip pack was inflated (up to 1 GiB each, whole in memory) and
written out, on every worker thread at once. A gigabyte of zeros
deflates to about a megabyte, so a server pack at the 250 MiB download
limit could write some 240 GiB to the player's disk and hold a 1 GiB
buffer per thread, once the player accepted the pack prompt. Vanilla
never unpacks a pack.
Zip::copy_to now streams an entry to its file 64 KiB at a time, taking
each piece from a byte budget shared by the whole build: 16 times the
zips' size on disk, at least 64 MiB, at most 8 GiB. Past it the build
fails with FileTooLarge (so the reload drops the server packs, as for
any failed build) and the half-written overlay folder is removed. An
unreadable entry still costs only that file. Memory per thread is now
the compressed entry plus one piece.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
🤖 Agent: Realms subscription URL (subscriptions/$WORLD_ID), JSON-bool settings, InviteCodeList array/result parsing and the JSON Content-Type now match the 26.3 RealmsClient; same Microsoft session, no other auth (Mojang rules OK); footer buttons wired to the selected code. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
NeoForge 26.1 has neither. A tooltip is drawn from a style's textures, picked
in RenderTooltipEvent.Texture, and the anvil take is AnvilCraftEvent, whose
.Pre comes before the inputs are used and the anvil is damaged (no break
chance). `events` now looks the 1.21.1 names up under those, so ATM10's 6
Color and 4 AnvilRepair users are answered by the hooks that exist.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla Entity.getAirDrag is 0.98F. PrimedTnt, FallingBlockEntity and
ItemEntity scaled motion by the f64 0.98, so after four ticks a TNT's
Motion x was 0.092236816 instead of 0.09223682318…. Item underwater
and lava drags, LlamaSpit air drag, and hurting-projectile inertia
were the same class of float-widened constants.
Fixes#6814
Co-Authored-By: OpenCode <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
🤖 Agent: #11443 by GabrielNichols with main merged (its fork refuses pushes): the Recipes panel keeps its editable search and query when a recipe returns to an empty result, and opens in Creative inventory. The only resolution is THIRD_PARTY_NOTICES, where the pinned JEI paragraph now names the GUI sprites under the MIT LICENSE.txt shipped beside them. Verify and launcher green; clippy -D warnings OK.
The atlas holds 256 map pictures. Every framed map in range claimed one, seen or not, so past 256 the maps took slots from each other within one frame and showed each other's pictures. Now a frame's outline is culled before its map claims a slot, the nearest claim first, and a slot shown this frame is never given away: past the slots, the far maps go undrawn.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Drives the real goal selector on a built test world: hurt the frog with a
PANIC_CAUSES damage, tick the goals once, and assert the panic goal started
and left vanilla's 2.0 in `Navigation.speed_modifier`, the value
`SmoothSwimmingMoveControl` multiplies. Reverting the constant fails it with
1.25 against 2.0.
`FrogAi.initCoreActivity` builds the brain's core activity with
`AnimalPanic(2.0F)`, but the goal list used 1.25.
`PanicGoal.start` hands the multiplier to `PathNavigation.moveTo`, and
`SmoothSwimmingMoveControl.tick` computes
`speedModifier * getAttributeValue(MOVEMENT_SPEED)` and then multiplies by
0.1 on land or 0.02 in water. A frog's movement speed is 1.0, so a fleeing
frog should move 0.2 blocks a tick on land and 0.04 in water; at 1.25 it
moved 0.125 and 0.025, 60% slower.
push_from_ticked_cart cloned the ticked cart into the list and back each tick,
and with it a chest cart's rolled loot: its whole Items list as NBT. The stored
cart already goes without the loot for the tick (tick_carts_now takes it), so
the copies do too and the loot stays with the ticked cart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: discord, friends and heartbeat become default-on Cargo features (compile gating only, no new network call), so the --no-default-features server build drops 14 crates; main's newer native_client_services invite code is gated too. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: harness-only. Kills items and corpses between cases (the drop leak between the cow and item cases), and lists server_wool_vibration as known_flaky for the real-time player timing (1 of 4 on a loaded playhorizon).
🤖 Agent: exempts only STAB (action 8) from #11223's move hold, so a release still shoots from the position before the move (#8423). lunge_motion: parent 3/3 pass, #11223 3/3 fail, this branch 3/3 pass on playhorizon.
Show the newest build with a Latest badge, present Nightly dates in a player-friendly title, and separate older builds on the Versions page.
Co-Authored-By: Codex <[email protected]>
🤖 Agent: es_es now covers all 898 en_us keys (four recipe-transfer strings added after main grew), placeholders checked key by key; the old red verify/launcher was the horizon-checks-1 runner out of disk. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
#7676 and #9222 each added Game::native_level_play_sound and
native_level_send_particles; the mod API generator needs one of each,
so every build on main panics in build.rs. The host-bridge pair becomes
native_play_sound_in_place / native_send_particles_in_place; the names
mods call (play_sound, send_particles) are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add a direct Unbreaking and Mending durability comparison over the real
enchantment registry: item_damage remove_binomial (small and large paths)
and repair_with_xp, with --no-mending reproducing the Unbreaking corpus.
Brings in #11026 and #11534. docs/controllers.md keeps the guide's slider
hold beside main's server-list bumpers and Playing in the background.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#11223 made every PlayerAction hold the move queued behind it for the next
tick. For a spear's STAB that moved the jump the move carries
(handlePlayerPositionChange's jumpFromGround) a tick after the Lunge's
impulse, and the server took two moves in the next tick: lunge_motion has
failed every jump phase on parity-main since. Releases, drops and digs keep
the hold.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Extras moves onto the new settings_shell (one row per setting, search, description strip), same items and saves; design-system.md now lists Extras with Graphics/Controller as Blocks-frame pages; size (+1748) accepted because ~860 lines are the shell the next pages reuse (#11487 plan). (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: Requeued after merging main twice (lan_e2e.py keep-both: this PR's entity-die check beside main's new mob-thorns check; then #11735's clippy/lock fix); verify green. Breaking a painting, cushion, boat, minecart, frame or armor stand emits ENTITY_DIE like vanilla's kill path, and its sculk/vibration harnesses passed on the earlier head.
The 1.21.8 and 1.21.1 explode steps re-read a payload the renumbering had already put in the client's ids with
the pinned codec, so the particle's layout was chosen by the wrong id and sending any explosion failed. They now
read and write through the client's ids. 1.21.1's step read the knockback as three doubles where 1.21.4's step
leaves a double and two floats. 1.21.1's strength is 26.3's radius instead of a fixed 3.0.
Fixes#11721
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Unbreaks main: clippy 1.99's manual_is_multiple_of in horizon-core (from #10553) and the socket2 0.5.10 entry missing from both lockfiles under --locked; both workspaces' cargo metadata --locked and clippy -p horizon-core -D warnings pass on this head.
Same break as the workspace lock: #11464 and #11569 left "socket2 0.5.10"
referenced with no package entry, so main's launcher build fails under
--locked. Regenerated with cargo metadata; nothing else moves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
clippy::manual_is_multiple_of is in the stable toolchain now, and the workspace clippy step runs with -D warnings,
so main's own horizon-core fails verify before any PR's code is compiled.
Ensure advancement parent references and command lookups handle both bare
and namespaced identifiers, preventing orphaned advancement nodes and
matching vanilla's progression count on '/advancement grant @s everything'.
Co-Authored-By: Antigravity <gemini> <[email protected]>
🤖 Agent: Requeued at the author's main merge 552f2c1b4: same net patch as d8676fff0 (identical patch-id), whose parity failed only command_exec's 26 /say lines that #11687 has since fixed on main. The change: shader packs in the End draw the sky fan under the dragon fight's fog like the rest of the pack's sky.
🤖 Agent: a refused START_FALL_FLYING now flips the glide bit of sent_shared so a flags update goes out, as LivingEntity.stopFallFlying's set(true)+set(false) dirties the flag in vanilla (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: the Patch notes tab's three orphan strings are gone from the 86 lang files, so the launcher's i18n tests pass (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: /damage ids now come from the world's damage catalog and are replaced on each world open, so a pack's type doesn't leak into the next world (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: the single-player memory connection sends every ready view column in one batch before PlayerLoaded (about 0.5 s saved at render distance 12), then the 64-per-tick cap comes back; socket guests keep vanilla pacing. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: new client-only, read-only native hook chat_suggestion_applied fires after Tab or a click applies a suggestion, with the replaced range in UTF-16 units of the list's original input; fragment stamps cleanly on main. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: port-pack events answers VillagerTradesEvent/WandererTradesEvent with the trade_set and villager_trade registries 26.x moved trades to. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: Studio playback of .mcpr recordings through the normal client handlers (B1 slice of #9712, +2120), only active when a recording is opened; F1 now hides chat and tab list as vanilla Hud.extractRenderState does. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: new horizon-polytone crate reads Polytone targets and biome/dimension attribute layers from the public wiki only (Polytone is GPL, its source not read); no caller yet, first step of #11486. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: the hook runs after the vanilla shuffle so RNG draws are unchanged and a no-mod dispatch is an empty loop; fragment wind_burst passes tests.test_stamp_native_hooks and --check --only on main merged in (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: Studio rows and buttons now register as focus targets and the wheel moves one whole 40 px row per notch, which also stops part-scrolled rows drawing over the title and makes the last recording reachable. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: only block/ and item/ paths equal to the old id are renamed, so a mod's own pulley:block/chain and chain_command_block_* stay, and files are rewritten only when something changed (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: recipe suggestions reuse json_ids_of_every_namespace like /loot, and the dead ids_under goes (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: The unsupported placed features are now named in the log (capped at 20), which is what a pack author needs to fix them. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: under TAA the flame gate now uses a frustum widened by one rendered pixel per side (jitter is at most half a pixel: 2× margin), one extra frustum per frame, shader packs still bypass. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: with entity culling off the decision is Bypass before any bounds are read, so the posed-envelope walk over every model point of every copy is now skipped (TAA condition dropped), RNG order of cluster_poses pinned by a test. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: TCP keepalive (10 s + 3×3 s) now ends dead friends tunnels and relay bridges in ~19 s instead of holding them until the 120 s idle; the stack buffers replace one Vec per connection, not per read. (union of 74 heads on main: fmt, clippy -D warnings, tests green).
🤖 Agent: Chunk-section block writes now do one position lookup instead of two on the worldgen hot path; output unchanged, and CI parity on main+head (worldgen, structures, light and the save harnesses it selects) passes in full.
A /kill-ed cow stays 20 ticks (LivingEntity.tickDeath) and still moves;
the fill that removes the carpet or slab under it drops it onto the floor
while the next case measures. item-slab read power 2 before its summon on
vanilla's side in a playhorizon run. Wait 25 server ticks after the kills.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GitHub's runner moved its binaries to bin.<version>/ after a self-update, so
`pgrep -f "$runner/bin/Runner.Worker"` never matched: runner-gc took busy
runners for idle and deleted the probe builds and old prepared resources of
two running parity jobs (2026-11-07). Match bin*/ in runner-gc,
checks-runners.sh and linux-runner.sh.
runner-gc also counts shared-local/parity-target (19-24 GB per runner),
recreates it empty after removing it (a dangling .local link failed every
harness), and drops harness folders over 2 GB untouched for 6 hours.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Extend the spawn rule suite with Mob.checkSpawnObstruction: real entities
against water, lava, an obstructed level and Ocelot's special base, with
--no-obstruction reproducing the previous corpus.
The arena's /kill @e[type=!player] kills the previous case's cow, and the
cow drops beef and leather as it dies, after the selector was evaluated.
Those items fall onto the next arena and can hit the floor while the next
case measures, at a wall-clock moment: parity-main 42359055402 read power
2 before item-stone on vanilla's side, and playhorizon read 2 after
item-carpet in 1 of 3 runs at 67902962fd. Both cases follow a cow case.
A second /kill @e[type=item] removes the drops; setup lines aren't compared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/test_warden_memory_full_diagnostic.py is not a test_*_parity.py harness, so nothing runs it, and it is 816/839 by design. The canonical owned-memories harness stays. Drops the docs/parity run logs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: fixes main's red clippy since #11561 (1.95 lints on interpolation.rs: is_multiple_of on non-zero unsigned divisors, as_chunks_mut::<4> quads); workspace clippy clean on main + this.
is_multiple_of on a usize cell size, and as_chunks_mut::<4> for the four
columns' quads (each quad is now a [f32; 4]).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pose test asserted that Temper lands in the legacy variant3 byte. #11483/#11659 move every equine's temper to the signed equine_temper field; the assertion was outside this test's subject (LastPoseTick), so drop it rather than pin the storage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both PRs added a signed AbstractHorse temper: #11483 for the seven horse and llama species, this one for camels. Keep one field, equine_temper, for every equine: the loader reads Temper unclamped for all of them, temper()/modify_temper() use it for camels too, and the camel saver writes it. The camel_temper field and the camel byte-clamp path are gone; tests follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Native mods can draw terrain models with an explicit material (shade, UV shift); the shading follows Flywheel's (MIT, credited). Main merged in after #11117 landed; its test now builds faces with #11356's BakedFace::new (clippy -D warnings passes).
FogState now holds the torch flicker's LegacyRandom, so main's rain fog
test (settled_rain_fog_is_its_target_and_easing_is_unchanged) moved
`start` twice; verify failed on E0382.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LevelExtractor.setupRender calls allChanged when the effective render
distance changes; LevelRenderer has no allChanged in 26.3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's backup screens (#9982) came in beside the invite codes screen.
The footer's Copy/Delete/Edit used ids 180-182, which no arm handled (the
100..=200 arm read them as rows 16-17) and which main's Backups arm
(170..=189) now takes first: they carry the selected code's row id
(130 + 3 * row + Copy 0 / Edit 1 / Delete 2) instead. Request.Post's
Content-Type is application/json; charset=utf-8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The brewing stand's fuel slot and quick-move both read the brewing_fuel component of the stack, as BrewingStandMenu.FuelSlot and quickMoveStack do.
#11631 adds saved_piece_id (PiecesContainer.load's RENAMES) to the same file; read_jigsaw_start now asks it instead of its own copy of the four old ids. The upper-case id case is dropped from the test: Identifier.parse refuses upper case, so vanilla never reads 'MINECRAFT:JIGSAW' as a jigsaw piece. Land #11631 first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main added invite_public_part (horizon_friends::invite) after this branch
was cut; without the feature the server build's clippy
(--no-default-features --all-targets) could not resolve it. A friend's
world then shows no part of the invite, and the invite test needs the
feature.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's #11661 added LevelAccess::container_open and its test for the open copper chest; this branch keeps that name and its test and drops its own container_has_viewers, folding the copper golems' chests into GameLevel::container_open. The recheck_container_openers doc comments #11661 left stranded above container_open are back on their function.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
load_guest_records read stats/<uuid>.json directly, and on the server thread (the default single player) and on LAN and dedicated servers every player joins through it, so locateStatsFile only reached the in-frame path. The guest's own name goes to locate_player_json.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Identifier.parse reads 'nvi' as minecraft:nvi, so RENAMES applies to a bare id too. The new saved_piece_id had been inserted between tag_json and its doc comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: older clients 26.1→1.20.5 behind accept_older_clients (off by default); a 26.3 client never reaches ClientSession or the components_before_1_21_6 rewrites; per-step unit tests + lan_bot joins one bot per release; doc of older_client fixed.
Add a direct parity suite for the landing branch of
FallingBlockEntity.tick: real falling entities into air, solid blocks,
water, plants, slabs, cobwebs and moving pistons, with concrete powder's
water contact and the anvil landing event behind --no-contact.
🤖 Agent: attributes only, no code removed; the 30 kept allow(dead_code) now carry a reason (19 native mod API). Windows/macOS-only code is the risk, CI's other platforms cover it.
Extras moves onto settings_shell, which draws with graphics_style, so the
doc names the three settings screens that take the Blocks palette.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: socket2 0.6 keeps every call direct.rs makes (set_reuse_address, the unix-only set_reuse_port, set_only_v6, connect_timeout), so the friends socket code needs no change; lock loses 11 packages.
🤖 Agent: one workspace rust-version (1.95, which horizon-mods already needed); the second commit is clippy's let-chain/as_chunks/is_multiple_of rewrites, all on unsigned values with non-zero divisors, so no behaviour moves.
🤖 Agent: near sections no longer dither over far terrain (fade() is 0 or lod::HELD), and the replaced tile still stays behind for lod::FADE through previous, so no hole opens; the test bound lowered to 1,000 matches the half that now come in whole.
🤖 Agent: respawn_world_point and disconnect_crafting reuse generate/world only after a generate/finished marker, written after server.stop(); an old half-made world regenerates once.
🤖 Agent: LocatorBarClient.arc now flags a catch-up tick only before the jab (tick < stabTick); a catch-up right before it still changes the jab's motion, which the harness compares with vanilla, so lunge_motion keeps its signal.
🤖 Agent: drops the harness/probe/field counts from AGENTS.md and CONTRIBUTING.md; the commands and paths around them are unchanged, so nothing a reader runs moves.
🤖 Agent: horizon-ctl screenshot-sequence takes repeatable frames: each waits for the GPU (30 s at most) and for the pack's centre-depth copy, and F2 is refused while a sequence runs. Lands after #11238.
by_registry outlives a world, so a pack's type stayed accepted by /damage in
the next world opened. Same as sync_enchantment_registries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The flame culling doc now says TAA uses the widened frustum; the run
notes and samples stay in the PR body (run logs are not docs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep main's fall_step(entities) signature with the air guard first;
keep main's vibration_tests module beside the new test module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
entity_cull_decision_in_bounds returns Bypass for EntityCullingMode::Off
before reading the bounds, so the TAA condition only kept an unused walk.
Drop the docs/performance run notes (run logs are not docs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: mobs path-stop at shulkers and a held-still happy ghast; earlier parity failures were env + main's /say regression (#11687 fixed it); CI parity reruns with run-parity before landing.
#11532 and main since make loaded_pig_tags, loot_mine_pot_components, mob_discard
and command_exec run under a plain parity.py call, which is what ccb85ce45 and
aae27797d did here. Main's versions replace them, so this PR keeps only its own change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#11532 and main since make loaded_pig_tags, loot_mine_pot_components, mob_discard
and command_exec run under a plain parity.py call, which is what ccb85ce45 and
aae27797d did here. Main's versions replace them, so this PR keeps only its own change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: raid wave positions (new and cached) now need an entity-ticking chunk in entity ticking range (ServerLevel.isPositionEntityTicking), not just a loaded one; raid, raid_village and raider_celebration harnesses passed.
🤖 Agent: the player save now writes and reads the recursive hidden_effect chain (Poison I under Poison II resumes after a reload); SavedEffect became a struct, guest data uses the same path; active_effects, effect_sync, player_effect_amplifier and status_effect harnesses passed.
🤖 Agent: HORIZON_STILL now settles a frozen world's rain, thunder and rain fog at the level instead of easing them; it only runs with that dev variable set.
🤖 Agent: The shader pack reset (both kinds) lands the in-flight frame before replacing targets, so the next frame draws as a fresh pack's first; dev tooling, no change without the reset command. #11239 follows it.
🤖 Agent: Shader pack shadow settings are now read from the stage that declares them (fragment const settings), with main's noiseTextureResolution kept on the formats stage; only shaders/ packs see a change.
🤖 Agent: Native mods can draw a still-fluid volume in a block; the fluid shading follows Ponder's FluidRenderHelper (MIT, credited in THIRD_PARTY_NOTICES) and nothing changes without a mod calling it.
🤖 Agent: /raid check now finds a raid still counting down (getRaidAt has no started filter), and /raid sound plays the horn only for "local" at volume 2 with the level random's seed; raid and raid_village harnesses passed.
#10523's settings gear now takes its width from the Recipes search box of
this layout; both Escape paths (mod_browser_back, close_transferred_recipe)
kept; one JEI notice for the sprites and the icon.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The commands' minecraft:damage_type ids came from the vanilla export
alone, so /damage refused a type from a content mod or a world's data
pack with argument.resource.not_found. Vanilla's ResourceArgument looks
the id up in the server's registries, packs included.
build_registries now adds the damage catalog's types to the registry's
ids after the export is applied, keeping the export's tags.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The viewer's transfer is a separate server-side path: it only moves ingredients the player already has, leaves grid and inventory untouched when one is missing or the inventory is full, and never teaches the recipe; the vanilla recipe book keeps its own path. Second of the Recipes series, after #10523.
🤖 Agent: The focused text field is exposed to native mods before the character is inserted, so a hook can veto it; the hook fragments stamp cleanly on main. Main merged in (its ChatScreen is_draft argument added to the test).
Vanilla LivingEntity.onEquipItem posts GameEvent.EQUIP (or UNEQUIP) at
the mob and plays the equip sound at volume 1.0. Horizon only did that
for dispensers, copper golems and the player's armor, so saddling a pig
did not activate a sculk sensor and pig/strider/horse saddles played at
0.5.
Interact, inventory, pickup and /item paths now post the event from the
mob, and saddle/harness/armor sounds play at 1.0.
Fixes#9956
Co-Authored-By: OpenCode <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
Co-authored-by: monkeycode-ai <[email protected]>
A memory connection that has not reported the view loaded now sends
every ready view column in one batch, and the recorded quota is raised
to cover that send so the following tick is capped again. A socket
guest, a player who has already loaded, and a respawn stay on the
64-column quota. Unlit columns stay withheld, the far ring still spends
only the leftover vanilla quota, and the gates that send nothing are
unchanged.
Fixes#6763
🤖 Agent: Transient block entity fields from the update tag reach the native API without being saved; only mods that ask for them see anything new. Main merged in (its new Joined field added to the test).
🤖 Agent: only the automatic count changes, and only where hw.perflevel0.physicalcpu answers (Apple silicon): M4 Pro 13 → 9 workers with no loading or pacing regression over 3 alternating rounds; off Apple the count is unchanged (tested on Linux), and a manual Worker threads setting still wins.
🤖 Agent: #10015's early return dropped /say from chat_capture, so command_exec lost 26 announcement lines; the broadcast now skips only the acting guest (lan_acting), whose copy stays in the capture in order. Console and command-block paths are unchanged. command_exec PASS fresh on the desktop; the control on main 004a39be0 FAILs 16320/16346.
🤖 Agent: a head-on stop on a straight rail no longer blows a TNT cart up (Entity.move zeroes the blocked axis and OldMinecartBehavior never re-applies it), and a glancing hit off its rails now does; tnt_saved_power, tnt_data_adoption and both vehicle harnesses passed.
🤖 Agent: The player-expect move now refuses a full server like a normal join would, and operators still bypass it as vanilla's PlayerList.canBypassPlayerLimit does.
🤖 Agent: 26.2 packet layouts are only used inside a session that negotiated V26_2, and accept_older_clients stays off by default, so a 26.3 client's path is unchanged; the lan_bot tests cover both.
🤖 Agent: The Recipes settings button opens from the inventory browser; the JEI-derived icon is MIT and credited in THIRD_PARTY_NOTICES. First of the Recipes series (#11442 → #11453 follow).
🤖 Agent: only reinforcement_room uses the oracle's light op, so the wait (ThreadedLevelLightEngine.waitForPendingTasks, ticking like settle in a frozen world) changes no other harness's records; command_reinforcements --fresh --repeat 3 PASS 3/3 at desktop load 40-48, where main failed 7 of 10.
Main's thorns check counts events from before the hurt; the PR's helmet
path runs after that count like the plain one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: code-owner approval given; pruning only touches marker-carrying Nightlies, manual/legacy ones are outside the quota (tests cover all four cases).
🤖 Agent: vanilla's CheckerboardColumnBiomeSource sets bitShift = size + 2 with no clamp, and Java's >> masks the distance; wrapping_add(2) as u32 with wrapping_shr does the same. biome_source PASS with fresh Java on the desktop (main + this head).
🤖 Agent: a map crafted from the grid gets Item.onCraftedBy on the taken stack and the scaling recipe reads the level's maps; merged with main's merchant on_take; craft_map_take and merchant_crafted pass.
The new touching_unloaded_chunk was inserted between eye_columns and its
doc comment, which then documented the wrong function.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: dropping the --reference-only assertion makes a plain run go through runner.main()'s full Java/native comparison, and then the strict tags/pot report, so CI now compares instead of exiting in 0 s. mob_discard's default --resources is lab.RESOURCES (the prepared 26.3 folder) and it gets the missing import os. The two sound harnesses take main's #11555 version.
🤖 Agent: data get on a leashed boat now reads its leash from the same save path the world writes (write_boat_leash); merged main twice, boat_leash_view harness passes and the u/3 union builds with clippy -D warnings.
🤖 Agent: breeding now adds one fresh orb of the whole 1-7 roll (no award split, no merge), and XP bottle orbs start 0.25 along the hit face's normal with their motion turned away from it; orb_lava/orb_water/orb_saved_uuid/bed_explosion harnesses passed.
🤖 Agent: OptiFine custom skies draw their layers over the vanilla sky, only when a pack ships optifine/sky and the Horizon sky option is on (a frame without one only clears an empty list); merged main, clippy and the sky tests pass.
The portal parity suite only sent entities through portals: the PortalShape
search that turns an obsidian frame into portal blocks was never exercised.
Add a shape family over real frames and a --no-shape switch that reproduces
the previous corpus byte for byte.
The automatic pool size was every core but one. On Apple silicon the
efficiency cores count too, so a base M4 (4 performance + 6 efficiency
cores) ran 9 workers that kept the render and server threads waiting for a
performance core: fast flight's p99 frame went from 20 ms to 7 ms with 3
workers (#11294). The automatic count now leaves one of the performance
cores (hw.perflevel0.physicalcpu) instead; elsewhere it is unchanged, and
Graphics > Worker threads still overrides it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: a negative Age makes a baby only where AgeableMob.canBeABaby allows (frog, parrot, zombie nautilus, camel husk, slime, magma cube stay adults: box, voice, loot); ageable_integer_load, entity_death_loot and baby_pig_attachment pass.
🤖 Agent: Realms reset-world, templates and upload, stacked on #9982 (land after it); upload goes only to the endpoint the service returns; realms tests pass.
🤖 Agent: Realms backup list and backup info screens on main's Realms code (only the backups commit kept; the carried settings/invite copies already landed); restore asks the service and re-reads the list; realms tests pass.
🤖 Agent: advancements= in a selector reads the selected player's own progress, a guest's included (EntitySelectorOptions on the ServerPlayer); guest_advancement_selector passes on the merge, now runnable by parity.py.
The two reset-world commits rebuilt on the backups branch (both PRs extend the
same configure screen): the scroll wheel covers the product page, template and
upload lists, and the worlds tab has Options and Backup side by side with
Reset below.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This branch also carried early copies of the settings-tab and invite-code
commits, which landed on main as #9926 and #9968 and changed since. The tree
is main with only the backups commit on it, plus the configure screen's
tier fields that main added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#10015 broadcast /say from cmd_say to every guest and dropped the line
from chat_capture. The acting guest still saw it, but the command
harness reads the capture, so command_exec lost every /say line
("[Player] relative dimension", the h154 function's "[Pig] H154
fallthrough") and failed parity-main at ebcfa2263a.
When a guest's command is captured, the broadcast now skips that guest
and the line stays in the capture, in order with the other output, which
is delivered to them as before. The console path is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: roadmap.toml is the source; ROADMAP.md and roadmap.json regenerated (build --check passes). older-servers kept on purpose (1.8 PvP servers), only the sign-in item goes.
Mojang's approval covers Microsoft sign-in only; other account providers
(Ely.by, Yggdrasil servers) are against the rules Horizon plays by, so the
item can't ship. #1270 stays closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThreadedLevelLightEngine runs the checkBlock tasks a /fill queues at most
1000 per update. command_reinforcements fills ~100k blocks around its room
and then requires block and sky light 0 there; on a loaded runner the
vanilla side still read the old light (7, 9, 10 at different positions),
which failed parity-main and 7 of its bisect steps back to 11-05 merges.
The light op now waits for waitForPendingTasks on that chunk, ticking as
settle does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: follow-up to #10129: with glass off, macOS still gets its 32 px title bar (plain side colour), so content no longer sits under the traffic lights.
🤖 Agent: follow-up to #10177: the unmaximize-before-fullscreen step runs on Windows only, and apply() no longer re-requests fullscreen each call while the window reports maximized.
parity.py runs a harness with no arguments; --side was required, so every
automatic selection failed it at once. The default now runs Java, then native
against Java's facts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Raid.findRandomSpawnPos and the cached wave position both ask
ServerLevel.isPositionEntityTicking: an entity-ticking chunk in entity
ticking range. Horizon accepted any loaded chunk, so a wave could spawn
past the simulation distance, where its raiders would never tick.
Part of #11374 (the spawn positions; the raid/spawner order stays open).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: a copper chest someone has open now skips its weathering step (WeatheringCopperChestBlock.randomTick checks getEntitiesWithContainerOpen); random_tick and random_tick_sections harnesses passed.
🤖 Agent: using items (fishing rod, ender eye…) now posts vanilla's item-use game events through causeUseVibration, so sculk sensors react as in Java; sculk, server_player_sculk, server_wool_vibration and use_item_on pass against the 26.3 oracle in the gate's harness run (242 selected; failures were host setup only plus the lunge_motion timing flake).
🤖 Agent: crafting with a merchant result slot now fires vanilla's merchant crafted trigger and updates the trade, checked by the new --merchant-crafted-fixture in command_exec; merchant_crafted, command_exec and trading pass against the 26.3 oracle in the gate's harness run (242 selected; failures were host setup only plus the lunge_motion timing flake).
🤖 Agent: item entities, armor stands and boats now save the shared Entity flags (Invulnerable, CustomNameVisible, Silent, NoGravity, Glowing, HasVisualFire, Tags) as vanilla's saveWithoutId does. Its 242 selected harnesses ran on main + this head + #11017 + #11022: save_roundtrip, world_reopen, entity_nbt_boolean, the armor stand and boat ones pass; the only failures are setup (no sound/lang assets on the host, harness args) and the known lunge_motion tick-timing flake under software rendering.
MinecartTNT.tick reads the delta after AbstractMinecart.tick. The move
zeroes the blocked axes and no track step puts them back, so a cart
stopped head-on by a wall on a straight rail does not blow up, while one
glancing off a wall keeps its free axis and does, on or off its rails.
Horizon kept the speed the cart asked for before the move on the rail
paths and none off them.
Part of #11380 (the impact detonation; the team push points stay open).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: overflow honey now leaves through Player.drop (eye height − 0.3, thrown along the view, pickup delay 40) instead of popping at the feet; held_drop harness passed, plus Game tests for Peaceful anger and event 2011.
HoneycombItem.useOn gives a double chest's other half its game event and
particles and plays a second sound from the clicked block, on the server and
in the client's prediction. The wax sound leaves out the acting player and
goes out in the blocks source.
WeatheringCopperChestBlock.randomTick skips the attempt, and its draw, while
getEntitiesWithContainerOpen has anyone: a player's menu, or a copper golem
(CopperGolem.hasContainerOpen), a double chest counting for both halves.
Fixes#11378
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MobEffectInstance.Details nests the instance an effect hides under
hidden_effect, and the player save writes the whole chain. Horizon wrote
only the top instance, so a weaker, longer effect (Poison I under
Poison II) was gone after a reload instead of resuming. SavedEffect is
now a struct carrying that chain; guest player data goes through the
same code.
Part of #11384 (the player side; mobs still drop weaker instances).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Let native mods keep shared-item text aligned when a player accepts or
cycles chat suggestions. Report the original input, UTF-16 replacement
range, selected suggestion and resulting text. Cover keyboard and mouse
handling, server suggestions, emoji and length limits.
Part of #11212.
Co-Authored-By: Codex (GPT-6) <[email protected]>
🤖 Agent: the device list is now read on a worker thread, so a slow Windows audio driver no longer stalls a frame; the render thread only swaps in the result. No vanilla behaviour changes, no harness covers it.
RaidCommand.getRaid is getRaidAt, which returns a raid that has not
started its first wave yet. playSound plays the horn only for the text
"local", at volume 2 with the level random's seed. The status line
concatenates its floats the Java way (20.0, not 20).
Fixes#11372.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: effects now carry vanilla's showIcon apart from the particles (5-arg constructor, update, save codec default, packet bit 4), so warden Darkness and /effect hideParticles no longer show a HUD icon; 8 effect/HUD/sculk harnesses pass.
- Animal.finalizeSpawnChildFromBreeding adds one new ExperienceOrb of the
parent's 1-7 roll: breeding no longer splits it (4 into 3 + 1) or merges.
- ThrownExperienceBottle.onHit awards with direction: away from the block
face it hit, or back along its motion after an entity hit; the new orb
starts 0.25 that way and a random motion against it is turned round
(ExperienceOrb(level, pos, roughly, value)) (#11388).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Exclude actual crafters from automatic dropped-item name capture while
preserving chest/shulker names, contents and the other copy policies.
Cover the real destruction route against a frozen fresh Java reference.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
WeatheringCopperChestBlock.randomTick runs changeOverTime only when the
chest's getEntitiesWithContainerOpen is empty, so an open chest neither
oxidizes nor draws its odds (part of #11378).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`horizon-port-pack events` gives a fifth answer, data: NeoForge's
`VillagerTradesEvent` and `WandererTradesEvent` are gone since vanilla 26.1
made trades the `trade_set` and `villager_trade` registries (NeoForge's 26.1
primer), so a port writes trades as data instead of waiting for a hook.
On ATM10 that answers 33 uses (20 and 13 mods).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without a keychain, account-secrets.json was sealed under SHA-256 of a
constant and /etc/machine-id, which every local user can read; on a
system with none of the machine-id files the key was a constant built
into every launcher. Only the file's 0600 mode protected the Microsoft
refresh token inside, so a copy restored with wider permissions, or a
shared home, gave it away.
Format 2 (HZSECv2) adds a random 32-byte install secret to the key,
kept beside the file in account-secrets.key (0600, made at the first
write through a temporary file and a rename, under the file's lock).
The key is never the bare constant any more, and a v2 file whose
secret is missing or belongs to another install is refused like any
unreadable file, never written over.
A file sealed by an older launcher (HZSECv1) is still read with the old
key and sealed again under the new one at once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
The invite screen's list is the screen's one focusable beside its
footer, so its extractSelection outline is white; main's draw has no
list_focused parameter to ask.
Co-Authored-By: ZCode <[email protected]>
cargo audit flagged two crates in the locks:
- tokio 1.53.2 (RUSTSEC-2026-0386), reached through quinn in
horizon-mod-deps and in the launcher's lock. Patched in 1.53.3, a
patch release, so both locks move there.
- lru 0.16.4 (RUSTSEC-2026-0253), reached only through mysql 27 in
horizon-mod-deps. No 0.16 release carries the fix; mysql 28 depends
on lru 0.18, which does, so the crate mods may use moves to mysql 28
(mysql_common 0.37, pem 4, base64 0.23 and arc-swap come with it).
`cargo check -p horizon-mod-deps --features mysql` builds, and cargo
audit no longer reports either advisory. A native mod that asks for
`mysql = "27"` now has to ask for 28, since the lock is the allow list;
docs/native-mods.md names the new version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Guest break refusal is the sequence ack every PlayerAction arm sends, the packet rate limit is PacketRate::received,
and MinecraftServer.shouldRun has no haveTime budget to defer against in Horizon's loop.
neutralise breaks @mentions, #123 and github.com/ issue links in what
people write in the website's report form, so an anonymous report can't
put backlinks on the repository's issues. GitHub also links GH-123
(either case) to issue 123, which got through.
- At the start of a word, GH- followed by a digit gets a zero-width
space after GH, the way @ and # already do: the text reads the same
and links nothing. Other words (high-5, GH-pages, laugh-1) are left
alone.
- Test: GH-12, gh-7 and (Gh-8) are neutralised; look-alikes aren't.
Found in yahya-nusseibeh's review of Horizon (L2, 7 Nov).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Two ways one client could lock every user out of the Play with Friends
relay, with no account:
(a) The only connection cap was global (4,096), counted before anything
is read, and each connection may stay silent for the 10 s first-line
wait: about 410 idle connections a second from one address kept the
relay full, so hosts couldn't register and guests couldn't join.
(b) Every join, known code or not, is refused while the global
unknown-code budget (120 a minute) is empty. Each /64 may try 5
unknown codes per 10 minutes, so ~240 /64s keep it empty, and one
residential /56 has 256 of them.
- Limiter::open_connection / close_connection: connections open at
once per address key (IPv6 /64; a /48 four times as many), 64 by
default (RELAY_MAX_OPEN_PER_ADDRESS; a host keeps one connection per
guest, hence the margin). Counted before anything is read, or right
after the PROXY line behind the PROXY protocol; past the cap the
connection is closed at once, not held for the first-line wait. An
RAII OpenSlot gives the place back however the connection ends.
- Each site (IPv4 /24, IPv6 /48) has its own unknown-code budget, 24 a
minute with a burst of 6, checked before the global one and spent on
every unknown code, so emptying the global budget takes many sites,
not one home.
- docs/play-with-friends.md: the new setting and the site budget.
- Tests: the per-address cap (IPv4, IPv6 /64 and /48, off without
per-address limits), the per-site budget (shared within a /24 and a
/48, refilling), and a running relay closing a fifth silent
connection at once with a cap of 4, then letting new ones in once
the held ones go.
Found in yahya-nusseibeh's review of Horizon (H2, 7 Nov).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
The row said the name-based stats file is not moved to the uuid file. It names the function that
does it, and the segment rule that decides whether vanilla would look for it at all.
The test works in a temporary world folder: nothing is written when there is nothing to move, the
`players/stats/<name>.json` file lands on the uuid name and is not left behind, a uuid file that
already exists wins and leaves the old file alone, and the segment check refuses capitals and
separators while accepting a lowercase name with dashes, dots and digits.
PlayerList.locateStatsFile: the stats file is `stats/<uuid>.json`, but a version that wrote
`stats/<player name>.json` left its numbers under the old name. When the uuid file does not exist
yet and the name is one vanilla would write as a path segment
(FileUtil.isValidPathSegment: `[-._a-z0-9]`, and not `.` or `..` — the check is on
`<name>.json`, so a capital letter in the player's name refuses it), the old file is moved to the
uuid name and that is the file that is read; a failed move leaves the old one in place.
Horizon always resolved the uuid path, so a world opened after such a version started with empty
stats. The 26.3 advancements file has no name-based form (`PlayerList.getPlayerAdvancements`
resolves the uuid directory directly), so only stats move.
Pack::read preallocated from a zip entry's declared size and inflated it
with no limit. Shader packs are zips players download, so a deflate bomb
(or a forged size field) in any file under shaders/ aborted the game
with an allocation failure as soon as shaders.properties was read: 512
MiB of spaces zip to 509 KiB and took peak RSS from 4 to 516 MiB; at
1.5 GiB the review measured 1,543 MiB.
- Nothing is allocated from the declared size; a file that declares or
inflates to more than MAX_FILE (64 MiB, far above any .properties,
.lang or GLSL file, with room for the largest textures) reads as
missing, like a file the pack doesn't have. The same pack now stays
at 4 MiB.
- Folder packs are unchanged (files on disk, not inflated).
- Test: a small zip whose shaders.properties unpacks past a 16 KiB cap
reads as missing; the rest of the pack still reads.
Found in yahya-nusseibeh's review of Horizon (M1, 7 Nov).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Send pause-menu and death-screen exits through close_studio_replay when playback is active. Clear the replay timer and restore the prior camera so the title screen and the next world use normal player and hand rendering.
Co-Authored-By: Codex <[email protected]>
Open and validate a replay archive before replacing the current screen. Track standalone replay sessions so closing one disconnects its synthetic remote and restores the title screen. Start playback in first person after applying timestamp-zero world packets.
Co-Authored-By: Codex <[email protected]>
Inflate recording streams to a bounded temporary file, verify the stream CRC during the copy, and index a sparse set of seek anchors instead of retaining every packet in memory. Preserve whole-frame recovery and timestamp-group seeking.
Co-Authored-By: Codex <[email protected]>
- Dump resident chunk columns, player pose, center chunk, and dimension login into .mcpr stream at timestamp 0ms on recording start
- Allow all chunks in remote_chunk_in_range during studio replay and freecam so distant chunks are never discarded
- Reset title mode to Mode::Main when closing studio replay so the user returns to the main menu instead of multiplayer
- Add interactive glass button for '▶ Edit' on recording cards in StudioUi
- Allow single-clicking '▶ Edit' on any card to immediately load and open the editor
- Allow clicking the bottom 'Edit Replay' button to open the selected or first replay
- Handle Event::OpenStudioReplay in Loading::menu_option_events by queueing PendingWorldEntry::StudioReplay
- Handle PendingWorldEntry::StudioReplay in Game::poll_pending_world_entry to enter replay
- Add Session::replay and init_remote_for_replay to initialize client state when entering replay from title screen
- Dismiss title screen and uncapture cursor so timeline and studio HUD controls are interactive
- Release mouse cursor on Escape key in studio replay mode before opening pause menu
- Implement standard .mcpr container reader/writer (metaData.json + VarInt timestamped recording.tmcpr stream)
- Add asynchronous StudioRecorder worker to record clientbound packets with zero impact on gameplay FPS
- Add ReplayDriver with timeline seeking, variable speed control, pausing, and looping
- Wire zero-alloc packet recording tap into Session for singleplayer, LAN, and remote multiplayer
- Add in-game /studio chat commands and control socket commands for recording and replay management
- NeutralMob.isValidPlayerTarget also fails on Peaceful: a bee's anger at
the player ends there.
- BeeGrowCropGoal sends level event 2011 (data 15) before the crop grows;
the client draws it like 2012 (spawnParticleInBlock, happy villager).
- A honey bottle that no longer fits is player.drop(stack, false): from
below the eyes, thrown along the view, no thrower (#11377).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RealmsInviteCodesScreen lays out a footer of two rows (Copy/Delete/Edit
97 wide over Create/Back 150 wide, FOOTER_HEIGHT 64), titles itself
Invite Codes (n/5) once fetched with the gray subtitle over an empty
list, and shows the loading dots without a list behind them. Its rows
are 28 high with only the code's text, selected like every
ObjectSelectionList (the port's extractSelection); Copy answers the
clipboard and Delete/Edit act on the selection (ids 180-182), not on
per-row buttons.
Co-Authored-By: ZCode <[email protected]>
Pack::read inflated each of up to 512 zip entries to 1 MiB + 1 and kept
them all, and only Pack::from_files then checked the pack's 8 MiB total.
511 entries of zeros take 561 KiB zipped, and the mod registry reads
every submitted .hshader (index::inspect, Kind::Shader) on its blocking
pool: one upload made it hold 1 GiB (measured: peak RSS 3 -> 1,026 MiB),
a few in parallel could get it OOM-killed.
- Each entry is read with room for no more than what is left of
MAX_TOTAL (and its own 1 MiB cap), and refused as soon as it passes
it: "the pack is over 8 MiB", or the file named when it alone is over.
The same package now peaks at 6 MiB.
- A folder pack counts its total the same way.
- Tests: 64 MiB of zeros in a small zip, one file over its cap, and a
folder over the total are all refused.
Found in yahya-nusseibeh's review of Horizon (H1, 7 Nov).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
A cushion is an ordinary entity in vanilla (a BlockAttachedEntity), but
Horizon had no command target for it: @e never matched one, so execute
if entity, tag, data get, tp and kill missed every cushion. Target::Cushion
joins the level's candidates like a painting; /kill removes it with no
drop and lets its rider go, /tp moves it with its block, data get writes
it through cushion_to_nbt.
Fixes#11626.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
InviteCodeList.parse reads a JSON array, or an object whose 'result' is
one, dropping entries without a code; the port waited for an
'inviteCodes' object the service never sends, so the codes screen
listed nothing to manage.
Co-Authored-By: ZCode <[email protected]>
- RealmsConfigureWorldScreen.fetchServerData replaces its serverData with
getOwnRealm's answer; the port kept the server list's copy, whose
regionSelectionPreference is stale, so a saved region read back as
automatic. The detail answer now becomes the screen's server.
- Request.post sends Content-Type: application/json; the frontend
answered 415 to the invite codes' text/plain POST.
- RealmsPreferredRegionSelectionScreen draws each region's ServiceQuality
icon (ServiceQuality.getIcon, 10x8 at the row's right).
Co-Authored-By: ZCode <[email protected]>
Done used to rebuild the Configure screen with detail and tier cleared,
so the save that leaving the tab triggers found no slot to send and the
chosen region was dropped. The screen now carries both back, and the
save runs on the data it had.
Co-Authored-By: ZCode <[email protected]>
The click arm that handles the screens' drawn rows was guarded to
SubScreen::Configure, so the region preference's rows (and the create
screen's fields) never saw a press. The guard now covers the screens
whose rows it handles; the region selection draws extractSelection's
black entry with its white outline and the footer is vanilla's
150-wide Done/Cancel pair.
Co-Authored-By: ZCode <[email protected]>
PiecesContainer.load now names saved_piece_id, where the rename table lives,
and StructureStart.loadStaticStart spells out that a jigsaw start (the renamed
old ids included) comes back from its saved pieces while another structure's
start comes back from the generator's rebuild of its chunk.
A piece tag named minecraft:nvi, pcp, bastionremnant or runtime comes back as
jigsaw, as does minecraft:jigsaw itself; a monument, stronghold or jungle
temple piece (OMB, SHCC, TeJP) comes back as nothing so the start keeps the
generator's rebuild, and so does a tag with no id or one that is not a
compound. With the table narrowed to minecraft:jigsaw alone the first four
fail.
PiecesContainer.load maps four piece ids - minecraft:nvi, minecraft:pcp,
minecraft:bastionremnant and minecraft:runtime - to minecraft:jigsaw before it
looks the type up: a save from before those pieces were jigsaw wrote them under
the older names with the same pool-element tag. The saved-start reader only
took the literal minecraft:jigsaw id, so such a start fell through to the
generator's rebuild of its chunk, and where that rebuild makes a different
layout (a world whose structure data or version moved on) the start came back
with no pieces at all: nothing placed, nothing bearding the terrain.
saved_piece_id now holds that rename table for the reader; a piece of another
type (a monument's or a stronghold's, whose NBT is not a pool piece's) still
sends the start to the rebuild.
The service sends settings like {"name":"hardcore","value":false},
a JSON bool where the DTO says string; Gson's JsonPrimitive.getAsString
accepts it, serde did not, and the detail answer the Worlds and Players
tabs need failed to parse.
Co-Authored-By: ZCode <[email protected]>
RealmsClient.subscriptionFor builds 'subscriptions/'; the port's
'worlds/{id}/subscription' answered 404, which the configure screen showed
as a fatal service error instead of its tabs.
Co-Authored-By: ZCode <[email protected]>
MobEffectInstance.showIcon was not modelled: every effect showed its HUD
icon and every UpdateMobEffect carried the icon flag. Warden Darkness
(no particles, no icon) showed an icon. The flag now follows vanilla's
constructors, update, saves (show_icon defaults to show_particles),
consume effects and the packet flags (#11385).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Encode bare item templates using the primary record form while preserving
compound counts and components. Cover actual canonical load/save and the
real command route against a frozen fresh Java reference.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
respawn_world_point and disconnect_crafting reused generate/world whenever the
folder existed, so one crash left them failing until someone deleted it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #11223 the server takes what follows a jab on the next tick, so the
harness's time query comes back a tick later and every jump failed as a
catch-up. The motions are identical to vanilla's and stay compared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/parity.py runs a harness with no arguments, and these three required
--binary, --work-dir or --resources (mob_discard also used os without importing it).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RideCommand.mount forces the ride (startRiding(vehicle, true, true)), so
any entity takes a rider. Horizon only took boats, carts and mobs. A rider
now sits at the vehicle's default passenger attachment (its box height,
less the rider's own vehicle attachment) and gets off on sneak on top of
it, as Entity does for a vehicle that overrides neither.
Fixes#11603.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Select the library artifact that owns Game, retaining the exact one-test assertion so a thin executable with no tests cannot count as a pass.
Co-Authored-By: Codex <[email protected]>
Under `unittest discover -s tests`, the bare name test_rust_parity is
tests/test_rust_parity.py, so the drivers' fallback import took the wrong
module and four tests failed (no attribute verify_artifacts) in verify.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under `unittest discover -s tests`, the bare name test_rust_parity is
tests/test_rust_parity.py, so the drivers' fallback import took the wrong
module and four tests failed (no attribute verify_artifacts) in verify.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RideCommand.mount and dismount work on any ServerPlayer in vanilla. Horizon's
/ride reached only the player in place, so the console, a function, a command
block or another player could not seat a guest (every player of a server or a
LAN world, and in single player too): the guest's turn now runs through
for_player, as native_mount does. A guest as the vehicle answers
cant_ride_players, as any player does.
Fixes#11602.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reuse the native summon spawn-bounds predicate so negative endpoints stay valid, and use Java's integer width for the yaw direction lookup. Record 26 independent outcomes from both pinned vanilla jars and add a differential harness for the actual native calculations.
Co-Authored-By: Codex <[email protected]>
Propagate required dependency failures before expanding function tags so
invalid tags cannot run partial members. Skip whole invalid optional children
while preserving the existing ordered, deduplicated expansion.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Reuse the current forceSetRotation path so turning an entity preserves its mount, position and velocity. Extend the existing resource-backed rider regression.
Co-Authored-By: Codex <[email protected]>
Preserve the current teleport passenger handling and supply its rotation policy from the native movement methods.
Co-Authored-By: Codex <[email protected]>
Audio::should_change_device listed the output devices on the render
thread once a second. Windows has no cheap fingerprint to skip the list
(Linux reads /proc/asound/cards, macOS names the devices), so every
second the render thread waited ~90-120 ms on WASAPI's endpoints, even in
the main menu (#11490). The look now runs on a short-lived thread every 20
ticks; a later tick applies what it found with the same rules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Removing each one and asking the compiler (the workspace's targets and the
server build): most guarded nothing, since `pub` methods of `pub` types already
count as used. The ones native mods need stay, now saying so, as do the fixtures
that share another fixture's source and code whose caller has not landed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reject folders without parseable metadata, an object pack section and a non-null description before exposing their contents. Preserve existing parser and format behavior.
Co-Authored-By: Codex <[email protected]>
Ports vanilla 26.3's environment attribute registry (EnvironmentAttributes:
types, defaults, ranges) and one EnvironmentAttributeMap entry: a value or
a {modifier, argument} object, parsed as its codec does (hex, list and int
colors, FloatWithAlpha, BlendToGray) and applied as AttributeModifier.apply
with float math in Java's order (ARGB.alphaBlend, addRgb, subtractRgb,
multiply, greyscale, scaleRGB, srgbLerp; Math.min/max). The layer stack
that sums these comes next; nothing uses the module yet.
The new harness environment_attribute_modifier compares 8,835 cases with
the pinned client, the registry included.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
arboard 3.7 moves to windows-sys 0.61 (removing 0.60, windows-targets 0.53 and
its eight platform crates). socket2 0.6 and if-addrs 0.15 in horizon-friends
remove socket2 0.5; the launcher's lock also loses windows-sys 0.59.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Validate candidate functions at actual compilation permission before exposing them, using refreshed world content and candidate registry IDs.
Co-Authored-By: Codex <[email protected]>
#11158 took the player's boarding cooldown out (Player.removeVehicle zeroes it), and
may_board with it, so this branch no longer built once merged with main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Id tables from the vanilla data reports (1.20.6's packet list rebuilt from
the archived wiki page, which names no resources, via 1.21.1's report), with
the renames game_profile, set_carried_item and horse_screen_open. Packet
layouts from minecraft.wiki's archived pages for protocols 766 to 769 and the
release notes: 1.21.5's chunk format and per-entity data, hashed container
clicks, 1.21.2's window ids, teleports, movement flags, input and sea level,
1.21's particles, explosion sound and trades, 1.20.5's strict error handling.
1.21.5 item components (attribute modifier display, equippable shearing)
behind a quirk. A 1.21.1 client gets no recipe book.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Discord Rich Presence, Play with Friends (horizon-friends) and the
anonymous "I'm playing" ping (horizon-telemetry/live) were built
unconditionally, though a server never runs them. Each is now a
default-on feature of horizon-client and horizon-client-lib, so the
normal build is unchanged and the server build (--no-default-features,
horizon-engine) leaves them out: horizon-friends and 13 crypto and
network crates drop from its dependency tree.
Without `friends` the game keeps working: game/friends_off.rs stubs the
host side (no Friends button in the pause menu, as on another server),
no code reads as an invite, and a friend: address is refused as an
invalid address.
Fixes#11539
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clippy follows rust-version, so crates that move from 1.85 now get its
collapsible let chains, as_chunks and is_multiple_of suggestions. Almost all
from `cargo clippy --fix`, none changes what the code does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#11226 clamped scale to 0 before the + 2. Vanilla has no clamp and Java masks the shift
distance, so tools/test_biome_source_parity.py failed at scale -1 and below.
Fixes#11519
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both asserted --reference-only when no --reference-dir was given, so tools/parity.py,
which passes no arguments, failed them before they ran anything.
Fixes#11520
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Members declared 1.85 to 1.95, or nothing. horizon-mods needs 1.95 and sits
under horizon-client-lib, cargo-horizon-mod and the launcher, so 1.95 is the
version the game really needs; the lower numbers promised builds that could
not happen. Set it once in [workspace.package] and inherit it everywhere.
The separately built workspaces (launcher, telemetry server, mod registry,
third_party) cannot inherit it and keep their own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemModels::components cloned the item's whole default component map (an
enchanted sword's: a few dozen JSON values and strings) for every stack
with a patch, and the model resolver did it on every draw: the hotbar,
each held item of every player in view, the first-person hand twice. A
PvP fight with enchanted gear paid it a dozen times a frame.
ComponentsView reads the patch, then the defaults, with no copy; the
resolver, the swap animation scale, attack range, use duration and the
use animation read through it. components() stays for what needs the
merged map (bundle fullness, still built only for bundles).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
submit_quads hashed the quad's sprite name into the glint atlas for every
quad of an enchanted item drawn in the GUI (a generated sword: hundreds of
side quads, one sprite). It now keeps the last sprite's glint rect, as it
already does for the item atlas rect: the same uvs, one lookup a sprite.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The harness, probe, Game field, impl file and module counts had drifted
(324 harnesses against 502 on disk). Nothing regenerates prose, so name the
things and let the tree give the numbers. CONTRIBUTING.md's "about 290"
harnesses goes with them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Follow-up to #10571 (Skr1zzzz). The cache checked obfuscation on the raw
spans, so a plugin hologram sending §k in literal JSON stopped
animating, and an inline head whose skin arrives later stayed stale: the
animated test now runs once on the resolved text, as drawing reads it.
The key also holds lang::bidirectional() as the name tag cache does, and
a display out of view keeps its cached quads instead of dropping them
before the view check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#10129 made the native title bar transparent on macOS but drew the bar only with
glass on, so with glass off the page started under the traffic lights. The bar
is always there: glass, or the side colour as on Windows and Linux.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#10177 unmaximized before every fullscreen request on every system, and apply()
asked for fullscreen again on each call while the window still reported
maximized. enter_fullscreen keeps the #9984 fix where it applies (winit keeps a
maximized window inside the work area on Windows); the resumed() checks share
one helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#10532 builds a pack on a thread of its own, but dropping PackLoading (a
pack switch, packs off, another dimension) only detached that thread,
which went on for 5 to 50 s; switching back and forth stacked builds.
PackLoading now holds a cancel flag set when it is dropped: PackPost::new
checks it per program and compile() stops handing out jobs, and the
refusal it returns (is_cancelled) is not reported as the pack failing.
A thread that cannot be spawned now turns the pack off for the
dimension instead of retranslating it every frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#10166 landed with its messages as raw strings. Every message a player reads
goes through tr/trf with named placeholders, and every language file gets the
English stand-ins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main gave a sky program two more pipelines (its vanilla blends for the
disc and void, and for the stars). Here they are RenderJobs made with the
others, in their own slots, and the draw reads them with get().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
a_flame_crossbow_shoots_burning_arrows (#10488) expected 100 fire ticks and failed with prepared resources. Enchantments.FLAME is Ignite(LevelBasedValue.constant(100.0F)), and Ignite calls igniteForSeconds: 100 seconds, 2000 ticks, as the bow path already gives. The code was right; only the expectation in the test was off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Once the search field had the focus it kept the keyboard, so Enter did nothing and letters filtered
the list with the ring on a row. Moving the focus away now ends typing.
A key that moves nothing shows no ring, a row greyed out while focused does nothing, the wheel stops
behind a resource error as `scroll_at` does, and a description too long for the strip ends in an
ellipsis, with room for one more line on Extras.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A screen now implements settings_shell::Screen, describing its pages, groups,
rows and values, and Shell does the drawing, clicks, scrolling, the controller's
focus and declared buttons, keys and each row's description. OptionsUi reaches
it through one accessor, so the wheel and a stick's scrolling can't part ways.
Horizon Extras is the first screen on it. Its settings do what they did and are
saved the same way. It gains the shell's look and search, and its descriptions
move from tooltips to the strip, which grows for them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new crate, horizon-polytone, reads the Polytone resource pack format
clean-room from the public wiki: modifier targets (implicit path id, ids,
tags, regex, required) and the attributes_modifiers layers of biome and
dimension modifiers (groups, REMOVE, values, modifier objects, colors,
colormap references, expressions kept as source). Nothing uses it yet.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Packet and registry id tables generated from the vanilla data reports of the
three releases; packet layouts from the public protocol documentation
(minecraft.wiki's archived packet pages for protocols 770, 771 and 772).
Each step has a reader (older server) and a writer (older client) with round
trips; entity data serializers without 1.21.9's additions. The end-to-end
bot test now joins as every release down to 1.21.5.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
es_es.json held 271 of the 894 keys en_us.json now ships, so 623 strings
(70% of the file) fell back to English on Spanish screens: the whole PvP
and HUD page, splitscreen setup and profiles, Horizon extras, the mod
browser, the resource pack browser, friends, the on-screen keyboard,
Replay Studio, the music page, cosmetics and the shader and graphics
descriptions.
This fills the table: 894/894 keys, no missing or extra ones, every
placeholder kept. The four values that were still English (Particle
Culling and its three descriptions) are translated, and the shader page
name now says "shaders" like the rest of the file.
lang.rs also serves es_ar, es_cl, es_ec, es_mx, es_uy and es_ve from this
file.
Co-Authored-By: OpenCode (DeepSeek V4.1 Flash) <[email protected]>
A 1.21.1 model's minecraft:block/chain or item/chain (parent or texture) became
iron_chain in 26.3, so a mod block drawing vanilla's chain baked with the missing
texture. Carried models and block states get the RENAMED_IDS rename data files
already had.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The recipe index reads every content namespace since 31756ebfe, but /recipe's
suggestions still listed only minecraft: ids. List data/<ns>/recipe of every
namespace, as /loot's loot tables already are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Its rows and buttons only painted, so a pad reached them through the crosshair alone. The D-pad now
walks the recordings to the buttons, the right stick scrolls them, and Done declares its button.
A notch is now one whole row, since nothing clips a row that's partly scrolled off, and the scroll
limit counts the rows that fit, so the last recording can be reached with the mouse too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mirror translate's per-release readers as writers (client_session/step_*),
keep sent entities for entity data and 26.1's spectate by UUID, drop the
world_clock registry and split 1.21.11's interact into interact and attack.
End-to-end test joins a bot of each release through the adapter's Session.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The warning only gave their count, so a data pack or content mod author
could not tell which features were skipped or why. Log each one with its
reason, up to 20.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use a conservative input-pixel guard for the billboard camera frustum
without changing the ordinary model frustum, flame envelope or shader
pack fallback. Keep this PR independent of experience-orb culling.
Retain the separate flame measurements and image controls, with their
original binary provenance and an explicit note about the PR split.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Reuse resolved local model depth when placing dropped-item stack copies.
Skip the unused posed occlusion envelope with entity culling and TAA off,
and retain the original preparation under TAA after measured tail regressions.
Keep the existing cluster API and random sequence, with a regression test,
paired production measurements and explicit rendering-validation limits.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Without the change the first reads `Err(InvalidData, "external chunk stub has
inline data")` and the second `Err(Os { code: 2, kind: NotFound })` instead of
the 4000 bytes in `c.-32.0.mcc` and an absent chunk.
`RegionFile.getChunkDataInputStream` never trusts the stub's length once the
external flag is set: `streamLength != 0` only logs "Chunk has both internal and
external streams" and the stream comes from the `.mcc`. When the `.mcc` is not a
regular file, `createExternalChunkInputStream` answers `null`, so the chunk is
absent. Horizon refused both: a stub that also declared inline data failed with
"external chunk stub has inline data", and a stub whose `.mcc` was gone failed
with a `NotFound` io error, which aborts a reader where vanilla skips the chunk.
Use the existing air predicate and Gone result at the start of the actual falling-block tick. Cover admitted command actors with a real Game tick regression and frozen Java parity controls.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
PlayerPredicate.matches calls FoodPredicate.matches on the player's
FoodData: the food level in `level`, then the saturation, a float
compared as a double, in `saturation`. The player facts carried no food
and the matcher never read `food`, so any food bounds passed.
PlayerFacts now holds the food, always filled (two numbers), for the
player in place and for a server's other players, and player_facts tests
it after the level, as vanilla does.
The command_exec harness gets a group that fills and drains the player's
food in player loops and tests named food predicates, a non-player, and
a tick advancement whose player condition asks only for food.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Play in Background, off by default and per player, lets the pad keep driving the game while its
window has no focus, and a game the pad is playing doesn't pause on losing focus.
While several play, the router now plays each player's rumble on that player's pad. Rumble no
longer stops when the window loses focus: nothing asked for that, and it's useful to an AFK player.
Starting or leaving splitscreen builds a new renderer, whose atlas has none of what was drawn into
it at run time. Those two paths only forgot the skins, so the on-screen keyboard lost its letters
and rounded corners, and maps were exposed the same way. All renderer swaps now forget the same
things.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Left in the air at y 163, the player fell into the sea below while prepare() ran, which unlocked the boat
recipes: their toast showed over the first shots at a different moment each run, so main against itself
failed in the top right corner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`tools/port_map/animal-1.tsv` had no answer for
`Armadillo.addAdditionalSaveData` or `Armadillo.readAdditionalSaveData`. Both
now name `entity_nbt::mob_to_nbt` / `mob_from_nbt`: the state written as its
`ArmadilloState` name beside `scute_time`, and read back through
`switchToState`, idle when the save names none.
`mob_from_nbt` and `mob_to_nbt` are the pair `Game::load_mob` and
`Game::mob_tree_nbt` go through. An armadillo whose save names
`state: rolling` loads rolling and takes 4.5 of a hit for 10; six ticks into
the roll its shell is out; a save that names no state, or a name that is no
`ArmadilloState`, loads idle and takes the whole 10. Saving a rolling armadillo
writes `rolling` even when the compound it came in with said `idle`, and the
reloaded one has its time in the state restarted, so the shell is not out until
it has been rolling for six ticks again.
Without the state in the pair the loaded armadillo is idle (left: 0, right: 1)
and takes 10 for a 10 hit where it took 4.5 rolled up.
`Armadillo.addAdditionalSaveData` writes the state the armadillo is in beside
its scute timer, and `readAdditionalSaveData` switches to it:
output.store("state", Armadillo.ArmadilloState.CODEC, this.getState());
output.putInt("scute_time", this.scuteTime);
...
this.switchToState(input.<Armadillo.ArmadilloState>read("state", Armadillo.ArmadilloState.CODEC)
.orElse(Armadillo.ArmadilloState.IDLE));
`ArmadilloState.CODEC` is a `StringRepresentable.fromEnum`, so the key holds
`idle`, `rolling`, `scared` or `unrolling`; `switchToState` sets the synched
state, and `onSyncedDataUpdated` restarts `inStateTicks` when it changes.
Horizon models the same state (`extra.armadillo.state`, the value
`Armadillo.getState` is synched with) and wrote only `scute_time`, so an
armadillo that unloaded while rolled up came back idle: a hit for 10 went from
4.5 back to 10.0 (`Armadillo.hurtServer`: a ball takes half of the damage past
the first point), `hiding_in_shell` went false so the model lost its shell, and a
brush or a food click was answered by an idle armadillo instead of a ball.
`state` is now written from the live state - `idle` for one that is not rolled
up, as vanilla writes it unconditionally - and read back through `set_state`,
which is `switchToState` plus `onSyncedDataUpdated`: a save that names no state
(or names one that is not an `ArmadilloState`) loads as idle, and the time in
the state starts again at 0.
The command-execution furnace family could not give a checkable gap control
on this host, so this is a direct AbstractFurnaceBlockEntity.serverTick
harness instead: a real RecipeManager and a furnace, blast furnace or smoker
in a block-update level, compared with FurnaceCooking::tick. It pins the
cooking_fuel burn table for every fuel item, the 200/100/100 cook times and
speed multiplier, fuel consumption and LIT state, RecipesUsed experience, a
full output slot, timer decay, and an input changed mid-cook. --no-change
reproduces the corpus before that family.
757/757 cases match the pinned client.
DropperBlock.dispenseFrom finds its target with HopperBlockEntity.getContainerAt:
the block's container, else a container entity (chest boat or raft, chest or
hopper minecart) whose box meets the space in front, picked at random. Horizon's
dropper only asked for a block container, so items fell out next to a chest boat
instead of going in. It now falls back to the entity lookup hoppers already use.
The dispenser harness gains 48 dropper cases with chest boats, rafts and chest
and hopper minecarts centred in front, reaching in from beyond, just past the
space, two at once, behind a chest block and non-container vehicles. The oracle
and the fixture print the container entity's items (Tinv lines).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`tools/port_map/game2-002.tsv` recorded `gap: minor: anchor_pos is not read` and
`not written` for `Phantom.readAdditionalSaveData` and
`Phantom.addAdditionalSaveData`. Both now carry the anchor and say how: the key
only for a phantom that has an anchor, absent or unreadable as no anchor.
`mob_from_nbt` and `mob_to_nbt` are the pair `Game::load_mob` and
`Game::mob_tree_nbt` go through, so the test loads and saves a phantom by its
compound. A phantom at (0.5, 75, 0.5) whose save names `anchor_pos: [100, 64,
-40]` loads with that anchor, circles to (109.66, 62, -37.41) - the anchor,
`distance*cos(angle)` across and `-4 + height` up - and a phantom that moved its
anchor to (9, 70, -3) saves that position, not the one its compound came in
with. A phantom with no anchor loads with none and saves no `anchor_pos`.
Without reading the anchor the load gives the phantom (0, 80, 0), its own
position five blocks up, and the circle point (9.66, 78, 2.59); without writing
it the save keeps the stale (100, 64, -40).
`Phantom.addAdditionalSaveData` writes the anchor the phantom circles around
beside its size, and `readAdditionalSaveData` takes it back:
protected void readAdditionalSaveData(final ValueInput input) {
super.readAdditionalSaveData(input);
this.anchorPoint = input.<BlockPos>read("anchor_pos", BlockPos.CODEC).orElse(null);
this.setPhantomSize(input.getIntOr("size", 0));
}
protected void addAdditionalSaveData(final ValueOutput output) {
super.addAdditionalSaveData(output);
output.storeNullable("anchor_pos", BlockPos.CODEC, this.anchorPoint);
output.putInt("size", this.getPhantomSize());
}
Horizon models that anchor in `extra.home`, which
`PhantomCircleAroundAnchorGoal.selectNext` centres the circle on
(`anchor + (distance*cos(angle), -4 + height, distance*sin(angle))`), but
`mob_to_nbt` wrote only `size` and `mob_from_nbt` read only `size`. A phantom
saved and reloaded therefore kept the anchor `register` gives every new phantom
- its own position five blocks up, vanilla's `finalizeSpawn` value - instead of
the one in its save: a phantom at (0.5, 75, 0.5) loaded with
`anchor_pos: [100, 64, -40]` came back anchored at (0, 80, 0), and its first
circle point was (9.66, 78, 2.59) rather than (109.66, 62, -37.41).
`TagValueOutput.storeNullable` writes the key only for a non-null value, and
`read(...).orElse(null)` gives null for a save without one, so a phantom with no
anchor now writes no `anchor_pos` and loads back with none - the circle then
fills it with its own `blockPosition()`, as vanilla's does.
The brewing suite stopped at recipe matching, assemble and the slot filters;
nothing advanced the block entity. The tick family now runs the real
BrewingStandBlockEntity.serverTick and BrewingStand::tick for 0..8001 ticks
and compares slots, brew time, total brew time, fuel, total fuel, speed,
bottle bits, ingredient and the brew-event count. It covers the 400-tick brew
divided by speed_multiplier and its ceiling (2, 3, 0.3 and 0), blaze powder's
twenty uses and speed 1, fuel loaded with nothing to brew, splash and
lingering outputs, ingredient shrink and mismatch, finishing without fuel and
twenty brews from one blaze powder. --no-tick leaves the tick corpus out.
2860/2860 cases match the pinned client.
Write back live menu slots before spilling chest boats, chest rafts and
container carts. Reuse the container split/motion algorithm through its
actual double-origin path, preserving integer callers and empty-slot draws.
Keep ordinary hurt, hull, lead and pending-loot policies separate.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
NaturalSpawner.getRandomPosWithin samples up to chunk.getHeight(WORLD_SURFACE)+1,
and ChunkAccess.getHeight is firstAvailable-1, so the bound is firstAvailable.
Horizon already stored firstAvailable and added one more. Also correct the
oracle's fake LevelChunk.getHeight.
Fast lava, water evaporation, bed and anchor rules, the raid-bright check, the
day timeline, ambient light and the default clock of /time read the level's
dimension type facts instead of matching on the level, as vanilla reads
DimensionType and its attributes. No behaviour change for the three built-in
levels.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The fight tick, end crystal fire and respawn, the boss bar, the fight dragon
check, the exit portal and the DRAGON chunk ticket read the dimension type's
has_ender_dragon_fight, and the End flashes read its End skybox. No behaviour
change for the three built-in levels.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MobWorld carries the level key and its dimension type facts instead of nether
and end flags, and the spawn light test samples the type's
monster_spawn_light_level as vanilla's DimensionType.MonsterSettings does.
Fast lava, snow golem melting, piglin zombification, fixed-time and day
timeline checks read the type's facts. No behaviour change for the three
built-in levels; random draws are unchanged.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla reads each level's DimensionType and its attributes (fast lava, water
evaporates, the day timeline, infiniburn, sky light) rather than naming the
Overworld, Nether and End, so a datapack dimension using the Nether's type
behaves as the Nether. horizon-world block behaviour now does the same through
LevelAccess::dimension_type(); sites vanilla keys on the level compare LevelKey.
No behaviour change for the three built-in levels.
Part of #11282
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Code that matches on the Overworld, the Nether or the End should read the
level's dimension type, as vanilla does. Add DimensionFacts: the
DimensionType fields and gameplay attributes Horizon reads, the four
vanilla types built in code, and from_json for packs. A test checks the
built-in table against the prepared 26.3 dimension_type and timeline tag
files.
No callers yet; nothing changes for players, saves or packets.
Part of #11282.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla makes one level per entry of the dimension registry and keys each
by its ResourceKey<Level>. Add LevelKey: the three built-in levels as
constants, any other id validated as Identifier.parse does and interned,
plus builtin_or_overworld for today's lossy lookups from saves, guests
and packets.
No callers yet; nothing changes for players, saves or packets.
Part of #11282.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
player-expect is refused as "the server is full" once the places the
player limit counts and those of the other expected players reach
max-players: each expectation holds a place, so two proxies sending
players at once cannot fill a server past its limit. As at login, an op
who bypasses the limit is never refused, and a renewal holds no second
place.
Once a stop has begun (/stop from the console, a player or the control
endpoint, or a stop signal), player-expect, player-import and
player-confirm are refused as "the server is stopping": a player must not
arrive on a server that is about to close. A retry after the commit is
still answered, and export, release and drop stay open, since they are how
players leave. transfer-status says whether the server is stopping.
Refs #7484
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use the existing unshared audio path for level event 3007 and cover actual event capture, local playback, and random continuation.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Reuse bounded exact local geometry and normal rotations for GPU mob posing.
Keep the original walk and transform helpers when TAA is enabled, with the
policy refreshed every frame from the existing anti-aliasing setting.
Add byte-exact churn and bypass-switching coverage and measured crowd results.
Co-Authored-By: Codex (GPT-6) <[email protected]>
No parity suite reached the brewing recipes: brewing.rs's Brewing::find,
PotionIngredient and the BrewingStandMenu slot filters had no differential
corpus. A new suite loads the vanilla data pack's real RecipeManager and
compares every bundled BrewingRecipe's assemble with Horizon's find output,
the brewing_input and brewing_reagent property sets against is_bottle and
is_reagent, and the fuel slot's BrewingFuel against brewing_fuel. Cases cover
every recipe, every potion item and potion id against every reagent, the whole
item registry as a slot candidate, and non-matching pairs. --baseline keeps
only the exact recipe pairs and reproduces the corpus before the cross-product.
2811/2811 cases match the pinned client. The brewing-stand tick, brew time,
fuel use and remainders remain pending.
Part of #11126.
Load complete jigsaw starts from the owning chunk's structures.starts into
its shared generator cache, retaining regeneration for unsupported or
undecodable starts. Read pending saves in every dimension and preserve
reference counts alongside the original piece layout.
Cover resource-free round trips, generated layouts across two seeds and
pending-record loading from a different referencing chunk.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8KX3vDMZy53y1vXqeT
Portals and an empty lectern showed Open, and a block that keeps the click and does nothing
(an empty flower pot or bookshelf slot, a pressed button) showed Use, or the held item's own
row. An ender chest said Use and now says Open.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The firework tooltip corpus sampled flight durations 0-3, 7 and 255 and runs
of at most six equal explosions, so Fireworks.addToTooltip's run count above
six and the rest of the unsigned-byte flight range were never compared. A
deterministic edge corpus now adds all 256 flight durations, runs of 0..256
equal explosions in every shape, colour and fade lists up to 32 entries
(custom, negative and duplicate colours), equality-sensitive runs (same shape
with different colours), both components on one stack, firework components on
non-rocket/non-star items, and every combination of hidden components.
--no-edges writes the random corpus exactly as before.
3331/3331 cases match the pinned client.
ServerPlayer.doTick runs super.tick() (Player.aiStep's tickRegeneration)
and foodData.tick for every player but a spectator touching an unloaded
chunk. Horizon's guest tick returned for a spectator before the damage
clock, peaceful_regeneration and food_tick, so health, saturation and
exhaustion froze while watching and were still there on the way back to
survival.
A spectator whose box, inflated by one block, lies in loaded chunks
(Entity.touchingUnloadedChunk) now runs those three; hazards, travel
and movement statistics stay skipped. food_tick already counts a
spectator as invulnerable, so starvation hurts none.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The knee eases HDR lighting's gain into the peak; light written as light (alpha 1) has no
gain, and on a float frame the knee would compress it before the clamp to the peak. On a
unorm frame such pixels never reached the knee, so nothing changes there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Marked pixels (light sources, the sun, HDR lighting) clip at white before their lift, as on
a unorm frame; only unmarked ones show light over 1, which is how shaders will write light.
Without that, the float frame tinted the sun and its halo instead of lifting them white.
The native world-text pipeline targeted the swapchain's 8-bit format, which a 16-bit or
float frame's pass refuses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Level.updateSkyBrightness is `(int)(15.0F - skyLightLevel)`: the float
truncated. Horizon's environment::sky_darken (and the End copy in
Game::sky_darken) rounded, stepping the darkening up half a level early
at dusk and late at dawn, which moves bed eligibility and the light-
dependent spawns with it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Cache each baked face's shape for both collision-fullness states and pack
its own UVs once, preserving the original floating-point operation order.
Address neighboring blocks, AO samples and fluid samples from padded cell
indices instead of recomputing coordinate indices for each lookup.
Add asset-independent checks against coordinate lookups for padded steps
and all AO sample tables, plus literal shape bounds and packed UV values.
Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8KX3vDMZy53y1vXqeT
LivingEntity.travelFallFlying stops the glide on a climbable, and a
server simulates its players' travel (AUTHORITATIVE_SIDE_AND_SERVER).
Horizon's server travel cleared the flag only on the guest's temporary
body; as_guest now copies it back, so the glide ends on the server and
the client, which takes the flag from the server, stops gliding too.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
handleClientCommand(PERFORM_RESPAWN) for a player who won the game
calls restartClientLoadTimerAfterRespawn, as the death respawn does: the
player's movement and actions wait, and isInvulnerableTo spares it, until
its client reports the level loaded or 60 ticks pass. Horizon restarted
the timer only after a death.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HDR design step 3, the frame and the display pass (docs/design/hdr-linear-light.md):
with the switch, HDR on and no shader pack, the renderer draws into Rgba16Float where
the surface offers HDR output and the device draws it, whatever Smooth Gradients is.
0.1 and shaders/ packs keep today's frame; HDR off is untouched.
The HDR pass keeps light over 1 where the world shows, up to the peak, and clips it at
white under the HUD and menus and with HDR Lighting off, as a unorm frame does. On
today's frames, which hold nothing over 1, the pass computes the same values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Leashable.setLeashedTo goes through LeashData.setLeashHolder, which
clears the delayed holder a save named. Horizon kept it, so 100 ticks
after loading the forgotten holder still dropped a lead item next to a
mob the player was leading, or a holder that loaded late took the mob
from the player. Mobs and boats both.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Piglin.finishConversion cancels the admire, then drops every non-empty
stack of its inventory with spawnAtLocation (whole stacks, components
kept), then converts; the converted sound comes before all of it. Horizon
dropped only the admired off-hand item, before the sound, and the
inventory was lost with the piglin.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrownEnderpearl.onHit calls resetCurrentImpulseContext after the fall
distance reset, so a wind charge's or mace smash's impact height no
longer limits the next fall.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without a shader pack the rain also pulls the fog in
(AtmosphericFogEnvironment's rain fog), which eases toward the rain only in
tick_particles, and a frozen client skips that. With the rain at its level,
a frozen scene showed full rain but no rain fog. Under HORIZON_STILL a frozen
client now puts the rain fog at its target, from the same inputs the eased
path reads; the easing itself is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rain and thunder levels fade 0.01 a tick and stood still in a frozen
world, so a scene that wanted rain had to step the world about a hundred
ticks, and its mobs walked differently in each launch. Under HORIZON_STILL a
frozen world now holds both levels at their targets, as STILL already does
for a shader pack's smoothed values: /weather rain shows full rain with no
step. A running or stepping world, and normal play, fade them as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reuse the admitted modern SNBT decode contract for merge and literal modify.
Add actual Game storage and command-oracle regressions for typed values and
no-change copy callbacks.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
tools/test_use_item_on_parity.py never held an axe, hoe or shovel against a
block the minecraft:block_transformer registry names, so game.rs's
use_block_transformer was never exercised. It now compares log stripping,
copper scraping/waxing, copper doors and trapdoors, double copper chests and
soil tilling, which found two differences from vanilla:
- rooted dirt's hanging roots must use Block.popResourceFromFace, on the
clicked face with the face's own motion, and drop before the block changes;
- scraping or waxing a double copper chest must send a second block_change
game event and the same particle at the chest's other half.
The VanillaDispenserOracle stand-in player also swallowed the item use start
vibration LivingEntity.startUsingItem posts; it now posts it too.
5200/5200 cases match on the new corpus.
16.666667 steps a 60 fps clock. STEP_MS is a finite number more than 0,
and the reply gives it back as given.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Horizon pack's hot reload could rebuild the pack under the same name
mid-sequence, unseen by the scene check: it now waits for the reply. A
sequence doesn't start, or ends, while the level-loading screen shows,
which a new world keeps a moment after it is ready. The 30 s limit is on
the frames before the last: the last frame's PNG waits as screenshot does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
screenshot-sequence FRAMES STEP_MS PATH, under HORIZON_STILL with the world
frozen, makes the running pack start over and draws FRAMES frames on a
scripted clock: frame n is n frames and (n - 1) x STEP_MS milliseconds in,
STEP_MS after the frame before (0 on frame 1). The last is written as
screenshot PATH writes it, its -hdr.png too under HDR output, and the reply
names the pack and the last frame's clock. The same build, GPU and driver
write the same bytes every run, for shaders/ packs, Horizon's own and none.
Sequence frames are submitted inline and waited for on the GPU, so each
reads the centre depth the one before left, or the sequence fails; a frame
the surface refused is drawn again. It refuses to start with far terrain,
TAA, frame generation, chunk fade or the shadow cache on, refuses commands
that would change the scene while it runs, and ends with an error when the
camera, world, settings, pack or window size change. cancel stops it, quit
still quits. Documented in agent-control.md's STILL section, shaders.md and
api/shaders.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since smooth() keeps going through teleports, a pack remembers whether it
has drawn a frame of the world. A reset pack now forgets that too, as a
pack just loaded has seen none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pass that reads one of the pack's targets ([[post]] output) before the
pass that writes it got last frame's picture after a reset; a pack just
made reads it blank. The reset now drops the targets and their bind
groups, made again blank on the next frame; the pipelines stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Renderer::reset_shader_pack_history makes the next frame draw as a freshly
loaded pack's first, for either kind. A shaders/ pack's buffers, images,
storage buffers and shadow maps are made again when next drawn, its passes
get their declared uniform values back, and the frame uniforms, custom
uniforms and centre depth forget earlier frames; its programs stay. A
Horizon pack's post history goes black and its eased values, wetness and
frame count restart. Without a pack it does nothing. Nothing calls it yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds the Nord palette (polar night, snow storm, frost) as a launcher theme with its own dark and light tokens and sky art. Palette themes draw the square Blocks look: no glass, no rounded corners. The night tokens are square everywhere, and the Blocks status dots and controls follow. Blocks and the light glass look draw the baked sky, so resizing no longer rebakes the scene per frame and stays responsive. Settings gains the launcher-theme picker in the Blocks look. Nord now shows the sky instead of the scene's pale halo.
The first install on Windows now checks SHA256SUMS.sig (a Nightly's
SHA256SUMS.v2.sig, for the version its title names) against the release
key, then the archive against SHA256SUMS, as install.sh does. OpenSSL is
looked for on the PATH and in Git for Windows. Without OpenSSL 3 it says
so and keeps the GitHub SHA-256 check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Horizon took shadowDistance, sunPathRotation, shadowIntervalSize,
shadowNearPlane and shadowFarPlane from a program's vertex shader too, and
read nothing of shadow.fsh but its formats when the pack has no shadow
pass. Iris 1.11.7, with probe packs of ours, ignored those five declared
only in final.vsh, gbuffers_textured.vsh or shadow.vsh (its shadow
projection, shadow camera and sun stayed at the defaults), and applied
shadowDistance and sunPathRotation declared only in shadow.fsh of a pack
whose shadow buffers nothing read.
Part of #10490
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractMinecart, MinecartTNT, MinecartFurnace and MinecartHopper read
their fields through ValueInput's numeric getters, which take any
NumericTag: getIntOr (fuse, DisplayOffset), getShortOr (Fuel, the low 16
bits), getDoubleOr (PushX, PushZ) and getBooleanOr (FlippedRotation,
HasTicked, Enabled: byteValue() != 0). Cart::load took only byte, short
and int, and only double for the pushes, so {fuse:20L} left a TNT
minecart unprimed and {PushX:1} gave a furnace minecart no push.
Command harness group "minecart numeric fields": 3/18 -> 18/18.
Fixes#11146
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Match vanilla's column order and fallback when choosing a breathing destination.
Co-Authored-By: Codex (gpt-6.1-terra) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The pad's own click hid the guide, and so did Alt or Super on their own. The
inventory rows now hide for the keyboard and mouse too.
- Rows that promised something the game refuses: Equip on what can't be swapped,
Throw on a Riptide trident out of water or about to break, Use on an empty or
fixed item frame, Trade on a baby villager.
- Slots: Put rows on a slot that refuses the carried stack or has no room, Put Down
for stacks with different components, Take over a result slot with another stack
in hand, and any rows on a crafter's output or Put rows on a furnace's.
- Rows drew over a panel that fills a small view.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Now installer supports native to PrismLauncher json and correctly finds json, client.jar and other sources from minecraft, allowing it to install from portable installations without additional downloading from internet
Replace saved NBT and item slots through the existing entity while preserving its live identity and runtime menu data. Keep normal decoding, dirty marking and update logging; verify real data commands, typed opaque fields, inventory removals and fresh different-type/chunk-loaded identities.
Co-Authored-By: Codex <[email protected]>
Review: hurts dealt after the creature tick (ordered TNT, beds and anchors,
falling blocks, conduits) still waited for the next tick. Explosions and
falling blocks now settle right after each hurt, before the push, and the
conduit pass after it runs. And the hurt's own events (its sound,
InfestedMobEffect.onMobHurt) are handled before the teleport, where the
enderman was hurt and from its random's next draws, as
LivingEntity.hurtServer runs them before Enderman.hurtServer teleports.
Tests: an_explosion_teleports_endermen_before_it_returns (11/12 left
pending without the explosion settle), an_infested_endermans_silverfish_
come_out_where_it_was_hurt (no silverfish when the outbox only joined the
teleport's events).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mod block entities can replace, clear and atomically take an owner-controlled batch of client update fields without saving it or changing retained contents and revisions. The lazy queue is empty for new, decoded and cloned entities; normal loaded-chunk and block broadcast paths remain unchanged.
Validated with 3 real entity lifecycle/concurrency/poison tests and 3 reusable save/chunk/two-viewer Hub tests. Strict all-target Clippy passes for horizon-client-render and horizon-client-lib. No new dependency, third-party implementation or resource files.
Co-authored-by: Codex <[email protected]>
The comparison #1073 asks for, in the repository: FPS and 1% lows at render
distances 8, 16 and 32 for Horizon, vanilla 26.3 and vanilla with the common
performance mods, from the clean, all-windowed, idle-machine run already saved
in benchmarks/perf/, with the conditions, the startup/load/memory figures and
links to the full reports, the method and playhorizon.dev/benchmarks.
Co-Authored-By: OpenCode Agent <[email protected]>
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Enderman.hurtServer teleports inside the hurt (teleport(), or
repeatedlyTryToTeleport() for a projectile or a thrown potion). Mob::damage
has no world, so it only leaves the tries in counter2 and the enderman's
next AI step ran them. Game::settle_hurt_teleports runs them right after
/damage, and in the creature tick before the mobs tick and after the
systems' outbox: a command after /damage no longer finds it in place, and
under /tick freeze it teleports at all.
Command harness group "enderman command damage": 3/5 -> 5/5.
Fixes#11069
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Take back the server list's move arrows as targets: they split Tab's stop on
the list, and the D-pad still couldn't land on them.
- A right stick on Z takes over in every direction, not only its pulled end.
- A pad's toggle-sneak doesn't count as holding, or a rebound key's release could
leave the player sneaking.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.collide adds getEntityCollisions: the boxes of the entities whose
canBeCollidedWith(mover) holds. Mob movement only had block collisions,
so a zombie or a dropped-on mob fell through a living shulker and
through a happy ghast a player holds still by standing on it (#10445).
Seen carries the happy ghast's still timeout and whether it carries
someone; the mob tick lists the slots that may block (shulkers, happy
ghasts) so other mobs pay nothing when there are none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
compile() stopped handing out jobs once one pipeline was refused, so the
serial rebuild that follows compiled all the remaining programs cold.
Now every job runs and only the first refusal is recorded, so the
rebuild finds the driver cache warm. The test checks the jobs after a
refused one are still made.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The merchant fixture compares strict command and menu records, so the raw
packet trace (net/command_trace.rs in the client receive loop, the Java
trace reader, the oracle's serialized channel) and the per-operation
receipts were diagnostics only. The production client no longer writes or
joins anything for them; tools/test_merchant_gate_parity.py is a thin
wrapper like the other command_exec fixtures.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Releasing Shift while R3 held sneak in hold mode dropped it for both, and so did
the other way round.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tools/test_non_mob_saved_flags_parity.py needs a fresh --work-dir that
parity.py never passes, so every selected run would fail; the unit test
covers the four writers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrownTrident inherits AbstractArrow.lerpMotion, which restarts life even
when the motion is zero. The remote motion test compiles remote_motion out
of remote.rs, so its fixture needs the arrow fields; it now also checks
that zero motion keeps a stuck arrow in place and non-zero motion frees it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each was painted by hand or hit-tested with no target, so a pad couldn't use it:
the recipe book's search box had no keyboard, a command's suggestions couldn't be
picked, a server couldn't be moved, Realms Configure's tabs had no bumpers, and
the HUD editor's footer ended its crosshair.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The boat row said Ride while sneaking, in a boat or on a full one, which refuses
or opens a chest boat's chest. In play only moving the mouse took the pad out
of use, so the guide stayed up through keyboard and mouse-button play.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A book's page and signing title weren't fields, so a pad couldn't write one.
- A sign's keyboard closed on Enter after line 1, and nothing reopened it. Enter
now keeps it open, and A opens it again.
- A keyboard open over chat stayed over the death screen and took every button.
- B answered the death confirm during the second it's meant to wait.
- A guest who joined with a pad got P1's Controllers off in a new profile, so
that pad did nothing, on this join and every later one.
- Pad presses in another window could join a player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Track MerchantResultSlot removal and quick-move counts, reset them at the achievement callback, and award through the actual trading player's stat API without a recipe ID.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The join test's bot reads packets with the pinned readers; the world
packets the adapter now writes in 26.2's layout are counted (chunks) or
skipped instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Chunk columns (states down with stand-ins, repacked at 26.2's 15-bit
global palette, block entity types down, light masks as longs),
light_update, level_particles (26.2 field order, one speed, Int count),
explode (no trailing sound flag, silent blasts name intentionally_empty),
move_entity_pos/_pos_rot/_rot, entity_position_sync and animate in
26.2's encodings, swing_animation as 26.2's animate.
Tests write each in 26.2's layout and read it back with translate's
26.2 readers, made pub(super) for that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While ids are renumbered down, the values kept as pinned bytes that hold items
(pot_decorations, container, use_remainder, sulfur_cube_content) are read again
and written with that version's ids, pot sides as 26.2's four item ids, and a
tag slot display as 26.2's bare tag id (a composite of item displays for a list
of items). Quirks::{pot_decorations_are_items, tag_slot_display_is_id} written
the other way; ids::quirks_written gives the quirks of the version written for.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test server keeps every packet the hub hands to the game; a 26.2 bot
confirms its teleports by id alone, walks, starts and stops destroying the
block below in 26.2's action numbers, and swings with each hand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
layout_26_2 mirrors translate's 26.2 readers for a ClientSession: spawn info game
types back to bytes, the advancement tab position back inside its display,
argument types renumbered down with a string stand-in for 26.3-only parsers,
26.3-only synchronized registries not sent, map decorations and stats in 26.2's
numbering. Enabled with layout_26_2::set_enabled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A 26.2 client confirms a teleport by its id alone: the session follows the
player_position packets it is sent and the moves it reports, and fills in
26.3's position and rotation from the teleport it accepts. Its digging
actions past 0 are one up (26.3 inserted CHANGE_DESTROY_DIRECTION at 1), and
its main-hand swing becomes 26.3's punch (an off-hand swing has no packet).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- An idle pad's trigger rests at -1, which counted as moved past 0.5, so touching
it took over from the pad in use.
- An analog trigger jumped to full at gilrs' 0.75, so a higher threshold did
nothing.
- gilrs drops `~` from SDL rows, so sticks on the nine Linux rows with one ran
backwards, the Switch 2 Pro among them.
- The 8BitDo Ultimate 2 without a row looked up when pushed down, with X and Y
crossed.
- Rumble reached the pad from an unfocused window, adaptive triggers stayed on
over books and signs, a stale lightbar stayed after splitscreen, and in_use
outlived an unplugged pad.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hold shared the keyboard's slider_edit, so Start, a scroll, the mouse or a
Tab could leave some other slider held. The pad's own position also survived
Undo, so the next step jumped back near the undone value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
min(150, 35) + 20 = 55, then one anger tick: 54. The test pinned the dropped
anger the earlier port left.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the completed ordered request list for the current atlas so an
unchanged overflow does not regenerate and upload every trim each frame.
Preserve final LRU residents, retry failures, and invalidate the cache
when equipment, atlas contents or the graphics device changes.
Fixes#10539
Co-Authored-By: Codex GPT-6 <[email protected]>
`build_surface_shared` applies the compiled rule at every solid block of a
column. In the Overworld most bands specialise to "the ore veins, then one
block" (deepslate), so 1.7M rule applications per 64 chunks produce ~1M block
writes, each walking the rule tree from the top and going through
`set_block`/`set_block_worldgen_column`.
A band variant that is a block rule, or ore veins followed by one, gives that
block at every position where no vein places one. The walk now writes the whole
run of solid blocks at once: the top y through the single-block path, the rest
through `TerrainChunk::fill_solid_run`, stopping at the first y where a vein
can place a block (each vein's first test, its density), at the band's bottom
and at the write bounds. Only blocks that are opaque for the stage's heightmaps
and carry no fluid take this path, so one update at the run's top leaves the
maps as a write per block does.
`CompiledRule::apply` on a sequence also tests its conditional children in the
loop instead of through a recursive call.
Instructions (callgrind, proto phase, 64 chunks, seed 12345): Overworld
6.49 G -> 6.03 G (-7.0%), surface stage 1.98 G -> 1.53 G (-23%), rule
applications 599 M -> 329 M; Nether 2.97 G -> 2.73 G (-8.1%).
A first draft by DeepSeek V4.1 Flash, via pi, reviewed and reworked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main added enderman_hurt_teleports after the block contact damage; this
branch drops that call's world argument. Both kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
c843c1aa0 (the loot-pickup save test) calls mob_to_nbt with the old four
arguments; the bridge's fifth, uuid_of, is |_| None there like the other tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main checked bubble columns by name (6bd507f89); this branch reads the
block's exported liquid flag, which covers the same three blocks and any
mod block marked liquid, so the name list and its test go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Most NBT strings in a saved chunk (names, ids, compound keys) are ASCII.
The reader decoded modified UTF-8 one byte at a time and the disk writer
put every unit through its own limit check. Now an all-ASCII string is
widened in one pass when read, and a string whose units are all one byte
(1 to 127) is written in one extend when it fits under the byte limit;
anything else takes the old per-unit loop, so the bytes and the error
offset are unchanged. A new test checks ASCII, zero, mixed and non-ASCII
strings against a per-unit reference and the limit's error offset.
While the dragon fight's world fog leaves out the End's sky, Iris 1.11.7 still
runs a pack's gbuffers_skybasic (render stage SKY, vertex colour fogColor), and
the gl_Vertex values our probe packs read there (render distances 12 and 8) fit
a fan around the horizon: from (0, -16, 0) under the eye to eight rim points 16
above it, every 45 degrees, at the render distance. Horizon ran no sky program
in the End, so packs that paint their sky there left that band empty.
Frame carries the boss bar's world fog; PackSky draws only the fan then, in
the fog colour.
Part of #10491
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The driver compiles each program of a shaders/ pack as its pipeline is made,
and a pack's pipelines were made one at a time while the pack was laid out:
Metal's compiler service sat on one core for 5 to 50 s on a big pack (BSL,
Complementary, Photon) with four others idle.
The pack is now laid out first, its pipelines recorded as jobs whose pipeline
goes in a slot of its pass, then made all at once on up to 8 threads (wgpu
keeps error scopes per thread, so each is still checked). When wgpu or the
driver refuses one, the pack is built again a program at a time, as before,
so that program is skipped with its note exactly as it was.
Cold loads on an M5 (each pack's first load, nothing in the Metal cache),
pipeline creation: Bliss 6.3 s -> 1.8 s, Solas 5.2 s -> 1.6 s, Complementary
Unbound 9.0 s -> 2.3 s.
AngerManagement.increaseAnger adds angerByUuid's anger for a new suspect
(removeInt, uncapped). The mob layer knows no UUIDs, so convertFromUuids' port
dropped the saved anger of a suspect that was angered again first (a reloaded
warden hearing the player within its first anger tick); it is added there now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Direction.LEGACY_ID_CODEC reads a byte through from3DDataValue, which wraps it
(abs(b % 6)), and getIntOr("Steps", 0) keeps any int; the read clamped Steps to
0 and passed an out-of-range Dir through.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The description at the bottom of the page stayed the same whichever value was
picked, on the Gameplay Guide, Button Glyphs, On-screen Keyboard and Lightbar.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Skip only the Nautilus custom air handler under NoAI, preserving the existing Breath/drown path and normal custom behavior. Cover actual Game and full Mob ticks against fresh Java.
Co-Authored-By: Codex <[email protected]>
Left and right changed a slider as soon as the focus passed over it, so moving
through a settings page could change a value. A now takes hold of it, left and
right move it, A or B lets go and up or down moves on, with a white outline
while held.
The gameplay guide's on/off switch becomes Full, Basic and Off. Basic keeps the
right column. A file saved with the old switch keeps its meaning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`RealmsSelectWorldTemplateScreen.Entry`'s 15x15 `icon/link` and
`icon/video_link` buttons beside each row's version, opening the
template's page or trailer; the drawn rectangles are kept for the click.
Co-Authored-By: ZCode <[email protected]>
Part of #9996.
BlockSection::set and BlockSection::fill_run are 7.0% of the proto phase
in the overworld and 9.4% in the Nether (callgrind on generate_proto).
Each write located its packed word twice, and fill_run rebuilt the word
mask for every block.
PackedStorage now hands out a Slot (word and bit shift) that read_at and
write_at take, so set locates the entry once; the palette-growth branch,
which re-packs the words, looks it up again. A run in one column moves
256 entries per Y, so while a word holds a whole number of those steps
(every width generation produces) the bit shift stays the same along the
run and WordRun writes each block with one load, mask and store. Widths
that do not divide the stride go one set per block, with the same palette
edits in the same order.
Live palette slots hold distinct states (sections loaded from disk are
rebuilt through set), so "already this state" is old_id == id and "was
air" is old_id == air_id, both resolved once per call. set on a uniform
section has its own small path.
Palettes, their order, the free list, counts, the chosen width and the
packed words are unchanged: the 8 chunkgen_bench snapshot files (terrain
and decorated; overworld seeds 12345 and -1, the Nether, the End) are
byte-identical to main, and a new unit test replays random set/fill_run
sequences over every palette width against main's fill_run.
Proto-phase instructions: overworld 6.497 G -> 6.340 G (-2.41%), Nether
2.974 G -> 2.859 G (-3.84%).
A first draft by DeepSeek V4.1 Flash (pi coding agent), reviewed by Claude.
Co-Authored-By: pi coding agent (DeepSeek-V4.1-Flash) <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fekd3xkgubE8VfXrALh2td
`Warden.addAdditionalSaveData` stores an `AngerManagement`: a `suspects` list of
{uuid, anger} pairs, and nothing else about the anger. Horizon wrote an empty
`anger` compound and read nothing back, so a reloaded warden was calm.
`AngerManagement` now keeps the suspects that are not in the level by UUID next
to the live ones, both calming by one an anger tick, and looks the UUIDs up
again every two anger ticks through the game layer's map
(`Game::who_by_uuid`, reached from the mob layer through `MobWorld`'s new
`WhoLookup` seam). The chunk save names a suspect by its UUID, minting one for
a loaded suspect that has none yet.
A save can now name an entity it only knows as a `Who`: `mob_to_nbt` takes a
`uuid_of` resolver, `who_uuid` writes a `Who` as a UUID and `who_by_uuid` turns a
stored UUID back into the `Who` of a loaded player or mob. `EntityMemory::uuid_of`
supplies the id -> UUID map the game layer keeps, and the game layer's `mob_by_uuid`
now resolves through the same seam.
The neutral mobs' `angry_at` is the first writer: anger at another mob now saves
that mob's UUID instead of nothing. The warden's suspect list and the shulker
bullet's target are the two follow-ups this bridge unblocks; neither is written
here.
Floor floating Health values before short narrowing and preserve integer narrowing. Add an actual decode-to-save regression and an isolated Game/Java command fixture.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
ShulkerBullet.addAdditionalSaveData writes `Target` (the UUID it homes on, only
while it has one), `Dir` (`Direction.LEGACY_ID_CODEC`, the step it is on, absent
until it picks one), `Steps` and the per-axis target deltas `TXD`/`TYD`/`TZD`,
over `Projectile`'s owner and LeftOwner; readAdditionalSaveData reads them back
and keeps `Target` as an `EntityReference` it re-resolves every tick.
Horizon's `saved_with_chunk` turned a bullet with a target away - which is every
bullet a shulker fires - and `spit_or_bullet_to_nbt` refused it too, so the chunk
save dropped the bullet, and nothing read a step or a delta back.
The writer writes those keys now, the reader reads `Dir`, `Steps` and the deltas
into the kind, and `Target` goes through the UUID bridge: `Game::save_who_uuid`
answers it with `who_uuid` over `EntityMemory::uuid_of` (nothing for a mob the
level has no UUID for, never a made-up one), and `Game::bullet_target` resolves
the stored UUID with `Game::who_by_uuid`, which names the player or a mob that is
loaded. A bullet whose target is not loaded comes back with none and falls, as
`ShulkerBullet.tick` does with a target it cannot find, rather than homing on
whatever its old id means now.
A loaded bullet's own random stream is seeded from where it is, since
`Entity.random` is not saved: the same stand-in `Projectile::new` gives an
arrow's.
A save can now name an entity it only knows as a `Who`: `mob_to_nbt` takes a
`uuid_of` resolver, `who_uuid` writes a `Who` as a UUID and `who_by_uuid` turns a
stored UUID back into the `Who` of a loaded player or mob. `EntityMemory::uuid_of`
supplies the id -> UUID map the game layer keeps, and the game layer's `mob_by_uuid`
now resolves through the same seam.
The neutral mobs' `angry_at` is the first writer: anger at another mob now saves
that mob's UUID instead of nothing. The warden's suspect list and the shulker
bullet's target are the two follow-ups this bridge unblocks; neither is written
here.
RealmsResetWorldScreen's tiles, RealmsSelectWorldTemplateScreen's paged
list, the local-world picker and RealmsUploadScreen with the tar.gz
packer and the upload body. Follows the 26.3 bytecode of those screens,
client.worldupload.RealmsWorldUpload/RealmsUploadWorldPacker/FileUpload
and RealmsClient.fetchWorldTemplates/resetWorldWithTemplate/
requestUploadInfo/putIntoMinigameMode.
Co-Authored-By: ZCode <[email protected]>
In play the HUD's top corners name the buttons for what the crosshair is on, what's held and how the player is
moving. In an inventory the bottom corners do: plain face buttons at the bottom right, everything else at the
bottom left, by what the crosshair is on and what's carried. Rows follow bindings and step around what was drawn.
The mouse or keyboard taking over, a prompt, F1, F3 and the setting hide them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep spectral duration in the loaded arrow state and always write its integer
value, including the default 200. Cover the live command route and load/save
without raw input while retaining broader type and display diagnostics.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
A ledger build from today's upstream/main reports seven map gaps, all rows
whose where cell names a function that no longer exists: PrimedTnt.explode
points at Game::tick_tnt (it is tick_one_tnt), FlatLevelSource.createState at
FlatTerrain::structure_settings (from_settings), PacketProcessor's hand-off at
conn::read_loop (io_pool::Pool::push), three ServerCommonPacketListenerImpl
keep-alive rows at conn::write_loop (that loop is wait_alive), and
PackSelectionScreen.Watcher is a class row written as a port.
Resolve an absent saved show_icon from show_particles while preserving
explicit icon bytes and the remaining potion fields. Keep this in the
saved item encoder, without changing the general component view.
Add resource-free decoded/modelled codec coverage and a guarded,
zero-tick chest command regression against the pinned Java behavior.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Keep the map-crafting, transmute, mob-effect and frame-map groups and their
isolation sets. The merged game passes the focused 62-record Java replay.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Retain the real last trader on the villager when a trade is accepted,
then deliver the coalesced reputation event after Brain.tick. Keep it
across menu close and live NoAI edits, and refresh current offer prices
when the event actually runs.
Add an actual-owner merchant regression with complete raw capture and
separate strict-all byte-order diagnostics.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Killing an entity that is no mob posts ENTITY_DIE (frequency 15) at the
entity, as vanilla's kill paths do: BlockAttachedEntity.kill (cushions
punched, burnt, unsupported, struck or blown up; paintings; empty item
frames), ArmorStand.kill (broken, burnt out, blown up, /kill's
generic_kill), VehicleEntity.destroy (boats and minecarts, not a creative
hit, which only discards them) and Entity.kill from /kill for the other
non-living kinds. The attacker is the source when a hit kills it
(attributedTo), otherwise the entity itself. A frame holding an item only
gives the item up and does not die (ItemFrame.hurtServer). The event runs
on the server's path, so single player, LAN guests and dedicated servers
all get it.
New lan_e2e check entity-die: a sneaking host and LAN guest break each
kind 3 blocks from a sculk sensor, which now hears 15.
Part of #10046
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8LUjYucp4kaN6UfSg7
Apply the existing leash writer after the boat's base command tag so live
holders and pending links override stale input data. Keep holder policy
and boat behavior in their existing implementations.
Add an actual Game getter regression and a guarded Java/Game fixture
covering owner clicks, repeated reads, pending UUIDs and detached links.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Each frame picks the layers of the Overworld's or End's sky whose day,
weather, biome and height hold at the camera, eases them over their
transition, and draws them as 3x2 skyboxes with their blend mode, over the
vanilla sky's disc and sunrise (the End's box) and under the sun, moon and
stars. Packs without optifine/sky draw nothing new.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AgeableMob.isBaby is now a row of its own pointing at Mob::is_baby, which is
`can_be_a_baby && getAge() < 0`; canBeABaby points at
species::can_be_a_baby, and the six classes that override it to false carry a
row each so the false arms are not read as unported.
living.rs::a_negative_age_is_a_baby_only_where_can_be_a_baby_allows_it drives
Mob::new on the loading table's own types and sets Age to -24000, the
BABY_START_AGE a save or a /summon carries, then asks is_baby, dims and
voice_pitch - the three the game draws and hears from.
A parrot, a tadpole, a frog, a zombie nautilus, a camel husk, a slime, a magma
cube, a piglin brute and a creeper keep their type's box (the tadpole 0.4 by
0.3, the camel husk 1.7 by 2.375, the piglin brute 0.6 by 1.95) and voice at
1.0 ± 0.2. The control is a cow at the same age: is_baby true, Cow's
BABY_DIMENSIONS (0.45 by 0.7, eyes at 0.69) and 1.5 ± 0.2, and an adult box and
voice again at age 0. With is_baby back to `age < 0` the test stops at the
parrot.
entity_nbt's save test now runs a frog and a camel husk beside the slime, the
magma cube and the pig: the first four write no Age and keep none of a raw
compound's Age, the pig writes its modeled -37.
AgeableMob.isBaby is `canBeABaby() && getAge() < 0`, and canBeABaby is true
across the ageable tree but for the six classes that override it to false:
Frog, Parrot, ZombieNautilus, CamelHusk, Slime and MagmaCube (an
AbstractCubeMob is an AgeableMob). Everything outside that tree inherits
LivingEntity.isBaby's false, so a Giant, a Creeper, a PiglinBrute (it extends
AbstractPiglin and declares no isBaby of its own) and a Tadpole, whose own Age
runs up from -24000, are never babies either; only the zombies, the piglins and
the zoglin answer a synced DATA_BABY_ID instead of an age.
Mob::is_baby read `extra.age < 0` alone. The list of who can be a baby existed
already, but only as mobs::drops::can_be_baby, which gates the metadata baby
flag and the loot table: the box, the voice, breeding, growth and the goals
read the age and so called a frog, a parrot, a creeper or a tadpole with a
negative Age a baby. The list is now species::can_be_a_baby, on the species
table, and is_baby is `can_be_a_baby && age < 0`.
What that changes: a tadpole (Age -24000, its own DEFAULT_AGE) is drawn at
getAgeScale's 1.0 instead of 0.5 and speaks at 1.0 ± 0.2 instead of 1.5 ± 0.2,
as vanilla does; a parrot, a frog, a zombie nautilus, a camel husk, a slime, a
magma cube, a piglin brute, a creeper or anything else the table cannot make a
baby keeps an adult's box, voice, breeding, growth and drops. The Parrot arm of
voice_pitch was there for the same reason and is dead now: Parrot.getVoicePitch
is `getPitch(random)`, the plain range an adult already gets.
AgeableMob.addAdditionalSaveData writes Age only when canBeABaby, and the
branch that drops the values a raw compound carried named four classes by hand;
it asks the ageable table now, which also covers the frog and the camel husk
that were re-saving an Age vanilla writes for neither.
Record a marker only for Nightlies installed by automatic updates. Keep
manual and legacy Nightlies out of GUI and CLI cleanup, while retaining
the newest managed Nightlies separately from game releases.
Add regression coverage for manual, legacy, selected and pinned builds.
Co-Authored-By: Codex GPT-6 <[email protected]>
The Worlds tab's Backup button now opens RealmsBackupScreen: a row per automatic backup
with its date (mco.backup.entry), Restore per row, and Back. A row opens
RealmsBackupInfoScreen: the backup's metadata as translated key/value rows
(mco.backup.entry.<key> when the language has it). Restoring asks nothing (vanilla's
Restore button restores at once) and the list reads again when it is done.
RealmsClient gains backupsFor (GET worlds/{id}/backups) and restoreWorld
(PUT worlds/{id}/backups?backupId={id}, vanilla's 40 s/600 s timeouts), with Backup's DTO
(backupId, lastModified, size, metadata, changeList). The backup id goes into the query
percent-encoded, so it cannot add a parameter.
Download Latest still needs the download-and-unpack flow and is left for its own PR; the
rows show the date and the metadata screen the details (size included).
Co-Authored-By: ZCode <[email protected]>
The GUI runs the shared pruning path after saving its selection, but its
release-only filter excludes every dated Nightly. Include those builds
so the existing retention, selection, instance-pin and in-use protections
also apply to GUI Nightly updates.
Add regression coverage for following updates, selected and pinned
Nightlies, and retention across Nightly/release/local builds.
Co-Authored-By: Codex GPT-6 <[email protected]>
Use the level's backed maps for crafting recipe matching and run map
post-processing on the actual stacks taken by swap, pickup and quick-move.
Keep the original-copy callback used by quick-move and preserve cartography.
Add a real Game/Menu regression and a guarded connected-owner Java/Game
fixture covering previews, consumed inputs, transferred maps and saved backing.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The Players tab's second button now opens RealmsInviteCodesScreen: the count in the
title, the subtitle, a row per code with its expiration (or Never), Copy / Edit / Delete
per row, and Create. RealmsEditInviteCodeScreen edits one code: its active flag, the
seven InviteCodeExpiration choices (a click on the slider track picks the nearest — this
screen has no drag event yet), Copy to the clipboard (a new Outcome::Copy the title
turns into Action::CopyText), Delete, Done and Back.
RealmsClient gains inviteCodes (GET api/v1.0/invite/codes?realmId={id}),
createInviteCode (POST .../invite/codes), updateInviteCode (POST .../update) and
deleteInviteCode (DELETE .../delete/{code}), all on the invite-code service with the
token in x-mojangauth, with InviteCode's DTO. The existing redeemInviteCode now goes
through the same helper. An existing expiration reads back as the closest of the seven
choices (InviteCodeExpiration.closestTo).
Co-Authored-By: ZCode <[email protected]>
The configuration screen's Settings tab (RealmsSettingsTab): the world's name and
description fields (the click moves the caret between them), the region preference line
and its Region Preference button, and the open/close toggle moved here from the footer
(vanilla keeps it on this tab). Leaving the tab — switching tabs or Back/Escape — saves
the changed name, description and preference through RealmsClient.updateConfiguration
(POST worlds/{id}/configuration with RealmsConfigurationDto's JSON: the active slot, its
settings, the preference and the description).
RealmsPreferredRegionSelectionScreen: the automatic choices and every manual region, a
click selects, Done writes the choice back to the tab and Cancel drops it.
RealmsClient gains getPreferredRegionSelections (GET regions/preferredRegions) and
updateConfiguration; RealmServer reads regionSelectionPreference; RegionSelectionPreferenceDto,
RegionDataDto and RealmSetting serialization join the DTOs.
The tier's render/simulation distance options and the region quality icons are the next
PRs (the icons need sprites the GUI atlas does not carry yet); the region rows say their
name for now.
Co-Authored-By: ZCode <[email protected]>
Preserve the existing adapter and maintainer history while including the
upstream removal of stale Menu Navigation translation keys.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Select attribute and carpet registry names from the actual server release.
Upgrade incoming pre-component block entities on both update and chunk paths,
retaining tag identity and vanilla typed-field remainders.
Add independent registry and vanilla DFU regression fixtures, including
missing IDs, removed types and unchanged current-version tags.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Preserve published compatibility commits and newer maintainer changes before addressing the protocol-764 review. This merge alone claims no new compatibility or parity result.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Preserve the published older-server contribution and current upstream changes as separate parents before the next tested extension. No new compatibility or parity result is claimed by this merge.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate protocol 766 attributes, food, horse inventories, projectile power,
chat holders and item use into the pinned protocol. Supply the old static
enchantment and painting names, and normalize legacy wolf variant holders.
Reuse verified recipe and boat layouts and cover changed codecs with
independent vanilla wire fixtures, including full chat packet translations.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate protocol 767 packets, recipes, item components and generic boat
variants into the pinned protocol. Preserve old movement and interaction
layouts, and correct simple spell particles across the older adapters.
Add independent vanilla wire fixtures and document the experimental scope.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate protocol 768 particles, player-list defaults, vehicle movement and
integer model data through the existing older-server pipeline. Preserve old
creaking heart states and transient entities through explicit registry aliases,
and keep legacy trail lifetimes in the client particle provider.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translate legacy chunk framing, item patches, recipe previews, chat and
entity metadata into the pinned client representation. Preserve old click
operations through authoritative server resynchronization and cover older
stack/profile layouts with independent vanilla wire fixtures.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Map protocol 770's packet and registry ids, remove vanilla's unused chunk
padding, renumber command parsers before reading their properties, and
translate player actions, crouch transitions and hanging-entity metadata.
Convert pre-1.21.6 attribute and equippable components in both directions,
with independent vanilla wire fixtures and protocol regression coverage.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Reuse the packet layouts shared with 1.21.7/1.21.8, map the item and sound
registry ids before the lava chicken insertion, and generate inherited tables
only where the vanilla reports differ. Cover shifted discs and sound holders
and exercise the shared metadata and packet conversion cases on both protocols.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Generate vanilla packet and registry mappings, translate the changed layouts
and player metadata, preserve renamed chain states, and extend the offline
protocol probe to fail on decode errors and follow dimension changes.
Co-Authored-By: Codex (GPT-6) <[email protected]>
teleport_to and set_rotation move and turn any entity as /tp and /rotate
do; head_yaw, set_head_yaw, direction, dimensions and push.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
is_shift_key_down, is_crouching, is_sprinting, is_in_water, is_in_lava,
fire_immune, fire_ticks, air_supply, max_air_supply, is_silent,
set_silent, is_invisible and pose, for players and mobs, and the ones
Horizon keeps for armor stands and items.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
level.play_sound and level.send_particles send to every player in range,
as ServerLevel.playSound and sendParticles do, from any server hook (not
only a level hook's LevelWriteHost). The checks the level-hook versions
make move into native_sound_ok and native_particles_ok, shared by both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@@ -51,7 +51,7 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
| `horizon-client-input` | Key bindings and key names, toggle keys, mouse options, controller settings and glyphs, the clipboard, window options (`keybinds.rs`, `gamepad.rs`, `mouse.rs`, `window.rs`...). Re-exported under the old paths |
| `horizon-client-render` | The client's world data, rendering and screens (chunks, meshing, light, streaming, `gfx`, HUD, menus, settings): ~80 modules that horizon-client re-exports under the same paths. Nothing in it reaches gameplay |
| `horizon-client-render` | The client's world data, rendering and screens (chunks, meshing, light, streaming, `gfx`, HUD, menus, settings), modules that horizon-client re-exports under the same paths. Nothing in it reaches gameplay |
| `horizon-world` | Vanilla world data with no gameplay loop: NBT, regions, chunks, `datafix/`, `worldgen/`, `level/` (server-side block behaviour, ticks, redstone), entity physics, items, effects, food, damage |
@@ -65,6 +65,7 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| `horizon-mods`, `cargo-horizon-mod` | Content mods (`.hmod`: manifest, content fence, no code), the registry index and `.hpack`, and `cargo horizon-mod` (`new`, `build`, `run`, `check`, `registry`) ([docs/mods.md](docs/mods.md)). Code mods are native, built by the launcher ([docs/native-mods.md](docs/native-mods.md)) |
| `horizon-optifine` | OptiFine-format pack files below every feature that reads them: `.properties`, the name/range/block matchers, paths and `optifine/` folders, and custom entity models (`cem/`: `.jem`/`.jpm` parsing and baking) |
| `horizon-polytone` | Polytone-format pack files (`assets/*/polytone/`), read clean-room from the public wiki: targets, biome and dimension modifiers and their environment attribute layers (#11486) |
| `horizon-shader-api` | Horizon's own WGSL shader packs ([docs/api/shaders.md](docs/api/shaders.md)): `horizon-shaders.toml`, the `.hshader` package; shared by the game, the launcher and the registry |
| `horizon-shaders` | Shader packs (the `shaders/` folder format), rebuilt clean-room from a written spec of the format; no wgpu |
| `horizon-theme` | Design tokens and fonts for the launcher, the game's own menus and the website |
@@ -92,8 +93,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 -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 test -p horizon-client -p horizon-client-lib --lib --bins --tests # skips the examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client-lib --lib mod_content::tests:: -- --ignored # the 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
@@ -105,8 +106,8 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
- Harnesses and the game need the vanilla data in `.local/` (`minecraft/`, `horizon-client/26.3`, `datagen/`,
`tools/`): see [docs/parity-workflow.md](docs/parity-workflow.md). Without it, pr_ready lists the
harnesses for CI.
- A harness = `tools/test_<name>_parity.py` (324 of them) + a Java oracle (`mods/horizon/src/test/java/dev/horizonlab/parity/Vanilla*Oracle.java`)
+ a Rust probe: `crates/<crate>/examples/<name>_{probe,fixture}.rs` (horizon-world 175, horizon-client 104),
- A harness = `tools/test_<name>_parity.py` + a Java oracle (`mods/horizon/src/test/java/dev/horizonlab/parity/Vanilla*Oracle.java`)
+ a Rust probe: `crates/<crate>/examples/<name>_{probe,fixture}.rs` (mostly horizon-world and horizon-client),
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
@@ -120,7 +121,7 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
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.
@@ -176,7 +177,7 @@ state stays separate from the library tests. This compiles gameplay twice for ex
## How `Game` is organised
- `struct Game` (game.rs, ~290 fields) holds everything. Its methods are spread over ~180 files: game.rs keeps
- `struct Game` (game.rs) holds everything. Its methods are spread over many 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 stay private to the gameplay module and its descendants. Gameplay tests run in the gameplay library, separate from reusable-library tests.
@@ -49,6 +49,18 @@ Everything below is in the current release.
<sub>Nightly 2026-11-05 on a Mac mini (Apple M4 Pro, Metal): vanilla's textures, no mods or shader packs, render distance 32 with distant terrain on.</sub>
## Performance
**Horizon draws more frames per second than Java Edition, and loads faster, on the same machine, world and path.**`tools/perf_bench.py` runs each game in turn, from a fresh world (seed 1234), waiting for the terrain and then flying it over a village, a forest, an ocean and a mountain. Measured on 2026-10-29/30 on an idle Windows 11 machine (Ryzen 7 9800X3D, RTX 5080), Horizon v0.2.97 as the launcher installs it, 1280x720 in a window, VSync off, three games per figure. Frames per second, mean of the four scenes, with the slowest 1% of frames in brackets:
"Vanilla + mods" is Sodium, Lithium, FerriteCore and ImmediatelyFast. Horizon reaches the title screen in 2.9–3.2 s against vanilla's 7.5–7.7 s (the modded client's 10.8–11.1 s), loads a scene's chunks 2.9–3.8 times faster, and playing holds a steady working set of 1.1–1.7 GB against 1.9–2.9 GB. Every scene with its confidence interval and the run's load log: [the 2026-10-29 reports](benchmarks/perf/windows-9800x3d-5080-2026-10-29.md). Method, and how to measure your own machine: [docs/benchmark.md](docs/benchmark.md). [playhorizon.dev/benchmarks](https://playhorizon.dev/benchmarks) puts these runs beside a 2016 laptop, a Mac mini and a gaming desktop.
## Download
Get **the Horizon launcher** from [playhorizon.dev](https://playhorizon.dev), or from [the launcher releases](https://github.com/horizon-rust/horizon/releases?q=launcher&expanded=true):
@@ -410,16 +410,6 @@ The panel's later features (bans, worlds, server.properties import, logs and cra
</details>
#### Ely.by and other sign-in providers
Sign in with other account providers, such as Ely.by or any Yggdrasil-compatible one, beside Microsoft.
<details><summary>Technical</summary>
Issue [#1270](https://github.com/horizon-rust/horizon/issues/1270) asks for Ely.by and Yggdrasil-compatible servers with an account selector in the launcher. It builds on the Microsoft sign-in ([docs/microsoft-account.md](docs/microsoft-account.md)).
</details>
#### Join servers on older versions
Join servers that run older Minecraft versions, such as 1.21 or 1.8 PvP servers, from Horizon.
description="The dedicated server's anticheat: its [anticheat] settings, violation levels and the checks' rules, with no game state (docs/anticheat.md)"
description="The dedicated server's chat filter: its [chat_filter] settings, the slur list's matcher and the mutes, with no game state (docs/chat-filter.md)"
description="The client's lowest layer: chat text and translations, SNBT and NBT paths, the scoreboard, boss bars, game rules and the registry and particle tables commands name"
description="The client's world data, rendering and screens: chunks, meshing, light, streaming, the renderer, the HUD, menus and settings; no gameplay"
Loaded 100 of 1117 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.