Where multi-draws take their count from a buffer (MULTI_DRAW_INDIRECT_COUNT:
Vulkan 1.2+, DX12), the render thread no longer finds each phase 1 section's
facing runs nor uploads a draw per run: it lists one record per section, and
gpu_lists.wgsl's sides_draws writes one indirect draw per run the CPU would have
pushed (same count, base vertex and instance) into a call per pass and arena
page, drawn with multi_draw_indexed_indirect_count. Each call has room for
the most runs its sections' parts can have (Sides::part_runs). Same exclusions
as the flat path; HORIZON_GPU_LISTS=0 turns it off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In frames drawn flat (Metal at render distance 32, Intel Gen11/12 Vulkan),
the render thread walked every phase 1 section's sides each frame to find the
runs of quads that face the eye, then the flat plan copied them into one index
list. Now each section uploads a row once (where its quads start, how Sides
sorted them), and each frame phase 1 pushes one record per section: the GPU
(gpu_lists.wgsl's flatten_sides) finds the same runs as Sides::visible_from and
writes their indices into a call per pass and arena page, drawn indirect.
Phase 2, translucent quads, tiles, mirrors, shader pack terrain and shadows
keep the CPU lists. HORIZON_GPU_LISTS=0 turns it off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passes on 835f2b522; parity 171 passed on it merged into main 0454e97e3. The 5 failures aren't this change's. The budget only ever grows (max of the caller's 1,000,000 and the sweep's cells), so behaviour changes only where the old scan aborted. world_spawn#3 (seed -7565038591901794094, y 115 vs 108) searches with no_collision_or_liquid, not a movement, and its y follows chunk decoration order. sculk, first_person, server_player_targets (600 vs 599) and pack_screen are runner timing and environment. One cost to know about: scan_cells(sweep) adds a few multiplications to every movement, negligible next to the scan. Queued by pr-sweep-M.
🤖 Agent: recipe folder read once per load (json_folder), rebased on main after #6720. Verified: CI verify green on this head; recipe opens 28,882 → 8,172 at start (strace).
The crafting tables, the stations, the recipe book and brewing each listed and
read every file under data/minecraft/recipe again: a start to the title opened
each recipe file 7-8 times (14,581 opens for 2,043 files). json_folder::read
lists and reads a folder once and shares the bytes with any reader while one
holds them; the rest loader and /reload hold the recipe folder across the
tables, so it is read once, and nothing is kept after. Each table still parses
its own copy, with the same order, conditions and errors as before. Brewing
alone (a server join) still reads only recipe/brewing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's xtask check (mod_api::tests::generated_output_is_current) fails: the
hooks catalog's native_state_block mention landed without the regenerated file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: read the change: HandSway::tick is vanilla's again (unwrapped yaw), and tick_wrapped unwraps the game's -180..180 yaw by short steps first, so #6451's boat-turn fix stays (HandSway::rotation already wraps view - bob). It fixes main's first_person failure since #6451 (yaw off by exactly 360°: seen on #6503's CI parity). Verify green; CI parity runs first_person on this head. Thanks!
🤖 Agent: verified by perf-net-client: CI verify green, pr_ready on playhorizon (only failure the known llvmpipe SIGSEGV in horizon-client-render GPU tests, untouched crate), horizon-protocol tests 160/160 on the Mac, and the branch's horizon-bots joined a real vanilla 26.3 server.jar and a Horizon dedicated server (20/20 bots, 14952 and 17682 chunks, no disconnects). Merging.
🤖 Agent: perf-gen-light verified this head: pr_ready --quick green, the 10 generation/light harnesses pass on playhorizon, worldgen output unchanged; -2.8% CPU per proto chunk.
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on d1ee2d879 after the rebase onto #6698 (clippy, 2917 tests of horizon-client/-lib/-render, 600 Python tests). Tooling only (skip-release): pacing_bench.py, horizon-ctl stats raw/reset, spans::laps.
NeoForge's ItemFishedEvent fires later, in FishingHook.retrieve once the
loot is rolled, and carries the drops and rod damage: not this hook's
contract (Dyna's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Post::prepare built the scaled world targets while the old ones were still
alive and without catching a failed allocation, so dragging the Resolution
Scale slider could run a small GPU out of memory and wgpu's fatal default
handler panicked (#6607).
Drop the old target first, build the new one inside an out-of-memory and
validation error scope (a failed texture is invalid, so its view fails
validation too), and on failure step the scale down by a quarter, to a floor
of 0.25, then draw at the window's size.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The first layer of joining older servers: a ServerVersion type and, per
connection state, how a 26.2 server numbers each packet, generated from
both releases' packets reports. Nothing calls it yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
perf_bench measures throughput on short flights; pacing_bench records every
frame of the sessions where players see hitches (64 blocks/s flight, eight
long teleports, Nether trips, inventory and pause menu spam) and reports p50,
p99, p99.9, max, 1% and 0.1% lows and frames over 33 ms, with the timed spans
of the slowest frames (HORIZON_SPIKES) beside them.
- horizon-ctl `stats raw`: every frame time since the last reset, as `fly ...
raw` gives for a flight.
- spans::laps: a function's consecutive steps as spans, nothing read while
spans are off. Renderer::draw_frame and Game::entity_geometry time their
steps with it; they found the lead knot's model parse (41 ms) and the
arena page frees (8-14 ms).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on 08508f5d8 (clippy, horizon-client-render tests, 598 Python tests); CI parity decides the affected harnesses. Linux only: submit-thread queue waits now count in last_wait, so frame CPU, the frame graph and HORIZON_SPIKES stop reporting GPU waits as CPU.
🤖 Agent: the title's first frame before the world renderer's build. Verified: CI verify green on this head (incl. the new handoff-wait test); A/B in the PR (title median 3510 → 2482 ms).
🤖 Agent: rest loader starts before the handoff's CPU work. Verified: CI verify green on this head; A/B in the PR (rest ready −216/−233/−316 ms, 3/3 pairs).
🤖 Agent: renderer pipeline reuse across the loading/title/world renderers on one device. Verified: CI verify green on this head (fmt, clippy -D warnings, workspace tests), A/B in the PR (title artwork step median 4.8 → 1.5 s on llvmpipe).
The original uniform-name control already fails on unchanged upstream in the generated uniform block. Exercise the existing local identifier rewrite without expanding this SSBO-keyword fix into uniform code generation. The frozen upstream GPU fixture accepts this corrected control.
Co-Authored-By: Codex GPT-6 <[email protected]>
🤖 Agent: verified: CI green on this head, A/B on the Mac M4 Pro at RD 32 shows 0.1-0.25 ms less main-thread CPU per frame (with the sibling PR), nothing drawn changes. Merging.
🤖 Agent: verified: CI green on this head, A/B on the Mac M4 Pro at RD 32 shows 0.1-0.25 ms less main-thread CPU per frame (with the sibling PR), nothing drawn changes. Merging.
6aec3da41 wrapped the yBob easing inside HandSway::tick, so a turn of more than 180
degrees in one tick (a teleport, a scripted rotation) eased the short way where vanilla's
unwrapped yaw eases the long way. tools/parity.py first_person went from 0 to 2,973
differing cases, all in yBob. tick is vanilla again; the game's wrapped yaw goes through
tick_wrapped, which unwraps it by its short steps first, so the boat case keeps its fix.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on 873ff80cd (clippy, 989 horizon-client-render tests, 598 Python tests); CI parity decides the 132 affected harnesses. Emptied arena pages are freed 4 frames later on a helper thread instead of on the render thread.
The server's copy of a guest never travels with the client's own
velocity: handleMovePlayer moves its body, and server_travel starts from
whatever delta movement the copy last had, a few hundredths while
gliding. LivingEntity.handleFallFlyingCollisions reads that as the speed
the glide lost (moveHorLength), so (moveHorLength - new) * 10 - 3 stayed
negative and flying into a wall never hurt.
Carry the tick's movement into the copy's delta movement before the
travel, as handleMovePlayer does, and guard the rule with a test: a glide
into a wall at 1.2 blocks a tick hurts by minecraft:fly_into_wall.
Fixes#6494
🤖 Agent: bench-only (chunk_load_bench measures the workers' mesh_reusing + sort_solid path, percentiles, --mesh-threads, --json). Verify green on this head (rebased on main db34ce484d); pr_ready block in the comments (its one failure, armor_stand_motion_tests, also fails in other agents' runs on playhorizon). skip-release.
Surfaces at the strongest block and sky light rise above paper white, sky light by how a face
meets the sun, and the whole of it eases into the peak. Part of the frame's alpha carries it
to the last pass, where it amounts to vanilla's lightmap going past 1. Off while a shader
pack's post or material stage runs. Fog and water take it away as they should.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review:
- getHitEntitiesAlong returns the block it clipped when no entity is in the
attack range, and filterHitResult keeps a block within reach, so only a
miss falls back to the plain pick (a mob inside a spear's minimum reach
with the floor behind it is not the target).
- Minecraft.startAttack attacks an entity only inside the main-hand item's
attack_range.
- handleKeybinds takes the number keys before swap hands, drop, inventory,
chat and pick-block, which act on the key here: they select the queued
keys first.
- A remote client sends the new slot in the tick only when that tick acts
(ensureHasSentCarriedItem); else the next tick's sync does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: by Ludovic's AI agent (Claude). pr_ready --quick passed on 33e2c32d3 (clippy, 593 horizon-client tests, 598 Python tests); CI parity decides the 60 affected harnesses. Moves six entity models' bake to the startup worker: removes a 41 ms render-thread hitch on first sight.
🤖 Agent: verify passes, and parity passes on 5fe8c6def merged into main 4bc8e359c (server_knockback PASS, with the new pvp-shield-partial and zombie-shield-partial cases). The code fix came in through #6381, so this PR adds the two harness cases that pin it. Queued by pr-sweep-M.
A light source's mark ignored fog: fully fogged, it stood out, through water since #6471.
A sprite that shines all over took a threshold from one frame of its animation, so other
frames' pixels dropped out. The vignette's darkest corners lost the lift at once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A native mod (a Dynamic FPS port) needs to cap the frame rate while the
window is unfocused or minimised. Game::framerate_limit now asks
native_framerate_limit(focused, minimized, in_world, limit) after the
inactivity throttle and Battery Mode, and keeps the lower of its own limit
and the mod's; values below 1 are ignored. The API number comes from the
framerate_limit fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Minecraft.tick picks the crosshair target (raycastHitResult: the held
item's attack_range, a spear's 4.5 blocks) before handleKeybinds takes the
number keys and then the attack clicks. Horizon changed the slot when the
key came in and picked the attacked mob with the plain 3 blocks at the
click, so a spear-to-mace attribute swap swung at nothing (#6376).
The number keys now wait for the tick: its pick (block and mob, with the
active item's attack_range) runs first, then the keys select, then the
clicks act; a remote client sends the new carried slot before them.
tools/attack_swap_e2e.py drives the client against a vanilla server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An ingredient that names a type in a namespace outside minecraft: used
to fail the recipe file whole. It now reaches native_ingredient(kind,
data, items), a free function in crafting.rs which mixin bundles hook:
fill the item list with what the ingredient matches, as a tag is. The
list holds item ids only (components are not checked; a match-time
hook follows separately). Duplicates are removed keeping first-seen
order. The hook is called once per ingredient by each copy that loads
the recipes — the crafting tables', the network copy's — so at least
twice per load, and the answer must be the same. An unclaimed kind
leaves the list empty and the recipe fails, as an unknown kind does; a
bare type name is minecraft's own.
The catalog target names horizon_client_lib, the package crafting.rs
compiles into since #5446's split: horizon-mixins resolves the crate
by its package, and refuses horizon_client::crafting::... (proven on a
staged build; the same split left menu's and breathing's catalog
targets on the old crate name — their bundles would fail the same way,
left to their own fix).
Part of #5367: predicates over components need a match-time hook, not
this load-time list, and follow separately.
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: verify passes on 3740468b5; parity 45 passed. The one failure is a timing flake: server_player_targets' console effect is 599 ticks against vanilla's 600, a tick off on a loaded runner, in a step with no firework rocket (this change only adds rockets to selectors). Job rerun. Queued by pr-sweep-M.
posed_rider kept the id of the last mount, so boarding the same boat, cart or animal again
compared equal and the camera swept to the seat instead of cutting to it. Not riding clears
it, so every mount is a new seat.
Arena::trim dropped an emptied page's buffer (up to 256 MB) on the render
thread; giving it back to the driver took that frame 8-14 ms of CPU on Mesa's
lavapipe (spans, RD 12 flights). A dimension change (clear_sections) dropped
every page at once the same way.
Arena::trim and Arena::clear now give the buffers back; the renderer keeps them
in a Graveyard for four frames, so the GPU is done with them and the bind
groups naming them (flat draws' page tables) are gone, then drops them on a
short-lived thread, as chunk::free_columns does for unloaded chunks. The
driver frees them as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Linux the frame's commands are submitted and presented on a submit thread
(#5779); every use of the queue first waits for the previous frame there. In a
GPU-bound view that wait (uploads in update_world, the frame's own writes, the
next submit_later, offscreen pacing in land_frame) was counted as the frame's
CPU time in FrameStats, F3, the frame graph, HORIZON_SPIKES and perf_bench's
cpu_ms: on lavapipe 15-150 ms "CPU" frames whose thread worked 0.1-0.5 ms.
Queue now sums the time spent blocked on it, and the renderer adds it to
last_wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The lead knot, wind charge, shulker bullet, wither skull, evoker fangs and a
raid captain's banner loaded their model on first draw, each by parsing the
whole entity-models.json (2.4 MB) on the render thread. The lead knot's ran on
the first frame of every world, with no lead in sight: 41 ms of CPU and
125-230 ms of wall time in that frame. They now come from the document
GameCpuResources already parses once at startup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tridents and rockets ignored nether and End portals. They are now swept for
portal blocks, saved with the existing writers, re-spawned in the other
dimension and keep their portal cooldown on arrival. A rocket boosting a
player and a stuck trident that did not move stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
wind_knockback skipped both; vanilla's hurtEntities pushes every entity that
does not ignoreExplosion. Adds lan_e2e --check wind-remaining and lists
rockets and trident motion in simulation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Record early failures and errors selecting or exporting a compiled build,
without dropping compiler diagnostics or changing the returned build result.
Refresh the generated native-build CLI reference.
Co-Authored-By: GLM 5.3 <[email protected]>
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
🤖 Agent: server-only export endpoints (off without a token); fmt, clippy -D warnings and the full test suite pass inside crates/horizon-telemetry-server, CI green on this head.
Entity.handleOnAboveBubbleColumn ends in sendBubbleColumnParticles for every
entity at the top of a column, and PrimedTnt does not override it. The TNT
was pushed by the column but showed nothing at its surface. PrimedTnt::tick
now reports the column blocks it is at the top of (bubble_column_push_over,
added for mobs and items in #6474) and tick_tnt draws them with
Game::bubble_column_particles, which also sends them to the LAN guests.
Fixes#6626
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
#5317 was merged with three deviations from vanilla 26.3 in the `ArmorHurt`
path that its review had already named (Joansitoh's agent:
`LivingEntity.java:1887`, `:1907-1909`, and `doHurtEquipment`'s amount):
- the gate was `drops::head_is_body` minus Wolf, which also matches donkey,
mule, skeleton and zombie horse, camel, llama, nautilus, happy ghast and
sulfur cube. `hurtArmor` is overridden by `Horse` alone — the base one is
empty (`LivingEntity.java:1887`), `Wolf` and `Player` have their own rules —
so those species wore a body item vanilla never touches;
- nothing tested `#bypasses_armor`, so a fall, drowning, starvation, the void
or a magic hit wore the piece too (`hurtArmor` is called inside that gate,
`LivingEntity.java:1907-1909`);
- the amount was `dealt`, computed after the armour, toughness and
enchantment reductions and after the invulnerability window's subtraction,
where vanilla hands `hurtArmor` the damage `getDamageAfterArmorAbsorb`
received: 8 raw damage is 8/4 = 2 durability, not 1.
`Horse.hurtArmor` also asks `doHurtEquipment` for `EquipmentSlot.BODY` alone,
so the four armour slots are not this path's to wear; `hurt_mob_armor` now
wears the body piece only, which drops the slot loop with it.
`doHurtEquipment` also starts from the item's `equippable` component
(`Equippable equippable = item.get(DataComponents.EQUIPPABLE)`), which
`wearable` did not check: `equip_damage_on_hurt` defaults to true when the
component is absent (`horizon-items/src/items.rs`), so a damageable item
carrying no `equippable` — a sword put in a horse's body slot by
`/item replace` — wore out.
Tests: three cases in `mobs::armor_hurt_tests` (a donkey with a body item, a
fall on a horse, the amount before the reduction); each one fails on the
merged code and passes here. The `equippable` gate needs a live `Game`, so it
is the one part without a unit test.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Draw the active book without the retained inventory/container screen or its
native screen render hook, and hide that menu's slots from the agent UI.
When the book closes, leave the pointer available to a retained menu.
Add a standalone OpenBook packet proxy and book_menu check to the vanilla
server e2e harness. The check covers exclusive book widgets, page navigation,
return to the retained menu and mouse capture. It requires no container-close
fix and introduces no inventory packet queue.
This recreates the previously stacked book correction on current main,
preserving the gameplay change while isolating its network test setup.
Co-Authored-By: Codex <[email protected]>
GET /v1/export/benchmarks (raw reports, paginated by id) and
GET /v1/export/hardware (play sessions per machine description with CPU
threads and cores, paginated by offset). Both need a bearer token compared
in constant time (TELEMETRY_EXPORT_TOKEN), answer 404 while it is unset,
401 without it, gzip on request, and never carry an install ID. Wrong
tokens take from the address's rate limit; right ones do not.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The enums and the structs a mod is handed (errors, holders, phases,
panel events, the lifecycle context, the render frame) can gain variants
and fields without breaking a mod. Flow and the structs mods build
(NativeZoom, NativeScreen, AttachmentType...) stay open.
🤖 Agent: startup trace (HORIZON_STARTUP_TRACE + tools/startup_trace.py), skip-release. Verified: pr_ready on playhorizon (render lib tests --test-threads=1), CI verify green on this head after the rebase.
Measured with chunk_load_bench (seed 1234 village, 4,440 sections, 1,598
with geometry): 265 -> 210 us of CPU per section with geometry, p99 670 ->
530 us, with the same vertices and visibility (checksum afdcd05aec013a23).
- Padded::fill copies the section row by row and reads each border run
from a section found once (PalettedContainer::get_run), rather than a
chunk, section and palette lookup per cell.
- Hidden blocks (all six neighbours isSolidRender) are found from an
opacity bit row per padded row, read from a compact per-state table,
instead of six lookups into the per-state Info.
- Biome tints keep each cell's colour once computed (a cell is in the blend
square of up to 25 columns, and swamp grass samples noise), and mix the
biome zoom's corner jitter once per quart cell (BiomeZoom::corner_jitter
and select_corner, as generation already does).
- sides::sort sorts one integer key per quad (bucket, plane, index), which
orders exactly as the stable sort did, in buffers each thread keeps; the
sorted copy no longer replaces the mesh's recycled buffer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Meshes through Mesher::mesh_reusing with a recycled mesh and scratch, then
sort_solid, as the workers do; reports each section's best-of-rounds CPU time
(p50/p90/p99/max), the vertex bytes, the wall time of N threads meshing at
once, and optionally a JSON summary for A/B scripts. The checksum now covers
the visibility graph too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The loading screen built the world renderer on the main thread in the same
pass that swapped in the title artwork, so whenever the world tables were
ready by then the title waited for that build. The handoff now waits for one
presented title frame (or a second, for a window never asked to redraw), and
not at all when a world is waiting to open.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- is_blocking, is_using_item: the synced living entity flags (and a player's
blocking item), as the client keeps them
- is_swimming, is_crawling: the swimming pose (a player's or mannequin's
avatar pose, another mob's DATA_POSE, a drowned's swimming flag), crawling
out of water
- is_holding_item_right/left: the held items on each arm, by the main arm
- is_swinging_right_arm/left_arm: the current swing's arm, the main arm when
the swing names no hand
- is_ridden: the rider SetPassengers names
- is_sitting: foxes, pandas and camels from their own flags
- anger_time, height_above_ground, fluid_depth_up and is_wet's rain stay as
they were, with comments naming what the client lacks
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Block overlays come before tiles under the shared cap, so a pack's many
tiles can't crowd out vanilla blocks'; any tile named by its file pairs,
not only those under ctm/; the doc comment is back on load_optifine.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
update_recipes applied the data's recipe filters (#5910) to its own
copy, and RecipeIndex to its index, so a removed recipe never reached
the book - but book_entries wrote from the network copy, which the
filters never touched: a replace_input or replace_output showed
unswapped in the book's displays, while the property sets and the
stonecutter carried the swapped recipe.
network_recipes now runs the filters once, over its files, before the
ingredient normalization - a swapped name resolves, and every copy
agrees on the swapped recipe. update_recipes parses the already
filtered copy without running them again, so one swap cannot feed a
later one twice.
Fixes#6595.
Co-Authored-By: ZCode AI agent (GLM-5.3)
A tile with <name><suffix>.png beside it (0.png and 0_e.png) pairs with
its overlay as block sprites do, so emissive glass, lamps and ores keep
glowing when connected textures replace their sprite.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main now saves rockets (firework_rocket_to_nbt over RocketSave, Rocket::to_nbt):
drop this branch's own writer and use main's for /data get. A rocket has its id
from the start here, so its chunk save reads its UUID and kept compound by id,
as primed TNT's does, instead of by position.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passes, and parity run 41356682661 passes inline_object (555 s) on 7aeddbcf4 merged into main 8d93d68f1. One-line harness fix, skip-release. Queued by pr-sweep-M.
The menu bundle the title waits for built the GUI atlas, then decoded the
panorama's six faces after it. They now load side by side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The world tables (items, recipes, behaviours, mobs...) waited for the game's
own CPU tables and the world renderer's textures, which ran one after the
other on the resource loader. The tail now starts first, and the two
handoff jobs run side by side; a failure still waits for the tail.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
to_title_ms was the moment the menu bundle was ready, which can be seconds
before the renderer that draws it exists (its pipelines compile on the main
thread first): on llvmpipe it read 0.7-1.6 s for a title drawn after 11-15 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The start builds three renderers on one device (the boot screen's, the title
artwork's, the world's), and resource reloads and graphics options build more.
Each compiled every shader module and pipeline again though most come out the
same. gfx/reuse.rs keeps them under their descriptors' Debug text and hands the
same object back; what the latest renderer did not ask for is let go.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every named step of the start (resource tables, the renderer's device and
pipelines, the world's open on the server thread) and its marks (title drawn,
first frame in a world, the view filled, a dimension change) go to a file as
Chrome trace events when HORIZON_STARTUP_TRACE names one. tools/startup_trace.py
starts the game as a player does and prints the timeline.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The piglin brute's two activity sounds, the three ChatListener lines behind Chat Delay, the
angry enderman's render offset and the tipped arrow's potion colour are all in Horizon; the
rows still said missing.
A plant's 45° plane snapped its texture's right along the face normal;
directions now need to lie within about 25° of a block axis.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
[[particle_lights]] names particles, with a colour and reach, that join the frame's coloured lights
at their position, with their alpha as intensity. MaterialInput.colored_light gives those lights at
a point to the material stage, which could not read them before.
Fixes#6584
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Fireball.addAdditionalSaveData always writes Item, a fire charge by default,
and readAdditionalSaveData reads it back through setItem, which refuses an
empty stack and keeps a single one. Horizon wrote only the flight, so a
saved fireball or a /data get entity had no Item and a loaded one was lost.
fireball_to_nbt now writes it for the large and the small fireball (not the
wind charges, which are no Fireball), from the loaded item when there is one;
the projectile's loaded Item is kept beside its Tags. docs/server.md names
the control request RCON uses since #6443.
Fixes#6594
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Panes, whose tiles draw with the cube's UVs, read those as their texture;
plain overlay keeps the standard directions (Continuity's orient leaves it
out); Axes::texture refuses NaN and quads turned off the block's axes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SpriteIconButton.TextAndIcon.extractContents gives the label left = x + 2,
right = x + width - spriteWidth - 4 and centerX = x + width / 2, and
ActiveTextCollector.defaultScrollingHelper centres a label that fits on
Mth.clamp(centerX, left + lineWidth / 2, right - lineWidth / 2). Title's
Button::draw_label used x + 2 .. x + width - 2 for every button, so on the
first-launch Accessibility screen a label that fits (fil_ph "Settings sa
Aksesibilidad...", about 30 languages) ended under the icon and a wider one
(fr_ca, about 35 languages) slid under it.
ui::scrolling_text_around_at takes the centre; scrolling_text_at is the
default centre (left + right) / 2 and draws as before. Button::draw_label_at
passes the icon's room and the button's middle.
MenuTabBar.MenuTabButton.renderLabel hands the tab's label to
acceptScrollingWithDefaultCenter with a 1 px margin, its box 3 px lower on an
unselected tab, gray only on an inactive tab (WithInactiveMessage). The Create
World tabs drew it unclipped, so a label wider than its tab ran into the next
one; the Statistics tabs did too, drew every label at the selected tab's
height and grayed the active unselected ones. Both draw through
ui::menu_tab_label now.
The port ledger rows of acceptScrolling, RenderingTextCollector.acceptScrolling,
acceptScrollingWithDefaultCenter, defaultScrollingHelper,
extractScrollingStringOverContents, the slider's extractWidgetRenderState,
TextAndIcon.extractContents (it has a caller: the Accessibility screen) and
MenuTabButton.renderLabel say what is drawn now.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Vanilla screens already read the selected language (creative inventory with every tab, its
search, survival and hotbar tabs and the Destroy Item tooltip; pause, chat, death, crafting,
furnace, anvil, enchanting, brewing, trading, advancements, statistics, world select and
Create World were checked in de_de, pl_pl and ja_jp). What was left:
- Sign edit's Done button was a hard-coded "Done": now `gui.done` (also for its narration).
- The AI Agent page's four status lines were English sentences built in code: now
`horizon.agent.*` keys, with the connection count and the time since the last command as
arguments (`status_lines`, a function of the socket's state, so a test reaches it).
- Leaf Culling, Frame Generation, Shadow Cache, Screenshot Quality, Distant Plant Dither, the
Culling header, the Synchronous/Asynchronous and Leaf Culling values, their descriptions and
the controller binds' "Hotbar: Previous/Next Slot" were English keys that `en_us.json` did not
have, so every language showed English. They are in the table now. The Entity Culling
description's old key had drifted from the code; it is replaced. Translucent Batching, its
description and the GPU reason that blocks it get their translations too.
- 17 Extras and Splitscreen keys added to en_us.json after de/pl/uk/fr were written had no
translation there: added (Particles, Hide block-break particles, Smooth chat, Message
indicator, Keep channels for UI sounds, Splitscreen).
- zh_cn gets the new keys too. es_es, ru_ru and tr_tr still lack about 130 keys each.
`lang::scoped` runs a closure with a language that only that thread sees and returns the keys
it asked for and the language did not have, so tests render in another language without
touching the process-wide one. The tests: every text of the three graphics pages is a key of
the table; the Horizon options pages in de_de and pl_pl; the agent page and the Done button in
de_de, pl_pl, fr_fr and en_us.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
The server made a new body for a player from the defaults when they died and
respawned or went to another level, so /attribute @s minecraft:scale base set
0.1 was 1.0 again (box, eye height and reach as well), and so was every other
attribute a command or a datapack had set. Vanilla keeps the same ServerPlayer
through teleport, and ServerPlayer.restoreFrom copies the base values to the
body after a death (assignBaseValues). The old body's attributes now go to the
next one (with their permanent modifiers for a level change, base values only
after a death), and a player saved between two bodies writes them to the file.
restoreFrom then gives the respawned player its max health
(setHealth(getMaxHealth())): a max health base of 40 comes back with 40, not
with the fixed 20 the respawn set.
Also: a glide the client announced before the body's own tick used a
scale-1 box for that tick (Guest::dimensions); it takes the body's scale. The
port map's LivingEntity.getScale row still said there was no scale attribute.
Tests: the respawn health, the glide box, the carried attributes, a save
between bodies; lan_e2e check scale-body (host and guest: the Nether and back,
a death and respawn, the health after it).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
A conduit.jem that reshapes the eye alone no longer paints the shell,
cage and wind, whose vanilla geometry keeps its texture's layout. The
part-name test gives every entry a box of its own, so swapped names show.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve the maintainer merge and current main while adding macOS native
microphone purpose metadata as a separate follow-up.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Localize and XML-escape the microphone purpose string without requesting
permission or opening a device. Reuse the existing XML escape helper.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
Co-Authored-By: OpenCode Nemotron 3.5 Super VL <[email protected]>
A rule with orient=texture finds a face's left, right, up and down from
the quad's own texture (Axes::texture: the UV gradients of its corners,
snapped to the block's axes), so turned or mirrored models connect along
their texture. OptiFine's defaults are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#6381 landed the same order (the shield's push before the hit's) in creatures.rs:
main's version is kept; this branch keeps its two server_knockback cases, which
pin that order for a half-blocking shield.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Heightmap::update returns at once for a block two or more below the
column's first available height, but set_block_worldgen first checked the
state, the chunk bounds and went through Results for each heightmap of
each write. The surface rule rewrites most stone under y 8 to deepslate:
that cost about 14% of a proto chunk. The skipped path reads the packed
height directly and still rejects unknown states. Same heightmaps
(test: random writes against updating every map each write).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each trait hook now takes its host, where it has one, and one
generated #[non_exhaustive] <Hook>Event holding the hook point's
arguments, as NeoForge's events do: a later version can add a field
without breaking any mod. A number or bool a mod may change is a plain
field, written back once every mod has run, even after a cancel; other
mutable arguments stay &mut fields. One event goes through every mod,
so each sees the changes before it. Cancel and replace outputs stay the
return value. Dispatch signatures and the native_* hook points are
unchanged. Trait injections get the same pattern, their bindings
becoming the event's fields.
boat_server_passes runs inside the loop now, and lava breaks a boat
there: a for loop over the indexes skipped the boat that moved into the
broken one's place. The next boat is found after this one by its id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main brings the dedicated server export (#6462). Trait mods build for
the server target too: the generated game package owns horizon-client's
default features, so the server build's --no-default-features reaches
it, and no mod's API package turns them back on. The build key keeps
hashing the mods in bundle order, which decides trait dispatch order.
The launcher reference gains main's --server-output.
Vanilla puts a rider on its seat with Entity.moveTo, which sets the old position as well, so
the camera cuts to the cushion, boat or cart. Horizon kept the position the player stood at
as the previous one, so the first ridden tick drew the camera flying from there to the seat.
Only rules before the one that picked were tried on the face's sprite;
later ones naming the picked tile chain as any other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Trapped and ender chests read only their own files (cem_model.txt lists them with no
fallback); copper chests have no CEM name and always draw vanilla's chest.
- An entry whose name isn't OptiFine's for the model reaches the vanilla part of that name,
as mobs do; it is skipped only when neither exists.
- Tests: every OptiFine part name of the block entity tables lands on its vanilla part,
book.jem reaches the lectern's book, a copper chest draws vanilla's geometry and UVs beside
a chest.jem, and only entries no model has a part for are warned about.
- jem_files' precedence test moves next to it in horizon-optifine; read_small is private.
- docs/resource-packs.md names both test modules.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Recognize farmland and crop templates through their retained class chains and plant loaded tagged block items in inventory order. Add vanilla-oracle classification and real content-root regressions.
Co-Authored-By: GPT-6.1 Sol <[email protected]>
perf_bench reports for Horizon main 1bd24e4042, vanilla 26.3 and vanilla with
Sodium, Lithium, FerriteCore and ImmediatelyFast at render distances 8, 16 and
32, 1920x1080, three games each, with a page on why they are not player
numbers: the Mac ran at load 15-114, every game ran offscreen, and vanilla's
window was shrunk to 1920x930 by the screen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second pass skips rules tried on the face's own sprite (an overlay
naming both drew twice), and doesn't run when the picked tile is that
sprite itself (tiles=stone).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by perf-kacper-1: requeued after merging main (lan/entity.rs conflict: contains → contains_key for main's tracker map). clippy and the lan tests pass on the merged tree. Server bench before: no MSPT regression (50 bots 6.11 → 6.18, 100 bots 9.88 → 10.27, noise). Parity passed on the previous head.
Main's #6231 work turned the tracker's seen set into a map: the chunk-index
loop now asks contains_key.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ctm.properties: "Tiles output by CTM rules can also be matched by another
rule" (matchTiles=optifine/ctm/mygrass/1.png). After a rule picks a tile,
the tile rules naming that tile (and this block, if they name blocks) are
tried once more; <default> there keeps the first pick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by perf-kacper-1: verify green, pr_ready --quick green (979 render tests), retargeted to main after #6499, mergeable. A/B on the Mac mini M4 Pro at RD 32 + far 256 (100 fps cap): GPU 5.66 → 4.21 ms (pre-#6005: 4.77), CPU +0.19 ms; RD 12: GPU 1.25 → 0.97 ms, CPU 0.65 → 0.63 ms. Screenshots identical to pre-#6005 apart from clouds, mobs, water and crops moving. Metal only; other backends keep one solid pass.
These kinds were pushed with id 0 and got one only when a LAN guest was
connected. They now draw it from the one counter when created, as vanilla's
Entity constructor does. Part of #6537.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: verified by perf-kacper-1: verify green, pr_ready --quick green (975 render tests), A/B on the Mac mini M4 Pro at RD 32 + far 256: CPU 2.91 → 2.88 ms, GPU 4.45 → 4.39 ms; RD 32 without far terrain CPU 1.61 → 1.54 ms. No visible change (the fade work it skips is a no-op while nothing fades).
MinecraftServer.setDifficulty is the one door /difficulty, a dedicated
server's management and a test environment's difficulty all go through, and
nothing told a mod about it. native_difficulty_changed(from, to) now runs
there, after the change, with the two difficulty names, and only when the
difficulty is another one.
AbstractFurnaceBlockEntity.consumeFuel takes the fuel item's own crafting
remainder: it takes the fuel slot once the stack is used up, and is dropped
at the furnace while more of the fuel is left. furnace.rs only knew the lava,
water and milk buckets by name, so a content mod's fuel with a
crafting_remainder burnt without leaving it. The remainder now comes from the
items table (the buckets carry theirs there), and Furnace::tick hands the
dropped ones to its caller, which drops them with Containers.dropItemStack's
helper.
Fixes#6524
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
entity_nbt_in_level stamped UUID from level.player_uuid, the world owner's,
on a guest's turn too: on the server thread every player is a guest, so
/data get entity @s UUID gave every player offline_uuid("Player"). Use the
acting guest's UUID, as acting_uuid does. lan_e2e data_uuid checks the
guest and the host.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ConnReader read 16 KB at a time into the stack, appended it, copied each
frame out, drained the rest of the buffer to the front for every packet,
and copied the body again; each compressed packet built a new inflate
state. Reads now go straight into the buffer behind what is pending, a
frame is consumed by moving an offset, its payload is copied once, and
one flate2 Decompress is reset per packet. The in-memory pipe (single
player) copies with VecDeque's slices instead of byte by byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ResultSlot.onTake is where vanilla awards a craft, so the crafting grid's take
now tells native mods: native_item_crafted(player, recipe, stack) runs with the
player's uuid, the recipe the grid matched and the stack taken, on the
integrated server and a dedicated server, guests included. The menu records the
recipe and stack for a guest's click; the host's click fires it directly, once
per craft a shift-click takes.
claim_for_lost runs first in a world, so a controller that comes back
still takes back its own player (or the one waiting longest) when P1
waits too; only what it passes on, with P1 the one waiting, goes to P1.
The extras doc names Y (play as P1).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A lightning bolt does not override Entity.isIgnoringBlockTriggers (bytecode: only Marker, OminousItemSpawner, Display, ArmorStand, Interaction and Bat do), so BasePressurePlateBlock.getEntityCount and TripWireBlock.checkPressed count it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: merging "A player's thrown trident fires the projectile impact hook" (head 05c7ec158) after review by Ludovic's AI agent; thank you, @breachato.
It set the launcher's process-wide language (Russian, Spanish) while other
launcher tests run in parallel and read tr(), and it checked a copy of
the view's match rather than the view.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify passes on 5107acc19; parity 39 passed. The 3 failures are not this PR's: lunge_motion (known flake), inline_object (main's harness still builds client_probe with -p horizon-client since the client-lib split) and pack_screen (oracle errors: lang files missing from the runner's prepared resources). Queued by pr-sweep-M.
The hook now sees the raw strength: a 0.5 resistance hands it 0.4, and a
full resistance stops whatever push the hook leaves, as NeoForge's
LivingKnockBackEvent does. API 125's paragraph and the hook's doc say so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The items engine (block drops, entity drops, loot_table, interact_loot)
answered a mod's loot condition but had no arm for a mod's loot function, so a
block's own table could not carry one. Its modifiers now build the stack as the
main loot engine's LootStack, call native_loot_function, and write back only
what the hook changed: item, count, damage, enchantments, potion, ominous
bottle amplifier, stew effects, names and the components the engine keeps as
saved.
🤖 Agent: perf-net-client. Hardware AES for the connection cipher; online-mode RD 32 join receive CPU 8-9.5 s -> 0.5-0.6 s (EPYC replay), decrypt x240 on M4. pr_ready on playhorizon, ARM path tested on the Mac, CI verify+launcher green.
ServerGamePacketListenerImpl.handlePaddleBoat applies a paddle packet only
when the sender is the vehicle's controlling passenger, and Entity.addPassenger
seats the first player in front. The server now numbers the riders of a vehicle
as they get in and takes paddle packets from the first one only.
tools/test_boat_paddle_reset_parity.py gains a boat with two players: the
second rider's paddle packets must leave the metadata alone, the first rider's
still change it, and once the first rider gets out the second one drives.
Since the client library split the example belongs to horizon-client-lib, so
`cargo build -p horizon-client --example client_probe` exits 101 and the harness
fails on every PR that selects it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`loot_place_at` reported the shared weather levels in every dimension.
Vanilla's `Level.isRaining` and `isThundering` also need
`canHaveWeather()`, false with a ceiling (the Nether) and in the End, so a
mod's weather condition saw rain there while the Overworld had it.
Fixes#6486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/api/agent-control.md listed "A version" as not there yet, and
stability.md planned a hello reply. horizon-ctl hello (and its MCP tool)
now replies {protocol, release, features}: protocol is the control
protocol's own version (0.1.0, the first numbered line), release the game's
version (the string MCP serverInfo carries, which both pages still called
planned), features the command groups the game serves.
Fixes#6484
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
LivingEntity.knockback scales strength by KNOCKBACK_RESISTANCE inside the method, and NeoForge's
LivingKnockBackEvent fires at its start, so a mod's change to the strength lands on the raw
value. Horizon ran the hook after the resistance had scaled it, for mobs and for the player.
The hook now sees the raw strength and the resistance scales what it leaves.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A dedicated server now times its spans every tick and writes a tick slower
than 250 ms to the log with where its time went (HORIZON_SPIKES's line;
the variable still sets another threshold). A server more than two seconds
behind, which already skipped the ticks it missed, now says so as vanilla's
MinecraftServer.runServer does ("Can't keep up! Is the server overloaded?
Running Nms or N ticks behind"), at most every 15 seconds.
The official server shows 10-13 s freezes (bursts of 'moved too quickly'
from 15+ players at once) with nothing in its log to say where they went.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "HDR: glowing items shine, dropped, framed and held, the hand's too" (head e03324cd4) after review by Ludovic's AI agent; thank you, @senjinthedragon.
🤖 Agent: merging "Native mods: a mod loot kind sees where the roll happens (origin, block entity, level)" (head 26103c104) after review by Ludovic's AI agent; thank you, @ErikKalikas.
🤖 Agent: merging "OptiFine packs: CTM overlays face the right way on rotated blocks" (head 9fbd459b6) after review by Ludovic's AI agent; thank you, @some-du6e.
🤖 Agent: merging "A boat's entity tick Post comes after the rest of its tick" (head ae295952c) after review by Ludovic's AI agent; thank you, @breachato.
#6005 drew every solid list twice on Metal, once per pass, and each pass
clipped the other's quads in the vertex shader: at render distance 32 with
far terrain the cutout pass cost 2.2 ms of GPU, most of it shading opaque
vertices it then dropped.
Sorting a mesh's solid quads by facing side now keeps its opaque and cutout
quads apart (mesher::Part), and Sides draws either part. Where the renderer
draws two passes (Metal), meshes put all their opaque buckets first, so each
part is one or two runs; elsewhere each bucket holds its opaque quads, then its
cutout ones, and one pass draws the same runs as before. Phase 1 lists every
mesh's opaque runs, then its cutout runs (a fading mesh's quads all go to the
cutout pass), and flat draws start a call where the passes split.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Save and reload keeps primed TNT, rockets and thrown tridents" (head fd93627b1) after review by Ludovic's AI agent; thank you, @HrafnaValkyrja.
route_pads handed the first controller pressed in a world to P1 whenever no player had joined
yet, whatever P1 was playing with, so with P1 on the keyboard and mouse the only controller took
P1's controls instead of offering to join.
A pad is P1's without asking only in a menu, where it drives the menus, or while P1's own
controller is gone and its view offers "Use another". Anywhere else in a world the press starts
the join offer, where A joins as the next player and Y gives the pad to P1.
🤖 Agent: merging "A player who dies with a container open closes it at death, not at respawn" (head c7d2b1199) after review by Ludovic's AI agent; thank you, @xFedeT.
🤖 Agent: merging "Pistons and shulker lids push falling blocks, XP orbs and projectiles" (head 6578a0abf) after review by Ludovic's AI agent; thank you, @HrafnaValkyrja.
🤖 Agent: merging "Title panorama: read the faces through the resource layers" (head bbec3f1a4) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "parity.py runs harnesses on Windows: file locks, process trees, no peak or shim there" (head 5ebf42f7b) after review by Ludovic's AI agent; thank you, @Ragbie.
Content::read and Content::declare accept the mod's blocks that have an item as a crafting
remainder; drop_dangling_remainders now counts them too, so a valid mod is not left out.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Add the two missing rows of the tooltip_display visibility table to
tools/test_tooltip_display_parity.py: hide_tooltip with hidden components and
a non-creative flag, and a creative flag with no hidden components.
Add three lore stacks to tools/test_lore_tooltip_parity.py: a line with no
text of its own whose explicit italic:0b reaches its children, a plain-string
child next to an object child with bold:0b, and a packed #rrggbb colour with a
strikethrough a child inherits.
These kinds applied their own gravity and ignored NoGravity:1b, so a
summoned one fell (Entity.isNoGravity / getGravity is 0 in vanilla).
Arrows, orbs and fireballs also dropped NoGravity on save.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The sliders' "nits" were real nits only where the compositor passes PQ through, not on
KWin or macOS, and the game can't tell which: the values stay, the unit goes. Black Level
0 is now Auto, what the system reports, rounded down so an OLED stays at true black. The
HDR shortcut no longer reaches the title screen through the mouse or control socket.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The file check now accepts a remainder naming a code-declared item before that declaration is
validated. If accept_declared then refuses the declaration, the file item's remainder dangles
and the overlay failed for every content mod. A mod with an item whose remainder it does not
have is left out alone, with a warning.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The stage wrote alpha 1, so with a pack's material no light source on terrain shone.
It now writes the mark as fs_solid does, from the sprite before the pack's material.
Its emission also read a tier that shines all over as the top tier.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Content::read checked remainders against the mod's files only, before the native mods'
declarations were collected, so a remainder naming an item the same mod declares in code refused
the whole content mod. read_declaring takes the ids native_items declares in the mod's
namespace, and the remainder check accepts them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
holder_set writes any JSON object as an empty list (varint 1), so a
recipe whose ingredient is one of NeoForge's standard shapes —
neoforge:compound, intersection, difference — reaches a connected
client with an empty ingredient slot. The crafting copy resolves these
shapes to flat item lists, but the network copy loaded its recipes from
disk separately, and the resolved lists never reached it: neither the
book's displays nor UpdateRecipes' property sets and stonecutter.
The network copy now loads through one place, network_recipes: every
recipe file and overlay recipe with each ingredient object — NeoForge's
shapes, a brewing slot's item predicate — replaced by its flat item
list, covering the shaped key's values, ingredients, ingredient, the
smithing trio and the brewing pair. A recipe that keeps an ingredient
object no loader can parse (a mod's own kind) is dropped: the server
does not load it, and an empty ingredient slot in its place misleads
the client. Only vanilla recipe types are normalized; a mod's own type
keeps its JSON whole. A missing item catalog leaves the shapes
unresolved with a warning instead of silently.
An item the registries do not know is left out of the ingredients it
was named in, not sent as id 0.
The lan_bot test drives the whole path live: a server over the vanilla
data with a pack of compound recipes, two guests joining, each reading
its own UpdateRecipes and RecipeBookAdd.
Fixes#6368.
Co-Authored-By: ZCode AI agent (GLM-5.3)
Entity.handleOnAboveBubbleColumn ends in sendBubbleColumnParticles: two SPLASH
and two BUBBLE particles over the column block, sent by the server to the
players around. Only the player and arrows showed them. A mob (tick
applyEffectsFromBlocks) and a dropped item now ask for the same particles
through MobEvent::BubbleColumnParticles and the item tick loop, drawn and
sent to the LAN guests by one Game::bubble_column_particles, which the
arrow event uses too (its particles reach the guests now).
Refs #5768
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
comeOffTrack keeps the delta its move resolved, so a cart an obstacle stopped there is
standing still as far as MinecartTNT.tick is concerned; only the rail paths re-apply the
track's velocity and detonate.
Round 2 review fixes: load_optifine stops at 96 pairs so native mods' 32 always fit,
and table no longer cuts; it runs after the connected-texture tiles load, so a tile
identical to a block makes its layer shared (no overlay) instead of glowing; the 32-cap
test uses 40 distinct bases; insert keeps its doc; the docs list the limits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlayerList.respawn builds a new ServerPlayer whose lastEquipmentItems starts empty, so its first
detectEquipmentUpdates reports every worn item. The last-heard equipment is cleared at the
respawn.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Round 2 review: the trident glow moves to trident_geometry (tridents the server sends,
and those flying to whoever picks them up); the in-frame projectile branch it was in is
deprecated. A dropped variant beside its texture also takes its blink frames and their
overlays out of the atlas.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Launcher: version picker overflow and low-contrast text/buttons" (head b2d84a7f9) after review by Ludovic's AI agent; thank you, @Brineexee.
heal_hold_latches releases sneak and sprint when the OS says Shift or Ctrl is up,
to recover a keyboard release that never arrived. A controller's stick click acts
as that key without setting the modifier, so the heal released it the next frame,
before a tick could start the sprint: left stick click stopped sprinting. Skip a
mapping the pad holds (or whose release waits for a tick), as modifiers::reconcile
already does.
Fixes#6377
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Players on a server keep their tags: @a[tag=...] and /tag work for guests" (head 66359b492) after review by Ludovic's AI agent; thank you, @xFedeT.
🤖 Agent: merging "Native mods: a player changing dimension (native_player_changed_dimension, part of #5816)" (head 0e9f118ca) after review by Ludovic's AI agent; thank you, @Detimes.
Experience orbs, snowballs, eggs, potions and fireballs ignored nether and
End portals; vanilla sends them through like items. They are now swept for
portal blocks, saved and re-spawned in the other dimension, and keep their
portal cooldown on arrival so they do not go straight back.
Tridents and firework rockets still stay behind (no save/load writer yet).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Tags given to a summoned experience orb, arrow, snowball, egg, ender
pearl, fireball, small fireball, wind charge, End crystal or Ender Dragon
were dropped: @e[tag=] found nothing and /tag wrote nothing. They now keep
their Tags by entity id, and /data get writes them back. /data get entity
on a wind charge answered "no entity"; it is written like the other
fireballs now. lan_e2e --check selector-tags covers it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
LivingEntity.collectEquipmentChanges compares every living entity's slots with the last seen
stacks, players included, so a hotbar selection that moves the main hand or an item replaced in
place fires the event with the previous stack. Horizon compared mobs only. Each guest's main
hand, off hand and armor are compared the same way, per tick and only while a mod listens.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review fixes:
- native_emissive::table builds the frame's overlay table: native mods' pairs first
(their own 32), then the packs' OptiFine pairs flagged to draw cutout (opaque where
alpha >= 0.1, as OptiFine draws SOLID textures' overlays), sorted so the terrain
shader's per-vertex search stops at the first larger base layer
- an overlay that doesn't load is skipped (Sprites::try_insert), not drawn as the
missing texture; blocks sharing a layer (identical pixels) get no overlay; pairs past
the cap and oversized files are logged
- tests for the table and for the broken and shared cases
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The underwater overlay raised the frame's alpha, which HDR output reads as something
covering the world, so nothing shone with the eye in water. It tints the whole view,
so with HDR on it now leaves that alpha alone and tints the kept world too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review: the texture's overlay is mostly its glowing open eyes, which painted over the
shut ones and hid the blink. Frames pair with their own _blink_e (tested through the
renderer and the atlas); the docs list where overlays live, and that trims aren't covered.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reuse the launcher native build pipeline for a headless server target,
export an independent runtime, and document manual server selection.
Keep server cache entries outside playable versions and preserve export
permissions, resources, and post-build data.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
Co-Authored-By: OpenCode Nemotron 3.5 Super VL <[email protected]>
LocalPlayer's constructor calls KeyMapping.resetToggleKeys, so a toggled sneak or sprint (the
sitting and swimming poses the report sees) is not still on in the next world. close_world now
puts every toggle mapping up and releases the latched sneak and sprint keys.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A pack connecting glass with matchTiles=glass retiled panes but left their
seams; a pane counts as connected when any rule names it or one of its
sides' sprites.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Items draw from the HUD atlas, so their overlays would only take table slots; the docs
list placed blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
For each loaded block or item sprite whose <name><suffix>.png sits beside it, the
overlay is loaded into the sprite array and paired with it in the terrain shader's
overlay table, which draws it full bright over the base in the same fragment (the
native-mod emissive path). The table holds 128 pairs (native mods keep their 32);
horizon_optifine::emissive::suffix is shared with the entity overlays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- the armour stand's own body glows, and its overlays are read per lookup, not held while
wings_geometry reads the same lock
- the thrown trident glows (ETF's guide names it)
- a random variant's blink frame pairs with its own _e overlay, which the atlas packs; a
blink frame without one keeps its texture's glow instead of going dark every blink
- blinkLength near u32::MAX no longer overflows
- blink frames come after every variant in the atlas budget, and a dropped variant beside
its texture takes its overlay out of the atlas too
- docs/resource-packs.md lists random, emissive and blinking textures
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.baseTick immerses a boat and adds the flow of the water or lava it sits in to its
velocity, before AbstractBoat.floatBoat scales that velocity. Horizon's boat tick never ran
the fluid step, so a boat in flowing water moved as if the water were still.
LocalPlayer's yaw runs on without wrapping, so yRot - yBob is always the short way. Ours is kept
in -180..180 and a boat turns its rider through the wrap: the difference jumped by 360 degrees
for a tick, swinging the hands 36 degrees (a tenth of it) and then easing back by halves, like
jelly. The difference is wrapped, both when yBob eases and when the sway is read.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla's pane models mirror some faces' textures, which put a connected
tile's borders on the wrong side; OptiFine's pane models don't, so tiles
on panes take the cube's own UVs. Panes with connected textures also hide
the top and bottom strips they share with the same pane stacked on them
(Continuity's Glass Pane Culling Fix), where the other pane has that part.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 2 review fixes: the armour-stand armour loop, the elytra (when no cape is drawn on
it) and the ender dragon draw their texture's _e overlay full bright; render::FULL_BRIGHT
is shared; tests for the extra layers' glow flag and for overlays coming after every
variant in the atlas budget; the suffix test pins a lone backslash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Graphics: Advanced, Performance gets a Translucent Batching switch after
Graphics API ("Saves time"; it applies at the next start, as Graphics API
does). Where the device can't batch (translucent_batching_offered) it is
greyed and reads Off while the saved value waits, and neither a click, the
wheel nor click-then-Enter changes it; on macOS, where Metal never offers
it, it is hidden beside Graphics API.
The setting is now on by default, under a new key, batchTranslucent: every
save wrote translucentBatching:false while the setting was off by default
and had no switch, so under that key the new default would have reached no
one who had saved an option since. The old key stays ignored, as shaderPack
is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without mods the round doesn't listen and the Posts send nothing, so the
per-tick list of ticked boats (an entry and a kind per boat) isn't built.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Draw an arrow between the rotations of the two ticks it turns over" (head c77c94cb2) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Foxes take their food's consume effects and survive on a totem" (head 6def6595b) after review by Ludovic's AI agent; thank you, @JustKarkat.
MultiPlayerGameMode.useItem runs ItemStack.use on the client and swings
the hand on a shouldSwing() result (SUCCESS); CONSUME ends the click
without one. The remote path sent the packet and predicted everything
else but never swung, so a thrown pearl, splash potion, wind charge,
snowball or experience bottle, a bucket fill/empty, a spawn egg, a
writable book and equipping armor from the hand only did the itemUsed
dip on a server (#6425, #6409). The useItemOn half already swung.
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
world_runs is TickRateManager.tick: called a second time for the own
rockets, it spent two of a /tick step's ticks each client tick. The
test also runs the real rocket_spark now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Push the shield before the hit that it only partly blocked" (head c30dc0feb) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Button, slider and title-screen labels wider than the widget scroll, as vanilla does" (head 26e6743fb) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Buttons scroll a label wider than they are, as vanilla does, instead of panicking (Resource Packs screen in 8 languages)" (head 76e2ffdb8) after review by Ludovic's AI agent; thank you, @mariuslozo-boop.
🤖 Agent: merging "Translucent batching (3/4): the large pages shrink once the view has settled" (head b91226cac) after review by Ludovic's AI agent; thank you, @NerfPhaselock.
🤖 Agent: merging "Shader packs: a parameter or local named like a bool uniform hides it" (head 9696cd39e) after review by Ludovic's AI agent; thank you, @Joansitoh.
🤖 Agent: merging "docs(hosting): Horizon players join online-mode servers and sign their chat" (head a0f851b33) after review by Ludovic's AI agent; thank you, @OsamahDev1.
🤖 Agent: merging "Remote: handle arrow hit, pufferfish sting and limited crafting in GameEvent" (head f983b543a) after review by Ludovic's AI agent; thank you, @DmytroChup.
🤖 Agent: merging "Send an iron golem to a spot near its target, not straight at it" (head 367515af3) after review by Ludovic's AI agent; thank you, @MSedgeMC.
🤖 Agent: merging "Let Tab reach the world list so a keyboard player can join" (head 0de0b1d1b) after review by Ludovic's AI agent; thank you, @MSedgeMC.
Overlays took the face's model UVs, so on dirt's, sand's and grass's
randomly turned tops a transition picked for the north side drew on
another side; they now take the cube's own UVs, as Continuity's spec says
overlays do. Over a ctm_compact face, an overlay is cut into the same
pieces, so it keeps the face's depths and translucent sort order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging "Play with friends: the host attaches from any port when its direct port times out" (head f3de190aa) after review by Ludovic's AI agent; thank you, @Ragbie.
🤖 Agent: merging "Render tests: no OpenGL instance on Windows, where WGL hung them" (head 2ca08a955) after review by Ludovic's AI agent; thank you, @Ragbie.
MinecartTNT.tick detonates when the cart is in a horizontal collision and its delta
horizontally is still at least 0.1 a tick - on a rail the track re-applies the velocity
after the move, so a cart driven into a block keeps it. Horizon zeroes a cart's velocity
where the collision is resolved, so the check always read zero and the cart just stopped.
Completes the previous commit: the API number goes back to main's (the
stamp tool assigns it after merge), the hooks are listed in
tools/native-hook-fragments/experience_orb_pickup.json, and a guest is
swapped in for the orb pickup hook only when a mod sets listened in
native_experience_orb_pickup_listened, so a server without mods pays
nothing more per pickup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hooks go through tools/native-hook-fragments/ instead of a hand-set
API 154 (the number is stamped after merge); a guest is swapped in for the
orb pickup hook only when a mod listens (native_experience_orb_pickup_listened),
so a server without mods pays nothing more per pickup; the Python test that
compiled extracted source text is dropped (it pinned the code's wording).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
getHandHoldingItemAngle follows the player's main arm, so a left-handed
player's rocket sits on the left. The rockets boosting this player now
tick after the whole player tick, so they keep following it while it
waits for chunks. A rocket used on a block passes on cooldown, and in
adventure mode only off its can_place_on, as ItemStack.useOn does.
The boost and the hand offset are one helper shared with the in-frame
tick, and the test runs the real hand angle against vanilla's view
vector.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mount hook that removes mobs shifts self.mobs, so the indices collected
before the loop could name another mob, which could then be picked up or
pushed without having touched the vehicle (the id check only guarded the
mount write). Both loops now carry the mob ids and look each one up.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An RCON command used to run as the server console, so the operators saw nothing of it and its
`say` read `[Server]`. It now goes through its own control request (`rcon <line>`): the same
console at level 4, with the source named `Rcon` as RconConsoleSource.createCommandSourceStack
names it. Its feedback reaches the operators in game as `[Rcon: ...]` while
`broadcast-rcon-to-ops` is on (CommandSource.shouldInformAdmins), its `say` reads `[Rcon] ...`
and leaves the reply empty, and the console and an RCON client take success feedback whatever
`send_command_feedback` says (CommandSource.acceptsSuccess), as a player's source does not.
Fixes#6393
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The six CreateBuffetWorldScreen rows said missing; they now name the screen's
functions in title/buffet.rs, and the two CreateWorldScreen rows that mention
the Customize button name both editors PresetEditor.EDITORS has.
🤖 Agent: merging "Native mods: a player starting or stopping flight" (head 7673b4511, main merged in, both hooks kept) after review by Ludovic's AI agent; thank you, @Detimes.
🤖 Agent: Opened and checked by Ludovic's AI agent (Claude): JSON-only; stamp_native_hooks.py stamps all 72 pending fragments on this head, tests.test_stamp_native_hooks OK; unblocks the native hook stamp workflow on main.
PresetEditor.EDITORS has a screen for the flat preset
(CreateFlatWorldScreen) and for the single-biome preset
(CreateBuffetWorldScreen); the other presets have none. Horizon enabled
Customize for flat worlds only, and its single-biome overworld always fixed
minecraft:plains, so the preset could not be edited at all.
The button now opens CreateBuffetWorldScreen for the single-biome preset: the
biome registry of the prepared resources, sorted by the name each is shown
under and narrowed by a search box that matches the name case-folded, with
Done and Cancel in the footer. Done hands the biome back to Create New World
as WorldCreationUiState.updateDimensions does with
fixedBiomeConfigurator: NoiseBasedChunkGenerator over a FixedBiomeSource with
the overworld's noise settings, so the chosen biome becomes the overworld's
fixed biome source. Re-Create starts the screen on the world's own biome, and
changing the world type starts it over at plains, as a new preset's
dimensions do.
🤖 Agent: merging "A respawn into another level of the same dimension type makes a new level" (head 0ccddf82b) after review by Ludovic's AI agent; thank you, @breachato.
🤖 Agent: merging "The client level unloads when the server sends it back to configuration" (head 8b728305b) after review by Ludovic's AI agent; thank you, @breachato.
🤖 Agent: merging "ender dragon: the dying model dissolves through dragon_exploding's mask instead of fading" (head 7458a6ca1) after review by Ludovic's AI agent; thank you, @iLostName.
🤖 Agent: merging "camera fluid: lava fog starts when a corner of the near plane dips under the lava" (head 30715a97d) after review by Ludovic's AI agent; thank you, @iLostName.
🤖 Agent: verify passes on 0d3eb998a (rebased on main after #6189); silverfish back into infested stone, checked against the vanilla bytecode. Queued by pr-sweep-M.
🤖 Agent: merging "HDR: a light source lifts by its texel's own brightness, not the lit pixel's" (head 3451589c3) after review by Ludovic's AI agent; thank you, @senjinthedragon.
🤖 Agent: merging "Far terrain: finishes loading in about a minute instead of seven on 4-thread machines" (head 4d53099db) after review by Ludovic's AI agent; thank you, @some-du6e.
🤖 Agent: merging "Riders get off a mount that can't swim with one when their eyes are in water" (head fd43c4620) after review by Ludovic's AI agent; thank you, @Akirabane.
🤖 Agent: merging "Type keyless characters (Windows Alt codes) into the focused text field" (head 76b26c980) after review by Ludovic's AI agent; thank you, @xFedeT.
🤖 Agent: merging "Baby happy ghasts and nautiluses look like babies on a server" (head 02be02992) after review by Ludovic's AI agent; thank you, @some-du6e.
entity_chunks wrote none of them, so a world save or chunk unload dropped
them; tridents also had no load arm. Keys follow PrimedTnt,
FireworkRocketEntity and AbstractArrow/ThrownTrident addAdditionalSaveData.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
AbstractContainerScreen.tick closes the container as soon as the player is no longer alive, so
the server's doCloseContainer runs at death. Horizon closed a dead guest's container only in
guest_respawn, so the lid stayed up and PlayerContainerEvent.Close reached mods only after the
Respawn button. tick_player_menus now closes the menu (and drops the lid) for a dead guest and
counts a dead guest's other menus as closed.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ServerLevel.tickNonPassenger runs AbstractBoat.tick in full (its lava
damage, the ejection of riders under water too long, the bubble column and
what it picks up or pushes) before EntityTickEvent.Post. tick_boats sent
Post right after tick_boat, before those passes, so a mod reading a boat
there saw it before them, and a boat it moved was then pushed and picked up
from there in the same tick. The ticked boats' Posts now go out, in order,
after the passes.
Fixes#6388
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemStack.getRarity reads the stack's minecraft:rarity, which is the item's
default unless a command or a server set it, and then upgrades it for
enchantments. Horizon's item_tooltip::rarity started from the item's default
only, so /give stick[rarity=epic] showed a white (or, enchanted, aqua) name
instead of a magenta one. The rarity component, which both the command and
the network paths keep among the stack's components, now comes first.
Part of #6405 (the rarity; the missing lore is not covered here).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla registers the six CubeMapTexture faces with the texture manager, so
they resolve through the resource manager's pack stack: the topmost pack that
has a face supplies it, and the vanilla pack is the bottom layer. Horizon
flattens the stack into one overlay root and read the panorama from that root
alone, without the game's own resources the atlas build falls back to, so a
face missing from the overlay turned the whole cube into the grey placeholder
even when the game's resources had every face. The warning also dropped the
path it tried, leaving only the io error.
Each face is now taken from the first layer that has it, with the game's
resources behind the pack overlay, and a cube no layer completes falls back to
the placeholder with every path it looked in, plus the horizon-prepare hint
the sounds warning already gives.
Vanilla's grab outlives the window's focus: MouseHandler.grabMouse sets
mouseGrabbed and the disabled cursor mode, nothing releases either when the
focus goes, and the mode is re-applied when the window comes back, so the
pointer is locked again without a click (and put back at the window's
centre). Horizon released the pointer on every focus loss and never took it
back, and winit only applies a cursor clip while the window is focused and
does not re-apply one on WM_SETFOCUS, so the grab was gone: the pointer
stayed where it was, often outside the window, and clicks went to whatever
was under it.
The window now remembers whether it held the pointer when the focus went and
takes it again on the focus return, through the same capture(true) the rest of
the game uses. A screen that is open keeps the pointer free, and the existing
press-to-grab path stays as vanilla's fallback.
🤖 Agent: verify passes on 8512214d0 (main merged, with #6246's cem_texture/paints_with_jem kept). OptiFine ETF fixes, base of the #6217/#6219/#6220 stack; render-only. Queued by pr-sweep-M.
The same Windows checkout problem as the loot test: the breathing hook test
(new on main) looks for "\n}\n" in breathing.rs, which a core.autocrlf
checkout has in CRLF. It normalises the line endings first too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
block_entity_energy, data_map_types and item_fluid_tank listed files without
their @API marker, so stamp_native_hooks.py stopped on main and the native
hook stamp workflow has failed since #6291's merge. The hooks' own markers
are in native_hooks.rs, which the tool always stamps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Finish guest actions before collecting block and entity updates. Drain packets before this snapshot even when world ticking is frozen. Add a two-client regression that queues an instant Survival break behind a server barrier at one TPS.
Co-Authored-By: Codex (GPT-6) <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ModelLibrary::overlaid / resolve_with_textures / bake_resolved for CIT model and texture swaps (unused until the next PR; tests); carries #6232 (CIT textures into the GUI atlas, load time only); main merged by me (lib split, sprites prefetch). verify green on 17e6fd55b.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ModBlockEntity revision behind Deref/DerefMut: kept mod block entity geometry checked by two integers instead of a deep compare (11 ms → 0.04 ms/frame at 4096 large machines); update tags merge without bumping when unchanged; main merged (ModEnergyBuffer imports). verify green on 978591ea1.
parity.py started on Windows since #3428 but stopped before any harness
ran: machine_slot locked its slots with fcntl, which Windows lacks. The
rest of the run path was POSIX too: a timed-out harness was killed with
os.killpg, trace_main read the peak memory with resource, and the oracle
shim went on PATH as a `java` symlink to a Python script.
- tools/file_lock.py: an exclusive lock, flock or msvcrt's byte lock; used
by machine_slot, integration_env.suite_lock and test_datafix_parity.
- kill_tree: the process group where there is one, taskkill /T on Windows.
- Without resource, the trace has no peak_bytes and the scheduler assumes
DEFAULT_MEMORY, as for a harness that never ran.
- No oracle shim on Windows: a program started without a shell is found
there only as an .exe, and the shim's execv returns before the program.
Harnesses run their oracles fresh.
Fixes#6391
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): native_sign_changed after every updateSignText check, through as_guest_messaged; cancel keeps the text and resends the block entity; no cost without a sign update. Union of 6247+6260+6270+6279 with main: Python tests 594 OK, stamp_native_hooks OK; verify green on 6b6b55279.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): WitherSkull.getBlockExplosionResistance cap; main merged (kept #6262's test beside this one). verify green on ea514b4c6.
CreateWorldScreen.keyPressed, KeyMapping.setAll and EnchantmentHelper.createBook are all
implemented on main; the rows were written before the code that covers them, so they now
point at the wrong work.
- block_entity_cem.rs applies a pack's .jem to the block-entity models: chest(_left/_right),
trapped_chest(_left/_right), ender_chest, shulker_box, banner / banner_base / banner_flag,
head_*, bell, book / enchanting_book / lectern_book, conduit and decorated_pot, with
OptiFine's part names mapped to the vanilla ones (knob -> lock, stand -> pole, slate -> flag...)
- Entries replace or join the vanilla part's boxes and take their scale; animations come later
- The .jem's texture paints its model when the atlas has it; trapped and ender chests borrow
the chest's geometry without its texture, copper chests take the geometry only
- Closed-chest meshes are baked from the reshaped models, per chest model
- BlockEntityShapes follow pack reloads (built on the reload worker, swapped on apply); without
.jem files the renderer keeps sharing vanilla's shapes
- The .jem discovery and reading move from mobs/cem.rs to horizon_optifine::cem::files
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): content block states laid out in StateDefinition order (names sorted, first slowest) instead of refusing a property that sorts first; ids unchanged for blocks that already loaded (test); load-time only. Union of 6247+6260+6270+6279 with main: Python tests 594 OK, stamp_native_hooks OK; verify green on b40d434d4.
Silverfish.getWalkTargetValue gives a position standing on an infested host block a value
of 10, so a silverfish wanders back toward stone rather than away from it. Horizon had no
silverfish case, so stone scored the same as any other floor and the goal was missing.
An item takes the tier of its block, through the same table as dynamic lights.
World items are marked as other entities are. The hand stays at the world's
brightness: its own pass writes the kept world's alpha, 1 where the hand drew and
the mark where it holds a light source, and HDR output reads that for hand pixels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A guest's mount events carried entity id 0: native_acting_entity looked the
acting guest up through the hub, which is often out of `lan` while the
guest acts. The guest's entity id now travels with the action
(lan_acting_entity), like its name, UUID and permission.
- A mob a boat or minecart picks up now asks native_mob_may_ride first, as
vanilla's pickups go through Entity.startRiding; a canceled one stays off.
- Boarding a cushion no longer seats a player whose dismount from the old
vehicle a mod canceled: startRiding stops when stopRiding left the
passenger seated.
Fixes#6365
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CTM overlays (OptiFine/Continuity docs only); my commit b41a21a6b skips the overlay pass for faces without overlays, so meshing without CTM costs the same as before. verify green on b41a21a6b.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CEM animation fixes, re-read at the new head (test tweak + main merge only); no-pack draw path unchanged. verify green on bf724b491.
Dolphin.tick hurts a dolphin whose moistness ran out with damageSources().dryOut(), so the
death message says it dried out; Horizon used the drown source, which reads as drowning.
native_projectile_impact's doc comment lists tridents among the projectiles
it covers, but a trident a player throws flies in tick_tridents, not
tick_projectiles, and never called the hook. tick_tridents now tells the
mods about its mob and block hits (the face from the block clip, which
clip_block_stop now returns), and a canceled one does nothing: no damage,
sound or deflection on a mob, and on a block the trident stops where it would
have hit without sticking, as the doc comment says of arrows.
Part of #6366 (the trident; the canceled arrow that still sticks needs a
decision on where a canceled arrow goes).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's PistonMovingBlockEntity.moveCollidedEntities (and
ShulkerBoxBlockEntity.moveCollidedEntities) move every entity from
getEntities except PushReaction.IGNORE_ENTITY, which only a marker
armor stand returns. push_targets covered players, mobs, items, boats,
carts, stands and TNT only.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Title::draw's button loop drew every title-screen label centred over its own
full width, so a label wider than the button ran over the buttons beside it
(Create World's 590 px World Type label in so_so, Multiplayer's Join Server in
hi_in). AbstractButton.extractDefaultLabel hands the label to
extractScrollingStringOverContents with a 2 px margin, and vanilla slides it
inside the button; the loop now draws through ui::scrolling_text, which is
that helper.
Dolphin.addAdditionalSaveData writes GotFish and Moistness; Horizon kept both in the mob
but wrote neither, so a fed dolphin forgot it was fed and a wet one came back from a reload
dry and drowned.
NativeLootContext gains `place`: the roll's origin (LootContextParams.ORIGIN),
the broken block's entity as it was (BLOCK_ENTITY, saved with its id,
position and Items) and the level (dimension, game time, time of day, rain,
thunder), for conditions like vanilla's time_check and weather_check. The
game sets it with `with_native_loot_place` around a player's and an
explosion's block drops and a mob's death loot; both loot engines put it in
the context. Other rolls carry None. API fragment `loot_context_place`.
Fixes#6200
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A submission or a rating is taken only from the site's own pages: its Origin
header, then the session and its CSRF token. The registry's README says the
request must come from REGISTRY_PUBLIC_URL's origin, and that is what a
browser sends: a scheme, a host and a port, never a path.
The gate compared the Origin to REGISTRY_PUBLIC_URL as whole strings, so a
public URL that carries a path (the form is a page below the site's root) or
another case in its host name turned the site's own page away with "send it
from the registry's pages" (403). The origin is now taken from the public
URL, case folded as a host name is, and the environment value is trimmed of
whitespace before it is used, as a pasted deploy variable may carry it.
Fixes#6159
hurtServer runs applyItemBlocking (the shield's knockback) before dealDefaultKnockback
(the hit's own), and knockback halves the old motion, so the order decides the direction.
Horizon made the hit's push first and the shield's second, which sent a half-blocked
defender toward the attacker at twice the speed.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Sprites::prefetch decodes block and CTM textures on every thread, inserts keep their order (test: same layers/pixels with and without); load-time only, no per-frame cost; includes #6254. verify green on 5543bffed.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): overlay build walks each folder's run of the sorted map and inflates zip entries on every thread (1686→691 ms cold); test checks every file of a 3-pack stack; pack-bench.yml is ubuntu-latest, contents: read, manual/push only. verify green on e0eb57399.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Villager.getAmbientSound sleeping/trading branches; main merged, test modules kept whole. verify green on 736fe7c62.
The test looks for "\n}\n" to find the end of each loot hook's body in
loot.rs, read with include_str!. A Windows checkout with core.autocrlf has
the file in CRLF, so the search found nothing and the test panicked on every
branch there. It now normalises the line endings first, as the tests next to
it do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`/tag` reads a target's `Tags` out of `persist.raw` for eight kinds of target
(`tags_of`), and `raw_mut` -- the only writer, reached from `set_entity_tags`
-- spelled seven of them: `Target::Tnt` fell through to `_ => None`, so
`/tag add` and `/tag remove` on a primed TNT printed success and stored
nothing.
Vanilla has no such split: `TagCommand.addTag` calls `Entity.addTag` and
`removeTag` calls `Entity.removeTag` on whatever the selector returned.
Measured in a running client (creative world, `horizon-ctl`): before, `add
boom` left `Primed TNT has 1 tag(s): seeded` and `remove seeded` left it there
too; with the arm, `boom, seeded` then `boom`, and
`@e[type=minecraft:tnt,tag=boom]` matches.
Co-Authored-By: Hermes Agent (deepseek-v4.1-flash) <[email protected]>
#6246 picks a .jem's texture through cem_texture and paints_with_jem:
render_visible takes main's choice, and drawn_textures follows it (the
.jem texture and the vanilla one it falls back to are both listed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tick_player_menus built the open menu's name as a String and cloned the
last heard one for every player with a menu open, every tick. It now
compares borrowed fields and builds the owned menu only on a change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Stop timing after readback failure and do not unmap an unreadable buffer. Cover normal readback and destruction after a successful map callback.
Co-Authored-By: Codex (GPT-6) <[email protected]>
remote_player_names() built every player's name as Strings for each
player drawn, every frame, and native_mob_layer posed every mob in view
with no mod to ask. A direct lookup (remote_player_name) replaces the
list, and native_player_render_listened gates both hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mesher runs this for every face of every section: work out the face's
translucency and walk the overlay slots only when a rule gave one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractArrow.tick turns its aim 20% of the way to the flight direction each
tick, and vanilla draws the model between the last two ticks' rotations
(Entity.setOldPosAndRot, then Mth.lerp(partialTick, yRotO, yRot)). Horizon
kept only a previous position, so a turning arrow was drawn in whole-tick
steps of up to 36 degrees, a tick ahead of the position it was drawn at.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CEM texture fixes: a .jem texture paints its own layer only, atlas-missing texture falls back to vanilla, EMF folder precedence; no-pack path does less work (no texture clone); docs-only sources. verify green on 8a7c9259f.
SelectWorldScreen's world list takes keyboard focus and its rows answer Enter and Space;
in Horizon Tab went from the search box straight to the buttons, so the list could only
ever be used with a mouse and a keyboard player had no way to open a world.
ui::button, ui::button_as, ui::slider and ui::slider_ext clipped a label
wider than the widget by characters, so its end was unreadable ("Graphics
API: Prefer Vulkan", "Simulation Distance: 32 Chunk", "> Left Contro").
Vanilla slides it instead: AbstractWidget.extractScrollingStringOverContents
hands the label to ActiveTextCollector.defaultScrollingHelper, which draws it
left-aligned at left - (int) pos inside a scissor of the widget's box, pos
sliding over the overflow with a sine period of max(overflow * 0.5, 3) s.
The two ui.rs widget paths now draw through scrolling_text, which is that
helper; centered_text keeps its clipped centring for titles and text lines,
which vanilla does not scroll.
The host attaches every relayed session from its direct port, so each
attach to a relay reuses one address pair. The relay closes a finished
session first and keeps that pair in TIME_WAIT; a relay on Windows then
ignores the host's next attach from the same port (Linux takes it), the
connect times out instead of reporting the address in use, and
connect_from's fallback never runs. The second guest within a couple of
minutes got HostUnreachable.
connect_relay now gives the relay's addresses that timed out from the
direct port one more try from any port, after every address had its try
from the direct port (a dead IPv6 route doesn't eat the relay's attach
window twice). An attach from another port no longer offers what the relay
saw of it as a direct candidate: nothing listens there. The direct race's
dials keep their port.
Fixes#6342
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): HDR screenshot gains an ICC v2 profile (BT.2020, PQ to 203 nits) beside cICP; screenshot path only, no per-frame cost; unit tests on the profile. verify green on 4fa1a55b0.
Review points from #6280 (arismoko): an item merged into an earlier one
never ticked, so it is taken out of the round; a minecart's Post now
comes once the ticked cart is written back, so a mod reads and changes
the cart as it is. Unit test for the round.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): three stale tests fixed (armor stand first tick, TNT water and up bubble column); test-only. verify green on 9cabc6278.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): AbstractBoat.tick non-authoritative branch zeroes motion and deltaRotation: the boat stops on dismount. verify green on 9425bb9ac.
native_standalone_models(models) is asked once at start for the ids of
models a block entity or a mob draws as parts. Every resource load, a pack
reload included, bakes them beside the block models (StateModels::
bake_standalone; one a state already bakes unturned is shared, one that
doesn't read is logged and left out). native_draw_model(draw, model, tint)
draws one under a block entity's or mob's pose, as native_draw_block draws
a block's model. API from a fragment.
Step 1 of #6303 (the issue stays open for steps 2 to 4).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ItemEntity.ignoreExplosion via ServerExplosion.shouldAffectBlocklikeEntities (javap checked: griefing off + KEEP ignores items; no wind charges reach server_explode). verify green on 37fc39e48.
ClientPacketListener.handleConfigurationStart calls
Minecraft.clearClientLevel, which drops the client level there. Horizon's
Reconfiguring cleared the level state but kept heard_level, so
native_client_level_unload waited for the next login (after the new level's
load) or a disconnect, and a mod kept the old level's state through the
configuration screen, as on a proxy's server switch. Reconfiguring now takes
heard_level and fires the unload.
Fixes#6317
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handleRespawn keeps its ClientLevel only when the
spawn's level key is the current level's, and makes a new one otherwise.
Horizon compared the dimension type's name, so moving between two levels
sharing a type (a data pack or mod dimension using the Overworld's) kept the
old level's chunks and entities, and fired neither native_client_level_load
nor native_client_level_unload. It now compares the level key the client
last entered (heard_level), so such a move clears the world and fires both
hooks, with the right level named on the next unload.
Fixes#6316
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On every server, each tick, the mob and item rounds cloned each ticking
mob's type into a list and each boat built its type name, with no mod to
tell. A mod that hooks native_entity_tick_pre/post now also hooks
native_entity_tick_listened and sets it; otherwise no round is kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_blocks(blocks) and native_items(items), asked once at start on every
side, take horizon_mods::content::Block and Item: the declarations a content
mod's data/<id>/horizon/block/ and item/ files hold. They join the content
plan, inside the content mod whose id is their namespace when it is on, else
as a content mod with no files of its own (Content::declared), and pass the
files' checks (Content::declare, shared check_block and check_item), so their
state and network ids, saves and the joining client's content check are the
files'. A namespace whose declarations are refused is logged and left out.
The content root's stamp counts the declarations. API from a fragment.
Step 1 of #6300 (the issue stays open for its later steps).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
render_with's extra layers carry whether they glow since the emissive
change; two tests still built 4-tuples.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The per-tick comparison cloned every ticking mob's equipment each tick in a
game without mods. A mod that hooks native_living_equipment_change now
also hooks native_living_equipment_listened and sets it; otherwise nothing
is read or kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MoveTowardsTargetGoal.canUse asks DefaultRandomPos.getPosTowards for a random spot within
16 blocks and 7 up or down, a quarter turn off the target, and start walks to that spot.
Horizon navigated straight at the target's own position, so a golem always drove into the
entity it was after.
"Before you start" still said Horizon's client can't sign in to a Microsoft
account, so Horizon players could join offline servers only, and that it has
no chat session. Both stopped being true: the launcher signs players in
(docs/microsoft-account.md), the game joins online-mode servers, and since
#4375, #4376 and #4377 (Nightly 2026-11-01) it signs its chat.
- Online mode: every player joins with their own account, vanilla and
Horizon players alike.
- Offline servers: the same safety advice, and only players under an
offline name are limited to them.
- Secure chat: Horizon's client signs its chat too.
Refs #3332 (the website half is horizon-rust/horizon-site#38)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LocalPlayer.aiStep gates flight toggling with Abilities.mayfly; it does not
clear a server-supplied flying state when that permission is false. Stop
clearing flight on every movement tick so zero-speed server-forced flight
holds the player in place, matching vanilla 26.3.
Add a local-input regression and extend the resident movement parity harness
with independent flight flags, zero/default speeds and explicit flight end.
Validation: 39 movement tests pass; eight flight scenes match both vanilla
jars; player_travel and living_motion parity pass; focused Clippy passes;
the fast-profile game binary builds. Contributor confirmed the correction
on benchmark.ogulniega.com.
Full resident scene parity also exposes an unrelated attribute-cache mismatch
for negative fall damage, reproducible without movement/abilities operations.
Full pr_ready --quick is blocked by missing ALSA development files and an
existing Python block-entity fixture missing Entities.has_mods.
Co-Authored-By: Codex <[email protected]>
horizon-ctl save / save-all on a dedicated server, and closing a world
through the staged save, wrote the forced chunks but not the level's
attachments.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): EatBlockGoal.tick: destroyBlock and grass->dirt behind MOB_GRIEFING, Mob.ate outside (javap checked); two goal-driven tests. verify green on 7d7a73e84.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): CrossbowItem.onUseTick start/mid flags (reset under 0.2, each plays once): a charge jumped by a mod still plays both; unit test. verify green on 00c7de376.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Piglin.finishConversion runs PiglinAi.cancelAdmiring first: the admired off-hand item drops; same ADMIRING_ITEM test as the activity picker. verify green on 7671485c3.
native_hears_projectile_impacts (same fragment): without a mod setting it,
tick_projectiles keeps no copy of each flying projectile (an arrow's clone is
a Box, two item stacks and a Vec) and the impact hook is not called, so a
server without such a mod pays nothing per tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
46 of 78 named native_hooks while the method lives in another game
module; the generator found methods by name anywhere and checked the
target only against two module names. It now records each method's
module and requires the target to match it.
`Fox.aiStep` finishes the item in the mouth with `ItemStack.finishUsingItem`,
whose `Consumable.onConsume` runs the item's `on_consume_effects`: a chorus
fruit now teleports the fox. The chase is `TeleportRandomlyConsumeEffect`
over `Entity.randomTeleport`: 16 draws in a cube of the item's diameter, the
drawn height clamped to the level, ground found by dropping to the first
motion-blocking block, free space without liquid, and not a
`#consumable_does_not_teleport_to` block; then the fox's own teleport sound,
the portal trail (level event 2017) and a reset fall distance.
`LivingEntity.checkTotemDeathProtection` was ported for players only, so a
fox holding a totem of undying died with it. `Mob::damage` now runs the same
rule for every mob: the first hand with the item, the item used up, health
set to 1, `DeathProtection.TOTEM_EFFECTS` applied and entity event 35
broadcast for the clients' totem particles and sound. A blow in
`#bypasses_invulnerability` (`/kill`, the void) still ignores it.
Fixes#5839
Co-Authored-By: DeepSeek v4.1 Flash <[email protected]>
mobs/ai.rs: #6218 and #6228 landed test modules at the same place; each
module kept whole beside main's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): drowned trident uncertainty from difficulty (RangedAttackMob; Drowned field is -1). verify green on 3217c6759.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Creeper.getMaxFallDistance health-1 branch (javap checked); test skips without vanilla data. verify green on 4d31db99b.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): SpawnEggItem.useOn: a clicked block without collision takes the mob (bytecode checked). verify green on cbb7efe89.
native_data_map_types registers a mod's data map types for items and blocks.
Their values come from data/<mod>/data_maps/<registry>/<name>.json in the
world's data packs and from files a mod pushes through native_data_pack_files,
merged each time the server's tables are built: replace, values (entries and
#tags) and remove, a newer value overriding an older. native_data_map and
native_data_map_entries read them. Fixes#6312.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Translucent Batching's vertex and sorted-index pages grow up to about 2 GiB
and never shrank, so a page kept its high-water size after the water that
filled it was gone (an ocean left behind, a lower render distance). Now a
page of 64 MiB or more that a right-sized page (grown(used)) would halve,
or make 256 MiB smaller, gets that page in video memory and is closed:
#3466's move_off_page empties it into the new page within the same copy
budget as the section arena's compaction (32 MiB a frame, 8 MiB for sorted
indices), and trim drops it. Each mesh's pointer switches in the step that
records its copy. With no room for the new page, or a range no mesh owns,
the page stays as it is and the next try waits 5 s.
A shrink starts only a second after terrain stops arriving, in a frame the
section arena's compaction copied nothing (ShrinkGate). The game reports
terrain arriving: the view's outermost columns not all there yet
(ViewArrival, capped at the server's view distance and given up 30 s after
the view last changed), events the server sent that wait to be handled, far
tiles being sampled, and, for those 30 s, re-meshes and pending uploads. The
renderer also waits while translucent sections or far tiles wait to upload.
A shrink under way finishes. move_off_page always moves the first range it
is given, so a range larger than the budget no longer keeps a page closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each GPU test made a wgpu instance with every backend, so up to one thread
per core opened Vulkan, DirectX 12 and WGL at once while finished tests'
threads exited. On Windows with an NVIDIA driver the render lib tests hung
most runs (8 of 10 here, RTX 4070; #5238). The tests now take their
instance from gfx::test_gpu, which on Windows opens Vulkan and DirectX 12
only, as the game prefers there; WGPU_BACKEND still overrides. Linux and
macOS are unchanged.
Fixes#5238
Co-Authored-By: Claude Opus 5.5 <[email protected]>
It returned the decode error and left the value in place, so a corrupt
saved value could never be removed. It is removed now, and the error
still returned.
While any entity had an attachment, prune_entity_memory ran every tick,
and it forgot the UUIDs of entities outside its list (projectiles, TNT,
orbs), which then got a new random UUID each tick. The tick now drops
only the attachments of entities that left, by the same check that
admits an entity as a holder.
Review fix: `Streaming::loading` is also the field that says the loading
screen is up; without the parentheses the far terrain would still compile
and follow the wrong one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): owner console reaches native registered commands; Python hook tests pass merged with main. verify green on c73e087d7.
LivingEntity.travelInFluid lifts a mount that carries a passenger, is in
minecraft:can_float_while_ridden and is deep enough in the water. Horizon's
mob travel skipped that step, so a horse with a player on it only sank with
its rider.
[mod] version = "1" (as the fixtures write it) failed the strict
semver parse when another mod depended on it; missing parts now count
as zero, as in Cargo's requirements.
It ran the check only for xtask/, native-hooks.json and native_hooks.rs,
while the generator also reads every game file, mod_api.rs, loot.rs,
item_defaults.rs and native_render.rs.
Guests' attachments lived in their hub records, which a guest's turn
can't reach (as_guest has the hub out of self.lan), so Holder::Player
for anyone but the acting player failed in player_tick, use_item_on,
interact_entity and the other guest-turn hooks. They now live on the
server Game by UUID from join to leave, and go back into the record
only for playerdata to save.
Review fixes: on the server thread the near terrain can start and stop
loading between chunk batches. Each start dropped the queue and rebuilt
the whole wanted heap; the dropped tiles now go back into it where they
were. A sample still running when the hold starts no longer lets the
next one start at once. The queue test no longer moves the list it
checks later.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every save wrote <dim>/data/minecraft/horizon_attachments.dat, with mod
data or without, and a malformed file stopped the world or dimension
from opening. The file is now data/horizon/attachments.dat, written
only once a level has attachments, and a file that doesn't read is
moved aside with a warning while the level opens without them.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): status and End flash uniforms for shader packs, from the public uniform reference. verify green on 7745fddd2.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): client LevelEvent hooks, empty bodies; Python hook tests pass merged with main. verify green on ba04f5032.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): four shader translator fixes (bool-shadowing part removed: LGPL source); horizon-shaders tests in verify. verify green on 6e554aaa1.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): worn dragon head jaw follows wornHeadAnimationPos incl. the vehicle's positionScale; supersedes #6166. verify green on f5161d9eb.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): beacon update tag keeps the client's beam (loadAdditional reads only powers); unit test. verify green on 2d5ee61a5.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): ARM Linux CPU name from implementer/part; unit tests on real cpuinfo shapes. verify green on ce02c0329.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): random_ticks for content blocks; registry lockfile refresh; hook/mod Python tests pass merged with main. verify green on 5d08602df.
NativeDispensed, NativeServerPlayer, NativeCommand, the lifecycle types,
NativeZoom, NativeLootDrops and NativeScreenView moved into mod_api and
were imported back privately, so mixin code naming them at their old
paths hit E0603. They are re-exported there with their old visibility.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): mod block state property sorting before the template's is refused with a clear error (message whitespace fixed). verify green on dc1597e11.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): server LevelEvent load/unload hooks, empty bodies, one unload per load; hook/mod Python tests pass on the head merged with main. verify green on c60b4c8d4.
🤖 Agent: Reviewed by Ludovic's AI agent (Claude): Superflat grass icon uses the grass colormap (GrassEnv), as the inventory does. verify green on b3384f9bd.
main_with installed the mod list after logging, the crash handler and
the RAM limit, and an injection's helper indexed the list, so an
injection into any of those panicked in a release build. The list is
now installed first, and the helper does nothing when its mod is
absent.
Only horizon-mixins staged a bundle's data/ into native-data/<id>/, and
a bundle with no mixin content never reaches it, so native_data! did
not compile and native_built_data was empty. The launcher now stages
those bundles' data/ where horizon-mixins would.
The launcher's generated package owns the horizon-client binary but had
no build script, so on Windows a build with a trait mod fell back to a
1 MB main-thread stack and lost the icon. It is now staged beside
horizon-client and builds with that crate's build.rs, which finds
packaging/ where it looks.
Review fixes: the dropped tiles must come back best first, and larger
machines keep the first SAMPLE_JOBS of the queue. retiring_detail gets its
doc comment back, and docs/graphics.md says one sampling thread below 8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_item_fluid_tank makes a mod item a fluid container with a capacity;
its contents are kept in the stack's custom_data under horizon_fluid.
native_item_fluid reads one item of a stack, native_item_fluid_fill and
native_item_fluid_drain answer how much it takes or gives and the single item
it becomes. Vanilla's buckets are built in: empty, water and lava, a whole
bucket or nothing. Fixes#6278.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The inventory's own player in first person is drawn only by the preview,
so a preview stores when the world didn't draw the entity this frame or
the last. Shared variables are read as each entity's frame began, like
its own, so drawing it again shows the same value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BlockEntities::insert dropped the position's attachments on every
call, so a jukebox taking a disc, a lectern taking a book or
/data merge block lost them, where vanilla keeps the block entity.
A replacement of the same type (the same id for a mod's) now keeps
them; any other replacement, and a removal, still starts them over.
Alt + numeric keypad digits make Windows send a WM_CHAR after the Alt is released, which winit
turns into a key event with no physical key but with the character as text. Every screen's key
handler reads the key code first and returned, so the character was lost. GLFW's char callback
types it; the event now goes straight to the focused text field.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review fixes:
- When the near terrain starts loading again, the queued sample jobs
past the held budget are dropped (they come back once it is in): with
32 a thread queued, the hold otherwise waited for the whole queue.
- `Streaming::loading` counts neighbours' light views, as `busy` does.
- Without sampling threads, the far worker takes summaries and detail
requests before the queued samples.
- One filter for the columns that take generated detail; the status pair
is `detail_target_columns` / `refined_target_columns`.
- docs/graphics.md: the sampling and decoration threads and the hold as
they are now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stamp tool dropped a fragment hook's mod_api contract, and replaced
markers only in native_hooks.rs and a fragment's comments files, while
mod_api.rs and the generated files copy hook and helper comments with
their markers. After a stamp the generated files were stale and
xtask mod-api --check failed. Now every marker is also stamped in
mod_api.rs and the four generated files.
`EatBlockGoal.tick` gates both the `destroyBlock` of an edible block and the
grass block's `levelEvent(2001)` + `setBlock(DIRT)` behind `MOB_GRIEFING`, and
keeps `Mob.ate` outside that gate. Horizon pushed the two `MobEvent`s
unconditionally, so a sheep on a grass block with the rule off turned it to
dirt: measured in a running client, 0 -> 1 dirt blocks at 41.6 s of a 150 s
window.
`MobWorld.mob_griefing` already carries the rule (it gates an allay's pickup,
the dragon's block breaking, blasts and a mob's projectiles); this goal was the
outlier. The test world gains `with_built_test_world_rules` so the flag is
reachable from a test, and the new cases tick a sheep for 6000 ticks through
`mobs::ai::tick` with the rule on and off, one per half of the gate: dirt
events 4 on and 0 off with `EntityEvent` 10 in both, and the short grass under
its feet destroyed on, untouched off. Inverting the gate fails both.
Co-Authored-By: Hermes Agent (deepseek-v4.1-flash) <[email protected]>
native_living_breathe(entity, kind, can_breathe, consume, refill) is called
every tick for a player (on the server's copy) and for a mob with the
generic air logic. Horizon's own answers come in as the defaults and a mod
may change them; the air arithmetic moves to a new breathing module, which
is a free function hook like the loot and menu ones, because the player's
motion and the mob tick have no Game.
Fixes#6248.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
AgeableMob.isBaby is canBeABaby() && getAge() < 0, so a vanilla client
ignores DATA_BABY_ID on a frog, parrot, slime, magma cube, camel husk or
zombie nautilus. The client now does too (drops::can_be_baby). The test
gives each type a model, which MobTypes::load needs, and checks a parrot.
baby_index reuses doors::is_zombie.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tags_of answered nothing for a server's player (Target::Guest) and raw_mut had no compound to
write to, so tag <player> add <tag> was dropped and a tag= selector never matched. A guest's tags
live in the saved player compound its file is written from (Guest.saved).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
native_map_decorations(map, decorations) is asked each time a map is drawn
(held, in an item frame, on the cartography table's screen); a mod pushes
NativeMapDecoration icons that are drawn after the vanilla ones with the same
pose. Typed decorations saved in the map need code-side registration and are
the later step.
Fixes#6269.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
LivingEntity.baseTick stops a rider whose eyes are in water (not in a bubble column) from riding a
vehicle of the dismounts_underwater tag: camel, chicken, donkey, happy ghast, horse, llama, mule,
pig, ravager, spider, strider, trader llama and zombie horse. Horizon only did it for boats. The
window's player and a server's player (through the same tick as the mount's input) and the mobs
that ride a mob (a baby zombie on a chicken) now get off; a skeleton horse keeps its rider.
tools/test_mount_submerged_parity.py compares 14 mounts with a vanilla server, passenger packet by
passenger packet.
Fixes#6152
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
bounding_box_gizmos went through every loaded block entity each frame to find the structure
and test instance blocks whose box it may draw. BlockEntities now keeps their positions
(like the mod block entities), and the frame looks at those only. A sample of an RD 32
mountain flight on an M4 Pro spent 0.12 ms of every frame in that scan.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Split::about_to_wait listed the monitors every frame, splitscreen on or off. On macOS each
listing is a round trip to the window server (SLSGetActiveDisplayList): a sample of an RD 32
flight on an M4 Pro spent 0.1-0.3 ms of every frame there, 2-3 % of the main thread.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
baby_index listed the AgeableMob kinds by hand and missed eight of them,
the happy ghast and the nautilus among them. A server's client never read
their DATA_BABY_ID (16), and this game's own server never sent it, so a
hatched dried ghast was drawn and boxed as an adult (#6133). The index now
comes from finalize::ageable, which lists every vanilla AgeableMob, plus
the zombies, the zoglin and the piglin.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_block_entity_energy declares a mod block's energy buffer as
SimpleEnergyHandler is built: capacity, the most one insert and one extract
moves, and the faces a fill may come through and a drain may leave by. The
amount is kept in the block entity's saved compound as the int `energy`.
native_energy reads it through a side, native_energy_insert and
native_energy_extract move energy (with `simulate`), and native_move_energy
is EnergyHandlerUtil.move between two blocks. Fixes#6227.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Piglin.finishConversion calls PiglinAi.cancelAdmiring before it converts, which spawns the
off-hand item the piglin was admiring and clears the hand. Horizon copied the whole off hand
to the new zombified piglin, so the item that should have been on the ground travelled with it.
Add native_entity_mount, asked before a player or a mob gets on or off a
boat, minecart, cushion or mount (including /ride, stabs, bucks, teleports),
on the server thread and a dedicated server. A mod can cancel it.
Fixes#6273.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
AbstractBoat.tick drops a boat's cached motion on the side that does not drive
it, so the level takes a boat over at a standstill when its rider leaves.
Horizon kept the velocity of the ride, and the empty boat went on gliding.
Match vanilla 26.3 ClientPacketListener.handleGameEvent by playing the arrow hit player sound on event 6 at eye height, the pufferfish sting sound on event 9 at entity position, and updating do_limited_crafting on event 12.
Co-Authored-By: Antigravity <[email protected]>
- A layer takes its .jem's texture only when it is the first layer drawn on that model
(and not on the body's): the wolf armour does, the cracks over it on wolf_armor#main
keep theirs. One helper picks the .jem texture when the atlas has it, for the body
and the layers, without cloning a String first.
- Fallbacks follow chains up to three steps, babies included: cat_baby#collar takes
cat.jem's geometry (not its texture) through cat_collar.
- jem_files keeps every file of a name, EMF's first; each layer tries its candidates in
order (its own names by name, then its fallbacks, nearest first) and a file that fails
to read or parse gives way to the next. Each file is parsed at most once, and only
when a layer tries it.
- Warn once at load when a .jem's texture names a file missing from the pack root.
- Drop the `over` test wrapper and the bool of fallback_names (a fallback onto another
layer is geometry-only); LAYERS and FALLBACKS are slices; the "first by name" rule is
written once.
- Tests: a sheep's wool draws in its sheep_wool.jem texture; a wolf armour .jem paints
the armour but not its cracks; cat_baby#collar from cat.jem; a broken llama_decor.jem
lets trader_llama_decor.jem apply, a broken EMF file lets OptiFine's. The ignored
real-data test also checks both columns of FALLBACKS and every baby layer, and says
its part names are a sample.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An overlay over a face whose picked tile is translucent goes in the
translucent pass with it. The connectTiles test now resolves the sprite
name through build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_player_submit draws a player with a mod's own model (other players and
the window's player in third person); native_mob_layer adds geometry to every
mob and player after the vanilla model, in the same feet-and-body frame
native_mob_submit uses.
Fixes#6257.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Every received byte on an online-mode server went through a byte-wise
software AES (~450 ns a byte, about 2 MB/s for a whole core). CFB8 decryption
knows each byte's register up front (the ciphertext before it), so runs of
bytes are encrypted together: ARMv8 AES instructions on Apple Silicon (a small
allow(unsafe_code) module for the intrinsics), the RustCrypto aes crate
(MIT OR Apache-2.0, AES-NI picked at run time) elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 2 review: the first-sight and new-scan check clears an answered miss like the
regular check does; docs say a miss is answered once between checks; the test goes on
to the next regular check and a miss answered after it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proof: the official server's boat false positives (1357 claims at y 63.0857639 = a floating boat's top, isInferno banned twice) come from LanHost.states being the last level's entities when moves are read; states now park with their level. pr_ready --quick: fmt, clippy pass; the one lib test failure (armor stand, outside this change) passed in CI verify.
The registry is its own workspace, and its workflow runs on any change under
crates/horizon-mods. Its lockfile lacks png (and fdeflate, miniz_oxide), which
horizon-mods needs, so `cargo clippy --locked` refuses it and `check` fails on
every PR that touches horizon-mods.
Fixes#6196
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_entity_tick_pre and native_entity_tick_post for the mobs, item
entities, minecarts and boats that tick in an entity-ticking chunk, on a
server. Set canceled in the Pre and the entity does not tick this tick, and
its Post is not told. Fixes#6249.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- Within a frame every run starts from the frame's starting variables; only the
frame's first run outside a GUI preview stores them, and global_var /
global_varb are stored only by such a run.
- is_right_handed reads Mob::main_arm_left (the player's main hand option too).
- Variables refuse infinity as well as NaN and keep their old value.
- The saved table stays bounded: cleared if still at twice SAVED after eviction.
- is_in_gui is a thread-local set by in_gui() with a drop guard, no longer a
Frame field; parts write back only changed visibility and new skip_draws;
one lock() helper; one entry() per run for the entity's variables.
- Tests: non-finite variables keep 7, the client's synced flags and main arm,
the 4096-expression cap, and a preview not changing the next frame's value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
layer=cutout stays a cutout whatever the tile's alpha, but an overlay on a
translucent face goes in the translucent pass with it; connectBlocks and
connectTiles count only for method=overlay, as documented; a tintBlock the
game lacks tints nothing; connectBlocks looks states up by block. Also
fixes two compile errors the first overlay commit had.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_living_equipment_change for mobs and armor stands: each tick, a slot
that holds another stack than the mods last heard is told, with the slot's
name and the stacks before and after. Mob::equipment_stacks gives the slots
(and the equipment sent to clients now reads it). Fixes#6242.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ItemEntity.ignoreExplosion asks ServerExplosion.shouldAffectBlocklikeEntities, so with
mobGriefing off a blast that keeps blocks (a creeper's or a ghast's) never touches a
dropped item. Horizon hurt and pushed every item inside the blast whatever the rule.
CrossbowItem.onUseTick keeps a flag for the start and the middle sound and
plays each once the charge reaches its point, so a charge a mod's tick moved
past both points in one tick plays both. Horizon compared the charge with the
tick before it (`before < 0.2 && now >= 0.2`), so the sounds a jump passed
were skipped; only the end sound played.
The charge's two flags now live on the game beside `using` and are cleared
when a use starts and while the charge is under the first point, which is
vanilla's rule. `charge_sounds` holds it as a function of its own, with a test
for the jump, the ordinary crossing, a second charge and Quick Charge.
native_gui_layer_pre(layer, batch, canceled) and native_gui_layer_post(layer,
batch) fire around each vanilla HUD layer Horizon draws, so a mod draws below
or above a layer, or hides it. The jump/locator/experience bar is drawn as one
layer, and the mount's jump bar and the title and action bar are split out of
the passes that drew them together.
Fixes#6243.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The lift came from the finished pixel, after the block's light and face shading:
a soul lantern (light 10) lifted less of its glass than a lantern, and on two of
its sides less than on the other two. The shaders that mark the frame now work
the lift out from the texel before lighting, with the tier windows from the
globals, and write it into the mark's alpha: a band per tier, the amount within
it. HDR output reads it back and applies the gain to the lit pixel as before, so
a dimmer light still shows dimmer, but shines alike on every face.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
WitherSkull.getBlockExplosionResistance reports min(0.8, resistance) for a block
WitherBoss.canDestroy, so a blue skull's blast tears through blast-resistant blocks.
Horizon used each block's own resistance, so a blue skull left obsidian-like blocks standing.
ServerGamePacketListenerImpl's clientVehicleIsFloating and
aboveGroundVehicleTickCount: a vehicle move that doesn't go down, by a vehicle
that hadn't come down onto something, with nothing but air around it (water
holds a boat up), floats; a driver whose vehicle floats for more than
getMaximumFlyingTicks of the vehicle (160 ticks for a boat, 80 for a horse) is
kicked with "multiplayer.disconnect.flying", unless the server allows flight.
The server had no check on vehicle moves beyond moved too quickly and moved
wrongly, so a boat flying over land was never stopped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_player_container_open and native_player_container_close, per player
and per menu, with the menu type's name and a block menu's position. Any menu
a server player opens: containers, furnaces, crafting tables, trades,
workstations, lecterns, mounts, a mod's own. Fixes#6188.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
FireworkRocketItem.useOn and, while gliding, FireworkRocketItem.use
return SUCCESS (SwingSource.PREDICTED) on the client too: vanilla's
startUseItem swings the hand itself and stops, and a vanilla server
swings it for the other players only. A server's client never swung for
either use, and a rocket used on a block also sent UseItem and gave the
off hand its turn (a flint and steel there lit the block too, #2216).
The use on a block passes while gliding (the use boosts instead) and in
adventure mode (ItemStack.useOn).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A rocket attached to a server client's own player followed it and
boosted it in the entity tick, before the player's own move that tick,
so its spark spawned where the player (and the camera) had just been and
was never in view: an elytra boost left no trail. In vanilla the player
ticks first (ClientLevel.tickEntities); the rocket then boosts the glide,
sits at the rocket hand (getHandHoldingItemAngle, half a block out) and
adds its spark there. tick_own_rockets now does that after the player's
tick; rockets of other entities and free ones tick as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes: overlays count against the atlas budget only after every variant; the
suffix is read from the first file that sets it, Latin-1 too, and never holds a path
separator or ..; docs say what is paired and drawn; tests for a layer's glow, a
variant's own overlay (and none from the base), the atlas packing it, and where the
suffix is read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_player_start_tracking and native_player_stop_tracking: the hub's
per-client Tracker keeps what started and stopped, with the entity's ID, UUID
and type, from every place that changes the set (the tick's range checks, a
forgotten or cleared set, a leaving player), and the game tells the mods each
tick. Fixes#6231.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
CI's verify has no extracted data, so with_test_world doesn't run the
closure there; the other mob tests ignore its result the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_projectile_impact for each impact tick_projectiles finds, with the
projectile's id, type and owner and the entity or block it hit. A canceled
impact flies the tick again through what it hit (Projectile::tick_through,
arrows::tick_through): nothing of the hit happens and the projectile flies on.
Fixes#6203.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Cutting both axes at every crossing keeps the pieces on the quad's
diagonal square, so a split face shades exactly as the whole one does.
Tints::get and the biome rules share Tints::biome_id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Players' worn armour reaches render_visible as extra layers (rects, no names):
armor_layers now adds each layer's _e overlay as a full-bright extra layer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A .jem's texture paints only its own layer; overlays drawn on the body's
model (collars, eyes, villager type/profession, armour cracks) keep theirs.
- A .jem texture the atlas lacks falls back to the vanilla one instead of
hiding the mob; a texture outside assets/minecraft/textures/ is logged.
- emf/cem beats optifine/cem in both forms, the flat file beats the folder
form, a directory named x.jem is no file; scan is sorted.
- Two names of one layer (llama decor, undead horse armor): first by name.
- Fallbacks salmon_small/large -> salmon, cat_collar -> cat (geometry only),
player_slim -> player; nautilus_baby gets none.
- Small pufferfish fins and rabbit hind feet map to Horizon's part names.
- Files no layer uses aren't parsed; no CEM files: no vanilla map built.
- Reuse render::find_part; docs list custom entity models and mobs/cem.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block's states were its template's states times the declared combinations,
the template's properties varying slowest, but StateTable::new reads a state
table as vanilla's StateDefinition does: every property sorted by name, the
first name slowest. A declared name that sorts before one of the template's
(`bites` before `facing` on carved_pumpkin) stopped the game at load.
The states are now laid out in that order, with one lookup from a template
state and a declared combination to the state's row, used for the template
state's new id (the default state included), the collision shape fan-out and
the cached shapes. Declared names that sort after the template's lay out as
before, so those blocks' ids do not move. The refusal this replaces is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: server-tps-1: creaking heart scan follows chunk arrivals; tick_block_entities −0.45 ms/tick on the 50-bot bench (A/B alternated), test passes; merging once CI is green.
RecipeBookComponent puts the magnifier icon's clickable rectangle from eight pixels past the
book's left edge to the search box's own left edge, 25 pixels wide. Horizon used 17, so the
last eight pixels of the magnifier did not focus the search box.
native_interaction_key(button, key_mapping, hand, swing_hand, canceled) fires
before the attack, use and pick-block keys act on the crosshair, for a single
player's window and a server's player. A cancel skips the action; a cancelled
attack or use still swings unless the mod clears swing_hand.
Fixes#6213.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MerchantScreen.mouseScrolled moves scrollOff by (int)(scrollOff - scrollY), so one event
with a two-notch delta moves two rows. Horizon moved exactly one row per event whatever
the delta was.
CreateFlatWorldScreen.DetailsList.deleteLayer selects the row at the same visual position
after the removal, or the last row when the last one went. Horizon deselected everything,
so deleting a layer left the screen with no row highlighted.
Review fixes: the atlas budget counts variants beside their texture too; the body's
custom-model key is one helper for render_visible and drawn_textures; a texture that
keeps missing makes the mob choose at most once between regular checks; select's doc
points to matching; tests for the budget, a layer following the body's miss, and a
render assert that can't pass on nothing drawn.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
They only run with prepared resources, so they went stale unseen. The armor stand test expects a fall on tick 1, where a summoned stand starts moving on tick 2; the two TNT tests set up the wrong scene.
Fixes#6235
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
- A part's visible / visible_boxes start from what vanilla set and are written back
only when an animation changes them; visible_boxes 1 keeps vanilla's skipDraw.
- One const Frame default (first person); mob_geometry fills paused, first and
third person; GUI previews (inventory player, mount box) set is_in_gui.
- A second draw of an entity in one frame starts from that frame's starting
variables; a full table forgets only entities not drawn for 600 frames.
- global_var / global_varb live in one process-wide registry shared by all entities.
- is_aggressive, is_right_handed, is_sitting, is_tamed, is_sprinting, is_sneaking
read the synced mob fields.
- Non-finite part values keep the vanilla pose; NaN never lands in a variable.
- At most 4096 expressions per model; FRAME is read once per run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cut a ctm_compact face where its UVs cross 0.5, not at its middle, so a
dirt path's side or a slab's gets its quarters in the right place (a slab
side isn't cut across at all); no allocation per face. The blend test now
uses corners only the triangle split gets right.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
screen_draw::button now goes extractDefaultLabel ->
scrolling_string_over_contents (AbstractWidget.extractScrollingStringOverContents,
margin 2) -> scrolling_with_default_center
(ActiveTextCollector.acceptScrollingWithDefaultCenter) -> scrolling_text
(defaultScrollingHelper), as vanilla does. Same draws; it closes the
callee gap the port ledger opened on button (extractDefaultLabel without
a port of extractScrollingStringOverContents). Left open: the callee gap
on AbstractWidget.getWidth, a trivial accessor passed in as `w`.
the_offset_follows_the_vanilla_period_and_easing ended with a hash over
2.4M offsets from a Java transcription kept outside the repository: nobody
could regenerate it, and it checked a copy of the formula, not vanilla's
code. The pack screen harness now runs vanilla's own defaultScrollingHelper
at fixed clocks instead; the hand-checkable spot values stay.
scrolling_text says it must not run inside another Draw::Scissor (paint
replaces scissors instead of intersecting them; no caller does today), and
the clippy too_many_arguments allows go: the workspace allows it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
It came from reading an LGPL pack's source (Pixel Perfect), which
CONTRIBUTING puts off limits even to read. The other four fixes rest on
the GLSL specification and an MIT pack, and stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fifth of the cases now run in another language (the eight whose "Open
Pack Folder" is wider than its button, plus fr_fr and de_de, which fit)
at a fixed Util.getMillis. The oracle injects the language (en_us, then
the case's file from the prepared assets), sets Util's time source to the
case's clock, and records a text's own scissor (defaultScrollingHelper's
withScissor) as a scissor around it, the way Horizon draws it. The fixture
loads the same language before it lays the screen out.
3000/3000 match, 427 of them a scrolling footer label (offsets 0 to -25
px). With the old clamp in screen_draw::button the fixture panics
("min > max"), so the harness now catches #6129. The outputs are read as
UTF-8: the Macedonian labels broke cp1252 decoding on Windows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Witch.registerGoals adds HurtByTargetGoal(this, Raider.class), so a raider that damages a
witch is never taken as its target; Horizon used a plain HurtByTarget, so raid friendly fire
made the witch fight the raid.
Render inline atlas sprites and player heads in the text shadow pass instead
of skipping them. Use the text tint, shadow colour and one-pixel offset,
vanilla's sprite baseline, and foreground depth for world text.
Reference: Minecraft 26.3 Font.PreparedTextBuilder, PlainTextRenderable.render
and AtlasGlyphProvider.Instance.renderSprite, inspected from the pinned jar.
Add a vanilla oracle and Rust probe covering 96 sprite geometry/colour cases,
plus regression coverage for inline player head shadows.
Validation: inline_object parity passes three consecutive runs; 27 HUD tests
pass; renderer/client probe Clippy passes with -D warnings and no default
features; rustfmt and public-tree checks pass.
Full pr_ready --quick remains blocked by missing ALSA development libraries,
mixed system Rust/Clippy toolchains, and an existing Python fixture missing
Entities.has_mods (test_mod_block_entity_any_template).
Co-Authored-By: Codex <[email protected]>
The message carried the source's indentation between "the" and "game";
the test now checks the block's id and the single spaces.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The owner console (horizon-ctl command, the MCP command tool) ran every
line through the vanilla dispatcher, so a command a native mod registered
answered "Unknown or incomplete command". It now offers a registered name
to native_perform_command first, as the owner at level 4, the way the
owner's own chat does. The dedicated console was done in #5815.
Fixes#5682.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Endermite.registerGoals is the silverfish-style monster list: powder-snow climbing, a
melee attack, a stroll at 1.0, and a HurtByTargetGoal that alerts the others. Horizon
never put the endermite in the hostile family, so it fell back to the generic monster
block: stroll at 0.8, no powder-snow climb, and a hurt-by-target goal that never alerts.
#6126 warps the pointer before the Locked grab on Windows (#6076), so a
focused grab warps twice there. The fixture still expected one warp and
failed on Windows (CI's Linux run kept the old count). It now expects two
on Windows, one elsewhere, which also pins #6126's warp.
Fixes#6214
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A movement scan walks every cell of the box the movement expands to, so a
diagonal move covers about N x N of them. The client passes a 1,000,000
candidate budget, which a legal `movement_speed` of 400 passes within a
second of running: the scan then ended in `VisitBudget` and the game
aborted with "collision scan candidate budget exhausted" (#6124).
Vanilla walks the same box with no counter at all, so the movement is slow
there, never fatal. The budget now only has to cover the cells the scan can
visit (the sweep, and the step's own box raised by max_step), which leaves
every ordinary movement identical and makes the pathological one stall
instead of exiting.
`scan_cells` shares the cursor arithmetic with `BlockCollisionScan::new`
rather than repeating it.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Entity.processFlappingMovement emits a flap game event when the entity flaps over air;
Bee and Bat each override isFlapping, a bee every other tick while flying and a bat every
tenth tick while it is not resting. Horizon only modelled the bee, so a bat flying around
was silent to sculk sensors and a warden.
native_client_level_load when the client makes a level (joining a server, a
dimension change), native_client_level_unload when it drops one (replaced,
told after the new one's load as Minecraft.setLevel does, or leaving the
server). The client half of #6154.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
old_world_e2e asked horizon-client-lib for the horizon-client binary, which
only horizon-client has; three e2e docstrings, the agent-control guide and
xtask's comment still built horizon-ctl from horizon-client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Nautilus.canBeLeashed and ZombieNautilus.canBeLeashed return false while
AbstractNautilus.isAggravated, which is the brain holding ANGRY_AT or ATTACK_TARGET.
Horizon let a nautilus be leashed at any time, so a lead could drag off a nautilus in
the middle of a charge.
The registry is its own workspace, and its workflow runs on any change under
crates/horizon-mods. Its lockfile on main lacks png (and fdeflate, miniz_oxide),
which horizon-mods now needs, so `cargo clippy --locked` refuses it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ports main's lib.rs (block_edit, cit, lod_net, ram_limit, social_screen) into
client_lib.rs, main.rs's --player-dir logging and ram_limit into entry.rs, and
the horizon-optifine dependency into horizon-client-lib.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: test-only fix for command_exec on main: the boxed-in orb case (#5646) depended on vanilla's unseeded orb random; a stone ring keeps the top the only free side. verify + parity (command_exec PASS on the merge) green.
WanderingTrader.addAdditionalSaveData writes DespawnDelay and the wander target; Horizon
wrote neither, so a trader reloaded mid-visit forgot its countdown and started over, and
came back with nothing to walk to.
Villager.getAmbientSound returns null while the villager sleeps and the trade sound
while a player has its menu open. Horizon always played the plain ambient, so a sleeping
villager talked in its bed and a villager in a trade screen never made the trade sound.
🤖 Agent: fixes#6009, native builds use a real ASCII folder in ProgramData instead of the 8.3 short name that rustc expands back to the Cyrillic path; Cyrillic unit test, clippy for x86_64-pc-windows-gnu and pr_ready --quick green. Not run on a real Windows machine.
A test that fire in the air beside a block stays while its ignite odds are
above zero and that burn odds of zero never destroy it (round-1 review:
the fire test only reached burning out, and a swapped tuple went unseen).
native_fire_odds says to answer only for your own blocks and to keep the
odds within vanilla's 0 to 100; the fragment's summary names the campfire.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes found reviewing #5744 after it merged:
- no rule matching, and ETF's 0, draw the texture itself, not <name>1.png
- choose walks the textures render_visible draws: a .jem's own texture and the
warden's glowing layers with their own rules get picks
- rechecks count this window's ticks, so a frozen world (game_time stopped) still
rechecks every mob once a second instead of one in 20 every tick
- a texture drawn with rules and no pick (a wolf just tamed) chooses on the next tick
- a scan's generation replaces the stale flag: every splitscreen player's mobs choose
again after a reload
- variant images past a 4096x2048-texel budget stay out of the atlas instead of
failing the whole pack set
- tests for layers with their own rules, the villager exemption, the facts, the render
lookup and its fallback, and the atlas naming
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test blocked in a read with no timeout and waited for the deadline's
shutdown to wake it. Linux wakes a blocked reader on shutdown; Windows does
not, so the test never finished there (#6183). It now reads once the limit
has passed, and a new test checks the case the deadline exists for: a peer
that sends one byte at a time, under the read timeout the callers set.
Fixes#6183
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EvokerAttackSpellGoal.createSpellEntity walks down from the top of the target until it
finds a block whose up face is sturdy, which is BlockState.isFaceSturdy(.., UP) and not
"is a full cube". Horizon required a full collision shape, so on a soul sand, mud or
full snow layer floor the fang search skipped the block under the evoker and stood the
fang on that block's own collision top, two sixteenths inside it.
depthtex0 at the screen's centre, smoothed with OptiFine's default centerDepthHalflife (1 tick):
a depth texture can only be copied whole, so a one-pixel pass loads the texel into a 1x1
R32Float target, copied into one of three small buffers and read back frames later without
waiting for the GPU (as timing.rs reads its timestamps). Packs focus depth of field with it;
it was zero.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- currentPlayerHealth/Hunger/Air/Armor (fractions of their maximum) and maxPlayerHealth/
Hunger/Air/Armor, -1 in creative and spectator where the shaders.properties reference says
so; isSpectator, isRiding, feetInWater, inSwimmingAnimation, is_burning, is_hurt,
is_invisible (spectators too), is_sneaking, is_sprinting, read as the HUD reads them.
- endFlashIntensity and endFlashPosition (view space, length 100): the End flash state that
draws the flash sprite, 0 outside the End.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sky program that reads the pack's SSBOs (Noble's gbuffers_textured, the fallback of
gbuffers_skytextured) was drawn without bind group 4: wgpu refused the frame ("expects a
BindGroup to be set at index 4") and the game panicked. The sky pass now binds them as the
other passes do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FoodOnAStickItem.use passes on a client, and the server answers SUCCESS_SERVER (which swings the
hand) only when the mount can be boosted. The client counted both sticks as items whose use ends
the click, so every right click swung the hand, boost cooling down or not.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The update tag's carry-over of client state (a vault's and a trial
spawner's spin, a beacon's beam) is now keep_client_state, tested with a
real decoded beacon update tag: the beam stays, the powers and levels are
the tag's. Beacon::keep_beam and its test go.
The world border filter on the beacon tick goes to its own change: the
other remote block entity ticks skip the border too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LanHost.states, the entities a level's moves are checked against before its
tick describes them again (the anticheat's ground check, move collisions, the
prediction's excuses), was one buffer that every level pass refilled. On a
server with players in several dimensions, the Overworld's moves met the
Nether's or the End's entities: a player standing on a boat at sea had no boat
under them, and the ground check kicked and banned players for it on the
official server (claims at y 63.086, a floating boat's top).
The states now park with their level (Parked.lan_states, swapped in
enter_level), like the level's other entity state.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player.damageStatsAndHearts sends DAMAGE_INDICATOR particles to the level. Player.attack's path
also queued them for the served players (the window of the server thread, LAN guests); the
stab's own copy only drew them on the server's side, so the window never saw them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Keeps the branch mergeable into main. Conflicts:
- lib.rs and main.rs keep the split's layout. Main's new block_edit
re-export and ram_limit module go into client_lib.rs, and ram_limit::start
moves to entry.rs beside crash::install.
- block_entities.rs keeps both the attachment maps and main's per-chunk
index of mod block entities.
The generated trait picks up the updated block_entity docs.
world_drawn and render_needs dispatch through an ordered RenderHooks
trait, which a mod returns from Hooks::render. The renderer list is
installed once beside the mod list and called on the render thread
while the frame is built. block_entity_submit and the existing
block-entity drawing helpers come through the generated trait and
Host, so a trait mod can draw its block entities without a mixin.
A trait mod can depend on another mod's [api] crate. Required and
optional semver requirements resolve against the installed bundles.
Missing dependencies, version mismatches and cycles are rejected, and
hooks dispatch in a stable dependency order. Optional features turn on
only when that mod is installed, so mods call each other through
ordinary Rust functions rather than a runtime registry.
Mods can attach typed data to players, entities, block entities,
chunks and levels through AttachmentType<T> and a Holder. Values
decode lazily, unknown and undecodable entries round-trip untouched,
persistent values encode when they change and memory-only values go
away on unload. Opted-in player values survive respawn.
Entity values load and save at the entity compound boundary, with
passengers and portal arrivals. Player values follow guest and
playerdata state. Block-entity values use a per-dimension side table,
chunk values live in chunk extras, and level values live in the
dimension's data/minecraft/horizon_attachments.dat. Errors are typed.
There is no client sync.
AbstractButton.extractDefaultLabel hands the label to ActiveTextCollector
.defaultScrollingHelper: a message wider than right - left is not centred but
drawn left-aligned, scissored to the button, and slid back and forth with
Util.getMillis (period max(overflow * 0.5, 3) s, sine easing, (int) cast);
only a message that fits takes Mth.clamp(centerX, left + w / 2, right - w / 2).
screen_draw::button always clamped, so a label wider than its button made
i32::clamp panic (min > max). In the Resource Packs footer that is "Open Pack
Folder" in af_za, ca_es, io_en, isv, mk_mk, oc_fr, tl_ph and vp_vl (150 px
button, label 150 to 171 px), which crashed the client when the screen opened.
screen_draw::scrolling_text ports the helper and scroll_offset its formula
(checked against a Java run of the same formula); button takes the clock as
now_ms, PackScreen::draw passes it on (OptionsUi uses hud_overlays::now_ms,
the chat's Leave Bed button too), so tests and the parity fixture stay
deterministic. The footer is drawn by draw_footer, which takes its labels, so
a test can use a language's strings without changing the global language.
ui::centered_text (HudBatch buttons and sliders) still clips by characters; it
cannot panic, and is left as a gap in tools/port_map.
Fixes#6129
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
ZombieNautilus.applyImplicitComponents applies the zombie_nautilus/variant component,
so a spawn egg or a bucket can pick the coral variant. Horizon applied the chicken, cow
and pig variants from the same components but had no arm for the nautilus, so the egg
always left the biome's temperate variant in place.
- textureGatherOffsets (GLSL 4.60, 8.9.4), which naga lacks: a prelude helper gathers each
footprint at P moved by its offset over the texture size, for float, int and uint samplers,
a component and the depth compare form (Mellow's deferred1).
- dot(float, float) and normalize(float), GLSL genType overloads naga's vector-only built-ins
lack: scalar overloads in the prelude unless the pack defines its own.
- discard and return guarded in braces, `{ if (true) discard; }`: the old `if (true) discard`
took the pack's `else` (`if (c) discard; else x = 1.0;` lost its else). A return with a value
followed by code in its block is guarded too (Miniature's hand program failed validation).
- A returned value converts to the function's type (`return 1;` in a float function), as GLSL
does and naga does not: 73 more composite programs translate over 40 packs.
- A parameter or local named like a bool uniform hides it in its function instead of becoming
`(hz_bool_name != 0)` in a declaration (Pixel Perfect's fogify).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RangedAttackMob.rangedAttackUncertainty is 14 - difficulty.getId() * 4, so a drowned
throws straighter the harder the world is. Horizon hard-coded the normal-difficulty 6,
which made every drowned as accurate as a hard-mode one on easy and peaceful.
Creeper.getMaxFallDistance is
getTarget() == null ? getComfortableFallDistance(0) : getComfortableFallDistance(getHealth() - 1),
so a creeper that has a target accepts a drop as deep as its health allows. Horizon only
had Mob's shared health-minus-a-third formula, so a creeper would not path down a drop
that vanilla walks off.
The same helper is now used for the base case too: getComfortableFallDistance is
Mth.floor(f + 3.0F), which is what the hard-coded "+ 3" was.
An arm64 kernel prints no model name in /proc/cpuinfo, only CPU implementer and CPU part, so
telemetry sent an empty cpu, the benchmark report had no hardware.cpu (and the server then
refused it), and the crash report said aarch64. linux_cpu_name reads model name, else maps the
implementer and part as lscpu does (ARM Neoverse-N1, Apple, Ampere...), else the board's Model.
The three readers share it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A host sends a beacon's update tag when a block there changes or its
powers are set, and the client replaced the whole block entity with each
one: its beam sections were empty until the next tick walked them again,
so the beam blinked out on every update. BeaconBlockEntity.loadAdditional
reads only the powers; the beam the client walked now stays, as a
vault's spin does.
The client's beacon tick also follows LevelChunk.isTicking: only inside
the world border.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Players behind a shared address spend GitHub's 60 anonymous API calls an
hour together and then can't install or update ("GitHub's rate limit was
reached"). The launcher's API reads (release list, launcher tags, commits)
now go through launcher/src/github.rs:
- each answer is saved in <data>/cache/github/ and used again for 5 minutes
without asking (three starts in a row spend one call, measured), then
revalidated with its ETag (an anonymous 304 still counts, measured; it
saves the download);
- a 429, or a 403 with GitHub's rate-limit headers, or no connection: Horizon's
own repository is read through Kira's read-only mirror
(mcp.playhorizon.dev/github/public, kira#109);
- both failing: the last saved answer, else an error naming both.
A plain 403 is no longer called a rate limit. Release files still come from
their browser_download_url (github.com -> release-assets.githubusercontent.com),
which the API limit doesn't cover.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content block's states are its template's states times the declared
combinations, the template's properties varying slowest. StateTable::new
reads a state table as vanilla's StateDefinition does, sorted by property
name with the first name slowest, so a declared name that sorts before one of
the template's (`bites` before `facing` on carved_pumpkin) stopped the game at
load with "registry state order differs from vanilla's state definition".
Such a block is now refused when the plan is built, with its id and the
property to rename, so the mod's row says why and the game starts.
Fixes#6139
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two hooks on an authoritative server's level, for #5499:
- native_fire_odds answers FireBlock's flammability table
(FireBlock.setFlammable's igniteOdds and burnOdds) before vanilla's
table, so fire spreads to, burns and stands beside a mod block.
- native_smoke_source answers CampfireBlock.isSmokeSource for the block
under a campfire, when the campfire is placed and when that block
changes, so a mod block makes a signal fire as a hay bale does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractHorse keeps a pending jump until the mount is on the ground (and not still jumping); only
Camel.onPlayerJump refuses it at the start (cooling down or off the ground). Horizon cleared the
rider's pending jump on every tick, dash or not, so a release on a tick the camel was not on
the ground (a bounce after landing, a slope) dashed sometimes and not other times.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A mod block only got random ticks when its template ticks, so a crop,
sapling or soil on a template that never ticks (dirt, stone) never grew.
`properties.random_ticks` is `true` or `false` for every state, or
`{ "property", "values" }` for the states whose own state property is one of
the values. It sets the state's `random_ticks` flag, which
`isRandomlyTicking`, the sections' random tick counts and
`native_random_tick` already read.
Fixes#6138
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A 4-thread machine had one far decoration thread, which left idle time
while the far trees and structures came in. The threads run at idle
priority, behind the near chunks, the ticks and the frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`lod.detail_target_columns`: the drawn far columns that take their chunk's
generated detail (trees, structures), and `lod.detailed_target_columns`:
how many have it, as `target_tiles` and `sampled_target_tiles` do for
sampling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A trait mod can declare HEAD injections that the launcher turns into a
generated trait method plus an ordinary horizon-mixins helper, so the
injected code calls the mod's own Hooks object. Listen and cancellable
unit HEAD injections are supported. Other kinds, and targets outside
horizon-client, are rejected with a clear error.
Patched game sources are cached by Horizon and mixins revisions, Rust
version, profile, features, bundle order and mixin inputs, so editing
a trait mod's own crate rebuilds without another mixins pass. Only
bundles with mixin content are staged for horizon-mixins: before, a
trait-only bundle installed beside a mixin bundle failed with E_MANIFEST.
Trait mods had no way to call the game back. Host is generated from an
explicit `helpers` allowlist in native-hooks.json: each listed public
native_* helper becomes an object-safe method with the helper's
receiver and docs, implemented by forwarding to Game. Helpers whose
signatures use gameplay-private types or generics stay out, and the
docs list them with the reason.
Native mods could only reach the game through mixins, so every build
patched the game's source. A mod can now implement
horizon_client::mod_api::Hooks instead.
The trait is generated from native-hooks.json. Each catalog hook gets a
mod_api contract (listen, cancel, replace or collect), and
`cargo run -p xtask -- mod-api` writes the trait methods and their
dispatch from the hook functions' own signatures and docs. Hooks keep
their exact receivers. Level hooks get read-only or mutable level hosts,
and library hooks (loot tables, default components) dispatch through a
set-once table that entry::main_with installs. Types that mods need move
out of the game into mod_api, which re-exports library types rather
than copying them.
The launcher links a bundle's [api] crate into a generated executable
that calls entry::main_with with every trait mod, in bundle order. A
build with only trait mods skips horizon-mixins. pr_ready runs
`xtask mod-api --check` when the catalog, the hooks or xtask change.
native_level_load when a level opens, before its chunks; native_level_unload
once it has closed, after its last save: a level idle for 300 ticks, and every
level still open when the server stops or fails. Fixes#6154.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
0.2.33 handed MinGW-w64 the data folder's short (8.3) name when the user
name is Cyrillic, and linking still failed: "No such file or directory" on
C:/Users/\xca\xe8... A short name is only another spelling of the same
folder, and rustc canonicalizes its own path to find its sysroot, which
spells the long name again for the linker.
build_dir now uses a real folder of ProgramData's
(ProgramData\horizon-launcher-<id>, one per data folder) whenever the data
folder's path isn't ASCII; the short name is kept only as the fallback when
that folder can't be created. A unit test covers a Cyrillic data folder.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These screens asked self.keys for Ctrl and Shift, which only gameplay and the chat fill, so
Ctrl+arrows (word by word), Ctrl+Backspace and Shift+arrows (selection) did nothing in them.
They read InputWithModifiers.hasControlDown / hasShiftDown now, as dialogs and chat do (Cmd
instead of Ctrl on macOS, hasControlDownWithQuirk).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings mod-api-rework up to the main this API was built and tested on. The only conflict is crates/horizon-client/src/lib.rs: it keeps the split's layout, and main's new library modules move into client_lib.rs.
LivingEntity.hurtServer runs applyItemBlocking (blockedByItem's push) before
dealDefaultKnockback; hurt_player_from ran them the other way round, so a
partly blocking shield pushed its holder the wrong way. Adds two partial-shield
cases to the server knockback harness. Fixes#6109.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The "experience orb stuck" case (#5646) assumed the push out of a block only
changed how many ticks the escape takes. It does not: once the orb's middle
rises past y 216 with its box still in the 3x3 block, Entity.moveTowardsClosestSpace
finds north, south, west and east free with the same 0.5 distance, picks north
(the first), and depending on the draws the orb slides off the block (z 23.25,
22.x, or down to y 215 on the floor). Vanilla's orb random is unseeded
(RandomSource.create()), so both sides land on different outcomes from run to
run: the harness failed on main with an orb at y 215 against vanilla's 216.
A ring of stone one block higher keeps the top the only free side all the way
out; the case still pins the push (without it the orb stays at y 215).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Gliding asks canGlideUsing for every slot: a stack's own glider and equippable components (or a server's patch) count; main's wear kept; no allocation for stacks without components.
command_export kept the parsed command-registries.json (4.2 MB of
serde_json Values) for the whole run, though the registries take only
part of it. Cache Registries::from_export, the fields fill_from_export
fills (2.2 MB), and fill from it with fill_from_exported. A dedicated
server opens its world once: it keeps nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
has_mod_block_entity asks BlockEntities::has_mods first; the fixture's stub
lacked it, so the Python test (and verify on every PR) failed on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dedicated server's ItemRenderer kept what only drawing reads: the
~1,500 client item definitions (items/**/*.json), the special
renderers' entity models and the grass colormap, 1.4 MB of the 7.3 MB.
ItemRenderer::load_for_server keeps the prototypes (the stacks' default
components, which gameplay reads) and the item ids.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Block model baking and connected textures added their textures one at a time,
reading and decoding each PNG in turn. They now hand the whole list to
Sprites::prefetch first, which decodes it with par_map, then insert in their
own order as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientPacketListener.handleTakeItemEntity plays entity.experience_orb.pickup
at the orb for every orb taken; the server sends no sound (ExperienceOrb
.playerTouch plays none). On a server Horizon played nothing, expecting the
server's sound, so collecting a furnace's experience was silent. In a local
world it played once at the eyes, and not at all when mending took the
points. Each orb taken now sounds at the orb in both.
Fixes#6122
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerAdvancements kept all ~2,000 recipe files parsed (4.6 MB of
serde_json Values) for the server's whole run, only to build recipe book
entries when a player joins or unlocks a recipe. Keep each file as its
compact JSON text (0.57 MB) and parse the one an entry needs: parsing
every recipe takes 1.2 ms, the worst case of a full book on join.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SulfurCube.hurtServer answers true for the blows it only knocks about, so Player.attack goes on
with causeExtraKnockback (sprint, the Knockback enchantment), which reaches the cube's own
knockback with comesFromEffect: the launch scaled by power * 0.25 on top of sqrt(damage).
Horizon answered false, so attack_mob skipped the whole hit's aftermath, and the extra push
went through the generic Mob::knockback. The answer is now true for the tag, and the player's
extra push goes through Mob::knockback_effect.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The overlay walk looked at every overridden file once per folder it visited, and
inflated and wrote a zip pack's files one at a time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: MOTD also stored in server.properties and server.toml as vanilla's DedicatedServer.setMotd; main merged (difficulty kept); fmt, clippy, horizon-server tests, management_protocol + dedicated_server harnesses pass at 87e8e3122.
SpawnEggItem.useOn spawns in the clicked block itself when its collision
shape is empty (vines, grass, flowers) and only otherwise in the block on the
clicked face. Horizon always took the face's block, so an egg used on vines
put the mob a block out in the air, where it fell instead of climbing.
Fixes#6125
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Commands the player didn't type go through ClientPacketListener.sendUnattendedCommand's checks: confirm on parse errors or elevated permissions, never send a message argument unseen.
🤖 Agent: Fire and lava hit mobs after baseTick's timers (applyEffectsFromBlocks in aiStep), effects tick after them, and wither roses use the world difficulty.
scan_for_hearts read every resident chunk each tick (is it lit, was it
scanned) and then checked every scanned key against the chunk map: with
the 17,000 chunks of a 54-player server that is two hash lookups and a
cache miss per chunk, every tick. The scan now follows the chunk map's
membership changes: a chunk that arrives waits in a small set until it
is lit and scanned, one that leaves is forgotten. A chunk gone and back
between two ticks stays scanned, as before; a map that can't replay its
changes (cleared, or too many of them) is rebuilt from its keys. Ready
chunks are scanned in key order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The supervisor serves vanilla's GS4 query as QueryThreadGs4 does: the handshake (type 9, a token
below 2^24 as text), then the basic and the full stat to a request that carries the token, each
byte for byte what a vanilla 26.3 server sends for the same settings (checked on the wire). The
players come from the game's server-status, read at most once a second; the full stat is kept
5 seconds as in vanilla. Off by default; an invalid port leaves it off with vanilla's warning.
Refs #6108
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Mob.doHurtTarget calls causeExtraKnockback(target, getKnockback): (attack_knockback attribute plus
the Knockback enchantment) / 2, pushed along the attacker's yaw through LivingEntity.knockback,
which scales it by 1 - the target's knockback resistance, and slows the attacker by 0.6. Horizon
added a fixed velocity along the line to the target, with no resistance, no slowdown and no
attack_knockback. The attribute is now read (type default, or the mob's own from NBT).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A stack's minecraft:lore (as loaded, or as a server mod wrote it into
StackData.other) is now sent in its component patch, so a native mod's
menu items show lines under their name (prices, changes) in a stock
client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BlockEntities keeps the positions of its Mod block entities by chunk, so the
renderer reads only those within view distance instead of scanning every
loaded block entity each frame.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
wgpu clears what a vertex or index binding covers that nothing has written
yet, on the GPU, before the pass runs. Draws bound Translucent Batching's
large pages whole, so a page that had just grown had its new tail, up to
256 MiB, cleared in the next frame. Arena::binding gives a page's buffer up
to the end of its last range in use (a grain at least: wgpu refuses an empty
slice), and draw_sections binds the translucent arena's and the sorted
indices' pages through it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A container screen still checks its hotbar keys after Tab or an arrow moves the focus (AbstractContainerScreen.keyPressed), so Tab bound to the off hand swaps.
The splitscreen, friends, extras, HDR calibration, on-screen keyboard and
splitscreen-profile keys landed in en_us after zh_cn's last pass; a
Chinese player saw English on those pages. The bundle is complete again
(308 keys of 308), with the terms the earlier translations set: 分屏,
档案 for a profile, 与好友一起玩 for Play with Friends.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
winit's Locked on Windows is ClipCursor to a 1x1 rectangle at the pointer's current spot, and
SetCursorPos stays inside the clip, so the warp that followed the grab could not move it: the
pointer froze wherever it was (over another window or monitor). InputConstants.grabMouse warps to
the centre first; macOS already did, and now Windows does too.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: main merged again (submit thread, leaf and entity culling). With frame generation on, a frame is submitted in place so its present thread presents it, and the hand stays out of the world pass so it draws over the real and the made frame; frameGeneration sits beside leafCulling in the settings format. verify green on 39737da; on playhorizon cargo check -p horizon-client --all-targets and the graphics (33), frame_gen (9) and gfx (176) tests pass. Thanks @spidercraft.
🤖 Agent: Command block and command minecart admin lines go to operators through broadcastToAdmins (host and console included); Done's confirmation reaches only the editing player.
🤖 Agent: A world without singleplayer_uuid gives its owner their own (or a new) player file, never the newest other player's (PlayerList.loadPlayerData).
🤖 Agent: Native hooks experience_orb_can_merge / experience_orb_merge around ExperienceOrb.scanForMerges and the spawn merge; no-op stubs without native mods.
🤖 Agent: Review by Ludovic's AI agent (Claude). Extra atlas folders from blocks.json are packed (one walked list, 42e6769). Verify green. The parity failure was the runner's dead .horizon-prepare.lock link (2 s, before any harness), not this change; parity rerun.
🤖 Agent: Review by Ludovic's AI agent (Claude). Native column and surface-height reads on #5735, bounded by MAX_READ. Verify green on the whole stack. The parity failure was the runner's dead .horizon-prepare.lock link (2 s, before any harness), not this change; parity rerun.
🤖 Agent: Review by Ludovic's AI agent (Claude). Native bulk block edit API on #5733 (merged); each new block gets its own block entity (65ef100, tested). Verify green. The parity failure was the runner's dead .horizon-prepare.lock link (2 s, before any harness), not this change; parity rerun.
AbstractArrow.onHitEntity: the charge cannot be hurt, so the arrow takes
deflect(REVERSE, 0.2) and falls. The harness now also checks the arrow is
still in the world a second after the shot.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
new_block_entity no longer needs the block's template to have a block entity.
The level asks the game whether a mod block entity lives at a position
(LevelAccess::has_mod_block_entity) to remove it with its block and to keep
pistons from moving it, and the renderer lists mod block entities the
template's flag leaves out.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Results are taken and jobs started once a frame. With four jobs in flight
and tiles taking milliseconds, the sampling threads sat idle for most of a
slow frame. Queued tiles the view leaves are dropped at the replan.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On machines with four threads or fewer, far terrain samples one tile per
500 ms and meshes nothing while the near terrain loads. It asked
`Streaming::busy`, which counts every section being remeshed: block,
light and fluid updates near the player kept it held back long after the
near chunks were in. `Streaming::loading` counts chunks being generated,
decorated, finished or lit and sections meshing for the first time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A cart with any passenger (mob, guest or the player) uses the occupied slowdown and speed scale (AbstractMinecart.getSlowdownFactor keys on isVehicle).
🤖 Agent: Review by Ludovic's AI agent (Claude). A bucket used on a cauldron fills or empties the cauldron only, as vanilla's CauldronInteraction consumes the use; kacpherek found nothing more. Main merged in (lan_e2e check list: both sides kept, cauldron included). Verify green.
🤖 Agent: #5644 merged in. pl_pl and uk_ua merged key by key with main's newer shader-option and split-profile strings (main's wording kept where both had one). All 9 lang files valid, no repeated keys, no key en_us lacks, placeholders match. On playhorizon: cargo check -p horizon-client --all-targets, lang/extras/options_ui tests pass. verify green on 8726619. Thanks @mariuslozo-boop.
🤖 Agent: main merged again: its new Chat rows in Extras (Smooth chat, Message indicator and their header) read the language table too, en_us keys added. verify green on 79b92a3.
🤖 Agent: Review by Ludovic's AI agent (Claude). Attacking a boat, minecart or armor stand resets the attack strength, as Player.attack does for every entity. Main merged in; verify green.
native_inventory(uuid, kind) reads a player's inventory (Player: 41 slots,
main + armour + off hand; Ender: 27), live when they are on the server,
else from players/data/<uuid>.dat; never while they log in, are dead or
swapped in. native_move_stack moves one stack, checked against what the
mod showed, where it fits whole, or nothing moves; at most one side is a
file, written before the live side changes. native_send_map sends a
128x128 map picture to a player. For the official server's /invsee,
/endersee and the market's price charts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`key esc` went past it and closed the chat behind, unlike the game's own
key handling, where Escape answers no.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Level::read took the newest file of players/data when level.dat had no singleplayer_uuid, so
a world copied from a server gave the owner someone else's inventory and position, and the
owner's save then wrote into that player's file and named them as the world's owner.
PlayerList.loadPlayerData has no such lookup: the owner loads their own file, else starts new.
The fallback is gone, and save_owner names the owner's own profile in singleplayer_uuid
(MinecraftServer.saveAllChunks writes the integrated server's profile).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: Mod container viewers count the host's own player and copper golems too (ContainerOpenersCounter counts any ContainerUser), every tick from tick_world; spectators excluded.
🤖 Agent: Enchantment levels outside 1..=255 drop that entry and keep the rest (ItemEnchantments codec); levels past X keep their untranslated key unless a language names them.
A command block's or minecart's `chat.type.admin` line went to the server game's own chat, so no
operator saw it; it now joins the broadcastToAdmins queue (to every operator when
send_command_feedback, the world's host too, the console when log_admin_commands). A command that
sends its success unbroadcast (`sendSuccess(.., false)`) no longer reaches the operators either.
The confirmation of Done on a command block or a command minecart (advMode.setCommand.*) is sent
to the editing player only, through as_guest_messaged.
Fixes#6066Fixes#6067
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: IME: the options searches, game rule boxes and block editor fields allow the input method, and the chat TextField reports its caret so the composition and candidate window show (TextInputManager.setTextInputArea).
🤖 Agent: Review by Ludovic's AI agent (Claude). Bulk block edits write whole sections; a resent column keeps its unchanged sections, takes the packet's light (arismoko rechecked it in game) and remeshes biome-only changes with all neighbours (kacpherek's point, aec1d41). Main merged in. Verify green.
A command the player didn't type went straight to the server. Now
verifyCommand decides: a parse error or a restricted command asks before
it goes, and one with a message argument only reaches the chat box, or
the clipboard over a screen, for the player to send. Refs #6080.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_experience_orb_can_merge and native_experience_orb_merge (cancellable)
at spawn and on the tick scan, plus native_set_experience_orb for value,
count and age.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A loot table's function or condition that names a type in a namespace
outside minecraft: used to be silently skipped (functions) or failed
(conditions) at the roll. It now reaches free functions in loot.rs —
native_loot_function changes the stack, native_loot_condition says
whether it holds — which mixin bundles hook, as they do the game's own
hook points. The context brings the entity, the damage, the player who
killed it, the tool, the block, the luck, the biome and a seed from the
roll's random, drawn only when a mod's kind is in the table.
The tables the items engine rolls (block drops, entity drops,
loot_table, interact_loot) answer the condition hook with the tool and
block in the context; the function hook there follows when the stack
types converge. A bare type name is minecraft's own; an unclaimed kind
leaves the stack or fails the condition, as unknown kinds do.
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: Review by Ludovic's AI agent (Claude). Fox and dolphin item pickup follows vanilla (canTakeItem/pickUpItem rules); server-side only, nothing on the frame path. Main merged in; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). Arrows travel through nether and End portals as the vanilla Entity.handlePortal path does. Main merged in; verify green.
🤖 Agent: /tick step broadcasts the command's count before the tick's block events, joins still get the remaining ticks (ServerTickRateManager.updateStepTicks / updateJoiningPlayer).
🤖 Agent: Device loss recovery; main merged in and the rebuilt renderer now gets the Translucent Batching arguments build_renderer takes since #5984 (the LAND-STOP).
🤖 Agent: tools/build_times.py run: cold, no-op and incremental build times per crate in its own target folder, leaving the tree as it found it (tested); tooling only, skip-release.
🤖 Agent: A frozen level ticks no client block entities (Level.tickBlockEntities behind runsNormally): a /tick step advances a moving piston once; e2e against a vanilla server.
🤖 Agent: Windows CPU brand from the registry (ProcessorNameString) for bench reports and telemetry, cached once, reg run with CREATE_NO_WINDOW; one reader shared with crash reports.
🤖 Agent: Primed TNT runs applyEffectsFromBlocks' bubble column push after its move and before drag, as items and mobs already do; test with a soul sand column.
🤖 Agent: Powder snow snowflake now emitted on the server copy and sent to nearby players (PowderSnowBlock.entityInside, count 1, ALTERNATIVE randomization), the client copy no longer double-spawns; I merged main and dropped this branch's handleParticleEvent copy in favour of main's spread(); verify green.
🤖 Agent: Shulker bullet END_ROD trail on the client (ShulkerBullet.tick: two in the 0.3125 box), client visual only; I merged main (kept main's remote_projectile_power beside it), verify green on the merge.
🤖 Agent: verify and parity are green on levzzz5154's main merge, levzzz5154 approved it, and arismoko's in-game sample showed the hook changing and canceling the push. Queued for merge, thanks @mariuslozo-boop.
The constructor ran once per block type and every position took a clone, so a
mod's per-instance state (a serial, a seed) was shared. Vanilla calls
EntityBlock.newBlockEntity for each position; a block that makes none is still
asked once (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block's tint can sample the biome quart of the section next to it, so a
biome-only resend of one section must remesh its vertical neighbours, as a
light update does (kacpherek's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ModBlockEntity` keeps its fields (`ModBlockEntityFields`) behind
`Deref`/`DerefMut`, and each mutable access counts its revision on.
A new or cloned entity takes a fresh one, so one replaced in place never
matches. `KeptGeometry` keeps the revision it drew instead of a clone of
the entity and compares that each frame, O(1) whatever the entity holds.
The client merges an update tag field by field, leaving the revision
alone when the server sends the same tag again. Equality, `Debug` and
the save format are the fields' alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Review by Ludovic's AI agent (Claude). Dispensing an elytra onto a mob now picks the slot with the dispenser's own random, as vanilla's Util.getRandom does; the level random was used before (fixed on the branch). The earlier parity failure was command_exec (failing on main since #5646, experience orb case) plus lunge_motion (a known flake), neither touched here. Verify green on this head.
🤖 Agent: Explosions break and push boats and push falling blocks (VehicleEntity.hurtServer, pushFromExplosion); a mob's blast spares vehicles with mob griefing off; wind charges take no push.
TurtleEggBlock.decreaseEggs plays block.turtle_egg.break at the block and
sends level event 2001 for the eggs left; the last egg goes through
Level.destroyBlock, which sends 2001 too. The player's landing events reached
the game, but its block sounds were dropped (only the two fall sounds were
played) and its level events ignored, so a trampled egg vanished silently.
Block sounds (the egg, a shelf mushroom's bounce) now play at the block for
the local player and LAN guests, and the level events go out.
Part of #5858 (the player trampling eggs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Review by Ludovic's AI agent (Claude). Opt-in RAM limit; off, it costs one atomic store per frame. kacpherek's grace-period point is fixed and tested. I merged main again (ram_limit next to random_textures, render_distance_in_use next to the leaf-culling remesh). Verify green on this head.
Entity.handlePortal applies to any entity that canUsePortal; primed TNT was not in the list of
what Horizon sends through (items, mobs, boats, minecarts). It now is: swept through the
portal box (0.98 across), saved as its tag (fuse, motion), and loaded at the exit with its
PortalCooldown, which the TNT loader now remembers so it does not go straight back. A TNT that
carries a mob stays.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: A minecart's move collides with the entities AbstractMinecart.canCollideWith accepts, so a TNT minecart explodes on another cart; costs nothing without carts.
Frames with frame generation are submitted in place (its present thread
presents them), and the hand stays out of the world pass so it draws over
the real and the made frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: shader API stack after main's #6005 and #5926: fmt, clippy -D warnings, client-render + shader-api tests green on the stack's last head 31d53c8b4; offscreen pack proof earlier on the same features.
Remove the stale boolean assignment from the graphics round-trip fixture and close the Entity Culling UI match arm.
Co-Authored-By: OpenAI Codex <[email protected]>
🤖 Agent: Review by Ludovic's AI agent (Claude). Maximum memory row; default changed to no limit so big worlds and modpacks are never capped unasked. Launcher verify green on this head.
🤖 Agent: Review by Ludovic's AI agent (Claude). Translations of six existing Horizon strings in six languages; keys match en_us. I merged main; verify green.
FeatureBlockBindings::bind_tags built every block tag's state set up
front: vanilla's 300 block tags hold 406,779 states (blocks_motion and
mineable/pickaxe ~30,000 each), and world generation reads a few dozen of
them. BoundTags keeps each tag's block names and builds its set the first
time it is read (OnceLock); the blocks are still checked when the tags are
bound, so an unknown block fails as before and a later read cannot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Older chunk, entity and POI records of a current world upgrade as they are read (SimpleRegionStorage.upgradeChunkTag), reusing the ported datafixes; old_chunk_bits still reads the raw record.
🤖 Agent: reviewed: the record takes the client's own START/STOP (vanilla: causeExtraKnockback stops the server's sprint, LocalPlayer.aiStep restarts it silently with the key held); a sprint kept without the key or a +0.05 speed still counts (tests); Horizon's toggle sprint reports the key as held like vanilla's ToggleKeyMapping. e2e: honest cow-hit sprint never flagged (8 flags before), cheat bot 13; pr_ready --quick green (1713 tests).
🤖 Agent: A panic in the dedicated server's tick (console, control socket and players' turns included) still saves the world; crash fixture gated so the tick pays one OnceLock load (fix pushed).
🤖 Agent: Submerged boats eject their player after 60 underwater ticks even when the client drives the boat (AbstractBoat.tick on the server), the request kept until every rider left; harness against a vanilla server; main merged in (lan_e2e).
🤖 Agent: Social Interactions screen (P, pause icon): hide a player's chat locally as Minecraft.isBlocked in ChatListener.showMessageToPlayer, vanilla layout and sorting; no Mojang services touched.
🤖 Agent: Review by Ludovic's AI agent (Claude). Measured on macOS (M4 Pro, Metal, same binary A/B): with the thread on, 44–293 FPS with 20–180 ms p99 frames, against 296–549 FPS off. So I made it the default on Linux only, where it measured +32%, and opt-in elsewhere (HORIZON_SUBMIT_THREAD=1). Merged main (OIT conflict; the hand still draws after the composite). Verify green.
🤖 Agent: Creative grid and hotbar hover use the padded slot target (AbstractContainerScreen.getHoveredSlot) for highlight and tooltip, as clicks already did; oracle-backed e2e.
Entity.collide takes the entities AbstractMinecart.canCollideWith (AbstractBoat.canVehicleCollide:
canBeCollidedWith or isPushable, but not a passenger of the same vehicle). Horizon's cart move
only saw blocks, so two carts never had a horizontal collision and a TNT minecart never exploded
on another one.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: Mobs run each block's fallOn through the landing registry (hay, honey, slime, powder snow, beds, dripstone...), the farmland roll on the mob's random as before; powder snow's entityInside forgets the fall.
🤖 Agent: measured on playhorizon (alternated, THP off): idle anonymous −4.1 MB against #6041 on every start; Done and 20-bot MSPT unchanged. pr_ready ✅ (2099 tests + 21 content root); clippy -D warnings and fmt clean on the Mac.
LivingEntity.canGlide checks every equipment slot with canGlideUsing: the
stack has glider, its equippable slot is the one it is in, and its next damage
won't break it. Horizon only looked at the chest slot and the item's own
components, so a chestplate a server (or /give) gave glider and an equippable
couldn't glide. The client and the LAN host's replay now share one check that
reads the saved components and the server's patch, which can add or remove
either one; the patch's equippable isn't kept otherwise, so its slot is read
from the patch.
Fixes#5773
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: A CONSUME use (empty flower pot with nothing to plant) no longer swings the arm: InteractionResult.shouldSwing is SUCCESS only, in use_hand and the remote prediction; level test covers it.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on 212746390). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on e3ab4bd42). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on 936795944). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on 43b76c4a7). Thanks @some-du6e.
🤖 Agent: Recipe sync hooks: arismoko's three points handled by the author (empty load resets clients, last part within its cap, per-load docs); main merged in by the author.
🤖 Agent: Frog eating a magma cube: drawn in and removed, and with the fix pushed here (mob_died runs on RemoveKilled) the froglight and XP drop as vanilla.
On NixOS pkg-config only sees packages listed by a shell's setup hooks, so
installing them in environment.systemPackages leaves the check failing, and
the APT advice it fell through to did not apply. Point at the launcher's own
shell.nix, which now carries those packages.
Co-Authored-By: horizon-agent (GLM 5.2) <[email protected]>
The shell provided GPUI's libraries but not the two native mods' build looks up
with pkg-config, and the client it produces needs libudev at runtime.
Fixes#5248
Co-Authored-By: horizon-agent (GLM 5.2) <[email protected]>
Review on #6019: vanilla's MouseHandler.onButton clears activeButton on any
release, so left down, right down, left up sends no drag while right is still
held. track_drag_button now does the same and the test pins vanilla's
behaviour.
The merge keeps both blocks in game.rs: the drag button tracking and the
screen key release hook (#6014).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
as_guest runs for every guest action; the fixture's guest scan now waits
behind the OnceLock check, so a normal tick pays one load.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on 8881fc76f). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on a4bda5e9e). Thanks @some-du6e.
🤖 Agent: Leaf culling: the default stays vanilla's, server packs now turn it off like local packs (author's cf2c59e1b); main merged in (leafCulling beside translucentBatching).
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on 19996b09c). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on f2b47b87b). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gated to packs shipping the files, per-mob cost measured, verify green on 84bf62028). Thanks @some-du6e.
🤖 Agent: native_render_fog hook: main merged by the author, frame_fog runs native_compute_fog_color then native_render_fog (NeoForge's ComputeFogColor then RenderFog); no-op without a mod.
🤖 Agent: Review by Ludovic's AI agent (Claude). kacpherek's review found nothing to change. The sibling-path check accepts only `../<id>` manifests of the same staged mod, and I added the underscore dependency tables, with a test. Launcher verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). levzzz5154's review and his off-hand fix are in; uses an API fragment as docs/native-mods.md asks. I merged main (clean); verify green on this head.
Upstream added --player-dir handling to the startup code in main.rs. With the
library split that code lives in entry.rs, so the change is applied there and
main.rs stays the thin launcher.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HORIZON_CRASH_FIXTURE=guest|hub panics the tick during a placed guest's turn or in a console
line while a player is in. tools/test_dedicated_crash_save.py joins a real client, crashes the
server, and checks the player's files are byte for byte what the last clean stop wrote and a
block placed before the panic is there on the next start. The console lines and the control
socket now run under the same catch_unwind as the tick: a panic there ended the process with
no save.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Screen.keyPressed moves the focus on Tab or an arrow and returns false, so
AbstractContainerScreen.keyPressed goes on to checkHotbarKeyPressed: with
key.swapOffhand bound to Tab, vanilla both focuses the recipe book button and
swaps the hovered slot with the off hand. Horizon's key focus kept the key,
so the inventory never saw it. In container screens, Tab and the arrows now
move the focus and pass the key on.
Fixes#6028
Co-Authored-By: Claude Opus 5.5 <[email protected]>
build_renderer takes the Translucent Batching setting and the picked pack's
flat drawing since #5984.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#4347 let the input method in, but Game::text_field_focused left four fields
out of the window's gate, so the OS was never asked for an input method while
they had the focus: the language list's search, the settings search, the
resource pack screen's search and a game master block's boxes. The title
screens' game-rule boxes were out too: focused_field is None while the rules
screen is open, and has_caret_field asks it.
The chat box — where the report typed — is a screen_draw::TextField, and that
box never reported its caret, so flush_ime had no area: the composition was
not drawn and set_ime_cursor_area was never called, which leaves typing blind
until the commit even with #4347 merged. TextField now reports the caret where
it draws the cursor, as EditBox and MultiLineEditBox already do.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Match AbstractBoat.tick by resetting velocity and angular velocity on non-authoritative client copies. Cover dismount/reboard ownership transitions with a real vanilla tick oracle and the actual headless Game ticker, plus ice reboarding for host and guest.
Co-Authored-By: Codex GPT-6 <[email protected]>
PostChain's textures/effect inputs had their own png::Decoder with CRC
checks on (kacpherek's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolves far_terrain_choice with main's server far terrain (#lod_net): the RAM
limit's far_off still wins. kacpherek's review: the watcher kept the Over timer
while the cap was off, so turning it back on while over could end the game at
once. Over now follows the cap and restarts its grace when it changes; tested.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player-thrown tridents live in Game::tridents, apart from
creatures.projectiles, so the blast skipped them (kacpherek's review).
Same push as the other arrows: from the eyes (0.13), not in the ground.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Include server resource-pack overlays in leaf and entity culling safety checks, and restore the selected leaf mode when a server pack is removed.
Co-Authored-By: Codex <[email protected]>
main.rs moved to entry.rs on this branch; carry main's --player-dir
log/crash folder handling over to entry.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The obstacle list was scanned in full for every moving item. A grid of 8-block cells hands each
item the boxes around where it can reach this tick. 5000 drops against 1000 spread-out vehicles:
12.6 ms of box checks become 0.45 ms.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Combined staging branch: the game library split (#5446) and the single mod
file format (#5909) together, as agreed to land in one release. No conflicts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
windows_cpu ran `reg` on every call: twice per Hardware::detect (its own
cpu() and System::detect's), and again for every telemetry item. The name
does not change while the game runs, so it is read once, as the module
says of its other command probes.
System's cpu() no longer runs `reg` on systems that are neither Linux,
macOS nor Windows.
The Windows test compared the name only against the family/model format,
so a parser that kept only part of the name would have passed. It now
checks the exact value against the registry as PowerShell reads it, and
expects PROCESSOR_IDENTIFIER where the registry has no name (some VMs),
instead of failing there. Its comment no longer says a CI runner runs it:
no Windows job tests this crate. The parser test gains tab columns and a
name with runs of inner spaces; a comment that said padding is kept now
says it is trimmed.
docs: benchmark-report.md and telemetry.md say when cpu is
PROCESSOR_IDENTIFIER.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Vanilla upgrades every record it reads from a region file
(SimpleRegionStorage.upgradeChunkTag, from ChunkMap.upgradeChunkTag,
EntityStorage and SectionStorage.PackedChunk.parse), so a world 26.3 has
opened keeps the chunks nobody loaded since an older version in that
version's format. Horizon upgraded region files only when level.dat itself
was older and decoded those records raw: a 1.18 to 26.2 chunk, whose palette
names its states with `Name` (26.x reads `id`), loaded as an all-air column.
SaveDir's region reads (read_record, load_far, lod_surfaces, the End gateway
migration and the POI claims merge) now run the chunk, entity-chunk and POI
fixes on older records, from vanilla's defaults (-1, POI 1945) when a record
has no DataVersion, with the dimension from its folder and the generator type
from world_gen_settings.dat, found once per SaveDir. old_chunk_bits keeps
reading the record as saved, as IOWorker.isOldChunk does.
Chunks of before 1.18 come out of the fixes with `below_zero_retrogen`, which
the client's chunk loading does not handle yet: they are still generated
again, as before.
PrimedTnt.tick updated its fluid immersion but never ran
Entity.applyEffectsFromBlocks, so BubbleColumnBlock.entityInside never pushed
it (handleOnInsideBubbleColumn) and a primed TNT dropped into a soul sand
column sank.
The tick now asks motion::bubble_column_push for the columns its box touches,
after the move and before the drag, as items, mobs and armor stands already do.
Closes#6008.
Co-Authored-By: OhMyAgent (grok-4.6) <[email protected]>
- Rename "FOLLOWS UPDATES" to "auto-update" so it fits the version picker bar
- Make data/resource/shader pack titles and "None yet" lines readable over the background
- Give Add pack, Open Folder, Add server and Show all the same style as Add mods
SocialInteractionsScreen (P, or the pause menu's player reporting icon): the All, Hidden and
Blocked tabs, a search box, a row per player with the hide/show chat button and vanilla's
disabled report button. Hiding a player drops their chat (Minecraft.isBlocked in
ChatListener) and says so in the chat line. Blocking and reporting are Mojang's services, so
Blocked stays empty and reporting stays disabled.
Part of #5954.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
After a load that sent recipes, a later one whose mods ask for none sends
one last part with no types, so a joined client drops the server's old
recipes and hears of it (native_recipes_received with no types). The
last part's type list now counts against the payload cap: when it
doesn't fit, the recipes go in an earlier part. The sync hook's docs say
it is asked per load, the same for every player, where NeoForge asks per
joining player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: main merged after #4945/#4946 landed: main's new wanted_tiles queue now skips tiles waiting out their retry, as this PR's loop did. verify green on a6d37d7; tools/server_lod_e2e.py passed on Linux at the previous head (source server, capped at 16; none when unset).
🤖 Agent: Review by Ludovic's AI agent (Claude). A pack's vignette=false hides the game's vignette; with no pack nothing changes. I merged main and reworded a comment that named another project. Verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). GLSL translation fixes, each with a test (GLSL 4.60 §4.1.9, §4.2.2), packs-only code. I merged main in; verify green on this head.
Gate client block-entity tickers on runsNormally, keeping moving pistons alive until subsequent server steps. Verify18 snapshots against an actual vanilla server, including extension, retraction and unfreeze controls. Part of #5977; server notification flags remain a separate follow-up.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
🤖 Agent: main merged again (the two uniforms go after main's isRightHanded … hasCeiling, looked up by name); verify green on 67ebb02. Thanks @Joansitoh.
🤖 Agent: levzzz5154's five review points fixed on the branch (crash telemetry stays on --home's consent, servers ignore the flag, limits documented, splitscreen guests on the game folder's mods intended, tests for shared_dir_of and servers_file_of); main merged; verify green on 9d56f33. Without --player-dir nothing changes. Thanks @Solenopsisbot.
🤖 Agent: Review by Ludovic's AI agent (Claude). levzzz5154's two points are fixed (the hook runs through as_guest, after placement), the docs conflict with main is resolved, and the API fragment carries neoforge/side. Verify green.
🤖 Agent: Opening shulker lids push the local player on the server-thread client as ShulkerBoxBlockEntity.tick does on both sides; lan_e2e shulker_lift check; verify green.
Upstream added `pub mod lod_net;` to the old lib.rs module list, which this PR
moved to client_lib.rs: add it there. Upstream's new horizon-optifine
dependency is used by mobs/cem.rs, a reusable-client module, so it goes in
horizon-client-lib's manifest instead of horizon-client's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep both: the window key hook and the merged raw mouse button hooks (#6010)
sit side by side in native_hooks.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sprinting hit stops the server player's sprint (Player.causeExtraKnockback); the
client's own attack on a mob doesn't, and once the server's flag reaches it the
client starts the sprint again with the sprint key held, without a START_SPRINTING
(wasSprinting never changed). The record took the server's flag, so the replay
walked while the client sprinted: the live 'ground/air water, keys forward sprint'
flags. The record now takes the client's sprint commands; a sprint the server
stopped is played on only if vanilla's client could start it again (the sprint key,
or forward pressed again).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep both fog hooks: the merged computed-color hook (#6000) runs first,
then the whole-fog hook sees the result and can change distances too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PrimedTnt.tick updated its fluid immersion but never ran
Entity.applyEffectsFromBlocks, so a bubble column's entityInside push never
reached it. A primed TNT dropped into a soul sand column sank.
The tick now asks motion::bubble_column_push for the columns its box touches
after the move and before the drag, as items and mobs already do.
Closes#6008.
Co-Authored-By: OhMyAgent (grok-4.6) <[email protected]>
🤖 Agent: #5601 has merged, so this test-only follow-up leaves draft: main merged (corpus union, every function checked against its source), verify green on 938f032. Thanks @JustKarkat.
Record the empty-handed baby produced by the guest spawn-egg regression, for the before/after comparison in the pull request.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
Match PiglinAi.wantsToPickup and stopHoldingOffHandItem: respect the baby ignored-item tag, admire picked-up gold, and equip it or preserve the previous loved item instead of bartering. Babies have no preferred weapon tag.
Compare 31 observations with vanilla through the integrated-server command harness, including adult bartering, occupied baby hands and ignored leather. Extend the hand-rendering regression with a crossbow and a gold ingot. Refs #5867.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
On the Mac mini (M4 Pro, Metal, offscreen, render distance 12) the same
binary ran 549/537/450 FPS without the thread and 44/51/293 with it, with
p99 frames of 178/160/21 ms against 4 ms. HORIZON_SUBMIT_THREAD=1 still
turns it on anywhere, =0 off; Windows is not measured yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Follow SpawnEggItem.spawnOffspringFromSpawnEgg: construct the piglin and mark it as a baby without running adult spawn finalization. Keep items picked up later intact.
Add a server-thread LAN regression that uses a spawn egg from the guest and checks that both clients see the same empty-handed baby. Refs #5867.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
The ~1,300 recipe advancements' criteria held ~7,000 JSON objects as
serde_json Values (~4 MB of a server's idle heap), most never tested in a
game. Criterion.conditions is now a LazyJson: read into a Value the first
time a criterion is tested, then kept. The recipe and inventory indexes
read each criterion once without keeping it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: shader API series stacked on one union (each head holds the earlier ones); pr_ready --quick green on the last head 47b5a77 (2574 tests), shader-api tests + clippy on each earlier head; offscreen in-game run with a pack using all four features, no validation error.
🤖 Agent: Opaque terrain pass without discard: two solid passes on Metal (Apple's tiled GPUs, measured faster on M1 Pro), one pass elsewhere as before (fix pushed here); the far-tile opaque marking is fixed everywhere.
🤖 Agent: A boat used under water lands at the eye and is refused as in vanilla's BoatItem.use (ClipContext.Fluid.ANY on a full fluid cell); unit test, verify green.
🤖 Agent: far terrain 2/3 (the server samples tiles, off by default). #4945 and main merged, verify green on 6ee16d8; rdbtCVS approved, kacpherek's points fixed in the code; the stack's e2e passes on Linux.
🤖 Agent: far terrain 1/3 (wire format, off by default). Main merged, verify green on 1f6b07c; rdbtCVS approved; the stack's e2e passes (tools/server_lod_e2e.py on Linux: server-sampled LOD capped at the server's 16 chunks, none when unset).
🤖 Agent: requeued: the parity failure was the runners' (a dead .horizon-prepare.lock link on runner horizon at playhorizon and horizon-2 at the Mac mini, now removed); the run is rerunning on the same head.
hardware.cpu in every benchmark report and the cpu of telemetry items read
PROCESSOR_IDENTIFIER on Windows, which always holds family, model and stepping
(AMD64 Family 25 Model 97 Stepping 2, AuthenticAMD), never the name. The
telemetry server groups its benchmark summary by that string, so chips of one
family would have shared a row.
horizon_telemetry::system::windows_cpu reads ProcessorNameString from the
registry (reg query, hidden window, trimmed) and falls back to
PROCESSOR_IDENTIFIER. horizon-bench, horizon-telemetry and the crash report
use it; the crash report's own copy of the parser is gone and its test moved
next to the shared one, with more cases (CRLF, padding, missing, garbage).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
In vanilla Minecraft, right-clicking an empty flower pot with an invalid item or empty hand consumes the action (InteractionResult::Consume) but does not swing the arm. In Horizon, use_hand unconditionally called swing_arm() whenever the interaction consumed the action, which also triggered swing_packet to LAN guests.
Gate swing_arm on InteractionResult::should_swing(), which only swings on Success.
Co-Authored-By: Antigravity <[email protected]>
Count consecutive underwater ticks on the server for client-driven boats and use the existing rider-ejection path. Keep the request visible to all passengers until they have left. Compare real vanilla and Horizon passenger outcomes and cover host and guest immersion on the integrated server.
Co-Authored-By: Codex GPT-6 <[email protected]>
🤖 Agent: security-reviewed by Ludovic's AI agent (Claude): the secrets file is now encrypted at rest (ChaCha20-Poly1305, key from the computer's machine-id), mode 0600, atomic replace, no token in logs or errors, the owner gate unchanged. 58 account tests pass on Linux (playhorizon); verify and launcher green on 0de577b. Thanks @JulesB40.
🤖 Agent: native_camera_distance hook: a no-op fragment hook until a mod fills it, finite 0..=1024 overrides only, collision clipping kept; unit tests cover it.
🤖 Agent: Review by Ludovic's AI agent (Claude). The hook skips the screen the server pack prompt hides, as hud_layers draws Playing then; arismoko's in-game check confirms. I merged main; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). Docs only: bow PvP between players works since 5763854b0 (lan_e2e arrows, #5785's dedicated-server check). I merged main; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). EnderDragonRenderer has no dimension check; dragon_geometry returns at once with no dragon, and the dragon is cleared on a dimension change. I merged main; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). Far tiles sample an explicit mip, only on tile faces (UVs past a block); block faces keep sprite_grad. A few ALU ops per far-tile fragment. I merged main; verify green.
🤖 Agent: Translucent batching 1/3: off by default (only horizon-graphics.txt turns it on), so players see no change; with it off the frame adds one bool check (follow_translucent_layout) and a branch per draw run, uploads and compaction keep their old paths. Measured gains in the body are for the opt-in path.
🤖 Agent: Review by Ludovic's AI agent (Claude). levzzz5154's four points are addressed (overlay gate, client-only guard, gamepad note, #5617 merged in); API fragment. I merged main (merges with #5731 cleanly); verify green.
Reuse the padded container slot target for Creative grid and hotbar highlights and tooltips. Compare 50 rendered tooltip samples against actual vanilla slot hit-testing, including quarter-pixel boundary and Survival controls.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
The split saves Apple's tiled GPUs their hidden surface removal; elsewhere
the second pass doubled the solid draw calls (one per section without
multi-draw) and the vertex work for no gain that was measured. Other
backends keep the single fs_solid pipeline, the flat one included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Management protocol serversettings/difficulty get/set as MinecraftServerSettingsServiceImpl, saved to server.properties and server.toml; the management harness covers it.
🤖 Agent: Boat paddle flags reset to false (AbstractBoat.DATA_ID_PADDLE_*) when a stroke stops or the rider dismounts; new harness boat_paddle_reset compares against a vanilla server.
🤖 Agent: Thrown potions hit players with vanilla's pick margin and leftOwner rule (Projectile.canHitEntity / checkLeftOwner); costs nothing when no potion is in flight; unit test covers the thrower case.
🤖 Agent: Placing a painting swings the hand as vanilla's HangingEntityItem.useOn (InteractionResult.SUCCESS) does; tests pass, parity runs on the head.
🤖 Agent: OptiFine series step verified (licence gate, gating to packs that ship the files, per-mob cost measured, verify green on 64d0b1a4e). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gating to packs that ship the files, per-mob cost measured, verify green on b88a1aad1). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gating to packs that ship the files, per-mob cost measured, verify green on d454a1fcb). Thanks @some-du6e.
🤖 Agent: Shader packs: a custom variable/uniform expression may end with one ';' (trimmed), extra statements and empty expressions still rejected; unit tests.
🤖 Agent: Shader packs: custom image dimensions expand pack option macros (default and chosen), the option table is still built once; literals unchanged, unknown names still rejected; unit tests.
🤖 Agent: Shader options: a declared default missing from its authored range stays selectable (inserted first), empty ranges still create no option; unit tests.
🤖 Agent: Shader packs: a one-literal vector constant (ivec3(1)) in workGroups/workGroupsRender fills every component (GLSL 4.60 §5.4.2); negative or nested expressions still rejected; regressions fail before the fix.
🤖 Agent: Shader packs: isRightHanded, is_on_ground and the dimension's bedrockLevel/heightLimit/logicalHeightLimit/hasSkylight/hasCeiling get real values (black-box probe of the uniform values, no source read); set per frame only while a pack is on.
🤖 Agent: Client hook native_mob_submit as an API fragment; I fixed arismoko's two review points (walk state from mobs::anim::state: no walk while riding/dead, babies x3; hook asked before the sulfur cube's block). Verify green on 62e9cf2ba; the hook is a no-op stub without mods.
A 2048 MB default would give every player who updates a RAM limit they
never chose. On macOS and Linux the game starts stepping the render
distance down near 1843 MB, and big worlds or modpacks can pass that and
reach the last-resort crash. The row now starts at no limit, the end of
the slider; a typed or slid value is kept as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cargo still accepts the underscore spellings before edition 2024, so a
dependency there skipped the lock check (levzzz5154's note on #5756).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The shader params doc comment goes back on native_shader_params: #5897 put
native_biome_colors between that comment and its function.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Reviewed lod.rs (seam stand-in rule in select and evaluate, coarse backdrop in the wanted queue, load order only). fmt/clippy/lod tests green in verify on this head. Thanks @some-du6e.
🤖 Agent: Requeued after merging main (conflict beside native_data_pack_files, both hooks kept, 3 files +24 vs main). Server hook native_server_data_loaded(reload) as an API fragment; empty stub, no cost.
A fluid hit always snapped to the top of the water cell, so a boat landed
above a submerged player. In a full water cell the hit now stays on the
ray, as vanilla's VoxelShape.clip has it, so the boat collides with the
player and nothing is placed.
Fixes#5967
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: Client hook native_biome_colors + native_refresh_biome_colors as an API fragment: runs only when a mod asks for a refresh (rebuilds sections like a Biome Blend change), nothing per frame; Biomes::with_colors unit-tested.
🤖 Agent: Harness diagnostics only: a JVM oracle that dies without an exception reports its exit/signal and last output (chunk_generation, datafix); no change to what is compared.
🤖 Agent: World border wall invalidated when the camera changes SectionPos (LevelRenderer.repositionCamera); an i32 compare per frame; world_border_render passes; verify green.
🤖 Agent: One line: rope_rot_old (yRotO/xRotO) set at the start of the server-client tick, as the local tick does, so the held lead end stops easing toward yaw 0; verify green.
🤖 Agent: A full dynamic glyph region no longer caches the missing glyph forever: overflow is retried after begin_frame clears the region (one bool check per frame); font unit tests cover overflow and no-provider chars; verify green.
🤖 Agent: Pointer menu mode: the on-screen keyboard auto-opens over chat/signs and A on a text field opens it, as focus mode does; pure helpers unit-tested; pad UI only, verify green.
🤖 Agent: Toast label colour packed with hud::rgba (yellow 0xFFFF00 / pink 0xFF88FF as AdvancementToast), unit test on the packed bytes; client UI only, verify green.
🤖 Agent: queued with my fix (9ec5db50d, see above): the remote arrow's flight rotation as vanilla's `AbstractArrow.tick` has it, without the two hunks that went against 26.3. Thanks @Lostpold88.
🤖 Agent: verify was green before main's splitscreen extra conflicted. Main is merged in (40704247c), and the extra stays off by default. Queued for merge, thanks @Stink-O.
Resolves docs/native-mods.md with main's native_player_command and
native_perform_console_command entries. levzzz5154's review:
- native_player_respawned runs through as_guest, so the helpers that act on
the player (native_player, native_give_items...) reach the respawned one;
- it fires at the end of guest_respawn, after the bed, anchor and world-spawn
placement and the hardcore switch, as PlayerRespawnEvent ends
PlayerList.respawn; the docs name the later-tick trips and the End exit;
- the fragment carries neoforge PlayerEvent.PlayerRespawnEvent, side server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the client collision snapshot in sync with the animated lid and apply its existing swept-volume push, including while a container menu is open. Cover lift, support and closing for both players in survival and creative on the integrated server.
Co-Authored-By: Codex GPT-6 <[email protected]>
The dragon was drawn and ticked only in the End (its fight), so /summon ender_dragon in the
overworld put an invisible, motionless dragon there that only made sounds. EnderDragon.aiStep
does not depend on a fight (ServerLevel.getDragonFight is null outside the End): draw the
level's dragon in any dimension, and tick it outside the End without the fight.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Util.getRandom(slotsWithGliders, this.random) draws from the entity's random,
so the pick must not take a draw from the level's stream.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
levzzz5154's review: only logs/ and crash-reports/ follow --player-dir (crash
telemetry stays on --home, whose consent it reads); a dedicated server never
takes it. The launch reference says what stays shared (command history, Play
with Friends' relay) and that horizon-ctl logs and the launcher read --home.
Tests: shared_dir_of, servers_file_of.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's DataPackCommand ends in ReloadCommand.reloadPacks, which
reloads the server's resources: recipes, tags, loot and advancements
with the functions. Here enable and disable rebuilt only the function
store, so a pack's recipes stayed (or stayed away) until the next
/reload. They now reload the content tables too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reject unsupported negative and nested scalar expressions rather than dropping their operators when expanding a vector. Add a regression for the overbroad expansion.
Co-Authored-By: Codex GPT-6 <[email protected]>
Far tiles marked only leaves as opaque, so every stone, sand or ice face
of the far terrain went through the cutout pass and its discard. They are
marked as the mesher marks section quads now: opaque leaves and faces
over opaque texels.
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
Certain server resource packs (notably on SMP servers like HugoSMP using custom font and GUI sheets) contain PNG textures with stripped or corrupted CRC-32 chunk checksums.
Vanilla Java Edition (26.3) reads images via STB Image (NativeImage.read), which does not enforce or fail on PNG chunk CRC-32 errors. Setting decoder.ignore_checksums(true) matches vanilla behavior and allows custom font sheets, emojis, scoreboard icons and sprites to decode properly.
Fixes#5979.
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
keep_unchanged_sections kept the old section whole when its blocks matched, so a
server that resent a column with new light and no separate light update left a
resident client with stale light (arismoko's review on #5733). Vanilla's
handleLevelChunkWithLight always applies the packet's light. Such a section now
takes the packet's light and remeshes with its neighbours (light_updated); a
section whose light is unchanged is still shared with the old column.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
A splash, lingering or XP bottle stopped at blocks and mobs only, so it
fell through any player. The step now meets players too. The thrower and
their vehicle group are skipped until the potion has left them, as
Projectile.canHitEntity and checkLeftOwner do.
Fixes#5876
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
ChatComponent's 200-tick fade reads Gui.getGuiTicks, a whole number, so it
steps 20 times a second. Passing the frame fraction made every player's fade
smoother than vanilla's; it now applies only with the Smooth chat extra on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Send the default false values when paddle entries disappear from tracked metadata, including dismounting mid-stroke. Compare 18 transitions with actual vanilla servers.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
- NativeMobRender takes the walk cycle, head turn and age from mobs::anim::state,
the state the vanilla models read: no walking while riding or dead, a baby's
legs three times as far (LivingEntityRenderer.extractRenderState,
WalkAnimationState.positionScale).
- The hook is asked before a sulfur cube's swallowed block is drawn, so a
mob a mod draws carries none of the vanilla model.
Review points from arismoko's review.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack of Horizon's own with [shadow] draws from the shadow camera's lists
(d36550e82), which makes frames flat where the device can, and flat draws
read translucent quads in the section arena. translucent_split now counts
that pass with a shaders/ pack's shadow programs, and a renderer made with
such a pack picked starts without the split (picked_pack_draws_flat, which
reads the pack's manifest; it was shaders_pack_picked).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RemoveKilled marked the dead target removed before the next tick's deaths
ran, and those skip removed mobs: a magma cube eaten by a frog dropped no
froglight and no experience. LivingEntity.die runs at the killing bite in
vanilla, before eatEntity removes it, so the handler runs mob_died first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: reviewed by Ludovic's AI agent (Claude): rdbtCVS's #5595 finished (main merged, "Built on {base}" in all 10 launcher languages); remove_version refuses a running build; verify and launcher checks green on e918209, merges cleanly with main. Thanks @rdbtCVS.
The file kept the Microsoft refresh token and the Minecraft access token in
plain JSON. It is now sealed with a key derived from /etc/machine-id
(/etc/hostid on FreeBSD), so no token is on disk in plain text and a copy
taken off the computer can't be read. A file that doesn't open is refused
without quoting it and never written over, as a broken one was.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
paths: one valid() test shared by resolve, in_root (CTM) and CIT. THIRD_PARTY_NOTICES.md
credits CIT Resewn Continuation (MIT), whose conditions cit.rs follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RenderAssets keeps both the PBR maps and the connected-texture table; Sprites::insert_file
passes the root to main's register (a resource path has no namespace, so no PBR maps).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's spread() already applies RandomizationType ALTERNATIVE in
handleParticleEvent, so this branch's copy of it is dropped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Windows F3/crash-report CPU name from the registry's ProcessorNameString with the old fallback; read once (OnceLock) so no per-frame cost; the launcher starts the game with CREATE_NO_WINDOW so reg.exe opens no console. Parser unit-tested.
🤖 Agent: MCP initialize now negotiates the protocol version (echo only a supported one), same 12-line helper in horizon-ctl, horizon-server and the launcher, with unit tests; verify and launcher CI green.
🤖 Agent: Docs only (mods.md, modpacks.md, template comment, manifest doc comment); checked the facts against the content loader (sounds key names a vanilla block, menu/entity_type kinds, mismatch refusal). The failing 'check' job is horizon-mod-registry's lock on main, unrelated.
Both sides of MobData, the pack reload and render_visible kept: a .jem's texture is
picked first, then its random variant. horizon-optifine listed once in horizon-client's
dependencies, and render_visible holds the CEM read guard instead of cloning its Arc.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Packs read these Iris uniforms as zero: isRightHanded (the Main Hand option), is_on_ground, and
the dimension type's bedrockLevel, heightLimit, logicalHeightLimit, hasSkylight and hasCeiling
(from the level's shape; hasCeiling in the Nether). A pack reading bedrockLevel 0 and heightLimit
0 in the overworld puts its fog or its sky's horizon at the wrong height.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GLSL 1.20's gl_FogFragCoord is a built-in varying: a vertex stage writes the fog distance and the
fragment stage reads it. naga knows no such name, so every program using it was skipped with
"Unknown variable: gl_FogFragCoord" (26 in MakeUp UltraFast, 26 in E-LITE, 18 in VanillAA, 1 in
Potato). It is now hz_FogFragCoord, a float output of the vertex stage and input of the
fragment stage, like gl_FrontColor and gl_TexCoord[n].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify is green and the branch merges cleanly with main. The saved switch stays off by default, so nothing changes for a player who doesn't enable it. Queued for merge, thanks @martipops.
🤖 Agent: verify is green, the branch merges cleanly with main, and levzzz5154's review found it ready (load order below the world's packs, both first open and /reload covered). The hook's API number comes from its fragment when it lands. Queued for merge, thanks @Sylviromi.
Read and change the dedicated server difficulty through vanilla management
requests, broadcasting the change and saving it for restart.
Part of #5147
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translucent quads (water, stained glass, ice) sat right after their
section's solid quads in the 256 MiB arena pages, so the back-to-front list
changed page every few sections and only short runs merged: 3,832 draw calls
a frame over the ocean at render distance 64. With the new
translucentBatching setting on, they get a vertex arena of their own whose
pages may grow to about 2 GiB (LARGE_PAGE_BYTES: DirectX 12 and OpenGL cap a
buffer at i32::MAX), and the sorted indices the same cap, so the list goes
out as a few multi-draws. Vertex data, sorted indices and draw order are
unchanged.
The split holds only while no picked shaders/ pack can draw terrain or its
shadow pass flat (translucent_split); a pack that changes that drops the
sections and far tiles, which the game meshes again. compact_arena moves
what a section holds in the section arena: its solid range when split,
skipping sections without solid quads. LOD tiles and their fade stand-ins
draw and release from the translucent arena too. Batching is offered with
indirect multi-draws, not OpenGL's, and not where vanilla frames draw flat
(translucent_batchable). Every build_renderer caller passes the setting,
splitscreen and resource reloads included.
Off by default. RTX 4090, 1080p, off -> on: +4 to +16 % FPS at render
distance 32, up to +43 % at 64 (ocean 284 -> 406 FPS, 3,832 -> 354 draw
calls); arena memory +1 to +15 %.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve the Home Mods refresh fix while incorporating upstream's correction
to BlockProperties::from_shared. The pull request remains one launcher file
relative to the pinned main revision.
Co-authored-by: Codex <[email protected]>
MinecraftServer.runServer catches what the tick throws, writes the crash report, then
stops the server. A panic on the dedicated tick thread unwound out of run_dedicated, so
nothing since the last autosave was written. The tick is now under catch_unwind; a
panic ends the loop and saves the levels and the rest of the world state.
What the panic interrupted is not trusted: the guest whose turn (as_guest) it was has
older data in its record than the game holds swapped in, so it keeps its last saved file;
a hub taken out for the network tick (self.lan.take()) is gone with the unwind, so no
player is saved then.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Projectile.shootFromRotation adds the shooter's known movement and drops
only its y on the ground, and sets no rotation after it: an arrow shot
while strafing drifts with the player in vanilla too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As serde_json Values, vanilla's ~1,450 loot tables (0.6 MB of JSON,
~10,450 objects, a B-tree node each) held ~7 MB of a server's idle heap.
LootTables keeps each table's text and reads it into a Value the first
time it is rolled; every table is still read whole when it loads (a bad
file fails the load as before) and the native hook still runs on it, in
id order, before anything rolls it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified at e241409 on the Mac mini: fmt, clippy -D warnings (horizon-client-render), mesh_pool tests pass; the new assert fails without the fix. Thanks @Teiwazik.
WorldRequest::prepare loaded the loot data (~40 ms on 4 CPUs) before the
world and installed the live features (bone meal's and saplings', ~46 ms)
after it, while the generator's decoration (~205 ms) kept one core busy.
Neither needs the world: they now run on scoped threads beside it and are
joined before prepare returns, so the loot data is still loaded before any
chunk is read. With a single worker thread they run in order as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The solid terrain shader could discard every pixel (the fade dither and
the alpha cutout), and one discard anywhere in a pipeline turns off the
hidden surface removal of tiled GPUs (Apple's) for everything it draws.
The world pass now draws each solid list twice: first the quads over
opaque texels that are not fading, through fs_opaque, which has no
discard, then the rest through fs_solid. Each vertex shader decides its
pass before any other work; a quad of the other pass lies outside the
clip volume. Shader packs and native materials keep the one pipeline.
M1 Pro, 1280x720, same commit before and after: GPU 10.4 -> 9.6 ms in a
flight over new ground, 12.9 -> 11.0 ms in rain, 9.5 -> 8.1 ms with 300
mobs in view. Screenshots are the same.
On the server-thread client, Game::tick returns tick_remote() before the
rope_rot_old write, so the first-person lead end eased toward the initial
(0, 10) every tick. Write it in tick_remote_level, after the unlit-chunk
early return, as the local tick does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The wall mesh only holds the stretch inside render distance at build time
and was rebuilt only when the border itself changed, so after a respawn or
a long walk it stayed on the old stretch. Invalidate it when the camera's
section (floor >> 4 on X, Y, Z) changes, as LevelRenderer.repositionCamera.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: reviewed (the replay takes the shared flags' sprint bit the server sends, as vanilla's client); rebased on main; pr_ready --quick green on 2a1dd55 (1711 tests); the new test reproduces the live 0.0294/0.0455 signature before the fix; a +0.05 speed lie still counts from its first tick.
Read ProcessorNameString from the Windows registry instead of displaying the raw processor identifier.
Co-Authored-By: OpenAI Codex <[email protected]>
Arrows, tridents, snowballs, fire charges and rockets never felt a blast,
so TNT minecart arrow launchers did nothing. They now take vanilla's
push from their eye position, with each type's real eye height (0.13 for
arrows and tridents, 0 for wind charges). An arrow in the ground stays.
Fixes#5908
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The book's model and the native recipe list made every crafting and
cooking id `minecraft:<name>`, so a content mod's or a data pack's
`mymod:thing` became `minecraft:mymod:thing`. Only a bare vanilla name
takes the prefix now (crafting::recipe_id), and the native list reads
cooking times from the recipes in use, not the content root's alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client whose STOP_SPRINTING and START_SPRINTING reach the server in two
server ticks gets the STOP's shared flags and movement speed (no sprint
modifier) after it sprints again: its flag cleared, LocalPlayer.aiStep
starts the sprint again, modifier included, and sends nothing. The
replay took the attributes but not the flag, so it sprinted on without
the modifier until the START's attributes came: 0.029 then 0.045 off,
the official server's "ground sprinting" flags (#5935).
Record the sprinting bit of the shared flags the server sends the player
(Change::Sprinting) and apply it to the replay's flag alone, as
SynchedEntityData.assignValues does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The window's recipe book model, which native_recipes lists the vanilla
kinds from, is made again from the server's recipes when they arrive.
The recipes of a join come before the window has its player, when the
helpers answer nothing, so native_recipes_received waits for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.collide stops a moving entity at the entities whose canBeCollidedWith(it) is
true. An item only looked at blocks, so one dropped onto the back of a happy ghast
(held still while a player stands there) fell through it, as it did through a boat.
The server's item tick now gets the boxes of boats, minecarts, living shulkers and
happy ghasts on a still timeout.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: rebased onto main (conflict with #5856's frozen test helper, both kept); pr_ready --quick green on 30dfdc3 (1689 horizon-client tests, 19 prediction tests on vanilla data).
For 10 ticks after the server's item use ends, the replay also tries the
client still slowed by it: the client finishes a use on its own ticks and the
server on its own (food eaten one after another with the key held), and the
server stops one for its own reasons. Slower than the keys allow is no
advantage; a use the server has and the client may not still counts at once.
tools/anticheat_e2e.py: prediction-items (a shield, food and a bow used on
the move) and prediction-water (a deep pool, a current, a bubble column, a
ledge) with a real Horizon client.
Fixes#5860.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Read and set the dedicated server message through vanilla management
requests, updating the value used by server-list pings immediately.
Part of #5147
Co-Authored-By: Codex (GPT-6) <[email protected]>
PowderSnowBlock.entityInside's SNOWFLAKE particles only ran on the
in-frame Game. The server copy skipped them, and guest ticks dropped
the event, so the window never drew them.
Emit them on server_block_damage, send ALTERNATIVE packets to nearby
players, and skip the client-side copy.
Fixes#5836
Co-Authored-By: Grok 4.6 <[email protected]>
A server's native mods name the recipe types their client halves need
(native_datapack_sync, NeoForge's OnDatapackSyncEvent.sendRecipes). The
server sends those types' final recipes, data packs merged and load
conditions held, to every Horizon client on a join and after each
/reload, on horizon:recipe_content in payloads within the clientbound
cap. The client's tables take them in place of its own for those types,
so native_recipes and native_recipes_of_type match the server, single
player included, and native_recipes_received (RecipesReceivedEvent) says
they came. With no type asked nothing is sent; vanilla clients are sent
nothing new.
Recipes::load is split into sources and from_sources so the server and
the client build from the same list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proof: pr_ready --quick green (1680 horizon-client tests); write timeout = read timeout; local frozen-player run ends both threads; ships in the next batched restart for the live measurement.
#5915's last revision made the shared pairs references, so the sort's
comparison borrowed a reference: main's verify (clippy -D warnings)
fails on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apply the existing replace-disk evaluator for armor stand block changes,
landings and changed equipment slots. Keep piston support flags current
and verify the behavior against actual vanilla servers.
Remove a needless borrow in the upstream properties sort for Clippy.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
fn vertex(input: VertexInput) -> vec3<f32> moves terrain in the world and the shadow map, for
waving plants and rolling water. It needs a [material] and shares its module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
🤖 Agent: proof: pr_ready --quick green on 98e49e3 (1680 horizon-client tests); live on the official server since 12:22Z as dd4b209 (this head minus the test commit), status pings ok every minute, 99 joins in 15 min; the deadlock of the first deploy is reproduced by the test without the fix.
A peer that stops reading (a frozen game, a dropped network) held its
writer in a full send buffer until TCP gave up, minutes later, and the
connection's place with it: play() joins the writer. Writes now time out
like reads. The turned-away report also says how many places are before
play and how many wait for a writer to finish.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: main moved after the queue (#5875 and #5755 merged), so I merged main in again (clean, d635bc57f). `cargo check -p horizon-client --lib --bins --tests` passes. Requeued.
Review on #5909: native versions published as .hmod would reach
launchers that drop their content or refuse them. The registry keeps
.hnmod until launchers and the game read native .hmods.
docs/api/mod-files.md holds every rule of a .hmod/.hnmod in one
place: zip and path rules, top-level entries, the horizon-native.json
schema, what mixins/<id>/ may hold and the size limits.
The registry's Cargo.lock catches up with horizon-mods' png
dependency so its --locked check passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proven on a union with main c1f4a7c19 (+ #5875, #5755, #5679): `pr_ready.py --quick` passed (fmt, clippy -D warnings, horizon-client tests, doc tests). I added a lan_e2e check, `attack_over_slab` (97c18d712): a NoAI zombie stands behind a bottom slab, the player aims at its head over the slab and left-clicks. It passes for the host (20 → 19.06) and for the guest (19 → 18). Queued for merge. Thanks @repositorykreml1n!
Carry the fingerprint captured at read start into asynchronous completion. Keep an older result stale when a newer fingerprint was observed, allowing the next render to load the updated list.
Add a deterministic regression using owned temporary native-mod files and real discovery and fingerprints. It fails with the original completion behavior and verifies both files after the follow-up read.
Fixes#5907.
Co-authored-by: Codex <[email protected]>
🤖 Agent: verified by Ludovic's AI agent (Claude). ItemStack.isBarVisible needs isDamageableItem, which an unbreakable stack isn't, and the unbreakable unit component now reaches clients. pr_ready --quick passed on main c1f4a7c19 with this PR, #5875, #5903 and #5679 merged in (fmt, clippy, horizon-client tests incl. the new sync test).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against ShulkerBoxBlock.playerWillDestroy and Block.popResource (offsets drawn before the block_drops check, as vanilla). pr_ready --quick passed on main c1f4a7c19 with this PR, #5903, #5755 and #5679 merged in (fmt, clippy -D warnings, horizon-client tests, Python tests). CI parity covers the new command_exec cases.
🤖 Agent: playhorizon 4 CPUs: idle RSS −17 MB with THP off (241.7 → 224.7, every start) and −18 MB median with THP on (4/4), live heap −13.6 MB, Done 2.06 → 1.955 s (4/4); 20-bot MSPT unchanged (3 rounds). pr_ready tests 2066 + content roots pass; merge with main checks; fmt/clippy/Python clean on the Mac.
The deadlock fixed in the previous commit only shows with a log writer;
the test hangs without the fix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An id can name blocks as well as sprites, and can move its sprites into the translucent pass. A
sprite pattern beats a block on the same sprite. Sections mesh again when the list changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A client that moved, or whose own entity changed section, had every
entity of the level checked against it each tick: players times
entities. sees caps the range at the client's view distance, so only
entities in chunks that close can be seen. The tick now keeps each
chunk's entities and walks only the chunks within reach; the flags,
pairs and their order are what checking every entity gave.
ShootTongue.tick sets the target's delta movement (setDeltaMovement, not an add) toward
the frog, and eatEntity removes a target that died (remove(KILLED)): the magma cube is
drawn into the mouth and disappears, with no death animation next to the frog.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
They live in their own lists, so a plate's entity count never saw them.
They now count, as vanilla's getEntitiesOfClass does. A rocket boosting a
player and a hook on an entity count but skip entityInside, whose step
their ticks skip too. A guest's hook ends when the guest changes level, so
it can't act on the blocks of the level it arrives in.
Fixes#5896
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
loot_table_load_stays_hookable searched for "\n}\n", which a CRLF checkout
(core.autocrlf=true on Windows) never contains. Normalize as the other
source-reading test in this file does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolve graphics.rs serialize conflict: insert leafCulling between
entityCulling and chunkUpdates, keeping main's textureBudgetMiB and
ditherCutoutMips additions intact.
Add four-mode benchmark screenshots for the PR.
Co-Authored-By: Antigravity <[email protected]>
make_room logs turned-away connections, and the log reads by_address:
count called it with that lock held, so the accept loop deadlocked the
first time the server filled with a log writer set.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .hmod's contents decide what it is: content only, native code
(horizon-native.json + mixins/<id>/), or both in one file. Native
bundle reading skips a .hmod's content entries, checks both
manifests name one mod, and refuses mod.wasm with a clear error.
The registry publishes native-mod versions as .hmod and still
accepts .hnmod; .hnmod keeps loading unchanged.
Launcher install, game content loading of native .hmods and the
GitHub install path are not done yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
decorated_region (through chunk_generation's oracle) failed in the nightly audit with
"oracle failed: " and nothing after it, and datafix with a CalledProcessError that
names the command; both Java oracles stopped with no exception to print. Show the exit
status (and the signal that killed it) and the last lines of its output.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A file data/<ns>/recipe_filter/<name>.json lists filters that match
recipes by id or type (with *), by an input or an output item, and
remove them or swap one input or output for another. They run on each
recipe's JSON after the packs are merged and its load conditions held,
so they reach every recipe type, mods' own included, in every loader:
crafting, the stations, brewing, the recipe book and the recipes the
players are sent. A filter with an unknown key or not exactly one
action is skipped with a warning, so a typo never matches every recipe.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use the source-only fluid ray for SpawnEggItem.use, preserve packet-supplied block hits for useOn, and compare both paths against vanilla.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
When every place is taken, the oldest connection still before play
(handshake, status, login, configuration) that has waited more than 2 s
is closed to admit the new one. A crowd of sockets that never finish can
no longer keep players out; players in the game are never closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The connection cap (#5776, players + 64) filled up on the official server
within minutes of a restart and every new join was reset. Now one public
address holds at most 10 connections (local and private addresses, where a
proxy or a port forward hands everyone over, stay unlimited), the handshake,
status and login phases wait 10 s for the client instead of 30 s, and once a
minute the log says how many connections were turned away and which
addresses held the most places.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A material stage stores a normal, a smoothness and a value of its own for solid terrain; a post
pass reads them, where that terrain still shows. Only a stage that calls set_surface pays for the
target and its passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
In single-player the player's attack goes through the integrated server as a guest attack. guest_attack calls
look_at_entity, which aimed at the centre of the target mob; Game::attack then drops the hit when a block outline is
nearer than the mob along that ray. The attack packet names only the entity, so a mob that the player's own crosshair
hits through a gap (a top slab, a closed top-half trapdoor, ...) had its centre hidden and the hit was dropped.
For mobs, look_at_entity now aims at the centre when it is visible, else at the visible point of a grid of points in
the mob's box nearest to the centre (nearer the eyes on ties), found with a clip_ray from the eyes. A mob whose whole
box is hidden keeps the centre, so a really blocked hit is still refused. Other entity kinds are unchanged.
Fixes#5772
native_server_data_loaded(reload) fires once the server's data tables are
built: on a world's first open, before its chunks and players, and after
every /reload, once the players were sent the new data. NeoForge's
TagsUpdatedEvent with SERVER_DATA_LOAD. A mod drops caches derived from
the data and rebinds what depends on it there instead of polling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
block_texture, block_texture_level, block_texture_size and block_texture_lod read the surface's
sprite anywhere on it. The LabPBR _level functions now count the texture's mip levels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
The ChatAnimation mod's idea, not its code: a line slides in from the left
while it fades in over 5 ticks instead of appearing whole, and the indicator
bar can be turned off. Both are Extras, off (the bar: on) by default, so a
vanilla client draws exactly what it drew before.
Chat::layout takes the fractional tick now, so the slide moves every frame
and vanilla's own fade does too. Only the unfocused HUD chat animates: with
the chat screen open the lines are drawn where click_at maps them.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
Cloning a registry state bumped one Arc per name and value; world
generation threads clone the same states, and 20 bots cost 0.25 ms of
streaming. Shared strings are now interned once for the process (the
registries' names and values, never a player's) and copied as pointers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/summon minecraft:tnt {Passengers:[...]} dropped them. Load them as riders
(EntityType.loadEntityRecursive) and seat them on the TNT's top.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla ShulkerBullet.tick draws two END_ROD particles in its 0.3125
box every client tick. Horizon moved the bullet and drew the model,
but never spawned the trail, so the sparkle behind it was missing.
Fixes#5842
Co-Authored-By: Grok 4.6 <[email protected]>
SectionMesh::clear kept `lights`, so a mesh taken from the pool still listed the sources of the
section it last held: a broken torch stayed in the colour grid, and sources piled up elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Keep native hook targets and source paths stable. Run gameplay tests in the gameplay library, and build ctl/probes from horizon-client-lib without gameplay. Update build/test selection and the fixed mixin-tool pin.
Co-Authored-By: Codex <[email protected]>
The HUD batch takes colours packed as hud::rgba (red in the low byte);
0xFFFF_FF00 there is cyan. AdvancementToast draws the label -256 (yellow),
or -30465 (pink) for a challenge.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
So it no longer sits on the lines #5731 adds its field to: the two merge in either order without a conflict.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve the throw origin and inherited movement by preventing queued movement from overtaking a deferred UseItem. Compare standing and gliding throws against a real vanilla server.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
Preserve collected components when breaking filled shulker boxes in Creative,
including with block drops disabled. Check all variants against vanilla through
the integrated server player-break path.
Fixes#5821
Co-Authored-By: Codex (GPT-6) <[email protected]>
`native_surface_heights` and `native_read_columns` return `Result<_, String>` as `native_edit_blocks` does, so a mod
tells "wrong level" from "ask for less". A read is at most 1,048,576 blocks (was 16,777,216: it runs on the server
tick), a height query at most 65,536 columns, and an empty span (`below + above` of 0) is refused. The docs say the
tints are the biome's own at the column's top, not blended as the game draws them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_music(music, volume)` changes the track the music manager plays
and its volume each tick (Minecraft.getSituationalMusic and
getMusicVolume; NeoForge SelectMusicEvent). `native_music_layers(layers)`
lists looping tracks played over it on the music source, each easing to
its own volume by 0.025 a tick; layers new in the same tick start
together, so a piece's stems stay in step and can grow fuller.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QRKvdg3SMVP1onGqS3
HudAtlas walked only a fixed set of texture folders, then skipped
minecraft: file sources whose sprite name matched the texture path,
assuming the walk had packed them. Pack folders such as custom/ never
got a sprite, so item models that used them were invisible.
Keep the have.contains skip, and only treat matching File origins as
already packed when they sit under a walked folder.
Fixes#5720
Co-Authored-By: Grok 4.6 <[email protected]>
When charging a bow while strafing on the ground, arrows drifted sideways
because player ground movement was added to the projectile velocity and
rotation was not aligned to the resultant vector. In addition, client-side
remote arrows did not update their pitch and yaw during flight, causing
them to fly horizontally like static lasers without following their
parabolic trajectory.
Align bow release with vanilla behavior by not injecting ground strafe
velocity and updating rotation in `shoot_from_rotation`. In
`remote_arrow_tick`, update pitch and yaw every tick using `lerp_rotation`
matching `AbstractArrow.tick()`.
Fixes#5855.
Co-Authored-By: Antigravity <[email protected]>
`native_biome_colors(biome, colors)` changes a biome's grass, foliage,
dry foliage and water tints. It is asked for every biome when a mod calls
`native_refresh_biome_colors()`, which swaps the mesh workers' biome table
and rebuilds the loaded sections and far terrain when the colours differ
(LevelRenderer.allChanged, as a Biome Blend change does).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QRKvdg3SMVP1onGqS3
native_mod_screen_open refuses what screen_hooks_open would: an overlay that leaves the screen at Playing (a
book, a sign editor, a dialog, a prompt), the world loading, no player, and anywhere but the window's own client
(no renderer, or a LAN guest's swap on the server thread), so a screen never opens that nothing could draw or
close. The docs and fragment say so, and that a gamepad can close it but not click on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client mod is asked once a frame for each mob in view (players aside)
with the mob's render state: id, kind, custom name, baby, partial tick,
age, body and head turns, pitch, walk cycle, hurt and death time. It draws
with the block entity drawing helpers under a pose at the mob's feet,
turned with its body, and sets `drawn` to leave out the vanilla model
(EntityRenderer.submit; NeoForge RenderLivingEvent.Pre, cancelled).
Name tag, shadow, fire and glowing outline still draw.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QRKvdg3SMVP1onGqS3
The registry's states were read into a BTreeMap of owned strings, then
shared: two allocations and a hash per string, 50 ms of the start. The
facts.json decoder now shares each name and value as it reads it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The renderer's own pass times were not reachable from the client. The F3
overlay now shows them under their own heading, next to the shader pack
group. A new gpu-passes control command times them without opening F3,
and the stats and bench summaries report them as gpu_passes.
The per-pass GPU timer only covered shader-pack passes, so the F3
overlay could not say which of Horizon's own passes cost the frame.
Give the same timer to the frame's own passes (world, occlusion test,
OIT, HUD, post, HDR, hand, outline, menu blur, native shadow) and add
compute_writes for compute passes.
Passes that already carry timestamps (shader packs, the frame timer),
screenshots, one-off clears and test code are left as they were. The
timer begins at the top of the frame so that passes recorded before
the first draw, such as sprite animation, are timed too.
One bad file from a mod would fail its whole table and drop the world's
pack content with it; it is now skipped with a warning. The hook's docs
list every kind the tables read, trade sets, villager trades and dialogs
included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
So #5675 (splitscreen), which edits the same two arms, merges with this in either order; only the one-line
serialize format string still meets it (keep both keys).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block state's properties were a BTreeMap<String, String>: a 544-byte
leaf and two allocations per property for each of the registry's 35,723
states (about 30 MB of a dedicated server's live heap), for 237 distinct
strings. BlockProperties keeps them as a sorted slice of pairs, read like
the map (get, insert, iter, indexing, order, equality and hashing pair by
pair); the registry shares its states' strings through one Arc each.
States made elsewhere (chunk palettes, commands) own theirs, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fox: FoxSearchForItemsGoal walks to the first dropped item within 8 blocks while
the mouth is empty (goal 11, after the stroll), and Fox.pickUpItem takes it:
the extra items of the stack drop at its feet, what it held is spat out ahead
(entity.fox.spit, pickup delay 40, thrown by the fox), it holds one item and its
eating clock restarts. Fox.canHoldItem swaps non-food for food once it has eaten.
Dolphin: MoveToItemGoal (priority 7) swims to an item floating in the water within
8 blocks with entity.dolphin.play, PlayWithItemsGoal (8) throws what it holds, and
Dolphin.pickUpItem takes a whole stack into an empty mouth. ItemGoal.drop throws
from the eye, forward at 0.3, with a pickup delay of 40.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
HORIZON_MEMORY_LIMIT_MB and /ramlimit cap the RAM the game holds. On
Windows a hard working set limit enforces it, so a low limit costs speed
instead of crashing. Elsewhere, and when the game is slow and faulting
hard, far terrain and the render distance in use step down and come back.
The game crashes with a report only if it can't run under the limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A slider and a typed box, saved per instance. Horizon's limit defaults to
2048 MB and reaches the game as HORIZON_MEMORY_LIMIT_MB; no limit is a
choice of its own. A Java instance's row sets -Xmx.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While PackConfirmScreen is open the frame draws no screen under it, but
the render hook still reported the hidden one, so a mod drew over a
screen nobody saw. It now skips that frame, as the input hooks already
do through screen_over_playing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`below + above` could overflow and `columns.len() * per` could ask for
gigabytes: a read past the level's height or 16,777,216 blocks now answers
None, reading nothing. Blocks past the top of the column read as air from
the chunk's own layout rather than the vanilla -64..320, the always-0 `tag`
and the unused `NativeSurfaceHeights` (leftovers of an async API) are gone,
the reads are tested through free functions on `World`, fork names are out
of the docs, and docs/native-mods.md describes the helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A run naming a palette entry or filter past the end panicked mid-edit, and a
filter index past u16::MAX wrapped: every run is now checked before anything
changes, and the edit answers Err. Palette entries and filters with NBT are
refused rather than silently dropped. New blocks get their block entity from
`new_block_entity`, so a mod block's own (API 15) is made, as
`native_set_block` does. The heightmap walk stops at the chunk's own bottom,
`Game::block_matches` calls `block_input_matches` instead of repeating it,
fork names are out of the docs, and docs/native-mods.md describes the helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A respawn, a join, the hand's item changing or the server stopping the use
reset the player's use without the mod use knowing, so the next vanilla
use was taken for the mod's and cancelled. The mod use now records the
slot and ticks it runs at, only that use is the mod's, and every reset
sends the mod its UseStop. native_held_item goes (native_held gives the
held id; the use events carry the custom name and data), as do the unused
screen events and the fork's wording; docs/native-mods.md gets a section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_quad draws through HudBatch::fill_quad (the HUD atlas's white, as native_fill) instead of borrowing the
lightning renderer's white_uv. The rules for opening a mod's own screen move into mod_screen_opens, with a test:
only over the world, never over another screen, the title screen or another mod's, and again a no-op. The docs
say every mod's own screen shares the id `mod` (check native_mod_screen() is yours) and that it doesn't pause a
single-player game.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`edit_runs` clamped runs to the vanilla column (-64..320), so a data pack's
taller dimension lost the blocks above or below it: each chunk's own
`min_y`/sections now bound its runs. `replace_remote` kept an unchanged
section whole, biomes included, so a resend with new biomes under the same
blocks kept the old ones: those sections now take the new biomes and
remesh. `edit_runs`'s doc lists what it does not record per block (block
entities, watched writes, block_log, path_writes, light_edits).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client of the server (single player's window, a LAN guest, a dedicated
server's player) asked only horizon-world's useItemOn whether the clicked
block takes the item, and that has no cauldrons. So after UseItemOn the
click went on to UseItem: the client predicted the bucket's own pour beside
the cauldron (a water or lava block that showed until the server answered,
and the empty sound a second time), and in creative the server poured the
bucket a second time beside the cauldron, where it stayed.
Like vanilla's client (AbstractCauldronBlock.useItemOn, CauldronInteractions,
which act only server side), the client now answers the cauldron's
interaction without acting, ends the click on SUCCESS or CONSUME and swings
on SUCCESS. A sneak with an item in hand skips the cauldron on both sides
(Player.isSecondaryUseActive). lan_e2e gets a cauldron check, which fails on
upstream/main and passes with this.
Fixes#5294.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With it off the static pool is vanilla's again (247 channels to whatever starts first), so the sound_engine
parity harness is unchanged; `uiSoundReserve` (Extras > Sound, "Keep channels for UI sounds") turns on the 16
channels positional sounds leave for the ones played to the listener. The test checks both, and its
`use test_backend::Null` moves into it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Skeleton arrows passed through portal blocks and kept flying. The portal
sweep in find_entity_portals covered only items, mobs, empty boats and
minecarts, so an arrow never got a portal state and never changed
dimension.
Vanilla does not exempt projectiles: Entity.canUsePortal is true for a
living arrow that is not a passenger, so NetherPortalBlock.entityInside
and EndPortalBlock.entityInside (NetherPortalBlock.java,
EndPortalBlock.java) call setAsInsidePortal on it. AbstractArrow.tick
(AbstractArrow.java) then hands it to handlePortal, and the transition
time is 0 for anything that is not a player
(NetherPortalBlock.getPortalTransitionTime). A stuck arrow skips its move
while in the ground, so it enters no portal.
Arrows now join the portal sweep from where they stood at the start of
their tick, which also covers an arrow shot this tick. An arrow that went
through is saved with arrow_to_nbt as a waiting arrival, like an item or
mob. On load it restores its portal cooldown, so it does not go straight
back through the portal it came out of. Tridents are unchanged.
Horizon let a player glide only when the chest slot held a glider from
its own item definition. A glider worn in the helmet, leggings or boots
did not count, and neither did a glider component a server patched onto
the stack, which Paper plugins do. The glide's wear also always hit the
chest piece, so a helmet glider would wear down the chestplate.
Vanilla's LivingEntity.canGlide checks every slot: canGlideUsing needs
the item to have DataComponents.GLIDER, the slot its EQUIPPABLE names
to be that slot, and a next point of wear that won't break it. The
server's updateFallFlying then picks one such piece at random with
Util.getRandom and runs ItemStack.hurtAndBreak(1) on it.
Horizon now checks each worn piece this way (gliding_pieces, also used
for the server's copy of a guest), and the wear goes to a random glider
piece (wear_glider), for the local player and the guest path alike.
pl_pl, uk_ua, de_de and fr_fr use the renamed horizon.splitscreen.profiles.*
keys. Main now keys the two "Connected to your friend" lines as
horizon.multiplayer.connected_friend_*, so de_de and fr_fr use those names,
and they gain main's two new keys (not_signed_in, invalid_address).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Review of the bundled de, fr and uk strings:
- de_ch read de_de's file with its "ß"; vanilla's de_ch writes "ss" (1
"ß" in its file, 312 in de_de's), so Bundled serves de_ch as de_de with
"ß" spelled "ss".
- de "Shortcut: %s" was "Kurzanwahl" (a phone's speed dial): "Tastenkürzel".
- de: the calibration's white step hint trailed off ("..., bei deinem
Licht."); fr: "one test picture at a time" had become "image test".
- uk: the Extras caption "Коліщатко: перенос" put a second colon on its
button ("...: перенос: Увімк."): "Перенос коліщатком".
- The lang test checks one German and one French string and that de_at,
fr_ca and fr_ch read the same file, instead of pinning two strings in six
languages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
de_de.json and fr_fr.json cover all 289 keys of en_us.json. de_at, de_ch,
fr_ca and fr_ch read the same files, as the es_* variants read es_es. AI
translation (Claude), not reviewed by a native speaker. Terms follow vanilla's
de_de/fr_fr (Optionen/Einstellungen, Fertig, Mehrspieler; Options, Terminé,
Multijoueur, "Oui/Non" for On/Off, a space before ":" and "?"). German uses
"du", French "vous", as vanilla does. The lang test now also loads the title
footer and the Graphics button in de, fr, pl and uk.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
pl_pl.json and uk_ua.json had 16 keys each; they now have all 289 keys of
en_us.json. AI translation (Claude), not reviewed by a native speaker. Terms
follow vanilla's own pl_pl/uk_ua files (Ustawienia/Налаштування, Paczki/
Пакети, Gotowe/Готово, byty/сутності, ...), and the ellipsis is the single
character vanilla uses in those languages. Placeholders (%s, %.2f) are kept.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
More of Horizon's own screens drew English literals (or looked up keys that
en_us.json did not have, which also shows English). They now go through the
language table:
- HDR calibration screen: title, the three step names and hints, Cancel/Back/
Save/Next (vanilla gui.cancel, gui.back, selectWorld.edit.save), and the
"Shortcut:" row; the Options > Graphics HDR descriptions and the Calibrate
buttons that had no en_us entry.
- Splitscreen profile page (split_profiles.rs): every label and line.
- Play with Friends screen, its chat notices and the two "Connected to your
friend" loading lines.
- The on-screen keyboard's key labels and button hints.
English text stays the fallback (lang::get_or) so nothing changes without a
translation. en_us.json gains 55 keys.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
Review of the bundled lang file test:
- `every_horizon_key_the_code_names_is_in_en_us` skipped a key whose group
had no other entry in en_us, so a new screen's keys were never checked
(deleting `horizon.title.not_affiliated` passed). A key is now any
`horizon.<group>.<name>` of lower-case letters, digits and `_`, with the
one server address that looks like one listed.
- The CRLF assertion failed on a Git for Windows checkout
(core.autocrlf=true) although CRLF is harmless to the parser; it goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every bundled horizon lang file must be served by Bundled::read (a file with
no match arm is never loaded), parse as a flat map without a repeated key,
have no key en_us lacks (stale or renamed), and keep the placeholders of the
English text. Every `horizon.<group>.<name>` key the client sources name must
exist in en_us.json. A language does not have to be complete: a missing key
falls back to English.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The title screen's "Not affiliated with Mojang or Microsoft" and the whole
Horizon Extras page (section headers, the eleven captions, their tooltips and
the On/Off values) were English literals that never reached the language table,
so they stayed English in every language.
They now read `horizon.title.not_affiliated` and `horizon.extras.*` from the
bundled `horizon` namespace (lang::get_or, with the English text as fallback,
as the multiplayer buttons do). en_us.json gains the 27 keys; other languages
fall back to English until they are translated.
Refs #1927
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The dragon was drawn only while the player stood in the End, so a
dragon summoned in the Overworld (/summon ender_dragon, which the
server sends to the client as an AddEntity) was heard, flapping and
growling, but never shown. Vanilla's EnderDragonRenderer draws an
EnderDragon in any dimension like any other entity; the gate was
added with the fight, when the dragon only ever existed in the End.
The dragon fireballs share the same block and are now drawn in every
dimension too.
The server doc still listed bow PvP between guests as missing; it works since 5763854b0 (lan_e2e 'arrows'), and a dedicated server run with two protocol clients agrees.
wgpu encodes a frame's commands in CommandEncoder::finish (~85 µs) and
submits them in ~25 µs, which held the window's thread at uncapped frame
rates. The frame now goes to a "Render submit" thread that finishes,
submits and presents it while the main thread builds the next one. Every
use of the queue waits for that frame first, so writes and submissions
keep their order. Frames with a screenshot readback, error scopes, VSync,
a reconfigure or the first frame's log stay in place. Culling's readback
starts on the submit thread right after the submission.
HORIZON_SUBMIT_THREAD=0 turns the thread off.
The first-person hand now draws at the end of the world pass, after a
depth reset, when nothing runs between them: one render pass fewer.
Closes#5777
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The shader options page and the split-profile prompt added six keys to
`en_us` only; zh_cn, ru_ru, es_es, pl_pl, uk_ua and tr_tr showed the
English text through the fallback, and a language's own words were
missing where the page is most visible. All six bundles carry them now.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
The integrated server retained minecraft:unbreakable, but network_stack
never emitted its unit component. The client therefore still drew a bar
even with the rendering guard in the previous commit. Send UNBREAKABLE
as Component::Unit; the client decoder already preserves unit components.
Add a regression test through stack_to_network, Slot write/read and
from_network, checking both ordinary and unbreakable damaged hoes.
It fails without the encoder entry and passes with it. Verify the actual
server-thread client retains the component and loses only the unbreakable
hoe's bar in the hotbar screenshot.
Written with Claude Opus 5.5 in OpenCode.
`native_mod_screen_open(mod_id)` opens a full-screen menu of a client mod's own over the world (`Screen::Mod`, its
`NativeScreenView.id` `mod`), `native_mod_screen_close` closes it, `native_mod_screen()` says whose is open, and Escape
closes it as any screen. It draws nothing itself: the screen hooks fire over it as over any other, so the mod draws it
in `native_screen_render_post` and takes its clicks, scroll, keys and typing from the mouse, scroll, key and
character hooks (#5543). Two drawing helpers beside `native_fill` for that: `native_quad` (a wedge or a slanted bar)
and `native_item_model` (an icon drawn with another `item_model`).
Ported from Stink-O/horizon ae531730c (`ui.show`) and 848a0686c, reshaped onto the screen hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Validate direct same-mod staged crate paths before excluding them from
registry requirements. Preserve lock checks and propagate discovery errors.
Co-Authored-By: OpenAI Codex <[email protected]>
ItemStack.isBarVisible is isDamaged, which needs isDamageableItem: a
max_damage and a damage, and no minecraft:unbreakable. draw_item_decorations
only looked at the item's max_damage and the stack's damage, so a damaged
unbreakable stack (/give @s diamond_hoe[damage=58,unbreakable={}]) showed a
green bar. It now asks stations::is_damageable, the port of
isDamageableItem the anvil and the grindstone use.
Fixes#5721
Written with Claude Opus 5.5 in OpenCode.
Chests (closed ones' baked geometry too), shulker boxes, bells, banners, decorated pots,
books and the other block entities draw each part again with their texture's _e
overlay at full brightness, shaded alike. The emissive overlays are shared by the
atlas and the mobs (an Arc), so the pack reload no longer moves them out of the atlas.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
overlay draws the 17 transition tiles over a face toward the blocks or
sprites it names (connectBlocks, connectTiles); overlay_ctm, _random,
_repeat, _fixed and Continuity's _horizontal/_vertical forms pick a tile
as their method does. Overlays come before the rule that picks the face's
own sprite, draw after it at the same depth with an alpha cutout (or
translucent with layer=translucent), and are tinted by tintIndex and
tintBlock.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::blink finds each entity texture's <name>_blink.png (and _blink2.png,
half shut) beside it, a random variant's beside the variant, with blinkLength and
blinkFrequency from <name>_blink.properties. The entity atlas packs the variants'
frames; render_visible draws the frame instead of the texture while a mob blinks,
each mob at its own phase, and shut while a villager sleeps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Follows the .jem reader's review changes: files over the model's byte
budget are refused by size, and the reader's warnings reach the log.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mesher asks ConnectedTextures for each face's sprite: a tile swaps the
quad's layer (in the pass its alpha needs), and ctm_compact's quarters
split the quad in four, its light and colour blended across them. Rules
that test biomes get the block's biome. Nothing changes without CTM files.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::emissive reads the pack's suffix (optifine/emissive.properties,
ETF's etf/ and textures/ copies, _e without one) and finds each entity texture's
overlay beside it, a random variant's beside the variant. The entity atlas packs the
variants' overlays; render_visible draws the body and each layer again with its drawn
sprite's overlay, full bright with the model's shading (OptiFine's look), armour
included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A rule naming blocks and tiles needs both; block rules are indexed by
block, so a state tests only its own; textures/ tiles reuse their sprite;
overlay and custom-name rules load no tiles yet; top passes the top and
bottom faces on; tests for connect=tile, repeat, h+v, skips, defaults and
insert_file. Also fixes a double borrow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Strings read or copied out of the files (part names, ids, animation
targets and expressions, axes, numbers written as text) cost one per
started 64 bytes, each time; animations pay theirs before the copy.
- A submodel costs its keys, a part the layers its keys go through.
- by_id no longer copies an id it already has.
- Tests: shared .jpm with a 1 MB expression and with 200k submodel keys,
the .jem's bytes in the byte budget, non-object animation items, junk
boxes that only exhaust when keys are charged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main rebuilds a heap of wanted tiles when the view moves and selects
incrementally. The backdrop becomes a class in `Wanted`'s order (seam,
backdrop, the rest), and `evaluate` keeps split tiles at the seam from
standing in, as `Plan::select` does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The entity atlas packs the variants horizon_optifine::random_textures finds; each mob
chooses its variants when this window first sees it (biome, height, blocks, moon,
time and weather fixed then) and again once a second (name, profession, colour, baby,
health, size); render_visible draws the chosen variant's sprite for the body and
each layer. Layers without rules follow the body's variant (sheep wool), villager
layers choose their own. A pack reload makes every mob choose again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- pow follows Java's Math.pow: a NaN exponent is NaN, and 1 or -1 to an
infinite exponent is NaN; x to 0 stays 1, NaN included
- Test clamp with a NaN upper bound
- The too-long test checks the error lands inside the expression instead
of pinning an offset that depends on when nodes are counted
- Boundary test: a 512-term sum (1023 nodes) compiles and runs, 513 terms
are too long
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Connected-texture tiles load as sprite layers (Sprites::insert_file reads
a texture outside textures/), and ConnectedTextures keeps each block
state's and sprite's rule list, built once at load. pick() walks them in
rule order for a face: ctm, ctm_compact (quarters and ctm.N), horizontal,
vertical, h+v, v+h, top, random (weights, loops, symmetry, linked),
repeat and fixed, with faces, heights, biomes, connect=block/state/tile,
innerSeams and the axis of logs. Without CTM files nothing is built.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review round 3: such a file now holds no rules (every mob wears the texture itself)
instead of falling back to an even choice; the cap is 64 KiB, and a test pins it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review round 2 fixes: creeper02.png is no variant 2; a .properties over 256 KiB is not
read; files() says it lists every variant; tests pin Latin-1 in a value that decides,
and the texture's own folder coming last.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- An entry the budget cuts down to no quads is left out, not drawn empty
- Animations are all or nothing per entry, checked and charged before baking
- A box costs max(faces, keys, 1); non-object items of boxes, submodels and
animations cost 1, and so do non-scalar animation values
- MAX_BYTES (now 4 MiB, pub) bounds the .jem and all its .jpm files together;
a cached .jpm counts once
- Boxes whose corners or UVs overflow to non-finite numbers are skipped
- baseId dedup compares only against the entry's own .jpm layer
- Tests: a real lone-surrogate escape case, valid oversized JSON for the byte
limit, the shared byte budget, repeated keys, non-finite boxes, 1 unit left
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review round 1 fixes: the texture's own folder is walked like the others (no more
probing, no gap cut where rules name a variant, inherited rules there too); inherited
rules only cut the file name, never a folder; variant 0 and names ending in _ are no
variants; Latin-1 .properties keep their rules; files() hands the atlas each variant's
path and name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::random_textures scans etf/random, optifine/random, optifine/mob,
mcpatcher/mob and (with a .properties beside it) textures/entity for numbered
variants and their rules, keyed by the entity atlas's texture names; textures
without their own rules use the name they extend (wolf_tame -> wolf).
paths::files_under walks a folder for files of given endings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each rule reads every entry, so a file of 200,000 rules took minutes; past the first
1024 rule numbers they are dropped (found reviewing the next PR, which reads them on
every pack load).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Names were pushed before the final count check, so a 10 MB list built
hundreds of MB of paths first. matchTiles and connectTiles stop at the cap.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- clamp keeps a NaN value NaN, like Java's x < lo ? lo : Math.min(x, hi)
- round returns Java's int: NaN is 0, results saturate at i32::MIN/MAX
- calls take up to u16::MAX arguments, so long keyframe tracks compile
- tests: laziness values, an expression just under the node cap, the
offset of the too-long error, a 300-frame keyframe, clamp/round NaN
and infinities
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The random hash steps by the golden ratio so the origin's down face isn't
stuck at mix's zero; weights that don't fit the tile count pick every tile
alike; dot products widen to i64; tests pin hand-computed values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_surface_heights(dimension, columns, heightmap)` and `native_read_columns(dimension, columns, heightmap, (below, above))` for server mods, in the level being ticked: a column's heightmap height, or the blocks round it as a palette and indexes with the biome's grass, foliage and water tints, thousands of columns a call. Ported from Stink-O/horizon cbceb3f0e, a3ba97c94 and 20a8a553c.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_edit_blocks(dimension, edit)` for server mods: a palette of block states and filters read as commands read them, runs up columns from a height or a heightmap's, made at once in the level being ticked through `World::edit_runs`, with what `fill ... strict` does for block entities. Millions of blocks a second where `native_set_block` does one: for world generators, terraformers, schematics. Ported from Stink-O/horizon ee55a05e6.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`World::edit_runs` (block_edit.rs): runs of blocks up columns written straight into chunk sections, a section replaced at once (`Chunk::replace_blocks`); the chunks changed that way go whole to the players who see them (`World.bulk_log`). A column a server sends again while it is resident keeps its light and is relit and remeshed only round the cells that changed (`Streaming::replace_remote`, `PalettedContainer::same_storage`, `LightChanges::classes_differing`), which also makes any resent column cheaper. Tests for the runs, the storage comparison and the sections dirtied. Ported from Stink-O/horizon ee55a05e6 and 8820a77ca.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_raycast(distance)` and `native_view_hit()` (the first block along the view, up to 1024 blocks, across the distant terrain past the loaded chunks), `native_held_item()` (the main hand's stack, its custom name and data as SNBT), and `native_claim_items(mod_id, ids)`: a right-click with one of those items is the mod's alone (the server never hears of it), and `native_poll(mod_id)` gives `UseStart` and `UseStop` with the ticks it was held, for a held-to-charge item. Tests for the claims and the event queue. Ported from Stink-O/horizon ae531730c and 896853699.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 6 review: with no block facts, '!stone' no longer holds (as a biome rule with no
biome already fails).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- keyframe: no usize overflow for infinite, huge or NaN k; past the end is the last frame
- parser caps nesting at 64 and nodes at 1024 ("expression too deep/long" with an offset),
so no pack can overflow the stack while parsing, folding or dropping
- programs deeper than the 64-value stack (long keyframe tracks) run on a per-thread buffer
- ifb returns 1/0; nbt(key, exists:false) compiles (raw test skipped, value 0)
- catch falls back on NaN only; min/max return NaN like Java's; round is Java's, exact
- unseeded random() starts each thread somewhere else; unary + parses
- !, && and || lower to comparisons and ifs (Not/And/Or/Bool gone); easings derived from
ease-in and moved with the splines to cem/curves.rs; Expr::slots removed
- bench prints CPU time; Error documented; tests for each of the above
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::ctm::connect: face texture directions (vanilla cube and
the axis-turned cube column), the 8-neighbour mask with innerSeams, and
the tile tables of OptiFine's templates: ctm's 47, ctm_compact's
quarters, horizontal, vertical, h+v, v+h and overlay's 17, plus repeat's
pattern place and a fixed, weighted random pick per position and face.
Shared with other connected-texture formats.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Past the node budget, later parts are left out instead of replacing their vanilla part
with an empty bone.
- Budget: every box costs at least one, the last box keeps the faces that fit, and the
"left out" warning fires only when something was cut (Bake { left, cut, unread }).
- Animation pairs count against the same budget.
- Layers carry a key index and a base's layers already present are not added twice.
- Per-part textures and sprites are noted in submodels too.
- JSON: a high surrogate before a non-low escape gives U+FFFD and the escape is read on its
own; one shared `get`.
- Tests for byte limits, a missing .jpm named twice, own keys over a .jpm's (also in a
base), the budget's edges, surrogates and comment marks in strings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 5 review: run the never-match cases on a named mob in plains, so a lone '!' kept
in biomes or names turns the test red; the crate doc names ETF's docs too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 4 review fixes: '!' with nothing after it skips the rule; an empty weights line
is an even choice instead of dropping the rule; one pass over the rule's keys; biome
patterns through Name::parse; the cap test through max_texture and select.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
matchTiles=ns:a/b now names ns:textures/a/b, as Continuity's tiles do; a
tile outside the resource root fails the file rather than shifting the
weights after it; weights past Integer.MAX_VALUE count 1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 3 review fixes: biomes read ETF's pattern:/regex: forms (matched with or without
the namespace) and their negation; an empty condition never matches; drop a no-op
check and two fixture rules that tested nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Second review round of the .jem reader:
- an entry's keys are layers (own, .jpm, baseId's) over shared objects,
so a big base or .jpm costs nothing per entry; the first entry with an
id is the one baseId finds
- the 1024-entry cap counts every entry, bones count against the face
budget, and a .jem or .jpm over 8 MiB is refused
- the JSON reader takes Gson's = and => for colons and ; between items,
quoted empty keys, and \u surrogate pairs; \u needs four hex digits
- per-part textures and sprites, not drawn yet, get one warning
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Windows a backslash separates folders, so a pack's ..\x would leave
the resource root once joined onto it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A tile written as a full assets/minecraft/ path in another namespace's
file kept that file's namespace.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round 2 review fixes: an nbt path ending in a list index no longer disables another
rule; seedSource=entity is accepted (vehicle skips the rule); health percent no
longer loses a point to f32 rounding; an unnamed entity passes '!name' lists; tests
check the even choice through select instead of private fields.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The tile cap is checked before every token, so names before a range can't
underflow it; a path with a backslash is refused (Windows reads it as a
separator); a height bound that isn't a number fails the rule instead of
opening the range.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes for the .jem reader:
- a small JSON reader keeps keys in file order (animations read what
earlier ones wrote) and accepts what Gson does: a BOM, comments,
trailing commas, single quotes, bare keys
- an entry's own .jpm wins over what it inherits through baseId; each
.jpm is read once
- .jpm paths go through paths::resolve, which refuses ones that leave
the pack; a missing or broken .jpm, or a baseId naming nothing, leaves
that entry out with a warning instead of the whole model
- rotations through an odd number of inverted axes turn the right way
- numbers must be finite; attach and animation values may be strings or
numbers; a file builds at most 1024 entries and 65536 faces
- shadowSize, per-bone textures and attachments are no longer parsed
until something draws them
- CLAUDE.md lists the crate
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes: conditions that don't parse (or nbt/teams/... that aren't read yet) skip
the rule instead of widening it to every entity; negated name lists exclude; an
unnamed entity fails name rules; variant indices stop at 1024; ETF's bare negative
ranges (-64--30) parse; more tests for each condition's failing side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes: a tile range or repeat pattern can't ask for billions of
tiles; a name= that doesn't parse drops the rule instead of matching every
block; weights keep their positions; flags read as Boolean.parseBoolean;
files that aren't UTF-8 are read as ISO-8859-1, as Java does; the path
check is shared with paths::resolve. Tests for load, heights and the
broken values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_optifine::random parses optifine/random .properties rules (textures/skins,
weights, seedOffset and the biome, height, name, profession, colour, baby, health,
moon, time, weather, size and block conditions) and picks a variant from an
entity's UUID-derived seed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Player.attack calls onAttack, which resets the attack strength ticker, for
every entity that passes cannotAttack. Horizon did it for mobs, players and
the dragon, but not for boats, minecarts and armor stands, so on a server
only the client's swing packet reset it: a client that skipped the swing
hit vehicles at full strength every time. A fixed item frame refuses the
hit of a player who isn't in creative, and a cushion that of a player who
may not build, so skipAttackInteraction is false there and they reset too.
server_packet_rules_e2e checks it with two bots that hit a boat each in one
go without swinging: eight hits leave one standing (it breaks on the fifth
without the reset), 24 still break the other. horizon-bots' --attack takes
a UUID, so a bot can hit an entity summoned with one, and --attack-rounds
stops it after N rounds. The harness turns the anticheat off, which would
drop those attacks, and force-loads the boats' chunks.
Fixes#5631
Written with Claude Opus 5.5 in OpenCode.
shaders.properties' `vignette` key (on unless `false`) says whether the game draws its vignette
over the pack's image; most packs turn it off (BSL, Complementary, Bliss, Bloop, FastPBR,
Hysteria...) because they draw their own. Horizon ignored the key, so with the Vignette option on
those packs got both. The game's vignette now draws only when the option is on and the loaded
pack doesn't turn it off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Enderman.teleport(x, y, z) fails for a passenger, repeatedlyTryToTeleport
returns before drawing, and EndermanLookForPlayerGoal.tick skips its
teleports while the enderman rides something. Horizon had none of these
checks, so an enderman in a boat "teleported" every tick (sound and
portal particles) and was snapped back to its seat, which also made its
held block jitter. Fixes#4865.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server's client keeps thrown tridents apart from its other projectiles,
so TakeItemEntity never found them and the trident just vanished instead
of playing vanilla's ItemPickupParticle. Look the trident up there too,
turn it into a pickup particle and draw it on its way to the collector.
The host also sent the pickup only for tridents that gave an item, so a
creative player's trident vanished on other clients; it now always sends
it, as Player.take does in AbstractArrow.playerTouch.
lan_e2e: trident_pickup throws a trident at each player's feet and checks
that it comes back and flies to them on both clients (fails before this).
Fixes#5577
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Java-format packs read both names as zero today. isElytraFlying is the
player's fall-flying state; cloudHeight is the cloud_height environment
attribute (default 192.33, dimension_type visual/cloud_height) when the
dimension's cloud colour alpha is above 0, as in vanilla's LevelExtractor,
and NaN otherwise ("no clouds"). The 192.33 literal of clouds.rs becomes
a shared constant.
Part of #3172.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Options > Graphics > Display: Frame Generation, Off or FSR 3.1 (off by default,
frameGeneration in horizon-graphics.txt). The world is kept at the screen's size
without the hand and HUD, the optical flow and interpolation make the frame
halfway (with mobs' and dropped items' own motion vectors), the hand (at its
halfway pose), outlines, post effects, screen effects and HUD are drawn over it,
and a present thread shows it half a real frame before the real one. Off under
shader packs, HDR, the title screen and blurred menus.
When the present thread gets anything but a frame from the surface (outdated,
lost, occluded, timeout, validation, suboptimal) or dies, the renderer stops it
and acquires the next frame itself, so gfx.rs's normal path handles the surface;
frame generation resumes once that path has a frame again. The F3 screen's first
line adds the frames reaching the screen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The camera's motion vectors see mobs and dropped items as still, so a made frame
would show them where the real one does. The game now tags each mob's and
dropped item's vertices with its entity id (Frame::entity_motion); one whose
model has the same shape as last frame gets each vertex's own motion, drawn into
a texture at the render size where the world shows it (its depth, or a
translucent item's texture). The interpolation takes that texture as
object_motion and uses it over the camera's motion vectors. Nothing runs it yet:
the Frame Generation setting comes next.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep entity portal state with its level so another ticking dimension cannot
clear it. Handle falling-block portals after movement and impact damage but
before placement, preserving cooldowns, saved data and vanilla's End transfer
duplication branch. Extend integrated-server command parity with direct
falling-entity probes and remove its stale in-frame runner override.
Co-Authored-By: Codex (GPT-6) <[email protected]>
A fill visits up to 32768 blocks of a handful of states. It now skips
blocks that are already the block it would place before matching the
filter (place_state would change nothing there), and matches each block
state against the filter once per fill instead of once per block.
Results, counts and side effects are unchanged. Ported from
Stink-O/horizon 242b45037 (its fill part, the same code).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Positional sounds leave 16 static channels free for sounds played to the
listener. Ported from Stink-O/horizon a72b65ea3 (sound_engine.rs, now in
horizon-client-render); that commit's other half, block destroy events
only within 64 blocks, is already here (lan/containers.rs level_event).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Give the pause action its own translated Splitscreen Settings label. Check the transient test override through the visible saved switch rather than waiting for an untouched settings file to be written.
Co-Authored-By: OpenAI Codex <[email protected]>
Rebuild the profiles and saved switch increment on current main after the compositor, controller, windows and profile-page model landed. Preserve pause dialogs and keyboard navigation, keep test overrides transient, and validate profile folder names.
Co-Authored-By: OpenAI Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla 26.3's PhantomSweepAttackGoal.tick bites on a plain box check
(getBoundingBox().inflate(0.2).intersects(target)) with no line-of-sight
or block test, and its targeting gate (TargetingConditions' eye-ray) is
what keeps a pane-hidden player safe. Horizon ports both, so:
- a phantom pressed against the far side of a foot-high pane wall, whose
eye-ray passes over the wall, bites the player pressed behind it, in
Horizon exactly as in vanilla;
- behind a window-height pane wall the eye-ray is blocked and the
phantom never takes the player as a target, also as in vanilla.
Two parity cases pin both sides of that behaviour, mirroring the live
geometry (player and phantom each pressed against their face of the
pane, 0.125 apart, bite reach 0.2). Refs #4928.
A .jem's animations compile with its model when the packs load and run
per mob per frame after the vanilla animation: part transforms and
visibility (this, part, part names, bone ids, a:b:c paths), per-entity
var.* and varb.*, and the entity, render and frame parameters of
OptiFine's and EMF's animation docs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each optifine/cem (or EMF's emf/cem) .jem replaces or joins the parts of
the model layer it names, with OptiFine's part names mapped to the game's
and baby models falling back to the adult's. Built when the packs load
(and on every pack reload, on the reload worker); a mob's draw only swaps
the layer's part tree and, when the .jem names one under textures/, its
texture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The CEM expression language from OptiFine's cem_animation.txt with EMF's
documented additions (keyframe, keyframeloop, easings, splines, wrapdeg,
degdiff, catch, randomb, global constants e and nan), compiled to a flat
stack program with constants folded; if, && and || only evaluate the
branch they take. Variables are caller-resolved slots. The example
cem_expr_bench times an animation pack's frame of 20 expressions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fix for the secrets file (#5095): when the keychain took a
secret but another process had written a newer copy to the file
meanwhile, the newer copy rightly stayed, yet the run stopped counting
it as seen in the file. Once that run stopped asking the keychain and
another process handed the copy over, the run couldn't read it.
The clean-up now says whether the file still has the entry, and the run
keeps counting it while it does.
Test: a launcher on the file renews A during a start's hand-over of A,
the keychain refuses B; after another start hands both over, the first
start reads the renewed A and B.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Main samples only the planned tiles, nearest first: the seam with the
near chunks is fine at once, but the far terrain ends in a hard edge
until the last of ~3000 tiles (Full) is in, minutes on a slow machine.
After the seam, sample a coarse backdrop first: each far tile's
ancestor two levels up (one where that reaches the seam), ~230 tiles,
about a tenth more work. Ready ancestors already stand in for missing
children; now no tile stands in within 3 chunks of the render distance,
so broad boxes never sit beside the near chunks. The horizon is covered
after under 1000 jobs instead of the last one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The summary charged a group with every mismatch whose command text also
appears in it, so two groups that probe the same thing (the orb groups'
`data get entity @e[type=minecraft:experience_orb,...]` lines) each
showed the other's failures. Positions are what the per-group ranges
already are; count by those.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Freeze flight-history interpolation after death and render death rays with
vanilla's depth writes, back-face culling and distance fog. Add a frozen-pose
regression and a vanilla oracle comparison for ray geometry and colours.
Co-Authored-By: Codex (GPT-6) <[email protected]>
The review of #5601 noted the new "experience orb fall" group never
reaches the one path of ExperienceOrb.tick that draws a random: the
push out of a block. This group pins it — a 3x3 block placed over a
settled orb leaves it colliding with only its top face free, so the
server nudges it up (Entity.moveTowardsClosestSpace) with gravity
skipped while its box is inside blocks, and it comes to rest on top of
the block. The draw's magnitude only decides how many ticks the escape
takes, so the checkpoints after it are the same either way; with the
push disabled the orb stays boxed in at y 215 and the group fails.
Corpus with both orb groups: 973/973 commands match.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Review fix for the secrets file (#5095): a start counted what its failed
hand-over left in the file only afterwards, so a hand-over by another
process in between left it reporting the keychain away for those
accounts. The start now counts the file's keys first, and drops each
one it hands over itself.
Test: another process hands the account over right as this start's
hand-over fails; the start still reads it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Review fix for the secrets file (#5095): a launcher that started while
the keychain was away, on a file a previous run left, asked the
keychain again only for secrets it had written itself. Once another
process handed the file over, it reported the keychain away for
accounts the keychain now held.
It now also counts what it read from the file and what its own start
left there. Test: two launchers started on an old file while the
keychain is away (one read the account, one didn't) both read it after
a third hands it over.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
native_living_knockback (NeoForge's LivingKnockBackEvent) runs in
LivingEntity.knockback once the knockback resistance took its share and
before the motion changes, with the pushed entity's id and type, the
strength, the direction (x, z) and a cancel flag; a non-finite value a mod
leaves is put back. In 26.3 every knockback has a DamageSource, so the hook
names it (damage type, causing and direct entity) as
native_living_incoming_damage names the same hit (`Hurting`,
`hurt_server_args`). The API number comes from the fragment
(tools/native-hook-fragments).
It is reached by every LivingEntity.knockback Horizon makes on players
(including LAN guests and a dedicated server's) and mobs: a hit's own push
(dealDefaultKnockback), a sprinting or Knockback hit and a spear
(causeExtraKnockback), a sweep, a goat's ram (RamTarget, `mobAttack`) and a
mob's spear push, which now carry their source in MobEvent::Knockback, and
LivingEntity.blockedByItem's push on a shield's holder, which was
open-coded past it (and past the knockback resistance) in shield.rs and is
now knock_back_player(0.5, ...). A sulfur cube with a block hit by an entity
doesn't reach it (SulfurCube.knockback doesn't call super there). The docs
list the pushes Horizon still makes another way: a mob's melee push from its
attack knockback or a Knockback weapon, an armor stand's, the ender
dragon's.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes for the secrets file (#5095):
- A start's hand-over wrote a snapshot of the file to the keychain, so
a token another process signed out or renewed meanwhile could come
back or be overwritten. Every keychain write now holds
account-secrets.keychain.lock, and the hand-over reads each entry
again under it. The file's own lock still never waits on a prompt.
- MCP secrets_file is true only while the file holds a sign-in: not
for sign-out marks alone, nor for a file that can't be read.
- Tests: a hand-over held on a prompt while a running launcher signs
out or renews the same account; what counts as a sign-in in the file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Parses OptiFine's custom entity model files: models, part, attach, scale,
baseId, .jpm part files, invertAxis, translate, rotate, mirrorTexture,
textureSize, texture, boxes (textureOffset box UVs or per-face uvNorth..
with their aliases, sizeAdd, sizesAdd, EMF's sizeAddX/Y/Z), submodel(s),
attachments and the animation expressions as text. Boxes bake into the
quads ModelPart draws, on vanilla's axes around the vanilla part's pivot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fix for the secrets file (#5095): Layered asked the keychain
while holding account-secrets.lock, so an unlock prompt held up every
other launcher and CLI process.
- Each call reads its file entry under the lock, asks the keychain
with the lock released, then removes the entry only if it still
holds what was read: another process's newer write stays. The start's
hand-over works the same way, from a snapshot.
- Once the keychain refuses or doesn't answer (a dismissed prompt),
the run stops asking it and parks new secrets in the file, until it
finds that another process handed the keychain one it parked.
- Tests: a keychain waiting on a prompt while another process reads and
writes the file, a dismissed prompt asked once a run, and a half
refused hand-over followed by a sign-out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Review fixes for the secrets file (#5095):
- The Tailscale access token administers the player's whole tailnet:
it goes through account::keychain(), the keychain or memory for the
run, never the secrets file.
- MCP account_list says secrets_file: true while sign-ins sit in the
file, beside keychain (sign-ins outlast the launcher).
- launcher/README.md and the sign-out help (docs/launcher-commands.md,
regenerated) no longer say tokens are never in a file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Review fixes for the secrets file (#5095):
- No store is picked once per run any more: on Linux and FreeBSD every
read takes the file's copy first and the keychain's otherwise, and
every write goes to the keychain, to the file only when it refuses.
A launcher that wrote to the file keeps reading, renewing and signing
out after another process hands the file to the keychain.
- Each start hands the keychain the file's secrets one at a time, each
leaving the file once the keychain took it, so a move it half refuses
never leaves a secret in both places, and signing out removes it.
- Signing out while the keychain is away leaves an empty entry that
hides the keychain's copy until it can be deleted.
- macOS and Windows keep the keychain or memory, as before.
- The concurrency test now runs real child processes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Review fixes for the secrets file (#5095):
- Every read-modify-replace holds account-secrets.lock (std File::lock),
so launcher and CLI processes signing in or out at once no longer
overwrite each other; each write uses a temporary file of its own.
- The file no longer sticks once made: at the first start the keychain
answers, its secrets move into the keychain and the file is removed,
so an account kept in the keychain before a start it missed comes
back beside the ones signed in meanwhile. If the keychain refuses any,
the file stays whole and in use.
- A broken file's error names only the error kind, line and column:
serde's own message can quote a string the file holds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
native_data_pack_files asks a native mod, while the server assembles its data
tables, for the pack files it contributes: (path, bytes) in the overlay's form.
It fires once per load — when a world opens, and again on every `/reload` — so
the ask is the mod's chance to recompute them from its config or from the
world. A mod that turns configuration into recipes, tags or loot needs no
content mod beside it and no data pack installed by hand: today neither can
recompute those files for a load.
The files join the load where the world's packs already do:
Game::build_world_tables is both a world's first open and reload_world_tables
(`/reload`, `/datapack`), and the hook is asked there and nowhere else. They
sit below those packs: PackOverlay::top is a path's last file, so a player's
data pack still overrides a mod's for the same path, and a later contribution
overrides an earlier one, as packs layer. The advancement prefetch takes the
same files, so a contributed advancement is there on the first open and not
only after a reload.
With no mod hooking it nothing is contributed: a world with no packs still
uses Resources.rest, and a world with packs builds exactly the tables it did
before.
The hook is API next (tools/native-hook-fragments/data_pack_files.json);
native-hooks.json and NATIVE_HOOKS_API are untouched.
Co-Authored-By: DeepSeek <[email protected]>
Native mod builds only showed in the version picker, so there was no way to delete one by
hand. The Versions page now lists them above the releases, with what each was built on, its
size on disk, and the same Use and Remove (asked twice) as a release. Removing a version a
program still runs from is refused.
Fixes#5326
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Many Linux systems run no Secret Service (GNOME Keyring, KWallet), and
the launcher then kept sign-ins in memory only: players signed in again
at every start (#5095).
Where the keychain doesn't answer, the secrets now go to
account-secrets.json in the launcher's data folder, readable by the
player only (0600 on Unix) and replaced whole through a temporary file.
The keychain stays first; a secrets file from an earlier start stays in
use so a flaky keychain doesn't sign players in and out, and signing the
last account out removes it. HORIZON_NO_KEYCHAIN still keeps secrets in
memory for the launcher's tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Inbox::reset, called on Event::Reconfiguring after clear_lod: requests a proxy's previous backend held are forgotten instead of holding in_flight slots for the 30 s timeout, and the next offer's per_second makes a new bucket.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: lod.rs (main's wanted-tile heap from #4747; a failed tile stays out of the heap until its RETRY passes, then the heap is rebuilt so it is asked again; main's MAX_LEVEL 22) and remote.rs (send_far_terrain and main's tick_chat_session both run before ClientTickEnd).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two games playing in one game folder at the same time (#4952, the launcher's
`play --account`) fought over its per-player files: one `options.txt`, one
`logs/latest.log` (the second game logged to the terminal only), one generated name in
`horizon-player.txt` (two offline players got one UUID) and one `servers.dat`.
`horizon-client --player-dir DIR` puts this player's own files in DIR: `options.txt` and the
settings files beside it, `logs/`, `crash-reports/`, `horizon-player.txt`, `servers.dat`,
`hotbar.nbt` and `screenshots/`. Its players still share what the game folder holds: saves,
resource and shader packs, mods (the enabled list included, `logging::shared_dir`) and native
mods' `config/`, which no longer follow `options.txt` but the game folder (`Game::game_dir`).
Without the option nothing moves.
Launch API 0.1.1 promises the option (docs/api/launch.md).
Tested: two games started at once in one folder with --player-dir players/Alice and
players/Bob each logged to its own players/<name>/logs/latest.log and saved their worlds in
the shared saves/; a third, without --name, kept options.txt, horizon-graphics.txt and its
generated horizon-player.txt in players/Carol/.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clicking a cauldron predicted nothing: the remote useItemOn dispatch had no
cauldron arm, so the click fell through to Item.use while the hand still held
the water bucket. BucketItem.use then emptied the bucket into the face that was
aimed at, which is the water block that showed for a couple of frames before
the server's cauldron state arrived, and the water left in the air when a full
cauldron was emptied into the bucket.
remote_cauldron_reacts now runs AbstractCauldronBlock.useItemOn/CauldronInteractions
on this client's level through a dry copy of the hand, like the host's
use_on_cauldron: the cauldron's new state and the sound it made are predicted
when the item is one the cauldron takes, and a Pass still leaves Item.use its
turn. Cauldrons are also hand operated blocks, so a guest's predicted cauldron
use forwards its sound to the other players the way doors and levers do.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
docs/native-mods.md keeps main's API 58 notes, then the API mods section.
With the game module in the horizon-client library, its unit tests ran in the
library's test process and shared process-wide state with the library's tests
(health::HURT_SERVER, the sulfur cube archetypes): six tests failed. The game
module now builds into the library only outside tests; its unit tests stay in the
executable's test build, as before the move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: game.rs keeps the api_mods export beside main's narrator module;
horizon-threads takes main's windows-sys features (the same Windows fix);
native-mods.md keeps main's API 47-57 notes, then the API mods section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A native bundle with [api] (crate and entry) and no [[injection]] is an API mod: its
crate implements horizon_mod_api::Mod and the game asks it right after the item and
block hook points it is named after, once the mixin mods there are done.
- horizon-mod-api: the Mod trait (use_item, use_item_on, right_click_block,
left_click_block, destroy_block, place_block, tooltip), Action and Host.
- horizon-client: the game module moves into the library and the executable only adds
the allocator, so a build can link the game from another package; game/api_mods.rs
dispatches, and a mod turned off in Options > Mods is skipped with no build.
- launcher: API mods skip horizon-mixins; their crate and a generated native-game
package build the executable, so horizon-client never depends on a mod.
- horizon-mods: the manifest's optional api field and its checks.
- docs: native-mods.md "API mods (no mixins)", stability.md; example in
launcher/tests/fixtures/native-api-demo.
- A sampler named like a function (Bloop's `uniform sampler2D texture;` read as
`texture(texture, uv)`) is the function where it is called, and the sampler elsewhere.
- A local named like a sampler, or like a sampler parameter, is in scope only after its
initializer (GLSL 4.60 4.2.2): `float depth = texture(depth, c).x` still samples the parameter
`depth` (Potato's depth-aware blur).
- A parameter's `const` (`const in float x`), which naga's parser refuses, is dropped: it only
promises the function doesn't write the parameter.
- naga's frontend nests arrays of arrays the other way round (`float x[3][2]` comes out as 2
arrays of 3; also `float[3][2]`, struct members and parameters), so indexing reads the wrong
element or fails validation. Two or more dimensions in a type are written reversed, so the
frontend's reversal restores GL's order; accesses are untouched.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
boot_paint.rs draws the boot screen with GDI (Win32_Graphics_Gdi) and reads the
window's size (Win32_UI_WindowsAndMessaging), which horizon-threads didn't turn on.
The forged reply was rejected on its empty biome palette and out-of-range
spans before the cap; it now names one biome and keeps every span inside
HEIGHTS, and asserts the cap's error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes (kacpherek on #4947):
- a refused, lost, timed-out or unreadable tile comes back as no columns,
and Lod asks for it again 2 s later instead of keeping it empty;
- sampling threads queue their requests and the game thread sends them
each tick with its other packets, so they no longer take the
connection's writer; requests queued when the far terrain ends are
dropped with their slots;
- docs and the e2e: horizon-lod-distance is off unless set.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The last step: a Horizon client of a dedicated server or of a LAN world
(not of its own server thread, which hands its generator over directly)
draws far terrain when the server offers it. Its far terrain's samplers
each send one tile request and wait for the reply, which the network
thread hands straight to them (lod_net::Inbox), keeping to the server's
limits (requests waiting, tiles a second) so a well-behaved client is never
refused. LOD Distance stops at the server's distance. A new dimension, a
reconfiguration or a disconnect cancels the waiting samplers at once.
tools/server_lod_e2e.py checks it on a real dedicated server: with
horizon-lod-distance=16, a client with LOD Distance 32 gets the server's
far terrain, capped at 16 chunks, and draws far tiles; with 0, none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes (kacpherek, dimaniojk on #4946):
- the worker hands finished replies back to the game thread, which sends
them before the tick's flush_all, so they keep their place among the
game's packets;
- a slot frees when the reply leaves, and replies wait while the player's
connection has more than 1 MiB pending;
- every request takes a token, refusals included; unreadable or over-rate
requests are strikes, and 64 strikes disconnect the player;
- a panic while sampling drops that level's sampler and refuses the tile,
so every job still gets a reply;
- only clients with the horizon brand (the ones offered it) are served;
- a level with no far terrain refuses instead of leaving the client waiting.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second step: a dedicated server, or a LAN world, offers far terrain to
Horizon clients while they configure (vanilla clients hear nothing) and
answers their tile requests. `horizon-lod-distance` in server.properties
(Horizon's own key, 64 chunks by default, 0 off) bounds how far; each
player may have 8 tiles waiting and ask for 64 a second (a burst of 256),
and a tile past the distance, over the limits or of the wrong shape is
refused at once. One "Far terrain server" thread, started at the first
request and behind the ticks (horizon_threads::behind_server), samples
them from the player's level for every player in turn, and sends each
reply to its player itself. Clients don't ask yet (the next step).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon-lod-distance now defaults to 0: far tiles reveal the generator's
terrain beyond view distance, which helps crack a seed. The offer starts
with a protocol version. A reply over 512 KiB or 16 384 spans, with spans
out of order, overlapping, empty or out of the height range, is an error
before it allocates; the server refuses such a tile instead of sending it.
(Real tiles measure 4-6 spans a column.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first of three steps that let a Horizon client get its far terrain
(LOD) from a dedicated server or a LAN world, whose generator and seed it
doesn't have: the offer a server sends Horizon clients in configuration,
the tile request, the reply (columns with biomes by name, block states by
id, or a refusal), the per-player check of a request's distance and shape,
the token bucket both sides pace with, and the `horizon-lod-distance`
property's reading (64 chunks by default, 1024 at most, 0 off). Nothing
sends or reads them yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Right past the render distance, far terrain showed as big flat-topped
boxes with grey stone sides: coarse ancestors standing in while the
sampling order filled a ring ahead first and refined broad tiles every
other job. Main now samples only the plan's leaves, nearest first; this
pulls that order into `sample_order` (no change) and tests it:
- At render distance 12, cells are 1 block wide to 256 blocks out and
2 to 512 (Reduced: 1 to the near terrain's edge, 2 to 256).
- Loading one job at a time, the ring within two chunks of the near
terrain reaches that detail all around after 270-301 jobs, all fine
tiles (7500-8500 density lattice points). The old order took
54000-119000 for the part in view alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 22:53:01 -06:00
908 changed files with 111268 additions and 7119 deletions
- Contributors first: `gh pr list --state open --search <area>` before you start; never duplicate theirs.
- **No hardcoded user-facing strings.** Keep UI translation-ready from the start. In the launcher (`launcher/`), wrap visible text with `tr("...")` or `trf("... {arg} ...", &[("arg", &val)])` from `crate::i18n` instead of raw string literals or `format!`. In the game, use translation keys (`Text::tr`, `lang::get`), never hardcoded English strings.
- **Claim before you start**: comment `/claim` on the issue (no issue yet: open one, then claim it). An assigned
issue is taken: pick another. `/unclaim` when you stop; a claim with no open PR linking it is released after 5 days.
@@ -39,7 +40,8 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| Crate | Owns |
|---|---|
| `horizon-client` | The game: a lib (`src/lib.rs`, ~100 flat modules: gameplay, networking, title screens) and the bin (`src/main.rs`→`game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) |
| `horizon-client` | Gameplay library (`src/lib.rs`: `game.rs`+`game/`, `entry.rs`) and thin executable (`src/main.rs`). Also the dedicated server (`--dedicated`). Re-exports `horizon-client-lib` for existing mod paths. |
| `horizon-client-lib` | Reusable client systems, `horizon-ctl`, probes and their tests. Its manifest points to the existing sources under `crates/horizon-client/`; the library root is `src/client_lib.rs`. It never depends on gameplay. |
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
@@ -56,6 +58,7 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| `horizon-friends`, `horizon-relay` | Play with Friends: invite codes, encrypted tunnel, the relay ([docs/play-with-friends.md](docs/play-with-friends.md)) |
| `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-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 |
@@ -83,8 +86,8 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
```sh
python3 tools/pr_ready.py # the self-check: fmt, clippy -D warnings, touched crates' tests, harnesses; paste its block in the PR
python3 tools/pr_ready.py --quick # same without harnesses
cargo test -p horizon-client --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo test -p horizon-client -p horizon-client-lib --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client-lib --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo build --profile fast -p horizon-client # optimized, quick to link: use it to play or run lan_e2e
python3 tools/parity.py --changed origin/main # the harnesses your branch can affect (CI's parity job)
python3 tools/parity.py --changed-files paths.txt --list # which harnesses cover these files, without running them
@@ -101,8 +104,11 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
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.
- Probes build into `.local/parity-target/`; harnesses find binaries via `lab.cargo_target()`, never `target/`.
-What a change reruns (of 329 harnesses, measured 2026-10-23): a file of the game binary (`game.rs`, `game/`)
~25, a file of the clientlib ~103, of `horizon-world` ~103–109, of `horizon-core` ~119. Plan for it.
-Harness selection uses Cargo dep-info and reachable-code narrowing (`tools/parity_reach.py`).
`horizon-client-lib` builds the control tool and client probes without gameplay; their dep-info
excludes `game.rs` and `game/`, so gameplay edits do not rebuild or select those probes.
Source paths remain in `crates/horizon-client/`. Measure actual harness selection on refreshed
records with `tools/parity.py --changed-files paths.txt --list`; historical counts are not a guarantee.
@@ -159,7 +168,7 @@ the same `horizon_client::<module>` paths), `G:` = bin (`crates/horizon-client/s
- `struct Game` (game.rs, ~290 fields) holds everything. Its methods are spread over ~180 files: game.rs keeps
the core (tick, render, input, inventory, world open/close) and each `game/<feature>.rs` adds an
`impl Game<'_>` block for one feature, with its own state types. Fields are private to the bin.
`impl Game<'_>` block for one feature, with its own state types. Fields stay private to the gameplay module and its descendants. Gameplay tests run in the gameplay library, separate from reusable-library tests.
/// The English description: the fallback of [`Item::description_text`].
pubfndescription(self)-> &'staticstr{
matchself{
Item::Fullbright=>"Show blocks and entities at full light, even in dark areas.",
@@ -450,9 +551,15 @@ impl Item {
Item::OwnNameTag=>{
"Shows your name above your head in third person, as other players see it. Hidden when the server hides your team's names or you're invisible."
}
Item::HideBlockParticles=>{
"Hides the debris particles that burst from a broken block and crumble off the one you mine. Other particles stay."
}
Item::AutoReconnect=>{
"Rejoins a server after a lost connection or a restart, never after a kick or ban."
}
Item::UiSoundReserve=>{
"Sounds in the world leave 16 of the 247 sound channels free, so a flood of them (a big explosion) can't silence menu clicks."
}
Item::MouseTweaks=>{
"Right-drag places one per slot, left-drag gathers the held item, Shift + left-drag moves every slot."
}
@@ -465,12 +572,19 @@ impl Item {
Item::SortKey=>{
"Sorts the part under the cursor. Click, then press a key; Escape unbinds it."
}
Item::ChatAnimation=>{
"A new chat line slides in from the left while it fades in, instead of appearing whole."
}
Item::ChatIndicator=>"The coloured bar at the left of a chat line.",
Item::NoAi=>{
"Hides and turns off every AI feature: agent control, horizon-ctl and the MCP servers. The launcher's switch too."
}
Item::DiscordPresence=>{
"Shows Playing Horizon on your Discord profile while Discord runs: menus, single player or multiplayer, the dimension and the time played. Never a world's name or a server's address."
}
Item::Splitscreen=>{
"In a world, another controller can join as a new player: press A when it asks, then pick a profile and where to play."
}
Item::AgentControl=>{
"Your own AI agent can play this game through horizon-ctl or an MCP client, on this computer only."
Loaded 100 of 908 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.