🤖 Agent: timing spans around every untimed call of a level's pass (about 200 ms of the SMP's 250-330 ms overworld pass had none, #15349); spans read nothing while off, and no behaviour changes.
🤖 Agent: the watchdog now reads the stuck server thread's stack (a SIGURG handler installed only when a tick hangs, 5 s wait, else the report says why), so the next SMP freeze (#15349) names the function it was in; nothing changes until a tick hangs.
tests/test_command_exec_group_cleanup.py fails on main since #13823: the new 'sulfur cube size attributes' group kills its cubes after its last tick without shrinking them, so a size 2 cube would split inside the next group. Same shrink as the other sulfur cube groups; both commands are setup commands, so no record changes.
en_us dropped "Your display's reported peak is often too high: calibrate to set it up." in 1c93067327; #14706 added its Ukrainian text back today, so lang::tests::bundled_files_are_served_and_have_no_stale_or_repeated_keys fails on main and on every pull request tested against it.
naga's GLSL frontend did not know the GL_KHR_shader_subgroup_quad swaps, so any program using
them (FidelityFX SPD in several packs) failed with Unknown function. They map to naga's QuadSwap gather.
naga rejected gl_SubgroupID and gl_SubgroupInvocationID when the workgroup had more than one
dimension, a WGSL rule GLSL does not have, so pack compute passes with a 2-D workgroup were skipped.
The official SMP's slow-tick lines spend ~200 ms of a 300 ms overworld
pass outside every named span. Spans now wrap each untimed call of the
level loop (enter_level, the mods' level hooks, streaming, update_world),
of tick/tick_world and of update_world, and level_tick wraps the world
tick, so the line names where that time goes.
Refs #15349
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerWatchdog's report only had the last 50 log lines, so a tick stuck
for 60 s on the official server (#15349) never said where. The watchdog now
interrupts the server thread with a signal (Linux, Android, Apple) whose
handler captures that thread's backtrace, and the report carries it as its
stack, as vanilla's report carries the server thread's stack. A thread that
does not answer in 5 s, or a system without the signal, is named in the
report's details instead.
Refs #15349
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep both the trapdoor fall_location_of and its test module and main's
mob-hit impl block in game/combat.rs.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
JukeboxBlock.setPlacedBy sets has_record with level.setBlock alone;
only JukeboxBlockEntity.notifyItemChangedInJukebox posts the
block_change game event. Since #14900 set_has_record posted it for the
placement too, and use_item_on failed 79 cases on main.
Fixes#15320
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#11899 left the shade of a world block entity to the entity shader,
which reads Vertex::normal, but the decorated pot's arm still
overwrote every normal with CULL_BACK (a baked-colour quad) to cull
the back faces of its flat top and bottom: the pot had neither a shade
nor a normal. mesher::cull_back keeps a shaded quad's normal with
SHADED_CULL_W (shade and cull, as the bat does) and gives CULL_BACK to
a baked one. The harness accepts SHADED_CULL_W as a shaded normal.
Fixes#15322
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Waypoint.Icon is written to the player's tag when it has data (a style
other than the default, or a colour) and read back when nothing is held
in memory. A tag without the key gives the default icon.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Move list state, foreground scheduling and the single background updater
into a private list_updates module. Retain the public social paths and
move the original state-mapping test without changing its assertions.
This is R1 after merged #15182, reapplied independently to main at
b66b2f8372. Preserve shared state, lock scopes,
request order and existing cache, failure and timing behavior.
Refs #15183
Co-Authored-By: ZCode <[email protected]>
ClientboundCustomQueryPacket and ServerboundCustomQueryAnswerPacket read what is
left of the packet as the payload and throw "Payload may not be larger than
1048576 bytes" past MAX_PAYLOAD_SIZE. Horizon read it with Rest, of any size.
RestN<N> is Rest with that limit; both packets use it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
BossAction, BossEvent, NumberFormat, SetDisplayObjective, ResetScore,
SetScore, SetObjective, TeamParameters and SetPlayerTeam, with their
codecs and the boss bar's two flag helpers, move byte for byte into
packets/play/hud.rs. play.rs re-exports the nine public items, so every
`packets::play::…` path still resolves.
The port map follows: the two rest-012 rows that name boss_flags and
SetPlayerTeam::write point at the new file.
Part of #13063.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CI leaves most pull requests' harnesses to parity-main and runs them on the pull request only when it changes a harness, an oracle, a probe or the runner (parity-scope in ci.yml). pr_ready ran every affected harness locally whenever the vanilla data was there: about 80 harness-minutes per pull request by the repository's own numbers. It now follows CI's scope and lists the others; --harnesses runs them anyway.
AGENTS.md gets a short "Don't lose time" section from measured agent sessions and CI: check for duplicates first, find code with the map, cargo check while working, no polling loops, one test per changed behaviour, don't hunt unrelated red checks, finish when a rule is unclear. CONTRIBUTING asks for a harness only where vanilla has something to compare.
docs/cleanroom, docs/native-hooks-neoforge and docs/how-horizon-is-built linked to the removed section and repeated its rules. The history of code removed for its license stays in how-horizon-is-built.
FullTextSearchTree.searchPlainText searches the plain-text tree (the
results' tooltip lines) alone; the id path is only searched for a
namespace:path query. #14910 also matched the item id path, so
recipe_book_view failed on main ("light_blue_candle" found recipes).
Fixes#15321
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CONTRIBUTING now says Horizon works in two stages: first it plays exactly like vanilla, then it improves on it. Performance work keeps compatibility with vanilla clients, servers and saves and the gameplay players rely on, but need not copy vanilla's internal order, random calls or slow paths. Pull requests say what they no longer reproduce exactly and how it was checked.
Allow the supported pinned Ubuntu 24.04 fallback in the runner-policy check while preserving the shared pool-routing and privilege guards.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Refs #15336. Pin the GitHub-hosted verify fallback for a controlled CI run; preserve the existing self-hosted routing and checks.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Report missing inline-feature fields against the complete flat feature map, matching the vanilla decoder.
Validation: 84/84 strict vanilla records, the feature-codec unit test, and all 5 Pong compatibility tests passed locally; required verify passed on f55a82aa12.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Keep the gate change unchanged and include main #15250, which carries the validated Guest.record test correction.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Match the already validated fix in #14219, #14088, #15250 and #15291: Guest.record holds the prediction record directly after main #14005.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
The requested bulk merge includes #15250 and #15291. Keep interaction_data and inline_feature_parse selected so those fixes retain automatic coverage.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Resolve the port-ledger conflict by retaining the explicit social_screen.rs::Entry description. The other path corrections are already on main.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
use_item_on (#15320), recipe_book_view (#15321), block_entity_render
(#15322) and fishing_hook_entities (#15323) passed at main's last green
parity-main (fdc87aecd, 2026-11-07) and fail now whatever a pull request
changes. Each has an issue naming the merge, or the range, that broke it.
Part of #15316.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Compatibility fix for main pull request #14005: the record moved out of anticheat onto Guest. Preserve the existing tick_end assertion.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
An issue was opened only for a harness that passed in the previous audit,
so one merged while its pull request's parity job failed or was skipped
never got one: nine such harnesses failed on main unreported (#15158).
Part of #15316.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Seven runs between 2026-11-09 and 11-11 searched up to 1,500 merges until
the job's 240 minutes ended, named no merge, and the next run started again
from nothing. The search now has --budget minutes (90), then says between
which merges each harness broke. What it learnt is kept in
.local/parity-runner/bisect.json while the base is the same, so the next
run narrows it further, and a merge already named gets no second comment.
Part of #15316.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
They merged while their pull request's parity job failed or was skipped and
fail on main whatever a change touches (#15158), so every pull request that
selects them fails and parity-main cannot pass. They still run when named and
in the nightly audit, and come off the list with their fix.
Part of #15316.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Canonicalize root entity attribute-list order while preserving all values, styles and other record fields.
Validation: 9 focused comparison tests and 105/105 strict vanilla parity records pass; current-head required verify is green.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
Mob::seen wrote a villager's reputation of the player into every Seen it
built, and no code reads the field (git grep finds only its definition and
that write; the trading code reads the gossip itself). Remove the field and
the lookup. The `player` parameter of Mob::seen is now unused and is named
_player: it has many callers, and a later change can drop it from them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
CombatTracker.getMessageForAssistedFall and DamageSource.getLocalizedDeathMessage
are ported with their .item forms; the rows now say what the code does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Piglin.holdInOffHand, PiglinAi.holdInOffhand and Drowned.rangedAttackUncertainty
are ported as vanilla's bytecode reads; the rows now say what the code does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Wolf.getLeashOffset, ArmorStand.brokenByAnything and HangingEntity.canCoexist
are ported as vanilla's bytecode reads; the rows now say what the code does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Qualcomm's Adreno Vulkan driver (Adreno 740, build 0676.41 of 2023-08-14) fails to link a
shader that indexes a constant array by vertex or pixel, which naga makes a private array, and
wgpu reports the failed pipeline as a lost device: the renderer never starts. The occlusion
test's box corners (cull.wgsl, gpu_occlusion.wgsl), the panorama cube (panorama.wgsl) and the
fading tiles' Bayer matrix (terrain.wgsl) are packed into u32 words read through a switch
instead, with tests that the packed tables are the old ones.
With the early depth test, occlusion culling on that driver now and then left sections out for
a frame (the world flashing black or see-through as a block broke); Adreno on Vulkan tests the
boxes in the shader, as Metal and DirectX 12 do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Realms downloads and uploads used predictable names in the shared temp folder and
File::create, which follows a planted link or reuses a planted file. They now use an
unguessable name and a new owner-only file (create_new, 0600).
- A pax record whose length was shorter than its own prefix ("1 x") sliced backwards and
panicked the Realms download worker; it is now refused.
- The Windows boot-screen painter restored its fonts in the wrong order, leaving one selected so
DeleteObject failed: one HFONT leaked per repaint. It now restores the DC's own font.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
In 26.3 Feature.DIRECT_CODEC dispatches on `type` straight to the type's
own map codec: there is no `config` wrapper, so `config` is just another
unread key and a missing field's DFU error names the whole map. The
command codec still fell back to a `config` sub-map, so
`{type:"minecraft:simple_block",config:{}}` reported `MapLike[{}]`. The
inline_feature_parse parity harness (#15158) now matches 84/84 records.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
vanilla-data.sh moves whatever a preparation adds to .local/ into
shared-local/prepared/<key> and links it back. Since the preparer locks
.local/ (.horizon-prepare.lock), that took the lock file too: .local/'s
lock became a link into one preparation. Once that preparation is
pruned (four kept, two runners share them) the link dangles, the
`.local/*` glob that drops old links skips dotfiles, and the next
preparation fails: ".local/.horizon-prepare.lock: No such file or
directory".
The lock now stays a plain file: it is left out of the entries, a link
to it is removed, and old .entries that list it are skipped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Forward coordinate-hit Reset World tiles to actions 70-75 so Generate New
World reaches the existing CreateWorldRequest flow. Keep active tasks from
accepting either coordinate clicks or direct tile actions.
Follow Minecraft 26.3 RealmsResetWorldScreen.init/FrameButton and
RealmsCreateWorldFlow.createWorld. Extend the existing regression with
actual mouse coordinates, bounds, creation metadata and running-task gates.
Fixes#15273
Co-Authored-By: ZCode (new-provider/gpt-6.1-sol) <[email protected]>
game/lan.rs is over 3000 lines and may not grow (docs/layers.md#file-size): a refused payload is
now logged at most once a second, kept in lan_quiet.rs, instead of once per connection through a
LanHost field.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
On Linux a socket read timeout is reported as WouldBlock, so a read cut short by the last part of
the deadline came back as that error instead of TimedOut (seen in CI's
a_router_trickling_its_answer_runs_out_of_time). Both kinds now end the request as TimedOut.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
LocalPlayer.isSuppressingSlidingDownLadder is !abilities.flying &&
super.isSuppressingSlidingDownLadder(), with LivingEntity's being
isShiftKeyDown() and ServerPlayer not overriding it. Horizon answered the
sneak for every body. A client body (client_side) that is flying no longer
suppresses the slide; a server body still looks at the sneak alone.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Spider.finalizeSpawn (Spider.java:160-172) rolls
nextFloat() < 0.1F * getSpecialMultiplier() on HARD, then nextInt(5) for
Speed, Strength, Regeneration or Invisibility, and applies it for good
(MobEffectInstance(effect, -1)), for every spawn reason. Only the chunk
generation port did that; the runtime finalizer (summon, spawn eggs,
spawners, natural spawning) had no spider arm. Add one, share the
setRandomEffect mapping (Spider.java:207-217) between both paths, and pin
the odds, duration, effect set and the CaveSpider exception with a test.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FallLocation.blockToFallLocation returns LADDER for Blocks.LADDER or a
block in #minecraft:trapdoors; Horizon mapped only the ladder by name, so a
fall off a climbed trapdoor said other_climbable. Move the name match into
fall_location_of and give it whether the block is in the trapdoors tag.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ServerCommonPacketListenerImpl.handleKeepAlive accepts an answer whose id is
the pending challenge; any other answer disconnects the player with
disconnect.timeout unless they are the singleplayer owner.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Replace only the two unseeded equality probes with deterministic built-in profession-domain checks. Keep saved VillagerData and all finalized reads exact, with regression tests for the corpus and strict feedback/callback comparison.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Keep quoted text, nested custom lists, modifier order and every styled
separator exact. Cover values, duplicate entries, modifier IDs and actual
state differences with regression tests; preserve the contributor commit.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
Send lit resident far columns with complete light layers, scan past blocked entries within the existing budget, and retain missing chunks for retry without player movement.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
IntegratedPlayerList.canPlayerLogin returns multiplayer.disconnect.name_taken
when the joining name is the singleplayer owner's (ignoring case) and a player
of that name is online.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Identifier.isValidNamespace returns false for the namespace "..", and
otherwise accepts a-z, 0-9, '_', '-' and '.'. from_parts only checked the
characters, so Identifier::parse("..:x") succeeded.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ClientPacketListener.handlePlayerAbilities sets the local player's walking speed from the packet
(Abilities.setWalkingSpeed), and AbstractClientPlayer.getFieldOfViewModifier divides the movement
speed by it. Horizon kept the packet but always divided by 0.1, so a server that sends another
walking speed got a different field of view (walking speed 0.2 with a movement speed of 0.2: 1.5
instead of 1.0).
The abilities code moves out of game/remote.rs, a big file, into game/remote_abilities.rs, with the
walking speed the field of view now reads (default 0.1 before any packet) and its test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ListOperation.Append, Insert and ReplaceSection give the old list back when
the result would pass the limit (256 for lore) or the offset is past the end
of the list. The command cut every result to 256 entries and moved an offset
past the end to the end instead.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MagmaCube.setSize (MagmaCube.java:60-66) writes size * 3 into
Attributes.ARMOR; LivingEntity.getArmorValue (LivingEntity.java:1883-1885)
reads that base back. Mob armor now reads the set attribute base instead of
only the type's armor default, so a summoned size-4 cube keeps its 12 armor.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RconClient.sendCmdResponse cuts the Java string with substring at 4096
UTF-16 units, so a character outside the BMP can straddle a cut, and
String.getBytes(UTF_8) writes each lone half as '?'. Horizon counted
characters instead. Split the output on UTF-16 units and write a lone
surrogate half as '?'.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
`width` and `interaction` share a HashMap bucket at every table size, and
keys sharing a bucket iterate in insertion order. java_hash_order ranked
neither, so they fell back to key order and `interaction` came first,
while Interaction.addAdditionalSaveData writes width, height, attack,
interaction and response in that order. The interaction_data parity
harness (#15158) now matches 120/120 records.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The official server's players.online (any u64 from a plain status ping) went into the history
as i64: above i64::MAX it wrapped negative, and near it SUM() overflowed, failing
/v1/live/history?source=official until the rows were pruned. A count above a million is now
treated as no answer: that minute stays empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
- Host: the inbound handshake slot is an RAII guard, so a failed spawn can't leak one of
MAX_HANDSHAKES until the direct port takes nothing.
- Sessions authenticate at most 16 direct-race streams, each under one 6 s deadline instead of a
per-read timeout a peer could drip past.
- Invite decode takes at most MAX_ENDPOINTS (8), as encode writes: a crafted code can't make the
guest dial thousands of addresses.
- A STUN attempt has one deadline, so datagrams arriving faster than the read timeout can't keep
the query, and the host's setup, going forever.
- A UPnP description's controlURL, URLBase or serviceType with control characters or spaces is
ignored instead of reaching a request line or header.
- The guest's direct-race listener handles each inbound stream on its own thread with a deadline.
- Relay: the connection count is given back by a guard, and attach tokens compare in constant time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
- UserList add/remove only log a failed save, so with a read-only or full disk the CLI and the
stopped server's API said "Banned X" or "Made X a server operator" though the file still said
otherwise. Each change is now saved and checked (saved()).
- ServerProperties::store wrote server.properties in place, default mode: a crash at start left a
short file read as defaults (a new management secret, settings dropped), readable by other
accounts. It now writes a new file (0600 when new, an existing file's mode kept) renamed over.
- system_message/kick translatable parameters are plain text, as vanilla's Message takes them,
not components tellraw would resolve with the console's rights.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
`portmap::default_gateway` runs `route print` on Windows. A program with no
console of its own, such as the launcher, got a console window for it each
time. The command now starts with CREATE_NO_WINDOW, as the other crates'
commands do.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- Backup zips (which hold server.toml and server.properties with the API token, RCON password
and management secret) are created owner-only.
- Pruning automatic backups deletes only `<stamp>-auto.zip`, not a backup labelled by hand
"nightly-auto".
- backups.every no longer panics on a wide last character ("6é") or overflows on a huge number.
- A Discord rate-limit header of "NaN" no longer ends the Discord thread.
- world.name may not be ".", nor backups, logs or .horizon, which pointed the world at the server
folder or the supervisor's own folders.
- init stops instead of replacing a whitelist.json or ops.json it could not read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
- One address could hold all 16 RCON places idle and keep the operator's own client out; each
address now gets at most 4.
- Management notifications went to every client from one thread with blocking writes: a client
that stopped reading stalled them for everyone. Management sockets get a 10 s write timeout
and a client whose write fails is closed.
- The event stream (SSE) gets a write timeout too, so a client that stops reading doesn't pin
its thread forever.
- same_token compares SHA-256 digests, so its time doesn't show the token's length.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
The council's polls are applied: rdbtCVS is the second organisation owner,
the council team holds CODEOWNERS and the security-manager role. The pages
that said Ludovic alone held these powers now say who does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tick_blocks built the random-tick list from the world map's keys, an
FxHashMap filled as the streaming workers finish, so two runs of one world
random-ticked their chunks in different orders and the level random's draws
landed on different blocks. Game::random_tick_chunks now sorts the list by
chunk position. Vanilla walks its simulation tracker's fastutil table, whose
slot order follows that table's history; position order is deterministic.
Fixes#15164
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#5879 lists these as the unknown blocks around the water flags of the official server. water_play now
builds each one beside the player (the server only fills loaded chunks), checks through the client that
the blocks are there as built, and plays in them: sprint-swimming down and up a kelp forest and holding
jump at its surface; jump held at a waterfall's foot, walking into it across the water it spreads,
sinking down it from the top, and standing at its foot while it stops and starts again; standing and
walking in a channel whose source goes and comes back; standing on waterlogged slabs, walking into
waterlogged fences and up waterlogged stairs.
The check's client renders offscreen when HORIZON_E2E_HEADLESS is set, as agent_control_e2e.py does,
so a run on a desktop opens no window that takes the focus.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The supervisor matched "Stopping server" and "Done (" anywhere in a stdout line, so chat
saying "/INFO]: Stopping server" told management clients the server was stopping and hid the
real notice. It now looks at the message after the first "/INFO]: " only.
- A quiet-commands payload's mod id went unescaped into the dedicated server's stdout and log:
a newline in it forged whole lines. decode now refuses control characters, DEL and the
section sign in the mod id and every line, as a chat command must pass.
- An empty batch (3 bytes) was queued and logged as a summary line, before any permission check,
hundreds of times a tick; it is now ignored, a batch with no room is counted on the player's
last waiting one, and a refused payload is logged once per connection.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
With Improved Transparency the cracks draw vanilla's destroy_oit_stage_* sprites, but the
crumbling shaders took their size from destroy_stage_0. A resource pack that replaces only the
plain stages at a higher resolution made the crack window larger than its sprite, so it showed
neighbouring atlas sprites (#11164). crumbling_sprite now reads the set the cracks draw.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
serde_json's index panics on a Map missing the key and on a non-object Value: a density
function "spline" without its "spline" field, a template pool whose elements aren't objects, or
a structure whose horizon:piece_limited isn't an object panicked the port. In the launcher that
killed pack_job's thread before PackDone, so the Modpacks page stayed busy until a restart.
Those reads now use get/get_mut and skip what isn't there, and pack_job turns a panic in a step
into an error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Keep main's minecart index/display extraction and expose cart_at to the below-world discard helper. Vehicle discard behavior is unchanged.
Co-Authored-By: GPT 6.1 Sol <[email protected]>
- dechunk sliced the device's chunk size into a from_utf8_lossy string: a chunk ending inside a
character panicked the sharing thread, under the reach lock on renewal, and the poisoned lock
then crashed the game at Host::stop. It now stops at such a chunk, and stop() and the renewal
loop take the lock even when poisoned.
- A router's HTTP answer has one 10 s deadline (each read was limited, not the answer), SSDP
discovery tries at most 8 gateways, and the mapping is renewed outside the reach lock, so
stopping sharing no longer waits on a slow device.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Play loads accounts.json, asks the services about each account (several requests, 20 s
timeouts), then saved that whole old copy without the lock: a sign-in finished meanwhile was
dropped (its secrets orphaned in the keychain) and a sign-out came back. save_to also wrote in
place, so a crash or full disk mid-write left a file read as no accounts.
save_to now writes a new file and renames it over the old one, and the launch paths save with
save_renewed: under the accounts' lock, the renewed names and skins go into accounts.json as it
is now, keeping accounts added or removed since.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
RealmsAvailability: GET mco/client/compatible, then GET mco/available for
a compatible client, once per account and shared by every caller. Nothing
consumes it yet: the Realms screen's gate and its refusal screens follow.
- realms/client.rs: client_compatible, has_parental_consent, and
RealmsError::message (vanilla's errorMessage, split out of text()).
- realms/availability.rs: Availability, Pending, AvailabilityCache, get,
current. Non-blocking; only an unexpected error is checked again.
- docs/realms-availability.md: how a screen consumes it (#14986).
- realms.rs: the module line, and a stale ResetKind comment made one line
so the file does not grow.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
`sudo horizon-server service install --system`, as the help says to run it, wrote `User=root`:
sudo resets USER to root, so the network-facing server (game port, RCON, management API, panel)
ran as root and left root-owned files in the operator's folder. The unit now runs as SUDO_USER,
else USER, and refuses to be written for root (from a root shell).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
- server.toml was read, changed and written in place with no lock by management clients, the
API, the CLI and sync_back: two overlapping writes dropped one change, and a crash after the
truncate left an empty file, which reads as every default and was then written into
server.properties. Writers in the process now hold one lock, and server.toml and
server.properties are written to a temporary file renamed over the old one.
- A whole-world restore whose last rename failed returned with the world moved aside and no
world in its place, and the game then made a new one. The old world is now moved back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
- A restore or update asked for while the game was stopped (or while it was already
stopping) was only consumed on Exit::Restart, so the game started on the current world and
the request waited for some later, unrelated restart, and a stuck update kept every later
one refused. Both are now applied at the head of the loop, before every start.
- An update timeout is clamped to a day (API, supervisor) and refused past it by the CLI:
Instant + a huge Duration panicked the supervisor after it had switched releases.
- A repeated stop from the API or the management protocol no longer kills the game while it
saves; only a second signal from a person does, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
The management server spawned a thread for every connection with no cap, and read the
upgrade request before authentication with only a 10 s timeout per read, so a byte every few
seconds held a connection forever, and a 1 MiB Content-Length was allocated up front. An
unauthenticated client could open thousands.
http::read_request now has one deadline for the whole request (REQUEST_DEADLINE, 10 s), grows
the body as it arrives, and both the management listener and http::serve hold at most
MAX_OPEN (64) connections at once through a Slot given back on drop; past it a new connection
is closed unanswered.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
- The relay-wide unknown-code budget now refuses only joins whose code turns out unknown:
a handful of /24s or /48s could keep it empty and every guest with a real code got
RateLimited. Per-address and per-site budgets are still checked before the lookup.
- Behind the PROXY protocol, a connection from outside RELAY_PROXY_FROM is closed before
anything is read, as the docs say, instead of holding a slot for first_line.
- Registrations get a per-site budget (IPv4 /24, IPv6 /48; 30 a minute, burst 10) on top of
the per-/64 one, and ended nameplates expire oldest first from a queue instead of a scan of
the whole map under the global lock on every registration.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
AbstractScrollArea.extractScrollbar requests POINTING_HAND over a scrollable
bar and RESIZE_NS while it is held. The pack screen drew its scroller without
asking, so the pointer stayed an arrow over both lists. The draw now requests
the hand or the resize cursor from the held state it already tracks.
`Zombie.isUnderWaterConverting` and the `ConversionTracker` it drives had no
row, nor did the two husk overrides that send the conversion to a zombie; the
behaviour is `undead.rs::custom_server_ai_step`, and the row cites it for
each method the jar declares there.
Names read from `javap -p` on 26.3: `Zombie.isUnderWaterConverting`,
`Zombie.convertsInWater`, `Zombie.convertsToWhenDrowning`,
`Zombie.getConversionSound`, `ConversionTracker.tick`, `Husk.convertsInWater`,
`Husk.convertsToWhenDrowning`, `Husk.getConversionSound` and
`ZombieVillager.convertsInWater` (false in the jar, so it is not part of the
arm).
`map-check`: 165 rows, no problems.
`SerializableChunkData.parse` builds the position with `getIntOr("xPos", 0)`
and `getIntOr("zPos", 0)`, so a chunk that does not carry either tag is the
chunk at 0, 0 and `Tag.asInt` accepts the narrower integer tags as well as
`Int`. `ChunkData::new` refused the whole chunk, so an edited or hand-written
chunk without them could not be opened at all.
The documented `bufferObject.<n> = <size> <file>` form was refused:
the loader read the value as a size and nothing else, so a pack with
`bufferObject.2 = 16 data/seed.bin` lost its buffer with the note
"not a buffer object".
The loader now reads that form: the file's bytes come from the pack,
relative to `shaders`, and the buffer starts with them. A file that is
not there, that leaves the pack, or that is bigger than the buffer it
fills is refused with a note, as the other pack properties are.
Co-Authored-By: OpenCode <[email protected]>
Font.PreparedTextBuilder.markBackground reaches one pixel left of the first
glyph and one above it, the glyph advance to the right and nine pixels down.
Horizon drew two pixels on every side (right and bottom included), so every
HUD title, action bar, boss bar and selected-item backdrop was a couple of
pixels too large. The fill now matches the glyph box.
Without the change the four length cases fail with `Err(InvalidData, "invalid
chunk payload length")` and the cut-off case with `Err(UnexpectedEof, "failed
to fill whole buffer")`.
`RegionFile.getChunkDataInputStream` answers `null` whenever the file cannot
hand over the chunk: a header cut off by the end of the file ("Chunk header is
truncated"), a declared length of 0 ("allocated, but stream is missing"), a
length past the chunk's sectors ("stream is truncated") or a payload shorter
than its own header. Horizon returned an `Err` for all four, so a reader that
walks a region gave up on the whole file instead of skipping the chunk.
A zombie at 3.0 absorption converts through `single` into a drowned: the new
mob reads 3.0. Without the copy the new mob has 0.0, the effect having been
copied but not the field the effect filled.
`ConversionType.convertCommon` copies the effects, then
`to.setAbsorptionAmount(from.getAbsorptionAmount())` - the golden hearts an
absorption effect left on the mob are a plain field, and the effect copy does
not rebuild them.
`conversion::single` copied the effects but never the field, so a zombie with
three absorption hearts that turned into a drowned came out with none.
Move the existing DTOs and response parsing into a private model module,
retaining public social paths and the existing parsing tests. Keep wire
and presence helpers visible only inside the social module tree.
Document the small pure-move preparation sequence, including a mandatory
manager module and a vanilla audit gate after all structural steps.
No cache, scheduling, action or UI behavior changes are included.
Fixes#15179
Co-Authored-By: ZCode <[email protected]>
tools/ci/legacy_releases.py writes the pinned SHA-256s of the releases
published before signing began, and tools/ci/resolve_reviewer_ids.py
fills in the numeric GitHub IDs the comment commands decide by. Twelve
of their thirteen siblings under tools/ci/ have a test; these two did
not, although both are pure functions over what GitHub returns.
Co-Authored-By: OpenCode <[email protected]>
Milking a cow, goat or mooshroom plays at pitch 1.0 with no level-random draw, as AbstractCow.mobInteract, Goat.mobInteract and MushroomCow.mobInteract do.
Shearing a sheep or mooshroom by hand now posts GameEvent.SHEAR with the player, as Sheep.mobInteract and MushroomCow.mobInteract do.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Its two version lookups fell back to a literal "26.3" when the lock
file was missing or had no version, so a version bump that touched
only research-lock.json left the ledger looking in the wrong folder.
tools/lab.py already reads the pinned version from there; the
fallback now uses it.
Co-Authored-By: OpenCode <[email protected]>
Its help marked "build --check" and "check --offline" as (CI), but no
workflow runs them: CI runs tests/test_roadmap.py, which checks the same
two things (the generated files match the toml, and the file keeps its
rules). The help pointed contributors at a command CI never runs.
Co-Authored-By: OpenCode <[email protected]>
Three tools took their first argument as a path to write into or
download into, so "--help" created ./--help: the pad glyphs wrote
14 PNGs into it, and fetch_dxc.py downloaded Microsoft's 22 MB archive
into it. runner_pause.py, a watcher the service runs with no
arguments, started watching whatever it was given and then crashed on
a folder that was not there yet.
They print their usage and exit instead, and the shared dev-tool test
covers the two that write.
Co-Authored-By: OpenCode <[email protected]>
🤖 Agent: CODEOWNERS moves from @ludovic111 to @horizon-rust/council, as the council decided (P35); the team exists with the six members and write access; reviewer tests pass. Paths guarded by /nightly are unchanged.
The council decided (P35, 2026-11-10) that code-owner reviews move from the
founder to a GitHub team of the six council members. The team
horizon-rust/council exists and has write access, which code owners need.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ClientBundleTooltip.getEmptyBundleBackgroundHeight is
getEmptyBundleDescriptionTextHeight(font) + 13 + 8, and that height is
font.split(BUNDLE_EMPTY_DESCRIPTION, 96).size() * 9: the description's wrapped
line count at one decimal. Horizon hardcoded two lines (18), so a translation
whose description wraps to three lines drew the progress bar and the box's
bottom two pixels too high.
tooltip_layout and bundle_tooltip take the line count; the tooltip's probe and
draw compute it from the same font and 96 pixel width the description is drawn
at. SmithingScreen's slot icons moved to game/smithing_icons.rs so the
3,000-line stations.rs does not grow.
ServerData.validateIcon keeps any PNG whose width and height are at most 1024
(MAX_ICON_SIZE); FaviconTexture.upload still needs exactly 64x64 to draw one.
Horizon used icon_pixels (64x64 only) as the gate too, so a server whose
favicon was, say, 128x128 had its icon dropped from servers.dat and never
saved on join. valid_icon now answers the save question, and icon_pixels keeps
answering the draw one.
container_screen: the oracle makes its screens with Unsafe.allocateInstance,
which skips AbstractContainerScreen's constructor and so its
`skipNextRelease = true`. #13723 made Horizon's Clicks start that way,
as the real screen does, and the oracle then disagreed on every case
whose first event is a release (373 cases). The oracle now sets the
field the constructor would.
stand_lodestone needs --work-dir, a frozen Java run (--reference-dir)
and a built game (--binary), which an automatic run never passes, so it
failed in 0 s on every runner. It joins the other capture-preserving
harnesses in parity_hosts.json's named_only: it runs when named, and
in the nightly audit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractSliderButton.handleCursor asks for RESIZE_EW while dragging,
POINTING_HAND when hovered and NOT_ALLOWED when inactive. Horizon's
widget_cursor only knew the hand and the forbidden sign, so the shell's
sliders (their held state already known in RowView) never asked for the resize
cursor. widgets::slider_cursor adds it, the shell's slider rows call it, and
the client maps Cursor::ResizeEw to EwResize.
The #[path] mod wheel block sat right after pub use Opt, the anchor the
widgets PR uses. It now sits after mod shell_pages, and its test moved into
the module's own #[cfg(test)] mod tests instead of appending to
options_ui_graphics_tests.rs, so the three siblings touch disjoint lines. No
behaviour changes.
The #[path] mod preset block sat before use crate::{, the same anchor the
widgets and wheel PRs use. It and its pub(crate) use now sit just above the
test modules at the end of the file, and the preset test moved into the
module's own #[cfg(test)] mod tests, so it no longer appends to
options_ui_graphics_tests.rs either. No behaviour changes.
Default reads None; 2.0 and 6.0 seconds read the delay message with one
decimal; back to zero reads None. The slider test from the previous commit
still pins the tenths.
Options.chatDelay's message is options.chat.delay_none at zero and
options.chat.delay with the delay to one decimal otherwise (both label and
value_text). This folds the message half of #15068 into this PR, so one change
covers the control: the IntRange(0, 60) slider from the previous commits and
the message here.
For merge hygiene the new values module sits above mod shell_pages (not after
pub use Opt) and the ChatDelay arms lead their matches (not after the wheel
sensitivity arm), so the wheel-sensitivity PR's hunks do not touch these lines.
Options.chatDelay is IntRange(0, 60) xmap(v -> v / 10.0, v -> v * 10, true),
a slider from 0 to 6 seconds in tenths: toSliderValue is (tenths / 60) and
fromSliderValue is floor(map(v, 0, 1, 0, 61)) / 10. Horizon had no kind or
set_slider arm for it, so the Accessibility row drew a button whose click
changed nothing. The value labels (generic, on_off, chat_px) moved to
options_ui_values.rs with the mapping so options_ui.rs does not grow.
`a_gateway_that_ages_marks_the_chunk` ticks a gateway one server tick: the
age reads 1 and its chunk is in `world.modified`. Without the mark the
chunk stays clean.
`TheEndGatewayBlockEntity.portalTick` ages the block entity and ends with
`setChanged`, so the age the beam and the chunk save read is saved.
Horizon's `tick_end_gateway` copied the server age onto the block entity
but never marked the chunk.
Options.mouseWheelSensitivity is an IntRange(-200, 100) through
logMouse/unlogMouse: the value is 10^(i/100), so the slider's handle sits at
(i + 200) / 300 and a drag picks the step by floor(map(v, 0, 1, -200, 101)).
Horizon used a straight line from 0.01 to 10.0, which put the default 1.0 at
0.099 of the track and made every drag pick a different sensitivity.
A mob, an item and a vehicle kept the PortalCooldown they were loaded
with, so their countdown never showed in a save or in /data get entity.
The portal state ticks the countdown down every tick, as
Entity.processPortalCooldown does; the save now takes it from there, as
the projectile writer already did.
Fixes#14539
Co-Authored-By: OpenCode <[email protected]>
Measured on the mod: two particles of a type with exclusion_radius 2,
spawned a block apart in the same tick, both show, with or without
physics; one born inside a block (dropped) keeps no clear one from
spawning beside it. Horizon also counted the pending particles of the
tick, so the second was refused. The check now looks only at those that
joined; a later tick's spawn near them is still excluded.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured on the mod: with limit 1, one particle with physics born inside a
block and one clear of it in the same tick, the clear one shows. Horizon
counted the pending one, which is dropped only when it joins, so the clear
one was refused and nothing showed. A pending particle with physics now
counts only once it joins: the limit is checked again at the join, after
the drop.
Part of #11486
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`a_doused_campfire_posts_block_change_from_the_potion_owner` puts a lit
campfire at (2, 4, 3) and douses it with a mob owner: the block reads
unlit and the posted events hold
`("minecraft:block_change", [2.5, 4.5, 3.5], Some(Who::Mob(7)))`.
With no source passed the event's source is None.
`AbstractThrownPotion.douseFire` passes `getOwner()` to
`CampfireBlock.douse(entity, level, pos, state)`, which calls
`level.gameEvent(entity, GameEvent.BLOCK_CHANGE, pos)`: the sculk sensor
hears the campfire go out from the potion's owner (possibly no owner at
all).
Horizon's `potion_hit_block`/`douse_fire` posted the event with no
source; the candle path stays sourceless, as vanilla's
`AbstractCandleBlock.extinguish(null, ...)` is.
The packet queued with `served.send` and the one the probe sends from
`Session::tick` are framed by different paths: `Conn::serve` takes the first
from the outbox, `Conn::sweep` frames the second straight into `out`. Whichever
the worker thread reaches first is written first, and the sweep is on a timer,
so a CI run took the probe's packet (id 33) before the queued one and the test's
two `assert_eq!`s failed. The order is common, but not something the pool
promises.
The test now takes both packets and tells them apart, then checks what it is
about: the kick goes after the queued packet, `kicked` is reported, the
connection closes, and nothing follows the kick. The kick is still sent only
after both packets arrived, so that part stays ordered.
Fixes#14660🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
`a_slot_toggled_in_the_menu_lands_on_the_crafter` opens a crafter with
slot 3 disabled, clicks it with an empty hand and checks the block
entity: the slot is enabled and the chunk is marked. Without the mark the
click leaves `world.modified` empty.
`CrafterBlockEntity.setSlotState` ends with `setChanged`, which is what
saves a toggled slot. Horizon applied the menu's slot states to the block
entity on every click (`menu_click`'s write-back) but never marked the
chunk, so a disabled slot could be lost on save.
The write-back is now `Game::apply_crafter_slot_states`: it copies the
menu's flags, and when one changed calls `container_changed` (the
container-marking path, which also re-reads comparators).
A 1.20.2-1.21.4 server's smithing-trim display (or trim recipe) is expanded into one
SmithingTrim per pattern, each with its own copy of the base and material displays. Those
can themselves be trim displays, so a few hundred bytes asked for billions of nodes.
Refuse a preview whose patterns x (base + material weight) exceeds MAX_TRIM_PREVIEW (4096),
counted with the new SlotDisplay::weight, both in SlotDisplay::read (1.21.2-1.21.4) and in
the 1.21.1 trim recipe translation; the 1.21.1 path also builds each display once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Every Commands packet becomes a dispatcher, and every resource-style
argument the vanilla tree doesn't match goes through intern(), which
Box::leaks its registry name for the life of the game. The name is the
server's, up to 32767 bytes and unchecked, so a server sending trees of
new names packet after packet leaked megabytes each time until the
client ran out of memory.
intern() now keeps only resource-location names of at most 256 bytes,
and at most 1024 of them; anything else is kept as "" (no suggestions
for that argument).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
text::plain flattens a server's Disconnect component into one String,
and format_java copies an argument once per %1$s in the server's own
`fallback` template. A fallback of 16 000 "%1$s" (64 KB) whose argument
is the same construct, nested three levels (well inside the depth-32
guard), asked for about 4e12 bytes; the allocation failed and the client
aborted instead of showing the reason. The login phase's json_text, a
second copy of the same expansion, had no depth limit either.
plain, format_java and json_text now stop at MAX_TEXT (1 MiB), cut at a
character boundary (push_capped). A disconnect screen needs a few
hundred bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
naga's GLSL front end refuses `const float[] w = float[](...)` and
`float w[] = ...`, which vanilla's GL compiler sizes from the initializer.
The size is written in, in the declarator and the constructor. Brackets
are matched in one pass and the declarator is looked for at most 256 bytes
back, so a long line stays linear.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
naga's GLSL front end refuses sampler2D function parameters that vanilla's
GL compiler takes. Each such function is copied once per set of samplers
its callers pass, the copy reading those samplers by name: overloads keep
their own bodies, nested calls are rewritten inside out, prototypes become
the copies' prototypes, and copies go after their samplers' declarations
(lucasmts59's finding in review).
Pack text is untrusted. Brackets are matched in one pass and the scans for
calls have a budget, so no text freezes a load. A sampler parameter with
no name, edits that would overlap, or anything else the rewrite can't
follow leave the whole shader as the pack wrote it, for naga to refuse.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SystemEntropy is splitmix64 seeded from 8 bytes. On a first start with
no secret and no level-seed (both defaults), the 40 characters of
management-server-secret were outputs 1-40 of that stream and the world
seed was output 41; splitmix64's mixer is a bijection, so the world
seed (which ops read with /seed, and seed crackers recover from
terrain) gave back the state and every secret character. The secret is
kept when the owner later enables the management server. On Windows the
seed fell back to the clock, so the secret depended only on the start
time.
Secrets (the management secret, the TLS keystore password) now come
from OsEntropy, every draw straight from getrandom with rejection
sampling, as vanilla's SecureRandom. SystemEntropy itself is seeded
from getrandom too, the clock only if that fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
PATCH /api/v1/config checked the raw JSON key with changeable_remotely,
which doesn't trim it, then applied "{key}={value}" through
parse_change, which does. A key " engine.path" (or "\tapi.allow_public",
" discord.webhook", " properties.enable-rcon") passed the deny-list and
was written to server.toml: at the next restart the API or panel opened
to the internet, chat went to another webhook, RCON came on, or the
supervisor ran another program.
changeable_remotely now refuses any key with whitespace or control
characters in it, so what it checks is what gets applied.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
players/kick put the management client's reason, newlines included,
into "console kick <name> <reason>", which Control::ask writes as one
line to the game's control endpoint. The endpoint reads one request per
line, so a reason of "x\nconsole op Mallory" ran a second request: any
console command, or any other control verb, which the management
protocol never offers.
The kick reason now drops control characters, and Control::ask itself
refuses a request with \n or \r before sending anything, so every caller
is covered (the API's /command also refuses \r now, as it did \n).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
A backup turned autosave off, then flushed. If the flush failed (the
control connection's 30 s reply timeout on a big world or a slow disk),
the ? returned before `console save-on` was sent, and the game kept
running with autosave off until the next backup that succeeded;
scheduled backups take this path, so a crash in that window lost
everything played since.
Once save-off has been asked, saving is now always turned back on: over
the same connection after a good backup, and over a fresh one when the
flush failed (a reply that timed out leaves the old one out of step).
The flush gets its own 300 s reply timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
26.3 packs write gl_VertexIndex where older ones wrote gl_VertexID: both
mean vanilla's vertex number, four per quad. Only gl_VertexID read the
glue's expression, so on the text pipeline (six vertices per glyph) a
26.3 pack read the raw index. Both now read it; the glue's own code above
the pack's keeps the real index.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack whose one terrain program (gbuffers_terrain) draws both solid and
cutout terrain ran it twice over the solid list, each pass moving the other
part's quads off-screen: every solid-terrain vertex was shaded twice (0.6 ms a frame measured with a path-traced pack on an RTX 2080).
Now the game also gives the loader terrain's inputs for one pass
(GameInputs::terrain_one_pass, GbuffersInputs::parts): the vertex stage
works out its quad's part from the sprite layer, as before, and hands it to
the fragment stage as a flat varying, which applies that part's alpha test.
alphaTestRef becomes a per-part value in both stages (0 for solid, 0.5 for
cutout, as Iris 1.11.7 did in #9307's probes), and alphaTest.gbuffers_terrain
still replaces both parts' tests. When both tests are the same, nothing
changes in the shader. Packs with gbuffers_terrain_solid or _cutout keep
their two passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mob.isSunBurnTick (Mob.java:500-506) counts isInPowderSnow and wasInPowderSnow
as not burnable, so a mob standing in powder snow never ignites; Horizon only
tested water and rain, so a zombie in powder snow ignited in daylight and
PowderSnowBlock.entityInside (PowderSnowBlock.java:88-99) then destroyed the
block under it with mobGriefing and put it out, leaving it to burn freely.
The scan at burn time is isInPowderSnow; m.in_powder_snow, the scan baseTick
made before the mob travelled, is wasInPowderSnow.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HORIZON_SHADER_PACK_TIMING pushed every pass's time into the window of its name, so two passes of
one name in a frame (a program drawn over several render passes, or deferred2.csh beside
deferred2.fsh) shared a window and its median was the time of neither. A frame's passes of one
name now add up to one sample, and a compute pass is timed as "<name> (compute)".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Zombie.doHurtTarget` (Zombie.java:315-325) rolls `effectiveDifficulty * 0.3`
for a burn of `2 * (int)effectiveDifficulty` seconds only when
`super.doHurtTarget` landed, the main hand is empty and the zombie is on fire,
and every species that extends `Zombie` inherits it: a burning husk, drowned,
zombie villager and zombified piglin now burn too, while a zombie holding a
weapon and a blow a shield took whole no longer do. The chance is drawn in
`do_hurt_target` where vanilla draws it, in the mob's own tick, and the game
lights the burn only for the blow it rides.
The turtle egg's break sound also draws its pitch from the level random
(Zombie.java:553-555): `0.9 + level.getRandom().nextFloat() * 0.2`.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GoatAi.initRamActivity wants TEMPTING_PLAYER and BREED_TARGET absent, and
GoatAi.updateActivity picks RAM before IDLE, so a goat being fed or bred only
follows; a player who starts feeding it during the prepare also ends the
activity (PrepareRamNearestTarget.stop starts the cooldown).
RamTarget.tick runs applyItemBlocking a second time for its mobAttack push and
scales it by blockingFactor (0.5 when the victim's raised item blocked part of
the hit). The push now names the damage the hit dealt so the host can do that,
and blockingFactor * speedFactor keeps vanilla's float-then-double order.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
KeyBindsScreen.extractRenderState walks the mappings and sets
resetButton.active from KeyMapping.isDefault, so the button is inactive until
some binding differs from its default. Horizon drew it active and reset every
mapping on any click.
OptionInstance.SliderableEnum.fromSliderValue maps a slider position by
floor(v * values.size()), with 1.0 just below, and clamps to the last value:
the four GraphicsPreset values change at 0.25, 0.5 and 0.75. Horizon rounded
v * 3, so dragging to 0.2 picked Fancy instead of Fast and 0.8 Fabulous
instead of Custom.
The client kept only the dialog tags of ClientboundUpdateTagsPacket and
read block and item tags from the game's own files, so a data pack's or
plugin's tags (climbable, mineable, ...) never reached a player on a
server. Dynamic now keeps every registry's tags; a server session builds
the game's Items with the server's block and item tags in place of its
own (by name, through the built-in registries), and Game::items() reads
it. The play-state packet after /reload replaces them the same way
(ClientPacketListener.handleUpdateTags).
The server tables (the synced enchantments from #9301 and now the items)
move to game/remote_tables.rs, so remote.rs shrinks.
Closes#7843
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BlockInput.place loads the tag into the comparator's block entity, and
ComparatorBlockEntity.loadAdditional reads getIntOr(OutputSignal, 0).
Horizon keeps a comparator's output beside the block entities and only
/data wrote it; /setblock and /fill now load it through the same
load_comparator_output.
Fixes#13870
Co-Authored-By: Claude Opus 5.5 <[email protected]>
container_edge_sound sent every block named *chest* to block.chest.open
and close. Vanilla's ChestBlockEntity opener counter plays the block's
getOpenChestSound/getCloseChestSound, which a copper chest takes from
CopperChestBlock.getHingeSound by its weather state. The copper golem's
transport path already had that mapping; it moves to
transport_items::chest_hinge_sound and both paths use it.
Closes#15039
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Include the root model scale in the appendage margin so animated elder guardian vertices remain inside the precheck bounds. The existing prepared-model coverage test remains unchanged.
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
stations.rs is over the 3000-line limit and may not grow
(docs/layers.md#file-size): the test module hangs off stations/anvil.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#14603 gave new_rocket the rocket's own random as a fourth argument; the
tests added by 188817342 still called it with three, so the client's tests
stopped compiling on main.
Refs #15025
Co-Authored-By: Claude Fable 5.2 <[email protected]>
dispenser.rs: main (#14603) moved the firework rocket out of the match
into launch_angled_rocket, which makes the rocket's own random. This
branch only renamed the random argument in that arm, so main's version
stays and the arm goes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A datapack dimension (`data/<ns>/dimension/`) gets its own `DimensionType`, and
vanilla's renderer reads the skybox, cardinal light, ambient light and
attributes from it, not from the level's key. The client's draw choices now
read the level's dimension type facts the same way.
- Cardinal light picks the diffuse lights and the section shading; the type's
`has_skylight` picks the section sky light.
- The skybox picks the sun, moon, stars and clouds, the End sky and its flashes,
the sun shadows and the shader sky type; the ambient light picks the entity
shadow darkening.
- The day timeline, `water_evaporates` and `piglins_zombify` pick the firefly
bush and creaking effects, the dripstone particle and the piglin conversion
shake; the minimap reads the level shape's ceiling.
- `Resources::environment_of` picks the environment table by dimension type id.
For the Overworld, the Nether and the End every site returns what it did before.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
#14335 read Game::studio_replay, which the layer split had moved to
Game::replay before it merged, so main stopped compiling.
Fixes#15025
Co-Authored-By: Claude Fable 5.2 <[email protected]>
On a server whose data pack adds an enchantment, the anvil showed the red
cross for a book of it: the client's result prediction read the world's
own enchantment table, which lacks the entry. AnvilMenu.createResult on
the client reads the synced registry, so the station previews and the menu
engine now read Game::enchantments(), the server's table in a server
session and the world's otherwise.
Closes#8688
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`SocialInteractionsScreen` declares no `mouseClicked` at all: its clicks come
from `ContainerEventHandler.mouseClicked`, the inherited child dispatch, which
reaches the search box's `EditBox.onClick` before the tabs, the rows' buttons
and Done. The body and the port map had attributed that to an override on the
screen.
`social_screen::click_kind` now answers which child the click belongs to -
`Click::Search` for the box, `Click::Action` for the list - and `Game::social_click`
performs it. The test drives `click_kind`: both corners of the box are the
box's, the pixels left of and just below it are nobody's, and Done's centre is
the list's `Action::Done`. Without the search branch the two inside corners
read `None`.
The caret move itself still needs the GUI atlas (`EditBox::click`), so it sits
in `Game::social_click` and is not asserted: every `Game` test in this crate
needs prepared resources and is `#[ignore]`d.
A snapshot could copy an old cached height and then a stale bit another reader had just cleared, retaining the old height as clean. Acquire the stale-bit snapshot first: marked columns rescan, and cleared bits publish the refreshed height. A concurrent Chunk snapshot test covers both heightmaps and fails with the original ordering.
Co-Authored-By: Kacper <[email protected]>
Co-Authored-By: OpenCode GPT 6.1 Sol <[email protected]>
Count performance cores on Linux systems with asymmetric core frequencies
(such as Intel Alder Lake, Raptor Lake, Meteor Lake and ARM big.LITTLE)
via sysfs cpufreq, bringing parity with the Apple silicon performance core
heuristics from #11294.
Co-Authored-By: Antigravity <[email protected]>
`BannerPatternLayers.CODEC` is `Layer.CODEC.listOf()` and
`BannerBlockEntity.loadAdditional` resolves it with `ValueInput.read` plus
`Optional.orElse(BannerPatternLayers.EMPTY)`, so a single layer the codec
rejects leaves no layers at all. The branch kept the valid ones with a
`filter_map`, which is the partial behaviour the body says it fixed.
The list now reads all or nothing, and the test asserts the empty answer for a
mixed list, a missing field, and that a list of valid layers still reads back
(and round-trips), so an always-empty result cannot pass it.
DecoratedPotBlock.getAnalogOutputSignal goes through
AbstractContainerMenu.getRedstoneSignalFromBlockEntity ->
getRedstoneSignalFromContainer, but Horizon keeps a pot's one slot in the
chunk's saved block entity rather than world.block_entities, so the
container signal found no container and read 0. The comparator path now
builds the pot's ContainerSingleItem view from the saved tag (the same one
hopper insertion already used) and reads the slot through it, so a pot
with items powers a comparator and a hopper insertion updates it.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ZombieVillager.defineSynchedData` seeds `DATA_VILLAGER_DATA` from
`initializeZombieVillagerData(this.random)`: one `nextInt(15)` on the entity's
own random, made inside `Entity`'s constructor. Horizon built a synthetic
`LegacyRandom` out of the mob's random state and id instead, so the profession
never matched vanilla's and the mob's own random never advanced.
The draw also has to sit in the right place in the stream. `Entity.<init>`
makes the UUID's two draws and then calls `defineSynchedData` last, while the
yaw `living_random` used to take belongs to `LivingEntity`'s constructor, after
it. So `living_random` now returns the random straight after the UUID, and the
two draws it left behind move to `zombie_villager::constructor_draws`, which
`Mob::with_random` calls in vanilla's order. Nothing moves for other kinds:
those draws are constants, so their streams are unchanged.
Two draws vanilla never makes are gone as well. `ZombieVillager.finalizeSpawn`
only picks the villager type and never rolls a profession, and neither does the
spawn path. `readAdditionalSaveData` redraws only for a finalized save with no
`VillagerData`, which `read_nbt` now matches.
Verified with the issue's repro (`parity_fuzz.py --replay`, seed 998): vanilla
`butcher` against Horizon `nitwit` before, both `butcher` after. The replay's
other difference, `Motion[1]` -0.0784 against 0.0, is pre-existing on
`origin/main` and untouched here. `seed_sync`, `zombie_cure_sound` and
`world_spawn#1`-`#4`/`world_spawn_sync` pass.
`mobs.rs` is past the 3000-line limit, so it gains no lines: the helper lives in
`zombie_villager.rs` and `living_random` loses the yaw line it used to take.
Co-Authored-By: DeepSeek V4.1 Flash (OpenCode) <[email protected]>
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
Villager.readAdditionalSaveData reads it with getByteOr, which narrows
any numeric tag (IntTag.byteValue is (byte)(value & 255)): FoodLevel:300
loads as 44. The loader kept the raw number.
Brings the branch up to date and lays out the new code and tests as
rustfmt wants them, which the verify job checks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds a fourth NoAI pig (self) summoned with last_hurt_by_mob set to its
own UUID, read at the same points as echo, so vanilla 26.3 shows that
EntityReference.getEntity resolves the holder too and baseTick ends the
record after 100 ticks.
Part of #12830
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Brings the branch up to date and lays out the new code and tests as
rustfmt wants them, which the verify job checks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Players are entities of the level from placeNewPlayer/respawn on, so a
guest whose client is still loading is equipped too, and guests and the
local player sort with the mobs and stands: entity sections first
(EntitySectionStorage.forEachAccessibleNonEmptySection, SectionPos.asLong
order), then join order by entity id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EquipmentDispenseItemBehavior picks the first living entity in front that
canEquipWithDispenser, players included; ArmorStand.canUseSlot rejects locked
slots and hands without arms. Fixes#12937
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Entity.load reads Fire with getShortOr for every entity. The player's
loader read it, Air and TicksFrozen through one unnarrowed read; Fire
is now narrowed, the other two stay ints as vanilla reads them.
Checks the borrowed lookup against the clone-and-flatten expression it
replaces, over an empty slot, filled slots, slots past the end and an
empty inventory.
Refs #14939
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Replace the open-ended UI scope with eight reviewable work packages and
separate backend, toast, face and host-adapter steps. Record vanilla
entry/lifecycle requirements, old-port defects, evidence limits and the
maintainer decisions required before implementation.
Keep the account overlay distinct from the approved host/share migration;
leave the old implementation PR and isolated merge branch untouched.
Co-Authored-By: ZCode <[email protected]>
tick_guests cloned the whole ItemStack of every guest's selected slot
every tick, only to compare its item with play.attack_item. The new
held_attack_item helper borrows the item, and the Arc is cloned only
when the held item changed. Same result, no clone on the common path.
Refs #14939
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`a_crafters_grid_counts_for_the_recipe_book` opens a crafter menu, puts
four oak planks in its first slot and asks the stacked contents whether
an oak-plank ingredient can be crafted. Without the crafter arm the same
call fails.
`CrafterBlockEntity.fillStackedContents` hands the crafter's nine slots
to the recipe book's stacked contents (`accountSimpleStack` each), so an
open crafter's grid counts towards the craftable check.
Horizon's `Menu::stacked_contents` read the player's inventory, the mod
sources and the `GRID` container, but a crafter's slots are its `MAIN`
container (`CrafterMenu.addSlots`): with a crafter open the book saw an
empty grid and marked nothing craftable.
Differential test over 2000 seeded random inputs: entity sets of up to 40
states (all four kinds, own and other ids), guest positions and slacks,
with entities placed on or next to every edge of the close box, checked
against a verbatim copy of the old scan. Plus an inclusive-edge test and
the own/item/orb exclusions. The inputs are multiples of 1/8 so the box
edges are exact in f64. No world or game resources needed; cargo not run.
Refs #14920
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
excused() ran a string compare on each entity's kind before the box test,
for every guest every tick. Move the box test first (it is a pure float
check) and move the entity scan into entity_near, so the excuse can be
checked against the old scan. The boolean result is unchanged: the
predicates are pure and any() is order-independent.
Refs #14920
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
`a_disc_change_posts_block_change` places a jukebox, records the posted
game events and calls `set_has_record`: the box posts
`minecraft:block_change` at its centre with no source. Without the post
the recorded list is empty.
A 0.2 pack can't show a colour outside sRGB today: the contract says every
colour is in BT.709 primaries, forever. This adds the key that will let a
pack ask for more, before 0.2 is on by default and packs are written against
that contract.
- `[frame] primaries = "bt709"` (the default) or `"bt2020"`. The check takes
`"bt2020"` only with `blend = "linear"`, since a change of primaries is only
right in linear light. The game refuses a `"bt2020"` pack for now, as it
refuses `blend = "linear"`.
- `Frame` gains `primaries` and `luminance` (that space's luminance weights)
after `black_level`. The renderer writes 0 and BT.709's weights. The
uploaded buffer grows by 16 bytes; 0.1 declares the same prefix as before.
- The reference's "Room to grow" says a later release adds a name to a
stage's prelude only under a manifest key the pack sets or in a new stage.
`tests/names.rs` holds each 0.2 prelude to its list of names
(`tests/names-0.2/`), and shows why: a pack's own `fn depth_at` in a post
stage is refused as a redefinition, its own `fn to_bt709` passes.
No pack's picture changes: a pack without the key, a `"bt709"` pack and 0.1
packs draw as before.
Part of #14584 (step 1) and #12989.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
`JukeboxBlockEntity.notifyItemChangedInJukebox` sets the `has_record`
block state with update flag 2 and then calls
`level.gameEvent(..., GameEvent.BLOCK_CHANGE, pos)`: sculk sensors hear a
disc enter or leave a jukebox. Horizon's `set_has_record` changed the
state but posted no game event.
SlotSources.CODEC admits a registered holder (`ns:id`), a holder list and a
`#tag` holder set wherever a slot source is accepted, and validates the
`group`, `limit_slots`, `filtered`, `contents` and `slot_range` shapes
(including a `slot_range` source context) against the loaded registry.
DataStore loads `tags/slot_source` in the same pass as the definitions, in
pack order with `replace`. `slot_source_tag` resolves a tag as TagLoader
does: first-seen member order, nested tags, required members and cycles
rejected. The command registry publishes the tags that resolve.
Part 1 of 3 for #13091 (PR #13951 is the last part).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Share only isolated-group/filter metadata; each PR keeps its own corpus and gameplay change. Preserve hopper-pot exact selection and all existing groups.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Share only isolated-group/filter metadata; each PR keeps its own corpus and gameplay change. Preserve hopper-pot exact selection and all existing groups.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Entity.load reads Fire with getShortOr, which narrows any numeric tag
(IntTag.shortValue is (short)(value & 65535)): Fire:40000 loads as
-25536. The arrow, the other projectiles and the boat already narrowed
it; the mob's and the armor stand's loaders kept the raw number.
LivingEntity.readAdditionalSaveData reads both with getShortOr, which
narrows any numeric tag (IntTag.shortValue is (short)(value & 65535)):
HurtTime:40000 loads as -25536 and never counts down. The loader kept
the raw number, so such a mob counted down from 40000 and was drawn
hurt all the while.
Replays pushes, duplicates, pops with re-queues during a run, deferred
events put back after the run, and retains against the old Vec model
(contains + push, remove(0)) and checks queue contents and pop order
after every step. Also checks 5000 distinct events pop in queue order
and a duplicate is dropped. No game resources needed.
Refs #14873
Co-Authored-By: Claude <[email protected]>
run_block_events and client_drain_block_events popped with Vec::remove(0),
which shifts the whole tail per event, and block_event deduped with a
linear contains. The queue is now a VecDeque (pop_front) with an
FxHashSet mirror for the dedupe. BlockEventData derives Hash.
All queue writes go through Ticks::queue_block_event, pop_block_event
and retain_block_events so the set cannot drift. Queue order, dedupe
results and the re-queue and reschedule paths are unchanged.
Refs #14873
Co-Authored-By: Claude <[email protected]>
Both wrapped the command_exec driver without --work-dir, so they shared
.local/command-exec-parity with each other and with command_exec, and two
running at once read each other's scripts and worlds.
Fixes#14855
Co-Authored-By: Claude Fable 5.2 <[email protected]>
The End's own dragon flies above the cleanup's floor at times, and killing it started vanilla's
death sequence. bird flap sensors ran on item honey bounce's seed sync, 36 groups earlier, and
failed in more than half of the runs once the cleanup ran between them (0 of 9 without it): each
bird now gets its own sync, with entity ids clear of the world's loaded entities.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityReference.getEntity looks the UUID up in the level and returns any
matching LivingEntity, the holder included, so LivingEntity.baseTick ends
such a record after 100 ticks. resolve_last_hurt_by_refs dropped a
reference equal to the mob's own UUID, leaving it unresolved and saved for
ever. Remove that exclusion and cover it with a test.
Part of #12830
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
CEM names a humanoid's armour with two models, `<mob>_outer_armor` and
`<mob>_inner_armor`, but 26.3 draws it on four layers of the model: the helmet,
the chestplate, the boots and the thinner leggings. A CEM name gave one layer
key, so no armour file reached any of them, and the 24 armour layers of the
zombie family's baby models were unreachable.
An armour model now reshapes every layer it covers: the outer one the helmet,
the chestplate and the boots, the inner one the leggings. Layers a model does
not have are dropped by the existing check against vanilla's layers, so no list
of mobs has to track the game, and a baby keeps its own file through the suffix
rule.
The rabbit's `left_foot`/`right_foot` mapped to `left_hind_foot`/
`right_hind_foot`, which 26.3 no longer has: the haunch carries the box under an
empty hind leg, so the foot is the haunch and the thigh the leg.
`mobs::cem::tests::names_reach_horizons_models` (ignored: needs the prepared
resources) passes with no gaps.
Fixes#8426🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
This reverts merge 5248e83f28 (#13953).
Since it merged (2026-11-09 15:51Z), entities in the command_exec
fixture world stop ticking: they no longer fall, die, despawn or pick
items up, and command_exec fails 159/184 (it passes at the merge's
parent, and fails at the merge, both run here). Every PR whose change
selects command_exec has failed parity since.
Vanilla's gate is right, but Horizon does not keep the ring around a
forceloaded or ticketed chunk resident the way vanilla's ticket levels
do (a FORCED ticket gives its neighbours FULL), so the residency check
holds back chunks vanilla ticks. #13852 needs that ring modelled first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
test_item_model_property and test_port_ledger imported their helpers as
tests.test_world_input_capture and tests.test_class_index, while every
other test imports siblings by module name (unittest discover puts tests/
on sys.path). The tests. form resolves to any installed top-level tests
package first, so both modules failed to import wherever one exists.
Eight offers put the thumb at the track's top; a hand over it, the vertical
resize while dragged, nothing left of the thumb or past its bottom edge, and
the disabled scroller of seven offers asks for nothing.
MerchantScreen.extractScroller requests CursorTypes.POINTING_HAND over the
scroller's thumb, or RESIZE_NS while it is dragged. Horizon drew the thumb
but asked for no cursor, so the pointer stayed an arrow over it.
libc declares proc_pidinfo/proc_taskinfo for macOS only, so horizon-threads
didn't compile for aarch64-apple-ios. iOS reads MACH_TASK_BASIC_INFO's
resident size instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RangeChoiceSampler::sample_volume kept or replaced each value with a
conditional the compiler turned into one branch per value. Final density
runs it over a chunk's whole 16x384x16 volume (the noodle caves), where the
choice changes often, so the branch was mispredicted: about 4.5% of proto
generation time at one thread.
The select now runs in its own function over plain slices, on the values'
bits, which the compiler vectorizes. Each value is the one vanilla's
conditional store leaves; a test checks it bit for bit against a scalar
reference, with NaN bounds, NaN selectors and signed zeros.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MWgQD4Rbxknozw5LZ
AgeableMob.canAgeUp to ai.rs::can_age_up, and Animal.mobInteract's food
branches to ai.rs::takes_food: an adult at age 0 that canFallInLove, else
canAgeUp.
A frog and a cow, both at Age -24000, through takes_food and feed_animal: the
frog refuses the food and keeps the age, the cow takes it and grows a tenth.
Without the fix the frog takes it: took_food=true ate=true age=-21600, where
the test wants false, false and -24000. The cow reads the same either way
(age -21600).
Animal.mobInteract's growing branch is `canAgeUp()`, which is
`isBaby() && !isAgeLocked()`, not `getAge() < 0`. ai::takes_food still
decided that branch from the raw age, so a frog spawned with Age at
getBabyStartAge (AgeableMob.finalizeSpawn's group baby is not gated on
canBeABaby), a camel husk (AbstractHorse's group spawn is 20 percent), or a
slime, magma cube, parrot or zombie nautilus with a negative age took the
food: it ate the item, played the eating sound and grew a tenth towards 0,
where vanilla refuses the click.
takes_food now asks can_age_up(m), the port of canAgeUp, the same predicate
the rest of the feeding code already used.
ChestBlock.isChestBlockedAt is a redstone conductor above the chest or a
cat sitting in the block above it (ChestBlock.java:318-319, the cat test
at :327-338). getAnalogOutputSignal (:346-348) reads getContainer(..,
false), whose combine(.., false) (:279) uses that predicate, so a blocked
chest combines to nothing and the comparator reads 0.
The comparator's container view (machines::Containers::view) only checked
the conductor; the cat test lived on the menu path alone. Hand the level's
mobs to the view and share one isCatSittingOnChest helper with the menu
path (chest_blocked_at). A hopper's getContainer(.., true) still ignores
the block, so hoppers and menus behave as before.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
`falling_model_vertices` works out the model seed from the block's starting
position itself, so its call in the falling-block loop fits on one line and
game.rs ends up shorter than on main (docs/layers.md#file-size).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first mob a fresh chunk generates (spawnOriginalMobs) read the structure mob
finalizer's data on the server's tick: every mob type, the biome tags, the spawn
registries, the enchantments and the item tags, from the prepared resources.
Where those files are slow to read, that one read stalled the server for 6 to
7 s, a minute after players first walked into new terrain (#13857).
The terrain now starts that read on a thread of its own when its level loads
(every dimension but the End, whose chunks generate no mobs). The cell stays a
OnceLock: a mob that needs the data before the read ends waits for it, and the
data is read once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The controller's radial menu and its page, the HDR options' tooltips and
the recipe viewer's fuel tooltip. Placeholders and edge spaces match
en_us.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new get_the_game hook (horizon-hooks, re-exported by horizon-platform)
answers with the page to open and the screen's texts as translation keys.
horizon-official-servers answers it with Horizon's launcher text and
horizon_addresses::WEBSITE. With no hook, the engine screen and
missing_resources say how to prepare the files and offer only Quit.
The screen's strings are now translation keys. The no_resources.rs entry
leaves tools/addresses.json's allowed list.
Closes#14479
Co-Authored-By: Claude <[email protected]>
The float frame is smooth, but the last pass rounded it to the HDR10
swapchain's 10 bits as it was. Around 90 nits one PQ code is a 1% step
in light, and a clear noon sky is two or three codes tall, so it showed
as wide flat bands with hard edges (#12989's audit, F3).
The pass into the HDR10 swapchain now adds triangular noise of one code
either way to the PQ signal, each channel's own, as the last thing
before the write: over the world and the HUD alike, after the black
level lift. A gradient shows as grain one code deep and its mean is the
light meant.
- The noise is a fixed function of the pixel (PCG's 3D integer hash):
no frame counter, state or texture, so a still picture stays still
and HORIZON_STILL needs nothing.
- Black stays black: within a code of 0 or of the top the noise shrinks
to what fits, so a signal of 0 is written as 0, one of 1 as 1023,
nothing leaves the range, and near-black isn't lifted.
In that last code the rounded mean leans to the end (a third of a
code from it shows as an eighth): the accepted price of a true 0.
- A pipeline override constant (`pq_dither`) turns it on for the HDR10
swapchain's pipeline alone. scRGB (a float swapchain), SDR, the SDR
screenshot, recordings and the screenshot's HDR10 copy (`-hdr.png`)
are as before, byte for byte; calibration's test pictures keep their
exact codes.
Refs #12989
Co-Authored-By: Claude Fable 5.2 <[email protected]>
AbstractHorse.onClimbable (horses, donkeys, mules, the undead horses, llamas,
trader llamas, camels, camel husks), Ghast.onClimbable, Phantom.onClimbable
and HappyGhast.onClimbable all return false, so LivingEntity never takes its
climbable branch for them: no 0.15 clamp and fall reset in handleOnClimbable,
no 0.2 climb against a wall in handleRelativeFrictionAndCalculateMovement, no
fall reset for a ridden mount in handleMoveVehicle, and isPushable keeps them
pushable in vines. Mob::on_climbable now answers that false, which every
caller (travel, pushEntities, the vehicle fall reset) already reads.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
armor_stand_to_nbt kept a saved `attributes: []` over the stand's live
speed_instance (or_insert), so a stand loaded with an empty list never saved
the movement_speed instance LivingEntity.aiStep -> removeFrost creates.
Append it to the saved list once the stand has ticked, keeping the other
saved instances. The mob corpus gains a stand summoned with `attributes:[]`.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PhantomSpawner.tick asks Level.canSeeSky, which is "sky light 15 here"
(BlockAndLightGetter.canSeeSky), not a scan for a full collision cube:
glass and stained glass keep the sky light and spawn phantoms, while
stone, water and a slab roof do not. The advancement location predicate
read the same hand-rolled scan and now reads the sky light too.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
Rule 3 told every pass that keeps a history to blend tone-weighted. Measured on Horizon's
own TAA, which accumulates the float frame's gamma, that raised the sun scene's largest
per-pixel standard deviation from 11 to 27 PQ code values. The rule now goes by the encoding
a pass accumulates: tone-weighted for linear light (a 0.2 pack's post buffers, or the frame
under linear storage), as stored for vanilla's gamma.
Rule 2 named the sharpening's limit above 1 as a fault to fix. Measured, sharpening the sun's
edge as the rest raises its flicker under upscaling by 55 to 69% for 1 to 6% more detail, so
the limit stays and the rule says why.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Step a of #14035 (layer plan M6). An external shader pipeline implements
ExternalPipeline; the renderer calls begin_frame and shadow_request on world
frames, resize and reset_history, and draws vanilla's way where it doesn't
answer. Nothing sets one yet: each point is one Option test.
ShadowView moves from pack_post (re-exported there) and ShadowCull,
shadow_box, shadow_runs and shadow_casters from gfx.rs, unchanged: the
engine's side of the shadow frame point.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DefaultDispenseItemBehavior.spawnItem builds the item with the
five-argument ItemEntity constructor, which draws a default motion (two
nextDouble) from the item's own random before spawnItem sets the thrown
one. Horizon passed the thrown motion straight in and skipped those draws,
so every later draw from the item's random, starting with
moveTowardsClosestSpace's escape speed, came two doubles early.
spawn_item_thrown makes the draws; the dispenser, the vault and the trial
spawner use it.
Fixes#14537
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Turtle.TurtleLayEggGoal sets the egg then posts GameEvent.BLOCK_PLACE, and its lay-egg pitch comes from level.getRandom(); TryLaySpawnOnFluidNearLand posts the same event for frogspawn. Turtle.TurtleBreedGoal.breed overrides Animal.breed, so Animal.finalizeSpawnChildFromBreeding's entity event 18 never runs for a bred turtle (Frog.spawnChildFromBreeding still calls it).
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
They opened HORIZON_HOME/h260 without removing it, so a second run found
the border the first had saved and its first worldborder set changed
nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since the radial menu, Pad::holds_action reads the actions pad_button
records for a pressed button; the helper called pad_action directly, so
four tests read a held action as not held.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ServerPlayerGameMode.useItemOn skips the block's useItemOn while sneaking
with something in either hand, so CampfireBlock.useItemOn never places the
food; the item's own use (eating) runs instead. The block use it does
consume triggers ITEM_USED_ON_BLOCK, which the campfire placement now fires
too. CampfireBlock.douse and AbstractCandleBlock.extinguish post
BLOCK_CHANGE after the extinguish sound, so water on a lit campfire or a
lit candle is heard by sculk sensors, and CandleCakeBlock.getCloneItemStack
answers a cake for the plain candle cake as well as the dyed ones.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
living_death_message is a free function in horizon_client_lib::living_death,
so the test needs a set for that module like hunger, landing and chat.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
Six rows still described behaviour the code has, and two of them cited
`Game::use_spawn_egg` in `creatures.rs`, where it no longer lives (it is
in `creatures/spawn_eggs.rs`):
- `SpawnEggItem.spawnMob`: the spawn path posts
`minecraft:entity_place` at the spawn position (the dispenser path does
too), so sculk sensors hear the egg; the path is now cited.
- `PotionContents.getAllEffects`: `brewing::contents_effects` is the
potion's effects plus the stack's `custom_effects`, and
`finish_drinking` applies them.
- `PiercingWeapon.makeHitSound`: the jab's sound plays at the target
through `audio.play`, shared with the LAN guests.
- `TooltipDisplay.withHidden`: the loot table's
`minecraft:toggle_tooltips` is applied in `loot.rs::Context::run`, next
to the `/item modify` path.
- `SlotDisplay.Composite.resolve`: `Book::stacks` flattens a composite
into its members' stacks.
Tadpole.aiStep counts the age up, and NoAI only skips serverAiStep, so a
NoAI tadpole ages, shows its age lock's particles and grows up like any
other. The NoAI branch of the mob tick now runs the aquatic aiStep parts
that NoAI doesn't skip from one place in aquatic.rs.
Fixes#14615
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes#14628: the five source_silence_game tests overflowed the 2 MiB
test thread in a Windows debug build (4 MiB is enough) and took the whole
horizon-client lib run down.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Shulker.teleportSomewhere calls level.gameEvent(GameEvent.TELEPORT,
oldPos, Context.of(this)) after setPos and before level event 2016. Emit
the matching MobEvent at the old block's centre so sculk sensors and
wardens hear the teleport.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
apply_late_wind_pushes gave each entity only the first push held for it.
Harness: tools/test_wind_bursts_all_push_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.baseTick's updateFluidInteraction applies the fluids' currents to a
thrown or hurting projectile (Entity.isPushedByFluid); only arrows had it.
Harness: tools/test_projectile_fluid_current_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
moveEntityByPiston's applyEffectsFromBlocks reaches CactusBlock.entityInside,
even for a push that moves the vehicle nowhere. The pricked vehicles are
hurt once the tick's pushes are over, so the targets found first stay valid.
Harness: tools/test_piston_cactus_vehicles_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.load reads Rotation (its pitch clamped to -90..90 after % 360),
OnGround and fall_distance, and a spawner never moves or turns, so its
save writes them back. Horizon saved 0 for all three.
Fixes#14572
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Primed TNT joins the boats and minecarts in the list of objects every
projectile, the trident included, can strike: a wind charge bursts on it, a
snowball breaks on it, an arrow or a trident turns back.
Harness: tools/test_projectile_hits_tnt_parity.py.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Game::apply_spawn_egg_entity_data` moved from `creatures.rs` to
`creatures/spawn_eggs.rs`, so `EntityType.appendCustomEntityStackConfig`
and `EntityType.updateCustomEntityTag` cited a function that is not
there. The leash row's gap is gone too: the tick checks the leashed
mob's life and its player holder's life and spectator state, so a dead
or spectating holder drops the lead.
`FireworkRocketEntity.explode` posts `GameEvent.EXPLODE` from `getOwner()` at
the rocket before the damage, and the entity event passes `!isSilent()` to
`createFireworks`, which gates only the blast and the twinkle of
`FireworkParticles.Starter`, so a silent rocket keeps its sparks and flash.
A rocket launched from a block face also counts as a use, as
`ItemStack.useOn` does for `FireworkRocketItem.useOn`'s SUCCESS.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
The falling-block loop rebuilt every block's model vertices each frame: the
model lookup, two sprite hash lookups per face, shading and uv mapping. They
depend only on the block state and the model variant picked, so they are now
kept in model space per state and variant; each frame copies them, applies the
same translation as before and sets the block's light. The output is the same,
bit for bit.
8,100 falling sand blocks (fast profile, Windows 11, Ryzen 5 7500F, RTX 3070 Ti):
151 -> 247 fps, CPU 6.6 -> 4.0 ms per frame.
Part of #13007
Co-Authored-By: Claude Opus 5.5 <[email protected]>
preedit's state is one static for the process, and three tests change it while
cargo runs them in parallel: one's reset or should_suppress_key could land in
another, which failed at random in CI. They now hold a test-only lock.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.readAdditionalSaveData reads last_hurt_by_mob as a lazy
EntityReference (the UUID) and sets lastHurtByMobTimestamp to
tickCount - ticks_since_last_hurt_by_mob. getLastHurtByMob only answers once
the UUID resolves to a living entity in the level, and baseTick ends the
record when that entity is dead or more than 100 ticks old.
- Mob.ai keeps the bare UUID (last_hurt_by_ref) until the game resolves it
into last_hurt_by (a mob, or the player) before the mobs' baseTicks.
- baseTick (mobs/hurt_by.rs) ends a resolved record as vanilla does; a
reference naming nothing stays, keeps aging, and is saved as it is.
- setLastHurtByMob(who/null) clears an unresolved reference.
- The tests' world loads the 2-column neighbourhood entities need to tick.
- The corpus gains a pig naming an entity that arrives later.
Part of #12830
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The game, entity_nbt, stations, creatures, minecarts, particles and remote modules were split this week; twenty rows
still named the file a function left.
creatures.rs is over the 3000-line limit (docs/layers.md#file-size): the
coat coin, the wolf pup's voice and the test of both move to
game/creatures/bred_draws.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Wolf.getBreedOffspring picks the pup's voice with
WolfSoundVariants.pickRandomSoundVariant from the breeding parent's random,
after the collar colour mix; Horizon left every bred pup on the classic
voice. The cow, pig, chicken, fox, cat and wolf getBreedOffspring also draw
the coat coin (which parent's variant the baby gets) from the parent's own
random, where Horizon spent a level draw for every species but the
mooshroom.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Asked by a maintainer to review open pull requests, an agent takes them
one at a time, oldest first, skipping drafts, its person's own and ones
already reviewed. It checks function, regressions, tests and scope,
reports only points with a file:line, each marked by severity, and
submits Approve or Request changes, never a plain comment. Based on the
review prompt rdbtCVS shared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SocialInteractionsScreen.mouseClicked gives super.mouseClicked the widgets
first, so the search EditBox's onClick runs on a click inside it and puts the
caret under the pointer. Horizon's social click only knew the tabs, the rows
and Done; clicking the search box moved nothing.
An agent asked for some reviews swept about 300 open pull requests, most
reviews saying it found nothing. Agents now review a pull request only
when its author or a maintainer asks for it, post only findings someone
can act on as a real review, and skip reviews that found nothing.
Maintainers' own agents may run wider passes when the maintainer asks.
Closes#14545.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part of #14043: a pure move to entity_nbt/orb.rs, so the orb's
fall_distance (#14440) lands in the moved file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MobBucketItem.spawn loads the bucket's entity data and sets fromBucket,
never PersistenceRequired. SulfurCube.loadFromBucketTag reads its own
lowercase age keys, and Tadpole.fromBucket is always true, so
requiresCustomPersistence keeps every tadpole.
Fixes#14538
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PotentSulfurBlockEntity reads its column with
CollisionContext.positionContext(the height under it); the shapes were read
with no context, so scaffolding kept its full top and stopped the geyser.
Harness: tools/test_geyser_scaffolding_parity.py.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Sniffer.emitDiggingParticles posts GameEvent.ENTITY_ACTION at the head block every ten ticks for the whole dig, outside the dust window. Only sounds that go through Entity.playSound are silenced: the state transitions and the seed drop, not playLocalSound's searching sound or the block's hit sound. A bred pair's egg takes its push from ItemEntity's own random.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
The generated Hooks trait takes the event only: entity, kind and message are
its fields, so the test impls take one argument.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
PotentSulfurBlockEntity.LAUNCH_ENTITY_TICKER pushes every entity in the
column; only mobs, items and primed TNT were.
Harness: tools/test_geyser_launch_parity.py.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Railway's builds of both services fail since horizon-telemetry gained horizon-addresses (and the
platform crates) and horizon-mods gained horizon-release-sign: the Dockerfiles copy a fixed list of
crates, so cargo can't find the new path dependencies. The old deployments keep running, so nothing
looked broken. Copy every path crate of each service's own Cargo.lock, watch them in railway.json,
and test both lists against the lockfiles so the next new dependency fails verify instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes#14493: since #13953 an entity ticks only once every chunk within 2
columns is loaded; with only chunk (0, 0) the pig never fell.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla ticks entities one after the other, so a charge never meets a boat or
minecart that an earlier charge already broke. The tick's hits are all found
first here, so such a charge ticks again without the broken vehicle.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
A piston's push is Entity.move(MoverType.PISTON): the drop adds to the
falling block's fall, and landing runs fallOn and posts HIT_GROUND in
that step. Review finding on #14440.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
WallHangingSignBlock.shouldTryToChainAnotherHangingSign lets the hanging sign
in hand chain onto the sign only when the side the player faces cannot execute
its click commands (SignBlockEntity.canExecuteClickCommands), so a waxed sign
with a run_command hands the click to SignBlock.useWithoutItem, which runs it
and returns SUCCESS_SERVER.
The client now answers that for the wall case as it already did for the
ceiling one (chains_hanging_sign: isHittingEditableSide by the sign's FACING
axis), so the click runs the command, plays the waxed-sign failure and places
nothing; the world sim's unconditional Pass for the wall edge is gone.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
The horse armour layer took its texture from the item id
(golden_horse_armor -> horse_body/golden), but the golden armour's
equippable asset_id is minecraft:gold, whose horse_body texture is
entity/equipment/horse_body/gold. The layer found no texture and drew
nothing. horse_armor_asset maps each horse armour to its equipment
asset; a test checks every vanilla horse armour against items.json and
equipment/*.json.
Fixes#13826
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractWindCharge.onHitEntity hurts what it hit by one point before the
burst; a vehicle takes it through VehicleEntity.hurtServer and breaks on the
fifth hit of a tick. wind_charge_damage only looked among the mobs.
Harness: tools/test_wind_charge_vehicle_parity.py.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
Part of #14440: PrimedTnt and FallingBlockEntity move with Entity.move and
keep its checkFallDamage, which posts HIT_GROUND after the block's fallOn
when they land after a fall (a piston's push too, for TNT). Extends the
`entity landing sensors` command-exec group.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ItemUtils.onContainerDestroyed` makes each stack a destroyed item held
with `new ItemEntity(level, x, y, z, stack)`, whose motion is 0.2 up and
0.2 less 0.1 sideways from the *item's* own random
(ItemEntity.java:61-66). The spill drew three doubles from the level
random instead, so the stacks barely rose and the level's stream
advanced three times per stack. Route it through `spawn_item_own`,
which already draws the four-argument constructor's motion (#13938).
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZzq8MxShENtiMj2DMbm5G
Join a Friend opens from vanilla's Friends icon slot, unchanged in place
and size at every GUI scale; it is now drawn inactive, and takes no click,
key or controller press, when no Hooks::tunnel resolver is registered
(net::joins_friends), as the pause menu's icon is without a sharing page.
horizon-official registers horizon-friends' resolver, so players of the
official game see no change; the plain executable, which refuses every
friend: address, no longer opens a screen that cannot join.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client sent the server's recipes (horizon:recipe_content) numbered its
recipe book displays from its own files, while the server numbers them over
its recipe set, a world's data pack recipes included. Each pack recipe moved
the server's numbers past the client's, and the server's book entries, put
over the book by number, replaced other recipes with server:N entries.
The display numbers now come from the synced recipe set itself
(RecipeIndex::load_from_sources), for the book, its toasts and the
RecipeBookAdd toasts; without synced recipes nothing changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KtNXhvTSpY15Rjzwuqh8Nf
SulfurCube.getEquipmentSlotForItem answers BODY for every
#sulfur_cube_swallowable item, so the body's SlotAccess takes it. Horizon
checked only the item's own equippable slot and refused every block.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`resolveForItem`, `resolve`, `markResolved`, `resolvePage` and
`isPageTooLarge` cite `WrittenBook::resolve` and `resolve_held_book`.
The spawn egg rows cited `Game::use_spawn_egg` in `creatures.rs`, where
it no longer is: the function lives in `creatures/spawn_eggs.rs`.
The resolve test rewrites a selector page and a score page (a plain page
is left alone), marks the book resolved and then leaves a resolved book
alone; the over-long test keeps the raw pages past 32767 characters and
still marks the book resolved; the codec test round-trips `resolved`.
`WrittenBookContent.resolveForItem` runs in `ServerPlayer.openItemGui`:
`resolve` replaces every page's selectors and scores with the text they
name and marks the book resolved, and a page whose resolved text passes
`resolvePage`'s 32767 characters cancels the resolution (the book is only
marked resolved).
Horizon kept the page components (`WrittenBook.page_tags`) but never
resolved them and had no resolved flag, so a page holding a selector
showed its raw component text and a book could not be told as resolved.
`WrittenBook` gains `resolved` (read and written by the component codec),
`WrittenBook::resolve` ports the resolution, and `Game::resolve_held_book`
resolves the book in the acting hand when `use_book` opens it (a LAN
guest's copy is the mirrored inventory, so its slot is sent back).
`set_written_book_pages` was already applied in `loot.rs`.
Dropping a particle with physics at its first tick still drew it for one
frame, so a falling leaf flickered with the newest wide copy through it.
The check now runs when a pending particle joins the engine list, and
set_polytone moves unchanged to particles/polytone.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon_hooks::lang::register(&'static [(code, json)]), re-exported as
horizon_platform::lang. The game's entry installs the registered files
into horizon_client::lang; Language::load reads them in the horizon
namespace right after the engine's bundled file for the same language,
before packs, and the engine's file wins on a key both have. Regional
variants share one alias table with the bundled files (fr_ca -> fr_fr,
de_ch -> de_de without ß). Nothing registered: languages load as before.
tools/lang_coverage.py reports crates/*/assets/lang beside the game's
folder; a crate's review record is tools/lang_review/<crate>/, and
--mark-reviewed puts each key in the folder that holds it. lang.rs's
file checks run on every crate folder, and a crate key the engine also
has fails them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The row said the four lid-step calls were counted and dropped;
`Behaviours::shulker_neighbor_updates` now runs them, and `tick_shulkers`
calls it once per counted update.
`a_shulker_lid_updates_the_shapes_and_redstone_of_its_neighbours`
observes the level as `shulker_neighbor_updates` runs: each of the six
neighbours gets one `neighborChanged` from the shulker's block, with its
own state as the changed state. Without the call the level sees none.
`ShulkerBoxBlockEntity.tick` calls `doNeighborUpdates` when its lid
starts or finishes opening or closing: the box's collision shape moved,
so the neighbours' shapes are updated (flag 3) and their
`neighborChanged` runs. Horizon's render tick counted those four calls
(`ShulkerTick.neighbor_updates`) and the game dropped them, so redstone
beside a moving shulker box never saw the update.
`Behaviours::shulker_neighbor_updates` ports the two calls, and
`tick_shulkers` runs it once per counted update. The box's collision
shape for the same ticks already reaches the world through
`shulker_collision_state`.
The loot data is one slot per process. A test that installed a world pack's
armor_stand table warmed that slot, and every other test's stand break rolled
it too. load_thread_loot_data keeps such a test's loot data on its own thread,
ahead of the slot; a game never sets it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A custom particle with has_physics whose box starts inside a block's
collision is dropped before its first tick, so its emitters never run.
Falling leaves start 0.05 under their leaf block, and a pack's leaf with a
0.08 hitbox reaches into it: in Java such a leaf never falls, while here
every one fell, and a leaf that emits a resting copy of itself each tick
left trails of them under every tree until the frame rate collapsed.
Compared in game against Java 26.3 with Polytone, with particles of its
own: a box overlapping a block by 0.01 goes, 0.01 clear of it lives, and
a particle without physics lives either way; the emitters do run every
tick. A test covers the three cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
WorldOptionsScreen.updatePortControlsState makes the port box read-only
without LAN (setEditable) but leaves it focusable: clicking it selects and
copies the port. Horizon dropped the focus instead, so with LAN off the port
could not even be selected or copied.
remove_entity moved to game/remote_removal.rs, Minecart::to_nbt/from_nbt to
game/minecarts/nbt.rs. A class row takes home, not port.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A write two below the height asks about no block on the next read; a write at or
above it is found again down that column alone, and the other columns keep theirs.
Co-Authored-By: Kacper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Use the existing nightly and reusable parity runners instead of a second
manual inventory. Keep the JFR checks and describe their JDK fixtures.
All 84 removed harness commands remain in parity discovery.
Co-Authored-By: Codex (GPT-6) <[email protected]>
A block written at or above a column's height (grass spreading, a crop, a mob breaking a
block) used to drop the whole chunk's map, and the next spawn attempt there scanned all 256
columns again. The write now marks that column; its next read finds its height down that
column only. The heights are those a rebuild gives. Natural spawning on the bench: 4.8 ms a
tick to 4.0.
Co-Authored-By: Kacper <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AdvancementDisplay, Advancement, UpdateAdvancements and
SeenAdvancements, with their codecs, move byte for byte into
packets/play/advancements.rs. play.rs re-exports the four public items,
so every `packets::play::…` path still resolves.
The port map follows: the two rows that name UpdateAdvancements::read
and SeenAdvancements::write point at the new file.
Part of #13063.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server started by a harness's build joined that harness's job (sccache
doesn't ask to break away) and ended with it, failing other harnesses'
in-flight compiles (os error 10054). The runner now starts it before any
build, in a job of its own, with no idle timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemFrame.readAdditionalSaveData compares the Item stack as read with
ItemStack.matches, count included, and removes the framed map on a
mismatch; only setItem then keeps one item. apply_entity_nbt compared
against item_frame_from_nbt's stack, already cut to one, so a count-only
change kept the old map's marker. It now compares the stack as read.
Fixes#14178
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The registry's own workspace still resolved rusqlite 0.37 ->
libsqlite3-sys 0.35, which bundles a SQLite older than 3.54
(RUSTSEC-2026-0459: uninitialised page-cache memory from a crafted
database opened with a "-wal" file of another page size). The registry
only opens its own database, so this isn't reachable as deployed, but
it is the same upgrade the telemetry server and the root workspace
already made: rusqlite 0.41 resolves libsqlite3-sys 0.39.1, and
fallible_uint keeps the u64/usize conversions working as before (an
error past i64::MAX).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stK8iXVDXqoi7midcqd
Adds the read-only helpers native_vehicle_of(entity) and
native_passengers_of(entity) for server mods (vanilla Entity.getVehicle
and getPassengers, first = controlling passenger). Helper-only fragment
riding_getters.json; API number is assigned when the fragment is stamped.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
- Reuse thread-local scratch queues (`RelightScratch`) across BFS light propagation passes, avoiding reallocation during incremental relighting.
- Pass borrowed slices `&[Pos]` to `relight_layer` to avoid redundant `Vec<Pos>` heap allocations.
- Cache consecutive vertical column lookups in `lt_sources` to avoid redundant map lookups.
- Preserve lazy loading of sections in `Region`, only loading and cloning sections that are actually read or written.
- Add `relight_single_block_bench` and update `relight_bench` to verify timings for both single-block and large batch edits.
Run against the 26.3 SignBlockEntity: an entry that fails is dropped
(ListCodec's partial) and Util.fixedSize keeps the first 4 lines of a
longer list with the colour and glow; only fewer than 4 valid lines load
the empty side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mods can read whether a dedicated server runs in online mode, the port it
listens on and whether it takes its players from a Velocity proxy
(forwarding.secret). The values come from the live network config and
listening socket, so they match what the server does. None off a dedicated
server and before it listens. The forwarding secret is never returned.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
`ObserverBlock.updateShape` reacts only when the direction to the changed neighbour is its
FACING. A change behind it, beside it, above it or below it starts no pulse.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`ObserverBlock.updateShape` schedules a tick two out when the block it faces changes; that tick
turns it on and schedules another two out, which turns it off. A lamp behind it is lit by the
pulse (`getSignal` answers 15 only for its facing); lamps in front of it, beside it and above it
are not.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`TripWireHookBlock.calculateState` scans `for i in 1..42` along the hook's facing for a hook
facing back and ends with `attached &= receiverPos > 1`: hooks attach 2 to 41 blocks apart and
never side by side or 42 apart. Both hooks are placed the way a player places them, because
vanilla runs the calculation from `setPlacedBy`, not from `setBlock`.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`PistonBaseBlock.isPushable` ends in `!state.hasBlockEntity()`, so a chest, furnace, barrel or
hopper ahead of a powered piston keeps it retracted and stays where it is.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`PistonBaseBlock.isPushable` answers false for a piston with EXTENDED set, so a push that meets
it fails and the pusher stays retracted, while a retracted one is pushed along. Both kinds.
The extended piston is held extended by a power source of its own, out of the pusher's reach:
unpowered it retracts on the tick, and that retraction updates the pusher, which then pushes it
(vanilla does the same; the first draft of this test asserted the wrong end state).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
26.3 gives `PushReaction.PUSH` to glazed terracotta alone, and `PistonBaseBlock.isPushable`
answers `direction == connectionDirection` for it: a slime block beside it cannot drag it, where
it drags an ordinary block, and in line with the push it moves like any other.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`PistonStructureResolver.canStickToEachOther`: a slime block drags slime and a honey block drags
honey, but a slime block and a honey block never stick to each other. Four pairings, each with
the block beside the one the piston faces either dragged or left where it was.
Built in open air: a slime block on stone drags the floor along too (vanilla's as well), which
would make the check about something else.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
`PistonStructureResolver.addBlockLine` refuses once `toPush` would hold
more than twelve blocks, and then the whole push fails: the piston stays
retracted, it does not move twelve of them. Pins 11 and 12 moving, with
the head where the first block stood, and 13 moving none.
The port matched vanilla; the ledger lists `PistonStructureResolver.resolve`
among the ported methods no parity harness covers.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
The five tests from `pistons_pop_fragile_blocks_and_refuse_obsidian` to
`replacing_a_moving_piston_leaves_its_extended_base` move, unchanged, to
level/tests/pistons.rs, beside the barrel and trigger suites that were split
out the same way. They are the piston tests that stood in the middle of a
4176-line file; the piston rules the next commits pin go in the same place,
so one file holds them. This takes 191 lines out of tests.rs.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011paghWkrnyQRBhJRodZpG2
TagValueInput.read returns the codec error's partial value, and DFU's list
codec keeps the valid elements as that partial (checked against
datafixerupper 10.0.21): vanilla keeps the other layers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.onEffectUpdated(effect, true) runs when a hidden effect
instance is promoted, and its refreshDirtyAttributes clamps absorption
to the new max_absorption. Horizon clamped only when Absorption was
gone, so Absorption II's 8 survived the promotion of a hidden
Absorption I (max 4). tick_effects now clamps whenever the tick changed
max_absorption.
mob_absorption_max_corpus gains a promote case (494/494; main: 493/494).
Part of #11384
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main rewrote the UI Brightness part of the HDR paragraph; this PR only added
the sentence that a shaders/ pack's picture is not lifted. Main's paragraph is
kept with that sentence inserted where the PR put it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`OldMinecartBehavior.pushAndPickupEntities` asks
`EntitySelector.pushableBy(this.minecart)` for what a moving rideable cart
pushes, so the cart's own team's collision rule and the target's both decide;
Horizon passed `None` for the cart's team on mobs and never filtered carts.
A still or unrideable cart keeps vanilla's else branch, which pushes every
pushable minecart with no team filter.
The per-candidate answer comes from `Game::cart_push_allowed` beside the
other team lookups; `pickup_from_cart` gets the cart's team from the same
`team_with_owner`. Two ignored tests on the real server game fixture cover
every collision rule over cart-to-cart pushes, the stationary branch, and a
cow the cart may not board.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
A dying cube mob is out of every selector's reach and splits 20 ticks later, inside the next group, so
the three groups that end by killing their size-2+ sulfur cubes shrink them first. Horizon resolves an
@s of another level only from that level, so each level's cleanup runs in it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
insert_text auto-merged main's read-only guard with this PR's UTF-16 room;
main's read-only test and this PR's UTF-16 test were appended at the same
place, both kept whole.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A harness that exited or crashed left the rest of its process group running, and on Windows a parity.py or
pr_ready.py that died left its harnesses and their games running too (taskkill /T can't follow a dead parent).
tools/process_tree.py starts each harness in its own Job Object on Windows (KILL_ON_JOB_CLOSE, started suspended
so nothing escapes before it joins) and, on POSIX, kills the rest of its session's group once it exits, before
it is reaped. pr_ready.py runs its Windows checks the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`ServerLevel.tick` runs `raids.tick` before `getChunkSource().tick`, so a
wave that lands in a tick joins before that tick's natural and custom
spawns and takes the lower entity ids. `tick_creatures` did the opposite:
natural spawning, then the custom spawners, then the raids.
The three phases move into `Game::tick_spawners`, in vanilla's order, each
with its own span; the LAN sync bracket still covers exactly them. A new
ignored test builds the real server game fixture, records spans and asserts
the phase order.
Co-Authored-By: opencode (DeepSeek V4.1 Flash) <[email protected]>
MaceItem.hurtEnemy resets the attacker's fall and sets its motion, and the
server sends that motion to the attacker in a ClientboundSetEntityMotionPacket
right after the hit. A LAN guest's PvP mace smash now sends the motion the
hit gave it, as it already did for mob hits. The server_knockback harness no
longer lists pvp-mace as a known difference.
Fixes#8246
`tools/port_map/game-012.tsv` records `Sheep.readAdditionalSaveData`; its
`Color` read now goes through `mobs/farm.rs::saved_color`, which gives white
for an id the legacy codec does not know.
`mob_from_nbt` is what `Game::load_mob` goes through. A sheep with
`Color: 99b` loads with `extra.color == 0` (white), not the wrapped 3.
Without the codec bound the sheep loads light blue: `left: 3`, `right: 0`.
`Sheep.readAdditionalSaveData` reads `Color` with
`DyeColor.LEGACY_ID_CODEC` and `orElse(DEFAULT_COLOR)`. The codec's id map is
`ByIdMap.continuous(..., OutOfBoundsStrategy.ZERO)`, so an id outside 0..=15
reads as the first colour, white.
Horizon masked the id with `& 15`: a saved `Color: 99b` became light blue (3)
instead of white, and a negative id became a wrapped colour the sheep never
had.
The read now accepts only 0..=15 and gives white for anything else, in
`mobs/farm.rs::saved_color` beside the sheep's other colour code.
`tools/port_map/game2-001.tsv` recorded `EnderDragon.readAdditionalSaveData`
with a gap: `sitting_damage_received` was not read. The row now says
`Dragon::from_nbt` reads it with `getFloatOr`, 0 when absent.
`Dragon::to_nbt`/`Dragon::from_nbt` are the pair the dragon's chunk entity
uses. A perched dragon with `sitting_damage = 12.5` saves
`sitting_damage_received: 12.5f` and `from_nbt` gives 12.5 back. A compound
with no such key loads 0.0.
Without the read the reloaded dragon has 0.0: `left: 0.0`, `right: 12.5`.
`EnderDragon.addAdditionalSaveData` writes `sitting_damage_received` (the
damage a perched dragon has taken since it sat down, which makes it take off
at a quarter of its health), and `readAdditionalSaveData` reads it back with
`getFloatOr("sitting_damage_received", 0)`.
Horizon wrote the field in `Dragon::to_nbt` but `Dragon::from_nbt` ignored it:
a perched dragon saved after some damage but before taking off reloaded with
`sitting_damage` 0 and had to be hit for the whole quarter again.
`Dragon::from_nbt` now reads the float, 0 when absent, as vanilla does. The
health read is folded into one line so the over-3000-line file does not grow.
`guest_dig_feedback` sends `(int)(progress * 10)`, the stage
`incrementDestroyProgress` computes, and `tick_daylight_detector` gates on
the dimension type's sky light, so the End is excluded like the Nether.
Both rows still described the old gaps.
Groups share one world, so an entity a group left stayed visible to later groups' unbounded selectors.
The bell group's persistent zombie burned in daylight and died at a random tick in each engine, which
made the entities group's PersistenceRequired check flaky. The harness now kills, between two groups,
the non-player entities above the groups' floor (y 100 in the overworld, 128 elsewhere), shrinking
cube mobs first and killing twice so drops go too. World-generated entities below stay out of it.
The wolf anger clock diagnostic continues the group before it and is named in CONTINUES_PREVIOUS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
run_command checks InventoryChangeTrigger after every command; vanilla's
/loot give calls no broadcastChanges, so its Diamonds! comes on the
player's next tick, outside the command's record (inline_loot_reference).
/item replace on a player marks what its own check announced
(cmds.broadcast_announced); the harness records those and leaves the
rest for the tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
totem_death_effects goes to mobs/living.rs (LivingEntity.checkTotemDeathProtection), and
RandomPos.moveUpToAboveSolid to mobs/random_pos.rs. Pure moves; no behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.getSecondsToDisableBlocking reads
Weapon.disableBlockingForSeconds. Horizon read the prototype's value, so a
patched weapon disabled a shield for 0 seconds instead of its own.
Test: a diamond sword with disable_blocking_for_seconds 1.5 reports 1.5.
ItemStack.postHurtEnemy uses Weapon.itemDamagePerAttack from the stack's
component. Horizon read the item prototype's weapon damage, so a patched
weapon wore 0 or the prototype's amount instead of its own.
Test: a diamond sword with item_damage_per_attack 3 wears 3 per hit.
BannerPatternLayers.CODEC reads its layer list all or nothing: one layer the
codec rejects leaves the empty layer list. Horizon skipped only the bad layer,
so a banner kept the layers before it.
The snapshot carried the panic state but Breed never read it:
getFreePartner skips a panicking partner and canContinueToUse ends the
goal when the partner panics. The test only skipped on CI (no prepared
resources), so it looked green without the check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A run- folder per invocation under .local/<harness>/ is never removed by runner-gc (it only
looks one level down), and these four harnesses aren't this fix's. Main's harness fixes own them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The same fix (compare with the main the pull request lands on) is open as #13882. A change to
.github/ also keeps a fork's runs from being approved automatically, so this branch got no parity.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Layer plan M6, step toward dropping horizon-client-lib -> horizon-anticheat. The record
(horizon_anticheat::prediction) is what the engine's movement replay (lan::prediction) plays again:
it moves to lan/prediction/record.rs unchanged. The guest holds it and the client's sprint
(was Player.record / Player.sprinting). Behaviour unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HoglinAi.updateActivity runs after the brain, so a hoglin given an attack
target enters the fight activity at the end of that tick and its
MeleeAttack hits from the next one: the goal now acts only in the fight
activity (brain_step runs after the goals). MeleeAttack.create(40) sets
ATTACK_COOLING_DOWN for 40 brain ticks and MemorySlot.tick erases it on
the 41st, so hits come 41 ticks apart (16 for a baby).
A hit one mob's tick deals another is handled after the mob loop, after
the target's own tick; vanilla deals it inside the attacker's tick, so a
target later in the list counts HurtTime and its damage cooldown down
once more that tick. The pig hit by a hoglin read HurtTime 10 on the tick
of the hit; vanilla's reads 9.
hoglin_repeat_attack reports its rand- rows instead of comparing them:
each hit is attack / 2 + level.getRandom().nextInt(attack), and
Level.random is RandomSource.create(); two vanilla runs disagree on the
pigs' Health.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CountDownCooldownTicks keeps ITEM_PICKUP_COOLDOWN_TICKS while it counts
down and erases it the tick after it reached 0 (stop), and
Allay.isOnPickupCooldown is the memory's presence: an allay loaded with
{value:30} saves {value:0} on its 31st tick, then nothing. ints[3] now
holds the value plus one (allay::pickup_cooldown).
EntityItemAccessor calls containerMenu.broadcastChanges for a player
whose slots /item replace changed, so InventoryChangeTrigger (here the
Diamonds! announcement) comes before the command's message. The command
harness now announces what a command earned before its feedback, as a
guest's command does (announce_guest_advancements); it left the
announcement for the next tick, outside the command's record.
allay_pickup_cooldown reports its rand- rows instead of comparing them:
the natural allay's flight starts at its first item scan
(Sensor.randomlyDelayStart with the allay's own random, which is
RandomSource.create()), and two vanilla runs of the script disagree on
those 30 rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ZombieVillager.defineSynchedData picks the profession with
VILLAGER_PROFESSION.getRandom(this.random), and Entity.random is
RandomSource.create(): the first two subjects' profession:none check
passed 1 time in 15 on either side, so 'zombie villager summoned data'
matched 9/10 from run to run. The third subject's profession comes from
its save and stays checked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Its name matches no harness pattern; test_command_exec_parity.py --only
"item count predicate" already compares the group with vanilla.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
It needs --work-dir and --reference-dir, which an automatic parity run
doesn't pass, so without the named_only entry it fails at startup on
every pull request that selects it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AttributeMap.pack lists the instances in the iteration order of an
Object2ObjectOpenHashMap keyed by Holder.Reference, which keeps
Object.hashCode: an identity hash. The same script run under another
main class saves the pig's list in another order, so the oracle's
order is the JVM's, not the game's. All 11 differences on main were
that order. The driver gets unordered_attributes(), which sorts the
entries of every attributes list in a record and keeps their text and
styles; the harness compares through it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With HDR on and a shaders/ (OptiFine/Iris format) pack running, the last
pass read the alpha the pack's final program leaves in the frame as
Horizon's light-source marks and lifted those pixels above paper white.
A pack that leaves alpha below 0.75 had most of its picture brightened as
if it were torches or lava, and HDR Lighting turned that on and off.
Horizon doesn't light the world under such a pack (HDR lighting is
already off there), so the lift is now off too: the pack is shown at
paper white, as a 0.2 pack is. The decision moves into a small pure
function in gfx/output.rs with a unit test. HDR off doesn't change.
Fixes#13807.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.checkBelowWorld runs in AbstractBoat.tick (through Entity.baseTick,
after the fire) and in AbstractMinecart.tick (after its hurt and damage wear
off), before either moves: under the level's floor minus 64, onBelowWorld
discards it. Horizon had no such check for vehicles, so a boat or minecart
that fell out of the world kept falling for ever (#8024 measured one at
y -243 where vanilla had removed it by tick 33).
The discard ejects the mob riders where they are and spills a chest boat's or
container minecart's slots (AbstractChestBoat.remove,
AbstractMinecartContainer.remove), with no ENTITY_DIE and no vehicle item.
The logic lives in game/boats/below_world.rs; minecarts.rs keeps its size.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A projectile added keeps an earlier UUID for its id (a client copy's from the
spawn packet) through or_insert again, so the client copy needs no reset.
Part of #12561
Co-Authored-By: Claude Opus 5.5 <[email protected]>
entity_nbt.rs may not grow (over 3000 lines), so the sulfur cube's own load
re-reads its saved Health after sizing it instead of the living load
skipping the Health Boost clamp: the result is the same, the clamp to the
unsized maximum no longer sticks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The living load refreshed a Health Boost holder's maximum and clamped its
Health before sulfur_cube::from_nbt sized it, so a size-3 cube saved with
Health 12 under Health Boost dropped to the constructor size's 8. Vanilla
sizes the cube (AbstractCubeMob.readAdditionalSaveData) before
LivingEntity's setHealth; its own load now sets the maximum.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The loads take the own random's UUID through one helper, so persist.rs and
creatures.rs keep their size. A client copy drops its own random's UUID: the
spawn packet's, kept before it is added, wins.
Part of #12561
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SulfurCube.setSize sets the max_health (4 per size) and movement_speed
(0.2 + 0.1 * size) attribute bases, so every load, spawn, split and bucket
release leaves both instances to save. Horizon's set_size wrote the cached
values only: the save held no max_health, and from the first tick a
movement_speed of the type's 0.7, which a reloaded cube then moved at.
set_size now records the bases (the spawn path did that alone before), the
load applies saved bases after the size as LivingEntity does, and Health
Boost changes refresh a sulfur cube's maximum like any model-owned one.
Fixes#13801
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The file-size check (docs/layers.md#file-size) does not let a file over
3000 lines grow. Breaking a block entity and dropping its resources both
read the custom name and lock from the world, so a helper does it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The file-size check (docs/layers.md#file-size) does not let a file over
3000 lines grow. The count bounds matcher moves to commands/item_count.rs
and the ignored test module to game/item_count_predicate_tests.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The controller guide offers Equip only when Equippable.swapWithEquipmentSlot
applies: the stack's slot is an armour slot and swappable is true. Horizon
read the item prototype for both.
Test: a helmet patched swappable:0b no longer offers Equip, and its patched
slot is used.
Boat steering, a boat rider's dismount vector and a minecart rider's push
used the platform's sine and cosine, where vanilla uses `Mth`'s 65,536
entry table, so a boat paddled at most yaws drifted from vanilla's path
(about 1e-4 of the push per tick). They now go through
`horizon_core::geometry::trig`, as `AbstractBoat.controlBoat`,
`Entity.getCollisionHorizontalEscapeVector` (reusing the mounts port)
and `Entity.getInputVector` do. The dismount also uses vanilla's float
products: the boat's width times `Mth.SQRT_OF_TWO`, the 1.0E-5F margin and
the player's 0.6F width.
`getWaterLevelAbove` scans from the hull's top up to
`ceil(maxY - lastYd)`, where `lastYd` is the boat's vertical velocity at
its last `checkFallDamage` (before collisions zero it); Horizon scanned
only the hull top's own row, so a boat falling fast into deep water could
pick another surface height.
Fixes#11381
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When drawing held items for non-local entities in held_item_geometry,
set fishing_cast_with_this_item from the remote hooks' active owner
state and the holding hand (with the main hand taking precedence when
both hold fishing rods), matching FishingRodCast and FishingHook's
client owner tracking.
The two native text field replacement tests pinned the old character count:
a maxLength of 4 kept four code points and one of 3 kept three. EditBox's
maxLength is Java String.length(), so the emoji takes two of them: 4 units
keep "a😀b" and 3 keep "a😀", which is what vanilla's setValue truncation
leaves.
EditBox.maxLength caps Java `String.length()`: UTF-16 code units, so a
character outside the BMP takes two of them. Horizon measured the limit in
Rust chars (code points) in both edit box ports, so a chat box with a maximum
of 256 accepted 256 emoji where vanilla accepts 128, and a pasted emoji could
take a place the next character should have been refused.
`ui::EditBox` (the screens' field) and `screen_draw::TextField` (the chat box
and the native screen API's field) now measure the maximum in UTF-16 units and
keep whole characters, which is the prefix vanilla's insertText keeps after
its half-surrogate guard drops the split one.
Vanilla's Entity makes its UUID from the first two draws of its own random
(Mth.createInsecureUUID), and Projectile.shoot spreads the shot with that same
random, from which an arrow keeps drawing (AbstractArrow.setBaseDamageFromMob,
its later rolls). Horizon shot dispensed, crossbow and mob projectiles with a
fresh random that never drew the UUID, player throws with the level's random,
gave an arrow's later draws a random of their own, and gave projectiles a
clock-seeded UUID when first asked.
An arrow now makes its own random in Projectile::new and draws its UUID first;
another kind makes it at the shot (shoot_own), the same stream since nothing
draws between. Every shot goes through shoot_own, the arrow keeps the random,
and the skeleton's base damage comes from it. add_projectile keeps the UUID; a
player's trident takes its shot's. Loaded and summoned projectiles take their
own random's UUID unless the tag has one.
The seed sync proof reads a dispensed arrow's and snowball's UUID and Motion
and a summoned arrow's UUID: 25/30 on main, 30/30 with this.
Part of #12561
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`BreedGoal.getFreePartner` drops a panicking partner and
`BreedGoal.canContinueToUse` requires `!partner.isPanicking()`. Horizon's
`Breed` neither snapshotted the panic state nor tested it, so a pair kept
breeding after one of them started panicking.
Metal keeps 512 layers per sprite array, so the 8 arrays terrain.wgsl binds
hold at most 4096 block textures and a set with more (10,271) was refused
at startup. The split now grows to fit the arrays (up to the GPU's limit
per array) only when the textures need it; up to 4096 it stays 512, as
before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Post BLOCK_CHANGE at the frame from ItemFrame.interact, hurtServer and
dropItem, after the changes and drops vanilla performs first. Preserve
BlockAttachedEntity.kill ordering and discard unsupported frames without
a spurious ENTITY_DIE.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
/damage: keeps this branch's villager reputation flush after a hurt and
main's enderman teleport settle (they act on different mobs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main split projectile_hit into a wrapper and projectile_hit_inner: the
reputation flush now runs in the wrapper after the inner hit. Trident and
wind charge hits keep both the reputation flush and main's absorbed-damage
and post-attack calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep both sides of the potions.rs test modules; the contents fixture passes
the impact motion that main's splash now takes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
entity_portal_delay walks every entity list; it ran for every item, mob,
boat, cart and pearl in the End each tick.
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, and so is one labelled `in progress` (claimed on Discord): pick another. `/unclaim` when you stop;
@@ -47,6 +48,29 @@ Feature → files → harness for the most-changed areas: [docs/architecture.md]
- **Render passes keep HDR's light.** A pass that reads, filters or redraws the world's picture (upscaling,
anti-aliasing, sharpening, frame generation, post effects, captures) follows [docs/hdr-passes.md](docs/hdr-passes.md).
## Don't lose time
Where agent time went, measured on sessions and CI in November 2026: reading code, compiling `horizon-client`,
waiting in loops, and work someone else had already done. Tests were a small part.
- **Check nobody else is on it.** One in three pull requests closed unmerged was a duplicate. Before you write
code, look at the issue's linked pull requests and run
`gh pr list --state open --search "<issue number, function or file name>"`; then `/claim`.
- **Find code with the map, not by reading.** Start from the tables below and
`python3 tools/port_ledger.py where Class.method`. Read the function you change and its callers, not whole
files.
- **Compile cheaply while you work.** `cargo check -p <crate>` after an edit. Clippy, fmt and tests run once, at
the end, through `pr_ready.py`. `cargo clippy -p horizon-client` and `cargo build -p horizon-client` are the
slowest commands here: never after every edit, and build the game only when you need to run it.
- **Don't wait in loops.** Start a long command in the background and carry on, or wait for it once. No
`sleep` or `until grep` polling of builds, harnesses or CI.
- **One test per changed behaviour.** None for a refactor or a file move, none that reads source text, and no
harness where vanilla has nothing to compare (Horizon's own features, performance work).
- **A red check isn't always yours.** When `verify` fails in a test your change can't reach, say so in the pull
request and move on; don't hunt it.
- **Unsure about a rule? Finish the work.** Take the reading that lets you finish, and say in the pull request
which one you took. Don't stop, and don't change direction halfway.
## Crates
Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-server` are separate workspaces.
@@ -96,6 +120,7 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
- Use independent expected values or a vanilla oracle; do not derive the answer from the code under test.
- Test outcomes, not only calls to stubs. Use mocks to isolate behavior, not as the behavior itself.
- Skip tests of trivial constants, derives and getters, or behavior already covered by another test.
- One test per changed behaviour is enough. A refactor or a move that changes no behaviour gets no new test.
- Avoid pinning implementation details; a harmless refactor should not break a behavior test.
- Make slow tests faster without removing cases or weakening their coverage.
- Never delete parity harnesses or tests that pin vanilla behaviour: they are the spec.
@@ -103,8 +128,8 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
## Build and test fast
```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
python3 tools/pr_ready.py # the self-check: fmt, clippy -D warnings, touched crates' tests; paste its block in the PR
python3 tools/pr_ready.py --harnesses # also the affected harnesses (by default only when you change a harness, oracle, probe or the runner, as in CI)
cargo test -p horizon-client -p horizon-client-lib --lib --bins --tests # skips the examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client-lib --lib mod_content::tests:: -- --ignored # the slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo build --profile fast -p horizon-client # optimized, quick to link: use it to play or run lan_e2e
@@ -530,7 +530,7 @@ unable to act, and never puts an account that is tied to a person into anyone el
| Account | Today | Can it be shared? | Plan |
| --- | --- | --- | --- |
| **GitHub organisation** (horizon-rust) | One owner (the founder) | Yes: an organisation can have several owners | **Step 1:** a second owner from the council, chosen by a 2/3 poll in the council's first week, with two-factor authentication on. **Step 2:** a `council` team; CODEOWNERS reviews and the release tools' owner powers (`paused`, `/nightly remove`) move from the founder to that team |
| **GitHub organisation** (horizon-rust) | Two owners since 2026-11-11: the founder and rdbtCVS (P28); a `council` team holds CODEOWNERS (P35) and is the security managers (P36) | Yes: an organisation can have several owners | **Step 1:** a second owner from the council, chosen by a 2/3 poll in the council's first week, with two-factor authentication on. **Step 2:** a `council` team; CODEOWNERS reviews and the release tools' owner powers (`paused`, `/nightly remove`) move from the founder to that team |
| **Discord server** | Owned by the founder | Partly: a server has exactly one owner; powers can be given through roles | **Step 1:** the 🏛️ Council role gets moderation and ban permissions; Kira applies the outcomes of passed polls, so nobody needs Manage Roles; two-factor authentication becomes required for moderation. Ownership itself stays with the founder until a legal entity exists |
| **Kira** (bot application, Railway service) | The founder's Discord developer account and Railway | The bot: yes (a Discord developer team). Railway: adding members may need a paid plan | **Step 1:** a Discord developer team with two council members. Railway members once the council decides on money |
| **Official server** (hosting panel) | The founder's account | Yes: panel sub-users | **Step 1:** sub-users for the Server and SMP lead and for one council member |
Needs Rust 1.90+, Python 3.10+, JDK 25, and on Linux `libasound2-dev libudev-dev`. `horizon-prepare` builds the game's resources from **your own copy** of Java Edition 26.3 (checked against `research-lock.json`) and never downloads it:
Needs Rust 1.90+, Python 3.11+, JDK 25, and on Linux `libasound2-dev libudev-dev`. `horizon-prepare` builds the game's resources from **your own copy** of Java Edition 26.3 (checked against `research-lock.json`) and never downloads it:
1.**Client:** play 26.3 once in the official launcher. Horizon reads `~/.minecraft` (or `--minecraft-dir` / `HORIZON_MINECRAFT_DIR`).
2.**Server jar:** the 26.3 server jar (`downloads.server.url` in `versions/26.3/26.3.json`), passed once with `--server-jar` or `HORIZON_SERVER_JAR`.
"horizon.no_resources.title":"No prepared game files",
"horizon.no_resources.prepare":"Prepare them with horizon-prepare --prepare, or pass --resources <folder>.",
"horizon.no_resources.looked_in":"No prepared game files in %s",
"horizon.no_resources.quit":"Quit",
"horizon.get_the_game.title":"Start Horizon from the launcher",
"horizon.get_the_game.about":"This is the game the Horizon launcher installs and starts. On its own it has none of Minecraft's files, so it can't start.",
"horizon.get_the_game.minecraft":"Horizon needs your own copy of Minecraft: Java Edition. The launcher signs you in and prepares the game files from it.",
"horizon.get_the_game.download":"Download the launcher at %s, then press Play there.",
"horizon.get_the_game.button":"Download the launcher",
"horizon.extras.header.world":"World",
"horizon.extras.pregen":"Chunk pregeneration",
"horizon.extras.pregen.tooltip":"Enables /pregen in your single-player and LAN worlds. With cheats enabled, use /pregen start to generate an area before exploring it. Does not enable the command on servers you join.",
"horizon.options.radial_mode.about":"Утримуйте кнопку відкриття радіального меню або натисніть її раз, щоб відкрити, і ще раз, щоб закрити.",
"horizon.options.radial_mode.hold.about":"Утримуйте кнопку відкриття й відпустіть, щоб виконати те, на що вказує стік.",
"horizon.options.radial_mode.toggle.about":"Натисніть кнопку відкриття, щоб відкрити меню; A або повторне натискання кнопки відкриття виконує те, на що вказує стік, B закриває.",
"horizon.options.radial_stick":"Вибір у радіальному меню",
"horizon.options.radial_stick.about":"Стік, яким ви вказуєте на комірки радіального меню.",
"horizon.options.radial_stick.right.about":"Правий стік вказує на комірки; лівим ви й далі ходите.",
"horizon.options.radial_stick.left.about":"Лівий стік вказує, а ви стоїте на місці. Під тим самим пальцем — хрестовина праворуч, тож оберіть кнопку відкриття на сторінці «Радіальне меню».",
"horizon.options.radial_stick.either.about":"Вказує стік, яким рухали останнім; жоден не рухає вас і не повертає огляд. Відкрити меню можна лише бампером, тригером або кнопкою View.",
"horizon.radial.pick.about":"Ставить цю дію в комірку й повертається назад. Поточну дію комірки позначено.",
"horizon.radial.pick.current":"Поточна",
"horizon.radial.missing.about":"Ця гра не знає дії %s: комірка зберігає її, але нічого не виконує. Натисніть або «праворуч», щоб поставити сюди іншу.",
"horizon.radial.page_of":"Сторінка %s з %s. Кожна комірка — це напрямок стіка вибору, поки кнопку відкриття утримано.",
"horizon.radial.slot.0":"Угору",
"horizon.radial.slot.1":"Угору праворуч",
"horizon.radial.slot.2":"Праворуч",
"horizon.radial.slot.3":"Униз праворуч",
"horizon.radial.slot.4":"Униз",
"horizon.radial.slot.5":"Униз ліворуч",
"horizon.radial.slot.6":"Ліворуч",
"horizon.radial.slot.7":"Угору ліворуч",
"horizon.radial.slot.about":"Дія, яку виконує цей напрямок. Натисніть, щоб побачити список дій; «праворуч» — наступна, «ліворуч» або Shift+клік — попередня.",
"horizon.radial.rename":"Перейменувати сторінку",
"horizon.radial.rename.about":"Дайте цій сторінці власну назву.",
"horizon.radial.name_title":"Назва сторінки",
"horizon.radial.new_page.about":"Додає порожню сторінку після останньої й показує її.",
"horizon.radial.delete_page.about":"Одразу видаляє цю сторінку та її комірки. «Скасувати» повертає її.",
"horizon.radial.delete_page.last":"У меню лишається щонайменше одна сторінка.",
"horizon.radial.reset":"Скинути всі сторінки",
"horizon.radial.reset.about":"Одразу повертає сторінки «Загальне» й «Камера» та видаляє решту. «Скасувати» повертає їх.",
"horizon.radial.reset.default":"Сторінки вже типові.",
"horizon.radial.opener":"Кнопка відкриття",
"horizon.radial.opener.about":"Кнопка, яку утримують, щоб відкрити меню. Натисніть, потім натисніть кнопку геймпада: бампер або хрестовину, але не A, B, X, Y чи правий стік, якими керують і скасовують.",
"horizon.radial.opener.about.left":"Кнопка, яку утримують, щоб відкрити меню. Натисніть, потім натисніть кнопку геймпада: бампер, A, X або Y, але не хрестовину чи лівий стік, якими керують, і не B, яка скасовує.",
"horizon.radial.opener.about.either":"Кнопка, яку утримують, щоб відкрити меню. Натисніть, потім натисніть кнопку геймпада: бампер, тригер або View; обидва великі пальці керують, а B скасовує.",
"horizon.radial.unbound":"Жодна кнопка не відкриває меню (кнопку під пальцем, яким обирають, звільнено): натисніть, потім натисніть кнопку геймпада.",
"horizon.radial.conflict":"Спільна з іншою дією: у грі цю кнопку забирає меню. Натисніть, щоб обрати іншу.",
"Radial Menu":"Радіальне меню",
"Named pages of eight actions, held open with a button.":"Іменовані сторінки по вісім дій, відкриті, поки утримуєте кнопку.",
"The shader pack lights the world itself.":"Пакет шейдерів сам освітлює світ.",
"How bright the HUD and menus are. Auto: at paper white.":"Яскравість HUD і меню. «Авто»: на рівні білої точки.",
"HDR output stopped the game the last time it started with this graphics driver. Choose a mode to try again.":"Минулого разу з цим графічним драйвером HDR-вивід зупинив гру. Оберіть режим, щоб спробувати знову.",
"horizon.pad.action.radial":"Радіальне меню",
"horizon.recipes.fuel.options":"Утримуйте SHIFT, щоб закріпити цю підказку, потім клацніть інгредієнт, щоб переглянути рецепти.",
Loaded 100 of 610 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.