🤖 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.
🤖 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.
🤖 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.
🤖 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.
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]>
🤖 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]>
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]>
🤖 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.
🤖 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.
🤖 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.
`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]>
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]>
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.
- 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]>
🤖 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]>
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]>
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_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]>
- 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]>
🤖 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.
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
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]>
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]>
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.
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]>
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.
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]>
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]>
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]>
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]>
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.
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]>
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]>
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]>
🤖 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]>
🤖 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.
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]>
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]>
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]>
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.
🤖 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
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]>
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]>
🤖 Agent: fixes the Windows-only account test flakes that blocked launcher 0.2.33 (per-store account lock). Verified: windows-latest green twice on this head (runs 41300356019 push, 41300386770 dry run: 352 passed), Mac mini account tests + clippy clean.
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]>
Every unit test shared the process-wide account operations lock. On Windows a connection
to the tests' closed port (127.0.0.1:9) takes about 2 s to be refused, and tests that hold
the lock meanwhile made another test's status read answer "busy" after its 3 s wait
(Retry instead of SignInRequired), or starved a 5 s lock_within behind blocked waiters.
The in-process lock now comes from SecretStore::operations: the system keychain keeps the
one process-wide lock (one keychain per computer), a MemoryStore has its own, as its
secrets are its own. The launcher uses a single store, so nothing changes for players.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
So a flag can be found again in the world and played there (#5879: water
flags that don't reproduce in the harness).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
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]>
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
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]>
Every merge cancels the parity-main run before it. In a busy hour of merges, the
last finished run fell out of the 10-run window, green_main_head found nothing,
and no Nightly went out for 14 h (2026-11-01) although parity-main had passed
on bd8c53bfa.
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]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5830 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5819 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5753 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5681 has.
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
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5663 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5652 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5658 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HudAtlas walked only a fixed set of texture folders, then skipped
minecraft: file sources whose sprite name matched the texture path,
assuming the walk had packed them. Pack folders such as custom/ never
got a sprite, so item models that used them were invisible.
Keep the have.contains skip, and only treat matching File origins as
already packed when they sit under a walked folder.
Fixes#5720
Co-Authored-By: Grok 4.6 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5824 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5814 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5827 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5619 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5590 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pr_ready --quick all green (1671 horizon-client tests, clippy, fmt); new replay test + 16 existing pass; anticheat_e2e --check prediction: honest client never flagged, cheating bot flagged 13x and sent back 12x.
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.
Until the client says it loaded the level (at join and after a respawn), the
ticks it ends with no move are excused: it is on its loading screen, where it
doesn't move, even once the server stops waiting for it after 60 ticks. Its
first move counts as any other.
Fixes#5849.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
parity-main fails on dedicated_server now and then: "+play-start
minecraft:update_attributes". #5597 held the join tick's dirty attribute
instances for one tick, because vanilla syncs them on the next tick, after
the first chunk batch. But this server sends no batch before placed_at + 2,
and only once the player's chunks are ready: on a loaded runner the first
batch came after that next tick, so the attributes went out before it.
They now wait until a batch has been sent (unacknowledged > 0, or the
client already acknowledged one), which keeps vanilla's order however long
the chunks take.
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]>
The gameplay facts load parses its 56 MB file twice: a validation pass that
only keeps the JSON errors, then the typed decode. The validation now runs
on a scoped thread while this one decodes, and is joined before the load
returns, so its error still wins. With a single worker (`parallel` false)
the two passes run one after the other as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The window now hands a key's characters to the character hook instead of
the key, so `world_options_key` must not read the missing text as a
backspace: only a key that carries text types, and a `None` stays the
control socket's `\b`.
The character hook fires after the key's screen effects, as GLFW's
separate char callback (`KeyboardHandler.charTyped`) comes after its key
callback, and its characters survive a cancelled press, as NeoForge's
`CharacterTyped.Pre` comes after a cancelled `KeyPressed.Pre`.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Code <[email protected]>
GLFW's char callback comes after its key callback, so the character
hook now fires once the screen has acted on the key, on the screen open
then: a key that closes the screen reports no character, and one that
moves the focus types into the box it moved to. Options keeps its text
on the key, as its key path routes it between boxes and key binds.
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]>
LivingEntityUseItemEvent.Start may change a use's duration, which
start_using_item keeps in use_duration (vanilla's useItemRemaining). The
first person pose asked getUseDuration again each frame for the remaining
ticks, and a server's client counted a food's crumbs from its consumable
time: both now count from use_duration, as getUseItemRemainingTicks does.
A use the server's flags start on the client takes getUseDuration, as
onSyncedDataUpdated does.
The fragment carries the catalog's neoforge, side and cancellable keys,
which the stamp tool now copies.
Co-Authored-By: Claude Opus 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]>
The content root appended each mod type's entity-types.json row by parsing
and rewriting the table, which needed serde_json's float_roundtrip to keep
a zombie's movement_speed to the bit. Cargo unifies that feature over every
crate in the game build (worldgen JSON included), while horizon-world's
parity probes, built alone, would parse without it. The rows are now
appended as raw text copied from the template's row, so no vanilla float
is reparsed and the feature goes.
The save test compared p.kind by value, then borrowed p: ProjectileKind
isn't Copy.
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]>
The stub moves next to native_item_bar: #5804's block highlight took the
spot after native_tick. The docs said the batch uses screen coordinates;
it takes the same GUI units as x and y (draw_slot_item fills there too).
They now also list the other screens that draw item icons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stub moves next to native_camera_shake: #5804's block highlight took
the spot after native_tick. Its docs no longer say mods can draw their own
hand geometry: the hook passes no vertex buffer, pose or light.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla saves Abilities.flyingSpeed with the player, so a speed a server
mod set now goes into abilities.flySpeed and comes back on the next join;
a file's own speed is read. Every abilities packet comes from one
Guest::abilities, so the join, a game mode change, a respawn and a level
change all carry it (tested with the save round trip). The fragment is
renamed to native_motion for both helpers, FLYING_MAX says why 50 and what
the movement checks do with a fast non-owner, the guest comment names
the server mod, the fork wording goes, and docs/native-mods.md gets a section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A key press a mod cancelled hands the characters no mod cancelled to the
text box, as vanilla's separate charTyped does; the control socket's
type \b no longer offers its backspace sentinel as two characters, and
its cancelled tap sends the release the docs promise. The key and
character hooks' docs name the control socket and say a key that closes
the screen reports its character there first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_perform_console_command hears a command a mod registered when it is typed on the
dedicated server's console (level 4, no player), before vanilla's dispatcher; the answer goes
to the console through native_feedback. The single player owner's console is not covered.
`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]>
Since #5100, turn_to_base_block (a dirt path or farmland turning to dirt)
emits a push_entities_up block event, which the block-update probe printed
as "X push_entities_up". Vanilla's Block.pushEntitiesUp only moves the
entities in the new block's box, and these cases have none, so the oracle
logs nothing: the dirt_path case differed and parity-main failed. The probe
now skips it, as it skips the other events that need someone there.
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]>
Renamed from native_get_clock (no other helper has a get_ prefix). Both fields are i64 ticks as
Level.getGameTime and getDayTime return them, without the clamp to 0: the day time counts on past 24000
and /time set can make it negative, and the docs say so. Documented as the shown world's clock, for
client mods.
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]>
🤖 Agent: playhorizon A/B, 20 bots, 3 rounds: tick_lan 4.22→3.96 ms (−6 %), MSPT 25.65→25.32. Same bytes (new test vs from_values). fmt + clippy -D warnings pass (Mac mini); workspace tests 3982 pass (playhorizon); the 2 Python failures there are its VSCREEN env, they pass on the Mac mini.
A native mod can now send a particle of its own type, drawn by every client
from the sprite set the mod's pack ships, instead of borrowing a vanilla one.
The type is the mod's namespaced name in ParticleOptions::named ("mymod:spark");
the client builds it as vanilla's SimpleParticleType with a sprite set: a
plain billboard, no tint, 20 to 40 ticks, on the engine's own random. The set
comes from assets/<namespace>/particles/<name>.json, the file vanilla's
SimpleParticleType reads; its textures resolve as particle/<texture> in the
atlas, the same rule vanilla's own particle JSONs use.
On the wire a mod particle is Horizon's own extension: the sentinel id
MOD_PARTICLE_ID (-1, never a vanilla registry id) followed by the string,
which vanilla's Particle codec has no room for. A client that does not know
the mod ignores it like any unknown particle, and native_send_particles
refuses an unnamespaced name.
native_send_particles keeps its signature; a mod builds the options itself,
so no hook point moved. NATIVE_HOOKS_API is 22 for the new capability and
docs/native-mods.md says what is supported.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[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.
LevelTicks and ChunkTicks hash a (type, position) key at every schedule,
poll and dedup check, and the chunk map at every lookup, with std's SipHash.
They are only looked up, never iterated in order (the keys of an area are
sorted), so they now use the Fx hasher, which moves from
horizon-client-render to horizon-core (re-exported under the old path).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
🤖 Agent: #5786. Auto-jump now in ServerboundPlayerInputPacket as vanilla's keyPresses. Staircase walk with autoJump on, same server: 75 prediction flags with main's client, 0 with this one. pr_ready --quick all green on 7a8a97dd5.
LocalPlayer.sendChanges sends input.keyPresses, which an auto-jump's makeJump
sets to jump for that tick. Horizon sent the keys as held, so with Auto-Jump on
the server saw jumps its keys didn't allow: the anticheat's prediction flagged
every auto-jump and the 0.4 a tick it left (75 flags on a staircase walk, none
with this change). The official server's log has the same lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5725. One prediction flag per slip across instead of 10-20 (87% of live lines continue an earlier slip). Strict cheat tests unchanged; new tests for a missed push (counts once) and a kept-up 0.05 speed lie (35 of 40 ticks count). pr_ready --quick all green on 7a8a97dd5.
Leads dropped through break_leash without telling native_loot_drops,
so a mod couldn't see or change the lead a player gets back, nor the
one a leashed mob drops when a player kills it.
Untying a lead, cutting it with shears (on the mob or its fence
knot) or re-leashing a mob from another holder now passes it through
the hook as a use (break_leash_used: the leashed mob, the acting player).
A mob a player kills while leashed records its killer (lead_killers);
the corpse drops its lead on its next tick as vanilla does
(Leashable.tickLeash), as a call of its own naming that killer. A lead
that snaps or loses its holder names no player and drops as before.
The API number comes from the lead_drops fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
87% of the official server's prediction flags (6,002 of 6,900 lines on
2026-11-01) continue a divergence that started on an earlier tick: once the
replay's velocity is off (a hit it missed), the offset dies out by the air drag
(0.91) or ground friction each tick, and each of those ticks flagged again.
The first tick off after ten that agreed now has its follow-up tick try the
velocity the client was left with (across only, at most 0.5), as another
candidate. A single slip then counts once; a blink leaves the velocity alone; a
tick that agrees only thanks to it never becomes the setback point. Not up and
down: the vertical drag (0.98) would let a slow fly pass for a slip.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5775. Official server crash 05:15Z (spawn worker WouldBlock, no save). Worker spawn failure non-fatal; connection threads capped at max_players+64. New flood test fails without the cap, passes with it; pr_ready --quick green apart from one ENOSPC test failure that passes alone.
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]>
A dedicated server crashed on the official server (2026-11-01 05:15Z) when the
system refused a new thread: Streaming::new's .expect("spawn worker") panicked
and the process died without saving. Worker spawn failures are now logged and
the pool runs with the workers that started.
The server starts a thread per accepted socket (vanilla's Netty uses a few for
all): past max_players + 64 live connection threads, a new socket is closed at
once, so a flood of silent sockets can't use up a container's thread limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section's first encoding unpacked its 4,096 block states, then built a
palette from them again (Paletted::from_values). For a list palette the
stored ids are now renumbered in first-seen order instead: the same palette,
indices and bytes, without the values. Global palettes keep the old path.
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.
tick_creature_entities takes the mob list, ticks it, and appended it back
into the emptied list: a new buffer and a copy of every Mob each tick (and
the old buffer freed). With no mob added meanwhile the list now goes back as
it is. The dead-mob set and the guest bindings, looked up per mob each tick
and never iterated, use FxHash instead of SipHash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5725 step 2. Guest item use (eating, bow, shield) now slows the replay as it slows the client. Vanilla 26.3 client walking/sprinting while drawing a bow 3 min: 503 prediction flags → 2 (0.045). pr_ready --quick all green on 3bc251ea5.
`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]>
An item in use (food, a bow, a shield) slows the client to its
use_effects speed (0.2 for most) and stops its sprint. The replay plays a
guest on the server's copy of its body, which takes the item use from
Game.sync_player_body, and that runs only for the window's own player: a
guest's copy never had one. So the replay walked a guest who was eating
or drawing a bow at full speed. That put it 0.8 of a walk (0.173 blocks a
tick) or of a sprint (0.224) off, every tick of it. 0.173 was the official
server's most common prediction flag.
guest_replay now sets the copy's item use speed and its no-sprint flag
from the item the guest is using, as it already did for the glider. A
vanilla 26.3 client sprinting and walking while drawing a bow for 3
minutes: 503 flags before, 2 after (0.045 blocks).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mimalloc backs the game's memory with transparent huge pages where Linux
allows them; many are only partly used. low_memory = true starts the game
with MIMALLOC_ALLOW_THP=0: 60 MB less idle and 190 MB less at 20 players,
for ~1.2 % more tick time (playhorizon, 20 bots, 5 rounds). Off by default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_loot_drops heard block breaks and mob deaths only, so a mod
couldn't tell, or change, what a player's shears, brush or hit
dropped, nor who caused it. An auto-pickup or a global loot modifier
needs those too.
The player-use drops now pass through it, with the acting player
named and the new NativeLootDrops::used set: shears on a sheep, a
mooshroom, a snow golem or a bogged; a brush on an armadillo or a
suspicious block; shears carving a pumpkin; picking sweet berries or
cave vines; and a player's hit taking an item frame or its item. One
helper, native_use_drops, builds the context. The berry and cave vine
drops are popped inside the block behaviour, so their run of spill
spawns is tagged and retold when the spill flushes (retell_runs),
keeping each stack's spot and the random draws when no mod changes it.
The API number comes from the use_drops fragment.
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.
Block light has a brightness but no colour, so a pack can't make a soul torch blue. Section meshes now
list their light sources, and a 3D grid around the camera holds the colour reaching each cell, read
through block_light_color. Block light keeps its own brightness, which walls stop, so the grid only
tints it and needs no occlusion of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Since #5641 a saved snowball, egg or ender pearl loads as a live projectile,
but NoGravity was dropped: the scene's floating snowball and pearl fell into
the floor and were gone before the save. Entity.getGravity is 0 with
NoGravity; the projectile now reads it, honours it and writes it back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod could cancel an experience orb in native_entity_join_level but
not read its value first, nor give experience to anyone. An
auto-pickup or a machine that pays out experience needs both.
native_experience_orbs(dimension, min, max) reads the orbs in a box,
as native_item_entities does for items. native_give_experience(uuid,
points) gives points as an orb a player touches does: Mending repairs
first (repairPlayerItems), then giveExperiencePoints with the level-up
sound. It reaches any player from any server hook, through as_guest
for a guest, and refuses one swapped out by another player's hook.
native_give_levels(uuid, levels) adds levels or, negative, takes them
off (giveExperienceLevels), as the anvil and enchanting table charge.
Both go through one dispatch, native_as_player.
The API number comes from the give_experience fragment.
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]>
🤖 Agent: log-only detail for prediction lines (#5725 step 1); unit tests for the worst tick's offset/state; vanilla client on a dedicated server logs the new detail; pr_ready --quick all green on 21a12582c.
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]>
🤖 Agent: launcher on GPUI Fast (Apache-2.0; fixes the NVIDIA/blade crash #3812); macOS text fixed (font-kit) and checked against main by screenshot; launcher tests + clippy pass on macOS; approved as code owner.
- 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]>
Without the feature gpui-fast-platform has no font source on macOS: the
launcher drew no text at all and half its home page was blank. With it,
the home page matches main's GPUI build.
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]>
A prediction line said only how far off a move was. On the official server
it logs about 15,000 lines an hour, from ordinary players (mostly 0.1 to
0.2 blocks) and cheaters (1 to 10 blocks) alike, and nothing in a line
tells a glide, water, a ladder or a lag from a cheat, so the check can't
be trusted to act.
Each line now ends with its worst tick as the replay played it: where the
client said it went less where the replay ended (x, y, z), how far the
client said it moved, and what the replayed body was doing (ground or air,
water, gliding, sprinting, sneaking, swimming, flying, riding). Only
flagged ticks pay for the text; the state is a few bits per played tick.
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_push_player([x, y, z])` on a client, added to the local player's velocity as an explosion's knockback is (the window's player moves itself, as vanilla's client does), and `native_set_flying_speed(player, speed)` for server mods, a player's creative flying speed kept as vanilla keeps its abilities (a respawn or a game mode change keeps it). A test for the push. Ported from Stink-O/horizon 63ea5c582 and 848a0686c.
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]>
`native_get_clock()` gives a client mod the world's game time and time of day in ticks (none on the title screen), as `Level.getGameTime` and `getDayTime`: for anything timed to the day, the sun or the world's own clock. Ported from Stink-O/horizon's `game.get-clock`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A light job for a column of the last ring reads its neighbours past the
area as views of their decorated proto chunks. The server thread built
those views itself while it scheduled light (~8 columns per tick at 20
players). The proto chunk is now checked out to a worker, like a save,
which builds the view; the light job is scheduled once it is back.
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]>
🤖 Agent: merged main. Fox trust list, save/load, breeding and egg-on-fox trust (rdbtCVS's fix, approved). pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
🤖 Agent: merged main. Canceled hits forget their attackers on every damage path (PvP, /damage, thorns, dragon, blast, potion); test. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
🤖 Agent: merged main. ResolvableProfile texture patch drawn for heads, read from the active resource root, reloaded on pack change. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
The fallback for roots no command Target stands for checked only orbs after
the canceled-orb fix; end crystals and cushions also take an id at once
(rdbtCVS's re-review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The native-mods PRs each add a section to docs/native-mods.md at the same
place; holding the one ahead lets the series merge in one pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
# crates/horizon-client/src/game/native_hooks.rs
# docs/native-mods.md
The native-mods PRs each add a section to docs/native-mods.md at the same
place; holding the one ahead lets the series merge in one pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
# crates/horizon-client/src/game/native_hooks.rs
# docs/native-mods.md
The native-mods PRs each add a section to docs/native-mods.md at the same
place; holding the one ahead lets the series merge in one pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
# docs/native-mods.md
🤖 Agent: merged main. BucketItem.emptyContents in the Nether: sendParticles(LARGE_SMOKE, corner, 8, 1,1,1, 0, ALTERNATIVE) now reaches clients; ALTERNATIVE spread added. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
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]>
🤖 Agent: merged main. MinecartSpawner: BaseSpawner ticks at the cart, saved/loaded, drops and picks as a minecart, entity event broadcast. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
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]>
Include GitHub and local native bundles in both installed-mod views, retaining source/update actions and the existing build confirmation.
Co-Authored-By: Codex (GPT-6) <[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]>
ShelfRenderer passes its block entity as the ItemOwner (at the block's
centre, turned by its facing), so a compass on it points to its target;
without one it always points the same way. The client's copy of a block
entity has no item slots: the docs say to send the stacks drawn in the
update tag.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dropped_item_geometry resolved and meshed every item within 64 blocks,
even behind the camera. Apply EntityRenderer.shouldRender's frustum test
(box grown by 0.5) first, as mobs already do: ~0.2 ms -> ~0.01 ms per
frame with a few dozen items around.
draw_sections left a translucent sorted-index page bound, so the cloud
draw after it indexed past its end and wgpu aborted on the first frame.
Rebind the shared quad indices when a sorted page was used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
draw.keep() in native_block_entity_submit keeps what that call drew,
relative to the block as a sign's text is kept, and the block is drawn the
same again without calling the mod until its state, its block entity, its
light or the resource packs change. Idle machines then cost a copy moved to
the camera, not a call into the mod, each frame.
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]>
#5656's falling motion (-0.3015 against vanilla's -0.2277) is a tick of
gravity more, not a change in the game: main, and the exact tree CI
failed on (#5502's head merged into 76dc5e3bd), pass every time on a
quiet machine. On a loaded one the server catches up with two ticks for
one of the jump's moves (seen: "the server ran 2 ticks for move 7"), and
the server's copy of the player falls in the tick with no move. Before
the jab that changes the motion the jab sends.
LocatorBarClient's arc now records an error when that happens up to the
jab, so the failure says why, and lunge_motion joins known_flaky so the
runner gives it its one retry.
Co-Authored-By: Claude Opus 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]>
The Build button moved from Settings > Native mods to the Mods page, but the
message after installing a mod from GitHub still sent players to the old
setting (reported on Discord, 2026-11-01). Updated in English and the ten
translations.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mob_attributes asked the attribute catalog for movement speed (by name)
and recomputed its base value up to three times per mob and tick, on
top of the registry id built with format! per attribute (Attr::id, the
previous commit). The speed instance, the baby and the sprinting
modifiers now share one lookup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The type's default of an attribute (base_value, read by mob movement and
every tick's entity tracking) and the LAN attribute sync built the id
"minecraft:<name>" with format! on each call: for a server's 2000 mobs that
is several allocations per mob per tick. Attr::id returns the id as a
static string.
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]>
Noise, lookup tables and the like had to be computed in WGSL. A pack can now ship up to eight PNGs,
which the check decodes and every stage reads as <id> and <id>_sampler.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
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]>
An item stack drawn under a block entity's pose as a campfire's, and a block
state's property (its facing) read without a world lookup. Helper-only, with a
fragment of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: unit tests for the split choice and resplit; HORIZON_SPRITE_SPLIT=512 renders the same world as the default on the Mac mini; pr_ready --quick green. Merging after verify + parity.
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]>
🤖 Agent: merger STOP was a conflict with main after #5571 landed; main merged (clean). clippy -D warnings, the native hook tests (55) and rustfmt pass on it; the rest was proven in union3 with #5571.
A GPU with 512 layers per texture array refused to start ("GPU supports 512
texture layers; 1291 required") although terrain binds 8 sprite arrays. The
split is now the smallest of SPRITE_SPLIT, HORIZON_SPRITE_SPLIT and the
adapter's max_texture_array_layers (one less on OpenGL when a full array would
be padded past the limit), computed once when the renderer is made and used
by the sprite arrays, the shader constants and the shader pack atlas. Mip
chains prepared before the adapter was known are cut again for it. Startup
only fails when the sprites need more than 8 arrays of that split.
Co-Authored-By: Claude Opus 5.5 <[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]>
🤖 Agent: vanilla's Pig/Strider getControllingPassenger use player.isHolding, which checks both hands; the server path (lan_vehicles → tick_mount_riding) shares this check. Main merged; its test passes. union3 = main ba8b5b414 + #5516#5535#5566#5571#5578#5573#5569: pr_ready --quick passes (python 511, rustfmt, workspace tests 2856, content root); clippy -D warnings clean after the #5573 borrow fix.
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.
🤖 Agent: Review points from some-du6e fixed and confirmed (seat_own_stands ordering, the cancel() test, port-ledger citations); local lan_e2e proof posted by the author's agent; CI green on 6f941a5e9. Queued for merge.
🤖 Agent: launcher account status, profile heads, refresh and targeted sign-in (Microsoft only); rdbtCVS's points fixed earlier; main merged here (lang union, both sides' account tests, a string main dropped removed). Proven: union of main 956e35469 with 19 PRs, launcher clippy + tests green (the i18n tests after the string fix), game clippy/tests green.
🤖 Agent: native loot table and drop hooks. Fixed here: NativeLootDrops.player only while the hurt memory lasts (dropFromLootTable's playerKilled). Proven: union of main 956e35469 with 19 PRs had this PR's code (clippy, workspace + content-root tests green); the author's later commits are docs, a test and rustfmt, and CI verify passes on this head.
🤖 Agent: native consume effect kinds; fixed here: an empty namespace is minecraft's. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native block contact / landing hooks; fixed here: no item/projectile sweep without mod blocks (hot path), vanilla stepOn-then-inside order, getOnPosLegacy. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native bone meal hooks; fixed here: Some(false) keeps the meal as growCrop does, None leaves the template's. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
"Microsoft account · signed in" came back with this branch's tables in the
main merge; the code lost it in 86149f7da (i18n tests).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: fence knot clicks and orb smoothing; fixed here: the orb draws its correction step (previous kept), knot hit only within entity range; main merged. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: #5521 by rdbtCVS finished (main merged: serialize keeps screenshotQuality and main's shadowCache). Screenshot Quality defaults to 1x, so at 1x and 100% scale F2 saves the window's frame as before; the scaled frame draws offscreen inside OOM/validation error scopes. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green (clippy -D warnings, workspace tests).
🤖 Agent: native block entity renderers; main merged here. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: OptiFine .properties reader (new crate, no dependents); fixed here: no .. paths, linear glob, no link loops; skip-release. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: parallel horizon-prepare (same output digest, release check covers both lane layouts); fixed here: the extraction test on case-insensitive disks. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native can_survive / may_place_on hooks; fixed here: a piston head's neighbour change asks can_survive. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native advancement criteria triggers (+ the author's on_fire fix); main merged here. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: biome modifiers place mod features; fixed here: unread modifier types and unknown names are skipped with a warning instead of dropping every content mod. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
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]>
🤖 Agent: splitscreen part 9 (on #3801, testing only); main carried up the stack. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 8 (on #3794, testing only); main carried up the stack. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 7 (on #3777, testing only); main carried up the stack. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 6 (HORIZON_SPLIT testing only, off by default); main merged here (main's headless loop runs before the split's event loop). Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
A native pack had no way to light or tint weather, so vanilla's streaks stood out against a pack's
own lighting. Horizon still places and animates the streaks; the pack only returns their colour.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
origin/main's identity hashes mod components' defaults so peers that
disagree are told apart; it read items.json raw, so a native mod's change
slipped past it. It reads them through item_defaults::read now. The read
and hook docs mention the stack-size range.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Server mods could only give plain items to the acting player
(native_give_items). An auto-pickup or a machine's output needs to hand
over the stack it has, with its damage, name and enchantments, from any
server hook. native_give_stack(uuid, stack) places it as Inventory.add
does and returns what did not fit.
Game::give's slot loop moves into inventory_add, shared by both, so
Inventory.add stays one port. The API number comes from the
give_stack fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
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]>
🤖 Agent: stop riding a remote vehicle that is gone (Entity.baseTick); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests). Stacked on #5630, tested together.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests). Stacked on #5630, tested together.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests). The image follows the text and the bundle component, the rule Horizon's bundle tooltip already uses; moving both under the title, as vanilla's index 1, is a separate parity fix.
🤖 Agent: tadpole AbstractFish goals and TadpoleAi brain, mob_ai scenarios; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: fancy leaves sorted front to back on the mesh workers (no render-thread cost); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
A pack's sky stage only replaced the dome: Minecraft's clouds, sun and stars still drew over it, so a
pack with its own had two of each. Each key defaults to true, so existing packs don't change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
The clamp moves from the item table into item_defaults::read, after the
hook, so loot and item models read the same 1 to 99 (a negative size too).
The durability tooltip no longer underflows when a mod lowers max_damage
below a saved stack's damage (getDamageValue clamps to the max).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack with dozens of options needs them in categories. The first page is the one the button opens,
"page:<id>" opens another, "" leaves a slot empty and columns = 1 gives full-width buttons. Every page
has to be reached from the first and every option has to be on a page, or the check refuses the pack.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
The skip compared Paths, which compare case-sensitively, so it never fired
and the extraction test failed on macOS's case-insensitive file system (the
later member, tag.json, wins there). It now checks whether a case-folded
path exists.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each copying, hashing or compiling step spreads over that many threads; the lanes run theirs side by side, so it is no total limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The objects check summoned its stands on the floor, where a guest that
never moved a remote stand still matched the host. This one starts 3
blocks up and turned to 200 degrees, so where it lands and its yaw are
compared between host and guest.
Written with Claude Opus 5.5 in OpenCode.
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]>
A mod setting minecraft:max_stack_size to 0 made merchant.rs clamp(1, 0)
panic on the server thread. Items now keeps it to ExtraCodecs.intRange(1, 99),
and the docs say what else a malformed value does.
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
ArmorStand.interactAt ignored `DisabledSlots` (not even read or saved), so
locked stands could be emptied and filled. The stand now keeps the bitmask:
bit `filterFlag` disables a slot (an empty hand then takes from the main
hand), `+8` stops taking from it and `+16` putting into it
(`isDisabled`, `swapItem`), and the save writes it back.
A lead tied to another player was untied by anyone who clicked the mob;
only the player it is tied to unties it (`getLeashHolder() == player`).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
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]>
LivingEntity.dropFromLootTable supplies LAST_DAMAGE_PLAYER only when
playerKilled (lastHurtByPlayerMemoryTime > 0), so NativeLootDrops.player is
None for a mob that dies to the environment after the memory ran out
(arismoko's review). Vanilla's own loot is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- swept_inside returns at once when no loaded block is outside minecraft:,
so a server's items and projectiles no longer sweep their blocks every tick
with no content mod (rdbtCVS's hot-path note).
- An item's step_on comes before its inside contacts, as
Entity.applyEffectsFromBlocks calls stepOn before checkInsideBlocks.
- A mob's step_on (and the magma floor, turtle egg checks with it) reads
getOnPosLegacy through block_on_legacy, so a mob over the edge of its
supporting block still steps on it (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mob equipment was an item id plus enchantments and a trim, so a picked-up
sword or armour piece lost its damage, name and components, a hand took one
item of a stack, a worn piece always compared as fresh, and death or
replacement dropped it repaired and unnamed.
`Gear.stacks` (the main hand keeps `main_hand_stack`) now holds the stack of
a slot that is more than one plain item. `set_item_stack` stores it,
`stored_stack`/`item_stack` read it back, replacing the item forgets it.
`equipItemIfPossible` takes the whole stack into a hand (one into an armour
slot) and drops the replaced stack as it was; `canReplaceCurrentItem` sees
the worn piece's damage and name; death drops the stack itself
(`Drop::Stack`), with the rolled damage when the drop is not preserved; the
save writes and reads it.
Co-Authored-By: Claude Sonnet 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]>
A modifier of a type not read yet (add_spawns, add_carvers, a mod's own
serializer) or naming a biome, feature or tag the root doesn't have (another
mod's ids, #c: convention tags) dropped every content mod. Real mods ship
those beside their feature modifiers, so that modifier is now skipped with a
warning naming the mod and file, as NeoForge skips an unresolvable holder
set (reservedbytes's review). A malformed modifier of a read type (unknown
step) still stops the build. The docs say #tag in features reads vanilla's
feature tags only for now (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[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
The whole matrix (7 mounts x 2 levels x 2 players + in_nether) takes about 10
minutes, and the nightly LAN suite already runs ~2,400 s of its 3,000 s
timeout (rdbtCVS's review). The default keeps the regression signal;
LAN_E2E_MOUNTS=all and LAN_E2E_LEVELS=all run everything.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Entity.commonTick sets the old position before the interpolation step, so
the frame lerps from the pre-step position: previous keeps it now, where
ExperienceOrb::tick had overwritten it and the orb still hopped (rdbtCVS).
- The knot is an entity, so a click counts as one on it only within the
entity interaction range (filterHitResult), not the fence's block range
(Joansitoh).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rdbtCVS's review points 1-3:
- resolve returns None for a path with a .., . or empty segment, so a pack
can never name a file outside the overlay root (vanilla refuses them).
- glob backtracks only to the last star: O(pattern x name), where every
star tried every split before (a player-chosen item name, per frame).
- properties_files reads each real folder once and at most 16 deep, so a
folder pack linking to its own parent no longer recurses to PATH_MAX.
Tests for each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
winit's MouseButton::Other(n) is the raw X11/evdev number on Linux, not GLFW's
index, so the hook docs and the fragment say a button past 4 is
platform-defined (rdbtCVS's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The player-file save in `save_game_metadata` only ran for a dedicated-mode
integrated server (the fixture), so a real `--dedicated` server (the
`DedicatedServer` role) skipped it on autosave and `save-all`: players were
only written when they left or the server stopped. It now runs for every
server role, as PlayerList.saveAll does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Identifier.bySeparator reads ":clear_all_effects" as minecraft:clear_all_effects
(Consumable.CODEC decodes both), so it is vanilla's clear, not a mod kind for
the native hook (arismoko's review). Test added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A chest boat or chest/hopper minecart open in a menu keeps its slots in a
stand-in under a placeholder key. Removing the vehicle (broken, killed,
exploded, a rider's death, a portal) dropped the vehicle's stale items and
left the stand-in with a full copy a stale menu could still take from.
`release_vehicle_container` writes the menu's live slots back to the vehicle
first (so they are what drops or travels), then removes the stand-in, before
each removal.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
DirectionalPlaceContext.getRotation is get2DDataValue() * 90, so a vertical
facing gives -90, not toYRot's 270. Both name East for vanilla's blocks
(from_y_rot, rotation segments), but the placement hook shows the raw value
(arismoko's review on #5516). Armor stands and boats keep toYRot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A menu was only validated once per tick, after the packet phase, so a click
could still land on a vehicle that was gone, a mount that died or left reach,
a lectern out of reach or a station block that was replaced. The click
packets (container click, button, slot state, rename, beacon, trade select,
bundle select, place recipe) now run one shared check first: the open menu,
whichever kind, must still be valid, else it closes like ContainerClose and
the click lands on nothing. The per-tick checks use the same predicates.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Leaving the server (a disconnect, a LAN close, a dedicated stop) kept the
guest's cursor stack, trade payment, grid and station inputs in the menu, a
trade unwritten on the villager and a vehicle's stand-in in the level.
`guest_close_all` runs the same close code as ContainerClose for every kind
of menu, in the guest's own level, and runs from `HostEvent::Left` and
`save_leaving_guests` (LAN close, dedicated stop, integrated stop) before the
playerdata is written.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
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_is_valid_bonemeal_target answers Option<bool>: Some(false) refuses
the meal even where the block's template takes it (BoneMealItem.growCrop
returns false when isValidBonemealTarget is false), None leaves the
template's BonemealableBlock (arismoko's review). The client prediction
falls through to growWaterPlant on a refusal, as vanilla's useOn does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each opener closed a different subset of the menus the guest could have
open (the trading screen left a mount or crafting state behind, a vehicle's
stand-in stayed, the old window id still took clicks). They now all start
with `guest_close_all`, as `ServerPlayer.openMenu` closes the open menu:
containers, vehicles, mounts, merchants, stations, lecterns, the crafting
table and native menus.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
position_riders ran before the player moved and before previous_position
was saved, so a name hologram on the local player was drawn a full tick
behind it. Vanilla ticks the player (tickNonPassenger) before its
passengers (positionRider), so seat_own_stands now runs after the move,
from the previous_position just saved to the moved position.
The snap test now walks toward x 5 when the far sync comes and checks no
step back toward it is left, so it fails if a snap stops clearing the
handler. The ledger citations move off the first doc lines they were
read as ports from.
Written with Claude Opus 5.5 in OpenCode.
Leaving the server (a disconnect, a LAN close, a dedicated stop) kept the
guest's cursor stack, trade payment, grid and station inputs in the menu, a
trade unwritten on the villager and a vehicle's stand-in in the level.
`guest_close_all` runs the same close code as ContainerClose for every kind
of menu, in the guest's own level, and runs from `HostEvent::Left` and
`save_leaving_guests` (LAN close, dedicated stop, integrated stop) before the
playerdata is written.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Leaving the server (a disconnect, a LAN close, a dedicated stop) kept the
guest's cursor stack, trade payment, grid and station inputs in the menu, a
trade unwritten on the villager and a vehicle's stand-in in the level.
`guest_close_all` runs the same close code as ContainerClose for every kind
of menu, in the guest's own level, and runs from `HostEvent::Left` and
`save_leaving_guests` (LAN close, dedicated stop, integrated stop) before the
playerdata is written.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PistonHeadBlock.neighborChanged calls state.canSurvive; the direct
piston_head_can_survive call skipped native_can_survive (arismoko's review).
can_survive keeps the piston rule as its default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
stamp_matches compared a missing outputs key with the digest of a missing output as None == None. The docs and comments said HORIZON_PREPARE_JOBS is how many things run at once; it is how many threads copy and hash, the lanes all run at once unless it is 1, and it counts logical processors. With the split there are five lanes. java.rs's memory figure now says what it compared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A held two-sided model carries a quad per direction (a calibrated sculk
sensor's tendrils and amethyst planes, big dripleaf, block/cross), and the
item render types vanilla picks in ItemBlockRenderTypes -- solidBlockSheet
(entitySolid), cutoutBlockSheet (entityCutout), translucentItemSheet
(entityTranslucentCull) -- all keep the composite state's CULL. Horizon
culled only quads whose sprite has translucent pixels, so held blocks drew
both sides: the coplanar pairs striped (#4856, #4855) and a hay block's far
faces bled through the near ones (#4801).
Two-sidedness in vanilla is geometry, not culling: ItemModelGenerator's
bakeExtrudedSprite bakes a South and a North quad per layer, which
horizon-assets already mirrors.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
The owner loads the file level.dat names, but when it left (save_guest,
PlayerList.remove) only its own file was written; the named one waited for
the world's save that follows (stop_integrated: free_player_beds, the
chunks, then save_game_metadata). A game stopped or failing in between
left the named file older than the own one, and the owner then loaded the
older state. `playerdata::save_owner` writes the named file
(`Level::write_player`, also the player half of `Level::write`) before the
own one, so the file the owner loads is never the older.
`playerdata::load` (PlayerDataStorage.load) now falls back to the player's
`.dat_old`, as vanilla does: `write_dat` moves the old file aside before it
renames the new one in, so a save cut off there leaves only the
`.dat_old`, which the named file's read already used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlayerList.loadPlayerData gives the single-player owner the file level.dat
names (`singleplayer_uuid`), not their own. Since #5069 the integrated
server saves the owner to both files, `players/data/<singleplayer_uuid>.dat`
(offline `Player`) and `<owner uuid>.dat`, but loaded the owner's own file
first. A build that saves the owner to the named file alone (0.2.93 and the
stable releases before #5069) leaves the own file behind, so after
switching back the owner got an older inventory and position, and the next
save wrote them over the newer ones.
`Level::read` now says whether its player came from the named file, and
`playerdata::load_owner` applies it before the owner's own file; the own
file stays the fallback when the named file is missing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_living_knockback (NeoForge's LivingKnockBackEvent) runs in
LivingEntity.knockback once the knockback resistance took its share and
before the motion changes, with the pushed entity's id and type, the
strength, the direction (x, z) and a cancel flag; a non-finite value a mod
leaves is put back. In 26.3 every knockback has a DamageSource, so the hook
names it (damage type, causing and direct entity) as
native_living_incoming_damage names the same hit (`Hurting`,
`hurt_server_args`). The API number comes from the fragment
(tools/native-hook-fragments).
It is reached by every LivingEntity.knockback Horizon makes on players
(including LAN guests and a dedicated server's) and mobs: a hit's own push
(dealDefaultKnockback), a sprinting or Knockback hit and a spear
(causeExtraKnockback), a sweep, a goat's ram (RamTarget, `mobAttack`) and a
mob's spear push, which now carry their source in MobEvent::Knockback, and
LivingEntity.blockedByItem's push on a shield's holder, which was
open-coded past it (and past the knockback resistance) in shield.rs and is
now knock_back_player(0.5, ...). A sulfur cube with a block hit by an entity
doesn't reach it (SulfurCube.knockback doesn't call super there). The docs
list the pushes Horizon still makes another way: a mob's melee push from its
attack knockback or a Knockback weapon, an armor stand's, the ender
dragon's.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
The repeat run's file check skipped horizon-resources.json, so a manifest the repeat rewrote differently passed. The comments that said at most three JVMs run at once now say four with the second server JVM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Horizon pack declares options, but they stayed at their defaults: the player had no way to set them.
The picked pack now gets the same Shader Options... button as a shaders/ pack, and its values live in
shaderpacks/<pack>.txt, the file that kind already uses. A saved value counts as a change to the pack,
so it reloads with it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
player_entity left on_fire false, so a criterion's player predicate with
flags.is_on_fire true never matched, a mod's trigger (the reviewer's
case) or a vanilla one. It is Entity.isOnFire on a server now:
remainingFireTicks above 0 (players aren't fire immune).
Written with Claude Opus 5.5 in OpenCode.
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
A pack treats hundreds of kinds of surface differently, where [[tags]] hold 32 sets. Each sprite takes
the id of the first [[materials]] entry that names it, as MaterialInput.material, through the same
generated switch as the tags. [shadow] solid_materials draws those ids into the map without their
cutouts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
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]>
The local player's model isn't drawn in first person, so a native pack's shadow pass gets it on its
own through the frame's shadow_only quads. The round blob under entities is left out while a pack
draws a shadow map, since the sun's shadow is already there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
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]>
remote_open_merchant left the menu title unset, so NativeScreenView.title was
None on that window although the docs promise the server's title.
Co-Authored-By: Claude Code <[email protected]>
Adds screen_key_pressed_pre and screen_char_typed_pre (ScreenEvent.KeyPressed.Pre
and CharacterTyped.Pre) on the open screen, fired from the window's key event,
the control socket's key and type commands, and the input method's commit.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Code <[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
The renderer's opaque entity quads now go into shadow_map through the shadow camera, with their
texture's cutout. They draw after the translucent map is copied, since where one casts the solid
map is the darker and decides. Unlit overlays, as entity.wgsl lists them, cast nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Keeps this pull request near the size limit; the block state stays in the
hooks, whose signatures can't change once merged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_screen_mouse_scrolled_pre` (`ScreenEvent.MouseScrolled.Pre`) hands a
mod the open screen, the wheel's movement in x and y as the game's scroll
handler already scales it (vanilla's doubles, the sign only when discrete
scrolling is on, a touchpad's pixels over 30) and GLFW's modifier bits, plus
`canceled`.
It fires in the `MouseWheel` arm once `scaled` is defined, before the dialog,
title, hotbar, bundle and every screen scroll, so a cancelled turn scrolls
nothing and leaves nothing half-applied; the hotbar reads the wheel only
while playing, which the hook's gate excludes. The pad's and the keyboard
focus's made-up wheel is not the mouse and fires nothing.
The hook doc names the screens that never fire, as the mouse button hooks
do, and the fragment is pending, so this is "API next".
Co-Authored-By: DeepSeek <[email protected]>
Both hooks get the block's state, as a renderer reads
blockEntity.getBlockState(); native_block_state_property reads its facing
without a world lookup, and native_draw_item draws an item stack under the
pose stack as a campfire does (resolve_item, submit_item_world).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/native-mods.md gets an "Items' default components" section beside the
render hook points: the target path, both sides, threads, vanilla clients,
creative search. The catalog test reads free-function hooks with one helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A failed exporter compile fails every lane at once instead of being
retried by each lane that waits for it.
- The gameplay facts exporter streams to a .partial file and moves it into
place: a run stopped halfway no longer leaves a short file the next run
would keep.
- pool::map takes no per-thread state (a jar clone costs two Arc counts);
pool::lanes and map are tested at 1 job and several. With 1 job the first
failing lane stops the rest, as documented now.
- The lanes' log and stage messages go through relay(), which a test now
checks; block behaviour reuses export_on; the stamp inputs are worked out
once; the fifth lane exists only when it splits; dead checks are gone.
- check_prepare_exporters.py also prepares with 2 jobs (the default path
below 6 cores, 5 JVMs) and repeats that run, which must start no JVM and
change no file.
- The docs say what HORIZON_PREPARE_JOBS does, that the split is not yet
timed on 6 cores, and the split's measured ~400 MB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_block_entity_render_bounding_box lets a mod widen the box its block
entity is culled with (NeoForge's getRenderBoundingBox), or make it
infinite, so a tall part stays drawn while its block is out of view. The
view distance check is strict, as Vec3.closerThan.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The other way round from a force-loaded chunk: the player and a horse in the
Nether while the other player keeps the Overworld ticking; the Overworld's pass
must leave the menu alone (in_nether, --server-thread). The player lands in
creative and is put back on the platform once it stands, so a cave below the
spot doesn't drop it out of reach.
A killed mount lies at the spot for 20 ticks (LivingEntity.tickDeath), and the
next one, summoned there 10 ticks later, took the clicks meant for it on a
slow run: wait until the client no longer sees it. LAN_E2E_LEVELS takes a
comma list (the_nether, the_end, in_nether), as LAN_E2E_MOUNTS does; the
force-load is removed even when the wait after adding it fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_screen_mouse_button_pressed_pre` and
`native_screen_mouse_button_released_pre` (`ScreenEvent.MouseButtonPressed.Pre`
and `.MouseButtonReleased.Pre`) hand a mod the open screen, the button's GLFW
index (0 left, 1 right, 2 middle, 3 back, 4 forward, `Other(n)` = n) and
GLFW's modifier bits (shift 1, control 2, alt 4, super 8), plus `canceled`
for a press the screen never sees.
They fire first in `handle_window_event`'s closure, before the screen or the
container hotkey, sort and header buttons act, so a cancelled press leaves
nothing half-applied and a cancelled release keeps a drag held. The pad's and
the keyboard focus's made-up clicks are not the mouse and fire nothing.
The gate and the view two more input hook families need are here: the pure
`glfw_modifiers` and `glfw_button`, `native_screen_open` (a screen of the
game's over `Screen::Playing`, no connect or load) and `screen_view_now`,
which builds only the `HudBatch` its rect needs. The hook docs and the
fragment name the screens that never fire: the title screen, the connect and
loading screens, the prompts, books and sign editors drawn over gameplay, and
any screen while a world loads or saves.
The fragment is pending, so this is "API next".
Co-Authored-By: DeepSeek <[email protected]>
New crate horizon-optifine, the layer every OptiFine-format feature (CTM,
CIT, random and emissive entities, CEM, sky, colours) reads its files
through: Java .properties with escapes and continuations, name matchers
(pattern:, ipattern:, regex:, iregex:), range lists with parenthesised
negatives, id lists with ! exclusions, block lists with states, OptiFine
path resolution (~/, ./, namespace:) and the optifine/ and mcpatcher/
folders of every namespace. Written from OptiFine's public format docs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod could not change a default component of an item it didn't add (a
stack size, a food, a tool rule), as NeoForge's ModifyDefaultComponentsEvent
lets it. native_modify_default_components hands a mod each item's defaults
in items.json's form as they are read, to set or remove components.
Every reader of items.json (the item table, loot, enchanting, item models,
banners, pot sherds, rarity in chat) now goes through
horizon_world::item_defaults::read, so a change reaches all of them, and
stacks are saved and sent against the changed defaults like vanilla's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The depth range now reaches past the build height below the camera and shadow_position clamps z,
so a point past the far end stays lit, the blank map included. The light's box culls the native
lists instead of a section radius that lost casters at low sun. The native camera's snap is
euclidean, the resolution is clamped once, and the distortion and the camera each live in one
place. The generated reference lists the shadow helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
The first cut told an entry's kind before its requirements were checked, so a
failed requirement still fired, and gave the hook no way to know which mob was
hit. The kind is now told after the check, with the mob's id. A bare type name
is minecraft's own, so ignite runs as minecraft:ignite instead of reaching the
hook. The docs now say what's told: attacker on victim, a player's weapon on a
mob.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fresh preparation on a 6-core laptop finished its other JVM lanes by ~17 s,
then ran the server lane alone for ~20 s more: its last export, the world
generator's block behaviour (11.5 s there), sat behind everything else on
that JVM. From 6 cores (pool::jobs, so HORIZON_PREPARE_JOBS too) it and the
structure blocks run on a fifth lane, in their own server JVM. It pays a
second Bootstrap and ~400 MB of peak memory, so it stays off below 6 cores:
forced on this 3-core box it took 44.9 s against 40.5 s.
Same output (digest unchanged either way). check_prepare_exporters.py now
runs its batched preparation that way and expects 6 JVM launches; it passes
(23,204 files).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured on the pinned 26.3 jars, sampling every process's RSS: the peak came
at the end, from the server worker alone (1.25 GB). Its live heap stayed under
~250 MB; the rest was garbage the serial collector promoted and kept, mostly
from two exporters:
- The collision exporter wrote, and the worker cached, 62 MB of verification
inputs (printf'd, ~4 s) that only the parity harness reads. Inside the
prepare worker it now skips them; standalone runs still write them.
- The gameplay facts exporter built its 56 MB file as a Gson tree, then a
String, then bytes. It now streams each state to the file; the bytes are
the same (re-serializing the parsed file with Gson.toJson gives them back).
The worker now peaks at ~480 MB. In the Rust process, every thread reading a
jar parsed its member list again (~10 MB each, eight at once); they now
share one parse through clones of the archive that read at their own offsets.
Its heap peak halved (96 to 51 MB).
check_prepare_exporters.py passes (23,054 files, 5 JVM launches); the output
digest is unchanged. "Movement and attributes" takes 12-13 s instead of 16-18 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An enchantment's effects entry that names an effect.type in a namespace
outside minecraft: used to be silently skipped at the firing point. It
now reaches native_enchant_effect(enchantment, kind, data, level) — the
post_attack components first, where the weapon's holder is the acting
player — and the game's own damage, sounds, effects and drops are open
to the hook. An unclaimed kind does nothing.
Co-Authored-By: ZCode AI agent (GLM-5.3)
The serial collector is as fast as the parallel one for these JVMs (fresh run
63-66 s either way, pinned 26.3 jars) and keeps the three that run at once to
1.25 GB together instead of 2 GB. An export's time no longer includes waiting
for javac. A compile error still names the export it stopped.
tools/check_prepare_exporters.py passes: 23054 resource files match between two
isolated runs and a batched one, with 5 JVM launches and no compiler.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The exporters, the data generator and javac are short batch JVMs, three of them
running at once. The C2 compiler's background work and G1's concurrent threads
cost them more CPU than those win back. With -XX:TieredStopAtLevel=1
-XX:+UseParallelGC (javac gets them through -J), a fresh preparation from the
pinned 26.3 jars took 67 s instead of 102-105 s on the same machine. The output
digest (tools/prepare_output.py) is unchanged. java.rs is not part of the
exporter bundle key, so cached bundles stay valid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fresh preparation ran everything one after another. The steps now run on four
lanes: the server exporter JVM (same order as before, since its exporters share
state), the client exporter JVM then the command registries', the data generator
then the creative inventory's, and the Rust-only copying. At most three JVMs run
at once, still five JVMs in total. A worker stops when its lane is done. The two
exporter sides compile at once. Asset extraction, structure templates, worldgen
bundles, server library unpacking, client library imports, inventories and pin
checks run on several threads. Every pinned file is still hashed in full.
Repeat runs always started the server JVM for the block behaviour and
structure-block exporters, which had no skip check. They now keep a stamp.json
in .local (exporter bundle key, server jar, preparer sources, output sha256), so
an unchanged repeat run starts no JVM. The prepared files are unchanged.
Every step logs how long it took. HORIZON_PREPARE_JOBS=1 runs it all serially.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The check now opens a tamed horse, camel, llama, trader llama, nautilus
(owned), chested donkey and mule by shift + right click and by the
inventory key while riding, and again by shift + right click after getting
off, on the host and on the guest, with a chunk force-loaded in the Nether
and in the End. Every way is reported on its own and must still be open 20
ticks later.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
The rect comes from the same container image the game draws, so the
merchant and the beacon report their wider, shifted window. An unknown menu
type is None rather than an empty id, and slot_count is checked against
slots() in debug builds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server names no player of its own as a mob's killer, and the drops
context's fields aren't flagged as never read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_screen_render_post` (`ScreenEvent.Render.Post`) hands the mod the
mouse in GUI pixels and the frame's partial tick besides the screen view,
as the event's `mouseX`, `mouseY` and `partialTick` do, so a mod can draw a
hover highlight.
The view also carries `menu`, the vanilla menu type of the menu the server
opened (`minecraft:generic_9x3`, `minecraft:furnace`, a mod's own id), and
None for a window the client opened. Every container, station and mod
window draws on the inventory screen, so `id`'s doc says they all report
`inventory` and are told apart by `menu`, `title` and `slots`.
The view is built without allocating: slots are counted the way `Game::slots`
lists them (`slot_count`, with `station_slot_count`, `mount_slot_count` and
`native_screen_slot_count` beside their list versions) instead of being
collected into a `Vec` every frame, and the id comes from the screen
`hud_layers` drew rather than from a second argument beside `self.screen`.
The hook and the fragment say which screens never fire it: the title screen,
which returns before its point, and the prompts, books and sign editors
drawn over gameplay after it, which keep `Screen::Playing`, are skipped, and
draw over what the hook drew. Vanilla fires Render.Post for them; that gap
is now written down.
With no mod installed the body stays `let _ = (...)`: the frame is vanilla's.
Co-Authored-By: DeepSeek <[email protected]>
- /summon minecraft:spawner_minecart with no NBT runs BaseSpawner.load on an
empty compound, as SummonCommand.createEntity does, so SpawnPotentials is
WeightedList.of(new SpawnData()) rather than empty.
- The save test passes no raw tag and changes the delay after loading, so it
checks the cart's own save arm.
- The cart model test covers spawner_minecart#main.
- Port ledger: MinecartSpawner's tick, save, load and display state are ported.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The loader dropped an on_consume_effects entry whose type the game
doesn't know silently. It now keeps the kind and the JSON the file
wrote, and the game hands them to native_consume_effect when the item
is consumed, on the server thread, for the acting player. The game's
own effects, sounds, teleports and drops are open to the hook; an
unclaimed kind does nothing, as an unknown kind on any loader.
A bare type name without a namespace is minecraft's own — apply_effects
is minecraft:apply_effects — so only a namespaced kind outside
minecraft's becomes Native; an unknown minecraft: kind is dropped. The
hook sees the item's own consumable component, not a patch on the stack.
Co-Authored-By: ZCode AI agent (GLM-5.3)
The wall-table test's registry has every block's default, a kill by a guest
who left names no player, and the load hook's docs say two loads can overlap.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeoForge's CompoundIngredient matches if any child does — the union of
their items, not the intersection (that is IntersectionIngredient).
The ingredient_type key is neoforge:ingredient_type, type only as the
older packs' spelling; children is the field name, ingredients as the
older spelling. neoforge:fluid is not among NeoForge's item ingredient
types (its fluid ingredients are a separate registry for fluid stacks,
not items), so it is dropped until a use case arrives with a source.
Review: rdbtCVS's agent in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
spawn_loaded_entities hands out the id before native_entity_joins runs,
and a cancelled orb doesn't give it back, so the fallback returned an id
no entity holds. Take it only when an orb holds that id (root_id), and
test both cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QT61YPiPzVNysmVHsH
Empty stacks a mod leaves don't drop, the load hook runs outside the
cache's lock, a mob's context names the table that rolled (one helper with
drops::die), the wall-variant table lookup has a test, and the docs say
which breaks the drops hook hears.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_loot_table_load (horizon_world::loot) hears each loot table's JSON as
it loads, after the data packs' (LootTableLoadEvent, LootTableEvents.MODIFY),
and native_loot_drops hears a broken block's or a dead mob's items with what
dropped them (global loot modifiers, MODIFY_DROPS).
Closes#5522
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds `screen_render_post`, the point of NeoForge's `ScreenEvent.Render.Post`: once a frame, after the
open screen has drawn itself and its tooltips, a native mod may draw over it. It is handed a
`NativeScreenView`: the screen's id (one per `Screen` variant, from `Game::screen_id`), the open
menu's title (borrowed from the menu, not copied: no per-frame allocation), the container panel in GUI
pixels or the whole window, and the menu's slot count.
The firing site is the end of `hud_layers`'s screen match, where every screen but Win and Options is
drawn. Those two keep their early returns: folding them into the match would draw the HUD and the
prompt path behind them, so the hook fires on their own path, after their drawing, instead. It does
not fire while playing with no screen, and it is a `&self` hook on the window only, so a mod can
read and draw but not change the screen.
With no mod installed the body is `let _ = (...)`: the output stays vanilla's.
The hook is API next (`tools/native-hook-fragments/screen_render_post.json`); `native-hooks.json` and
`NATIVE_HOOKS_API` are untouched.
Co-Authored-By: DeepSeek <[email protected]>
An 8x screenshot at 200% Resolution Scale with the video memory already full
crashed: the depth buffer's allocation failed, and its view raised a
validation error that only an out-of-memory scope was watching. The target
and depth buffer, and the whole scaled frame, now run inside out-of-memory
and validation scopes; a failure writes no PNG and tries again next frame at
half the size, down to the window's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sort solid cutout quads while the worker-side side buckets are built, so rendering uploads the canonical worker result without a render-thread permutation or duplicate allocation.
Co-Authored-By: Codex <[email protected]>
A rider kept its vehicle id after the vehicle left the level. A respawn into
another level keeps the id when KEEP_ENTITY_DATA is set, and a removed
nautilus or minecart leaves it behind. remote_ride then kept the player in
the riding pose with no vehicle under it: it did not move, sneaking did
nothing, and the seat stayed until a vehicle packet named it again.
Vanilla Entity.baseTick stops riding a vehicle that is removed
(isPassenger() && getVehicle().isRemoved() -> stopRiding(), Entity.java).
Do the same in remote_ride when no minecart, boat, cushion or mob has the
id, as tick_riding already does on the local path (boats.rs).
A tadpole is an AbstractFish with a brain: it gets panic 1.25, avoid-player and
the fish random swim goals, SmoothSwimmingMoveControl(85, 10, 0.02, 0.1, true),
SmoothSwimmingLookControl(10), water-bound navigation, and the TadpoleAi brain
(panic, look at player, follow temptation, swim/walk/stay-in-water gate).
Its travelInWater is AbstractFish's (0.01 speed, 0.9 drag, -0.005 gravity).
Adds mob_ai parity scenarios tadpole_idle, tadpole_tempted, tadpole_panic.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Adds step-on, inside and fall-on callbacks with entity and block context; landing damage is adjustable before sounds and hurt.
Co-Authored-By: GPT-6 <[email protected]>
The hardening edit put a raw newline inside the fragment's `doc_summary`, which
JSON reads as a control character: `catalog_matches_hook_points` failed to
parse it. The line break is the `\n` escape the other fragments use.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
tick_lan_mount_menus checked every open mount menu on every level pass,
not only in the player's own level. When a second level ticks (a player
or a force-loaded chunk in the Nether, a portal ticket), its pass found
no such mob and closed the menu the tick it opened. Like the station,
crafting and merchant ticks, it now skips players not in the level in
place (ServerPlayer.tick checks stillValid in the player's level).
lan_e2e mount_other_level force-loads a Nether chunk and checks that the
host's and the guest's horse inventories stay open, opened with shift +
right click and with the inventory key while riding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The issue asks the hook to run for every placer that goes through vanilla's
path, not only the player. `the_level_chooses_the_state_a_block_is_placed_in`
now drives a dispenser's `DirectionalPlaceContext` as `dispense_shulker_box`
builds it — `directional_can_place`, then `place_block` — and asserts the
furnace lands in the hook's state, with the hook handed the dispenser's own
click (its yaw, the front block it replaces).
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Review point on #5516: an answer naming another block would place a block the
item isn't bound to, and skip that block's `setPlacedBy` and sound. Vanilla's
`getStateForPlacement` sets properties on the block's own `defaultBlockState`,
so the hook's answer is taken only for a state of the block being placed;
another block's, like an id past the state table, leaves vanilla's state.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
A mod block took no bone meal: `BoneMealItem.growCrop` looked the block's
`BonemealableBlock` up in the port's classes and stopped. The level now
answers vanilla's three questions for it, ahead of the `BonemealableBlock` a
content template would give it, so a mod block can refuse, roll and grow:
- `is_valid_bonemeal_target` — `isValidBonemealTarget`. Asked on both sides,
as vanilla asks it, so a client predicts the use with the mod's answer.
- `is_bonemeal_success` — `isBonemealSuccess`, drawing from
`native_level_random` so a mod's draws stay in vanilla's order.
- `perform_bonemeal` — `performBonemeal`, with the level's ticks.
The hooks are `native_is_valid_bonemeal_target`, `native_is_bonemeal_success`
and `native_perform_bonemeal` (a fragment, no API number taken), plus
`native_level_random` for a world hook, the stream `native_level_random
(dimension)` hands a server hook.
One insertion point serves every placer: `Sim::grow_crop` is where the
player's `BoneMealItem.useOn`, a dispenser's `BoneMealItem.growCrop` and a
villager's bone meal meet, and the meal is used up and level event 1505 fired
by the callers as before, so a mod block gets vanilla's particles and sound.
The client's copy of the level now forwards the one read-only question
through `DryLevel`, and a mod block's growth particles stay inside it, as
vanilla's `GROWER` type does, where the port knows no type for it.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Reorder solid cutout quads front to back during GPU upload while preserving the canonical section mesh and shared index batching. Include the before and after leaf-scene captures used for validation.
Co-Authored-By: Codex <[email protected]>
F2 now saves the frame at the game's resolution (Resolution Scale above
100%) times a new Video Settings option, Screenshot Quality (1x, 2x, 4x,
8x; 1x by default). The frame is drawn once into its own offscreen
target, with the HUD zoomed to the same layout, and capped to the GPU's
texture and buffer limits; if the large targets can't be allocated the
window's frame is saved as before.
Fixes#3030
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ExperienceOrb.createInterpolationHandler returns a LinearInterpolationHandler, so a client spreads each server position correction over 3 ticks (doInterpolate in Entity.commonTick) and the orb's own move carries the target along (applyPredictedMovement). The remote path put the orb at the new position at once, so it jumped on every correction; the comment claimed vanilla's NO_OP handler.
LeashFenceKnotEntity.interact runs only when the ray meets the knot (its box from recalculateBoundingBox, 0.375 wide, y + 0.375 to y + 0.875) before the post. A click elsewhere on the fence is FenceBlock.useWithoutItem or LeadItem.useOn, which only ties the player's own mobs (LeadItem.bindPlayerMobs). interact_knot ran the take-back and shears branches for any fence click, so clicking the top of a post with stone took a tied horse's lead instead of placing the stone, and shears snipped every lead.
The prepared root holds features without vanilla's old "config" wrapper and
with a block state as an id, so the zinc ore never decoded and the
content-root test failed over real data (CI skips it without .local/).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`getStateForPlacement` had no way in for a mod: a block could only be placed
in its default state. `LevelAccess::before_state_for_placement` now asks the
level, once per block whose placement state is computed, with vanilla's
`BlockPlaceContext` (the clicked face, the placer's rotation, the clicked
position, whether the clicked block is being replaced, inside, the item in
hand) and the fluid of the position the block would go in, so a block can
face the player, sit on the clicked face or come waterlogged. The answer is
`Some(Some(state))` to place that state, `Some(None)` to refuse the placement
as vanilla's `null` does, and empty to let the block's own placement run.
`native_state_for_placement` is the hook a native bundle answers with, and
`native_state_with_property` builds the state from the block's default state
and one of its properties (a content block's declared ones included).
`Ctx`, the resolved context, is now `PlacementContext`, public and documented
as what a mod block is handed. Both placers that go through vanilla's path
reach it: the player's `BlockItem.place` and a dispenser's
`DirectionalPlaceContext` (both `Sim::state_for_placement`), and the answer
still takes its survival and entity obstruction checks.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Main's handlers take a loop handle (EventLoopCtl) and about_to_wait's body is Game::idle: exit_on_error takes the handle, the split guard stays in idle, and run_event_loop stops the narrator after the split's loop.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The dither changes vanilla's look for every cutout from 64 texels up, so it now runs only under
Distant Plant Dither (ditherCutoutMips in horizon-graphics.txt). Turning it on or off reloads the
textures, since mips are made when they load. Decoded textures don't hold mips, so the sprite cache
keeps its version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
An advancement of a content mod or a data pack can name a trigger of
the mod's (mymod:pipe_connected); the tree keeps it, but only the game's
own events called Game::trigger, so nothing could grant it.
native_trigger_criterion(uuid, trigger, test) is vanilla's
SimpleCriterionTrigger.trigger for one player: each open criterion of
that trigger goes to the mod's test, then to its player predicate, and
those that pass are granted and awarded as the game's own are. The test
reads the criterion's conditions and can check a vanilla item predicate
in them. It reaches the acting player inside their hook, and any player,
in their own level (for_player), where no player is swapped in;
minecraft: triggers are refused.
Fixes#5481.
Written with Claude Opus 5.5 in OpenCode.
Keystroke::parse takes a 'static str; the app comes from gpui_platform::application(), and
AsyncApp::update returns nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_can_survive answers BlockState.canSurvive before the block does: as it is placed (BlockItem.canPlace)
and from the shape update of a block whose template checks it, which breaks it with its drops.
native_may_place_on answers VegetationBlock.mayPlaceOn, the blocks a mod plant can stand on, which its
template's canSurvive asks. Both through LevelAccess, on authoritative servers, as native_update_shape is.
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]>
The profile's texture id comes from a server's NBT; Identifier's characters and
no '.', '..' or empty segment keep it from reading a PNG outside the resource root.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The ten lang tables conflicted where both sides added strings: main's table as the
base, plus this branch's account strings, without the "Microsoft account · signed in"
this branch removed.
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]>
Both sides added an Extra field and its NBT round trip (rabbit MoreCarrotTicks here,
ocelot Trusting on main); kept both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fox.onOffspringSpawnedFromEgg's only caller is Mob.checkAndHandleImportantInteractions
(a fox egg used on an adult fox), so the trust moves from use_spawn_egg to
spawn_egg_offspring. The port map's Fox.clearTrusted and readAdditionalSaveData rows
now say what is ported.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content mod's placed features now generate in vanilla's biomes through
NeoForge's biome modifiers (data/<ns>/neoforge/biome_modifier/, and
forge/biome_modifier/ for older mods): add_features, remove_features and
none. When the content root builds they rewrite the biomes' feature
lists, an added feature at the end of its step as NeoForge adds it; the
generator's FeatureSorter then orders them as vanilla does. Biomes no
modifier names keep their text, and a root with no modifiers keeps
vanilla's bundle. An unknown biome, tag, feature, step or type fails the
build with the mod and file named.
Part of #4953, closes#5431.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NLEqWLKW2LHgtLer35pT8m
Follow-up to #5408. A summoned orb spawned but native_summon returned None,
since no command Target lookup covers orbs: return the stack's first
spawned id. Passengers without Pos were not placed and got lost: share
/summon's recursive place helper (place_summoned), which also supplies
the Fire and Rotation defaults.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QT61YPiPzVNysmVHsH
known() now checks the whole subtree, not only the immediate type:
not(not(unknown)) and or(true, not(unknown)) both failed to exclude
the file, because the outer combinator was a known kind and only the
direct child was checked. Both now fail closed, with tests.
Review: Aria's agent swarm (Dyna) in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
An ingredient that is one of NeoForge's standard object shapes now
resolves to the flat item list it matches, where the string parser
dropped the recipe file whole: neoforge:compound and
neoforge:intersection keep the items every sub-ingredient names,
neoforge:difference the base's without the subtracted's, and
neoforge:fluid a fluid's item form, its full bucket (a water or lava
tag names the same still fluid). A kind outside these still fails the
file - a mod's own ingredient types come with their own hook, not the
loader.
Co-Authored-By: ZCode AI agent (GLM-5.3)
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]>
Translucent terrain is drawn over a copy of the solid shadow map, so a pack can tell a point under
water or stained glass from one in shadow: shadow_map_translucent holds that depth, and shadow_color
the texture colour times the tint of the nearest such surface, with alpha 1 on a fluid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
setValuesFromPositionPacket measures tooBigToInterpolate from
PositionMoveRotation.of(entity), which is getClientPositionAndRotation:
the interpolation target while one runs. handleEntityPositionSync
measures from the entity's position. MovePacket keeps the two apart.
The mob and armor stand branches of move_entity now share stepped_move.
A mob put somewhere at once also resets its previous yaw and pitch
(snapTo's setOldPosAndRot), so it doesn't spin for a frame, and every
spawn starts with a fresh interpolation handler, a stand's included.
Written with Claude Opus 5.5 in OpenCode.
mod_loaded now asks which mods are loaded (the mod-content root's new
horizon-mod-ids.json marker), not which namespaces they own — a mod that
also owns a namespace doesn't make that namespace another mod. An
unknown condition kind fails the file through not and or as well, not
just on its own: `not` of an unknown kind is not a pass, and an `or`
over one doesn't short-circuit past it. And the stations, the recipe
book and brewing apply the world's packs first and gate the merged
file, as the crafting loader already did: a false-conditioned pack file
leaves the lower recipe gone too, not still active underneath.
Review: Aria's agent swarm (Dyna) in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
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]>
An armor stand is a LivingEntity, so a vanilla client moves it with the
SteppedInterpolationHandler over its update interval (3 ticks), position
and yaw together, and ArmorStand.tickHeadTurn has the body follow yRot.
Horizon eased stands with the generic three-step lerp and set the yaw at
once from each packet. Stands now share the mobs' stepped handler:
ArmorStand::interpolate takes its step, keeps the yaw continuous and
saves yBodyRotO, and the renderer and the F3+B hitbox turn the body by
Mth.rotLerp(partialTick, yBodyRotO, yBodyRot).
A move of more than 64 blocks now snaps (tooBigToInterpolate), with
nothing left to blend from (Entity.snapTo). TeleportEntity snaps too, as
setValuesFromPositionPacket does; before, only EntityPositionSync did,
so a far teleported mob also slid there over three ticks.
Written with Claude Opus 5.5 in OpenCode.
A pack asks for a shadow map in its manifest and the renderer draws the solid terrain the light
sees into it each frame, from the shadow camera's lists shaders/ packs already use. The material
and post stages read it as shadow_map with shadow_sampler and the prelude's shadow_position, and
the frame gains light_direction, has_shadow, shadow_view_proj, shadow_distance, shadow_distortion,
shadow_resolution and shadow_center_texel. A pack without [shadow] gets a 1x1 map that lights
everything. Translucent terrain and entities don't cast yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
DebugLevelSource: barriers at y 60 and the block states in registry order on
a grid at y 70, one every other block. A Debug world's blocks never change
(Level.setBlock fails; setblock, fill and clone report it), nothing ticks
(scheduled, random, natural spawning), the login packet says isDebug, and
level-type minecraft:debug_all_block_states creates it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
EntityRenderer draws an entity at Mth.lerp(partialTick, xo, x), but armor
stands were drawn at their tick position, so a falling stand jumped once
a tick (20 times a second) while boats, TNT and mobs slid. ArmorStand now
keeps its previous position: ArmorStand::interpolate saves it before each
interpolation step on a server's client, a stand riding a mob or the
player takes it from its vehicle's previous seat (as the mob riders do,
so it stays right while the level is frozen), and the in-frame tick
saves it too. The body, its light, the culling box, the name tag, the
flames and the F3+B hitbox all use the blended position.
Fixes#4416
Written with Claude Opus 5.5 in OpenCode.
Rabbit.RaidGardenGoal: a MoveToBlockGoal (0.7, 16) to carrots at their maximum
age on #supports_crops ground, which takes one age off them (or the crop when
it had none) and leaves the rabbit full for 40 ticks (moreCarrotTicks, saved
as MoreCarrotTicks, running down by 0 to 2 a tick).
Co-Authored-By: Claude Sonnet 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.
`feed_animal` takes the player who feeds an animal into love since this
branch; the probe passes none, so `--examples` builds again.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Main's #5397 translated the same 17 strings into ja, pt-br and tr that dd722ee7a did, with
other wording. Git merges both without a conflict, which left each of those tables with 17
duplicate keys. Main's wording is kept and this branch's dropped, so the tables differ from
main only by this branch's 26 account strings and the key it removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82d7UpxGuCncnLpJsq
`Fox.FoxBreedGoal.breed` hands the breeding to the baby as trust, which needs
the player who fed each parent into love: the animals kept the love counter
but not its cause. `Animal.LoveCause` is now kept (`Extra.love_cause`, fed
from the player who hands over the food, saved and read), and
`fox::trust_breeders` gives the baby both players, once each.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
`Fox.getTrustedEntities` and `Fox.addTrustedEntity` were modelled as "no
trusted players yet": the save wrote an empty `Trusted` list, nothing read it
back, and the avoid-player goal had no trust test, so a fox fled the player
who spawned it and nobody could become trusted (two rows of #3512).
- `Extra.trusted` holds the two `DATA_TRUSTED_ID` slots; `fox::trusted_entities`,
`add_trusted_entity` (first free slot, then replacing the second),
`clear_trusted` and `trusts` are the entity's own methods by name.
- The save writes the real list (`Fox.TRUSTED_LIST_CODEC`: int arrays) and
`mob_from_nbt` clears and rebuilds it, as `Fox.readAdditionalSaveData` does.
- `AVOID_PLAYERS` no longer flees a player the fox trusts, which is what the
row meant by a bred fox still running from you; `AvoidEntity::player_when`
takes the avoider so the predicate can ask. A fox spawned from a spawn egg
trusts the player who used it (`Fox.onOffspringSpawnedFromEgg`).
Tested without prepared game data:
`mobs::fox::trust_tests::a_fox_trusts_two_players_and_the_third_replaces_the_second`.
Still missing from the row: the breeding hand-off (needs a saved love cause,
which the mobs do not keep) and `DefendTrustedTargetGoal`; both are in the
note of the map rows.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
A data file with neoforge:conditions loads only when every condition
holds, and is silently absent otherwise, as NeoForge's ICondition leaves
it out of the manager. The gate covers the recipe loaders - crafting's
three kinds and the mods' own types, the stations, brewing, and the
recipe book - and the packs' overlays over them; mod_loaded asks which
mods' data is in the root (minecraft, always, and the content mods
that are on), and neoforge:true, not, and and or combine. A condition
the game does not know leaves the file out: a future shape fails
closed. Ported modpacks already carry such recipes verbatim (the carry
step keeps them), so cross-mod recipes stop loading blind.
Co-Authored-By: ZCode AI agent (GLM-5.3)
BucketItem.emptyContents ends an evaporation with ServerLevel.sendParticles
(LARGE_SMOKE, the block's corner, 8, spread 1, speed 0, ALTERNATIVE).
Horizon played the hiss but sent no smoke for a player's bucket, and the
dispenser drew its own smoke one block too wide (nextDouble * 2 - 1 around
the corner instead of nextDouble from it) and sent none to other players.
evaporation_smoke does both: the window's puffs and the packet, now with
its randomization type (QueuedParticle.randomization). A client receiving
LevelParticles now honors ALTERNATIVE and ALTERNATIVE_WITH_SPEED, as
ClientPacketListener.handleParticleEvent does: positions spread over
[0, dist), speeds kept or scaled by nextDouble.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 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]>
Grass blades a few texels wide at 2048 average to about the same alpha everywhere by 64, and no scale of that alpha keeps the coverage, so the alpha test passes the whole quad or none of it. From 64 texels up, each level below the base takes the mean alpha through an ordered dither, about as many texels in as the base has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A new cart kind (spawner_minecart, made by /summon: there is no item) that
shows a spawner block, drops and is picked as a plain minecart, and saves and
loads the BaseSpawner fields. Its serverTick is the spawner block's, with
broadcastEvent as an entity event on the cart.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
longbridge/gpui-fast: Zed's GPUI with retained views and cached scroll layers.
The API changes are mechanical: focus and blur take the App, the application
comes from gpui-fast-platform (wgpu on Linux), menus say whether they're
disabled, shadows whether they're inset. oo7 follows to 0.6.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client helper that plays a sound event, vanilla's or the mod's own
from its assets' sounds.json, for this player only and up to a minute
later (SoundEngine.playDelayed), so a mod can time a blast's sound to
its shock wave. Audio's distance delay now goes through the same
play_after.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
A server mod's helper that throws a falling block at a velocity: it
flies, lands and places itself as sand does, or drops as an item, and
can hurt what it lands on as an anvil does. For explosion debris and
collapsing builds. Refused on a client, for another dimension, air, an
unloaded chunk, values that aren't finite, or past 1024 falling blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
A client hook asked once a frame for eight vec4s that every stage of a
Horizon shader pack reads as frame.mods, so a native mod can tell the
pack it bundles where and when something happened (an explosion's
centre and age for a cloud ray marched in a post pass). Values that
aren't finite read as 0; all are 0 when no mod sets them. The
reference is regenerated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
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.
A client hook asked each frame for turns in degrees and an offset in
blocks, which add up across mods. Horizon scales the sum by Distortion
Effects (0% turns it off), bounds it to 10 degrees and half a block on
each axis, and multiplies it into the view after the hurt tilt and view
bobbing. It moves what the player sees, not where they aim.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
Main's 31c1212bf added 17 strings to seven tables but not to ja, pt-br or the new tr, which
fails the every-language test on main. They are translated here as in the PR to main that
fixes it, so the lines are the same. tr.json also gets this branch's 26 account strings and
drops "Microsoft account · signed in", as ja and pt-br did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82d7UpxGuCncnLpJsq
A ResolvableProfile can carry a `texture` id (PlayerSkin.Patch body, a
ClientAsset.ResourceTexture). Vanilla draws a player head with that
resource-pack texture instead of the downloaded or default skin; Horizon
ignored it and drew the profile's default skin. Placed heads and head
items now read the patch, loading non-vanilla namespaces' textures from
the resource packs into a skin slot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01P1ZTfB3AfwKgGnv7UFKYVD
refresh_account and renew_for_game_with take the accounts.json a renewed profile is saved to.
The commands pass the launcher's own (Accounts::path()); the tests pass one that is never
there, so they no longer read or write the developer's launcher data. A new test checks that
a refresh saves the services' newer profile and leaves an unchanged one unwritten.
Main added pt-br.json and ja.json after this branch's strings: both get its 26 account
strings, and lose "Microsoft account · signed in", which the code no longer asks for (the
other tables dropped it already).
With main merged: profile_heads' test uses Accent::VIOLET (main made Accent a hue), and
docs/launcher-commands.md is regenerated for main's `completions` command, which main had
not written into it (the reference test failed on main too).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82d7UpxGuCncnLpJsq
native_block_entity_submit runs once a frame for each mod block entity in
view, as vanilla's block entity renderers do (64 blocks, frustum, chunk
fade-in), with a pose stack and helpers that add to the entity batch:
quads, model-element boxes baked as FaceBakery does, and block models
(submitBlockModel). Content mods' textures/entity join the atlas.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rdbtCVS's two review points on #4250:
- The launcher's own status workers (off the UI thread, showing "Waiting for
account access…") take the lock without the 3 s bound, so a worker queued
behind another account's renewal no longer shows a false "Could not check
sign-in". account list and MCP keep the bounded wait.
- refresh_account saves a renewed profile (new name or skin) to accounts.json
as launch() does, and the UI reloads accounts and profile heads when a
status arrives and the saved list changed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- audio.rs: main's shared-output fields (the device tracker's, f80c0360d).
- A guest in Window 1 leaves its F11 to P1: the game's window follows P1's option.
- disable() shows Window 1 again and says so (window1_shown), so a later
split into Window 2 hides it again.
- Clicks, key presses and the wheel from a window the focused player isn't
in no longer reach that player; releases still pass, so no key sticks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
Kept PadRouter's state_of with main's stick_axes, both Flags fields, and filled
main's audio device-tracking fields in Audio::share.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: integrated.rs keeps both Flags fields (split_players, load);
pad/hardware.rs keeps the PadRouter split and moves main's stick_axes
mapping into state_of. A shared Audio gets main's device-tracking fields
and leaves reopening the output to the game's own Audio.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's audio device tracking (#default_seen, devices_seen, device_ticks) added fields
the shared Audio built by share() now fills.
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]>
- A window that failed to open was tried again every frame, each time
re-laying out every player. Its players now play in Window 1 and the
window is forgotten.
- Vsync and the frame pacing's refresh rate follow the first shown
window, not a hidden Window 1; Window 1's visibility is kept in a
field, so Wayland (which can't report it) isn't told every frame.
- Only free windows remember where they were: a monitor's rect made a
later free window with its number open monitor-sized.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
route_pads released P1's pad hardware every frame, so with P1 alone the
reader lent for P1's turn lost its rumble and DualSense link a frame
after they started. It now releases only when nothing is lent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A controller first pressed with a trigger got a player, and the held
trigger then attacked or used: only buttons were held back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A LAN player who logged in under a registered seat's name (P2) passed
the integrated server's gate and the full-server check and held the
owner's permissions given to that seat. The gate now admits a seat's
name only from the memory connection (MEMORY_ADDRESS, port 0).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A refresh or a launch check holds the account lock across network calls.
Status reads (account list, the account page) now wait at most 3 s for
it, then say another operation is running, instead of waiting out a
network timeout. Writers still wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Translate the account status messages, print a failed `account refresh`
once, fetch a profile head again a minute after its skin download failed,
and check every account's sign-in at most once a minute on page switches
(an account added meanwhile at once), so switching pages no longer reads
the keychain each time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`account session WHO` renews an account and hands the running game a new
read-once session file, or the public status when it needs a browser
sign-in or a retry. `account sign-in --for WHO` runs the UUID-bound
re-sign-in from the command line. The launcher passes its own path to the
game as HORIZON_LAUNCHER_EXE. `account session` is kept out of MCP: its
file holds a token.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fetch public skin heads on workers with a bounded, URL-keyed cache and UUID default fallback, report each account operation's actual phase, and bind Sign in again to the account's UUID so a different Microsoft account is rejected before any credentials are stored.
Co-Authored-By: Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Show ready, refreshable, and sign-in-required states using the theme's success, warning, and danger colors, retaining icons and full labels.
Co-Authored-By: Codex <[email protected]>
Compare sign-in states directly instead of comparing a state to an Option's debug output, which continually restarted every account's status checks.
Co-Authored-By: Codex <[email protected]>
Inspect and renew due sessions on workers, expose Refresh and Sign in again actions, and share the operation through CLI and MCP for future launcher-to-game integration.
Co-Authored-By: Codex <[email protected]>
Review: device_event turned P1's GUI scale on for every mouse motion even with no other
player; it now hands the event to P1 directly while there is no compositor.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Upstream moved the title actions into title_action (the split guest's Quit guard goes there),
added the server's online mode beside our memory-seat limit bypass, the audio output's device
name (a shared output carries it), and dropped code mods' key hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 9 of the splitscreen series (#3541), on #3801. Each player plays in a window: Window 1
(the game's), Window 2, 3... opened for the players put there, or a borderless window filling
a monitor (over the menu bar on macOS, the system's borderless fullscreen elsewhere). Players
in one window share it. Closing a player's own window drops its players; the game's window
hides while nobody plays in it; titles say who plays where; a window reopens where it was.
Another player's F11 toggles its own window. HORIZON_SPLIT_WINDOWS (testing, until the Setup
screen) says each seat's window: `1,1,2`, or `m2` for monitor 2.
Live: tools/splitscreen_e2e.py adds `windows`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 8 of the splitscreen series (#3541), on #3794. While splitscreen watches the controllers
(HORIZON_SPLIT for now; the switch comes later), one reader serves every player, alone too:
the first controller used is P1's (its rumble and DualSense output lent to P1's turn). In P1's
world another controller pressing a button is offered to join on every view: A joins (as
P<seat>), Y drives P1 when P1 has none, B says no, 10 s and it goes. A controller that
disconnects leaves its player dimmed and asked: the same controller back takes it again, A on
another takes over, B lets it leave. Each view gets a short badge (who, and why: "Keyboard
and mouse" when it takes them).
Live: tools/splitscreen_e2e.py adds `alone` (watching, nobody joins).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Windows the game horizon-ctl launch starts inherits horizon-ctl's stdout pipe, which then
never closes while the game runs: subprocess.run waited forever. No pkill there either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 7 of the splitscreen series (#3541), on #3777. While several play, one controller reader
(PadRouter) serves every player's game: a controller nobody has goes, when pressed, to the
first player without one (P1 first); its first press is not passed on; a controller that
disconnects leaves its player without one. Each player's glyphs follow its controller.
Alone, P1 reads the controllers itself, exactly as without splitscreen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 6 of the splitscreen series (#3541), on parts 1 (compositor), 2 (audio), 4 (pointer
ownership) and 5 (layout). With HORIZON_SPLIT=N (testing only, no switch yet), N-1 more
players join P1's world through memory as P2, P3..., each its own Game in this process,
rendered offscreen and composited into P1's window. Keyboard and mouse go to the view
clicked last. A player who quits or disconnects drops out; alone again, P1 draws on its
window as before. Guests never open the controllers (pad.feed); routing comes next.
Live: tools/splitscreen_e2e.py (join, four).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 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."
/// A pack reload swaps the atlas: its skin slots are blank, so a forgotten
/// table holds nothing and hands out slots from the start again, even for
/// skins drawn in the last frame.
Loaded 100 of 849 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.