Upstream added --player-dir handling to the startup code in main.rs. With the
library split that code lives in entry.rs, so the change is applied there and
main.rs stays the thin launcher.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main.rs moved to entry.rs on this branch; carry main's --player-dir
log/crash folder handling over to entry.rs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Combined staging branch: the game library split (#5446) and the single mod
file format (#5909) together, as agreed to land in one release. No conflicts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: main merged after #4945/#4946 landed: main's new wanted_tiles queue now skips tiles waiting out their retry, as this PR's loop did. verify green on a6d37d7; tools/server_lod_e2e.py passed on Linux at the previous head (source server, capped at 16; none when unset).
🤖 Agent: Review by Ludovic's AI agent (Claude). A pack's vignette=false hides the game's vignette; with no pack nothing changes. I merged main and reworded a comment that named another project. Verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). GLSL translation fixes, each with a test (GLSL 4.60 §4.1.9, §4.2.2), packs-only code. I merged main in; verify green on this head.
🤖 Agent: main merged again (the two uniforms go after main's isRightHanded … hasCeiling, looked up by name); verify green on 67ebb02. Thanks @Joansitoh.
🤖 Agent: levzzz5154's five review points fixed on the branch (crash telemetry stays on --home's consent, servers ignore the flag, limits documented, splitscreen guests on the game folder's mods intended, tests for shared_dir_of and servers_file_of); main merged; verify green on 9d56f33. Without --player-dir nothing changes. Thanks @Solenopsisbot.
🤖 Agent: Review by Ludovic's AI agent (Claude). levzzz5154's two points are fixed (the hook runs through as_guest, after placement), the docs conflict with main is resolved, and the API fragment carries neoforge/side. Verify green.
🤖 Agent: Opening shulker lids push the local player on the server-thread client as ShulkerBoxBlockEntity.tick does on both sides; lan_e2e shulker_lift check; verify green.
Upstream added `pub mod lod_net;` to the old lib.rs module list, which this PR
moved to client_lib.rs: add it there. Upstream's new horizon-optifine
dependency is used by mobs/cem.rs, a reusable-client module, so it goes in
horizon-client-lib's manifest instead of horizon-client's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5601 has merged, so this test-only follow-up leaves draft: main merged (corpus union, every function checked against its source), verify green on 938f032. Thanks @JustKarkat.
🤖 Agent: shader API series stacked on one union (each head holds the earlier ones); pr_ready --quick green on the last head 47b5a77 (2574 tests), shader-api tests + clippy on each earlier head; offscreen in-game run with a pack using all four features, no validation error.
🤖 Agent: Opaque terrain pass without discard: two solid passes on Metal (Apple's tiled GPUs, measured faster on M1 Pro), one pass elsewhere as before (fix pushed here); the far-tile opaque marking is fixed everywhere.
🤖 Agent: A boat used under water lands at the eye and is refused as in vanilla's BoatItem.use (ClipContext.Fluid.ANY on a full fluid cell); unit test, verify green.
🤖 Agent: far terrain 2/3 (the server samples tiles, off by default). #4945 and main merged, verify green on 6ee16d8; rdbtCVS approved, kacpherek's points fixed in the code; the stack's e2e passes on Linux.
🤖 Agent: far terrain 1/3 (wire format, off by default). Main merged, verify green on 1f6b07c; rdbtCVS approved; the stack's e2e passes (tools/server_lod_e2e.py on Linux: server-sampled LOD capped at the server's 16 chunks, none when unset).
🤖 Agent: requeued: the parity failure was the runners' (a dead .horizon-prepare.lock link on runner horizon at playhorizon and horizon-2 at the Mac mini, now removed); the run is rerunning on the same head.
🤖 Agent: security-reviewed by Ludovic's AI agent (Claude): the secrets file is now encrypted at rest (ChaCha20-Poly1305, key from the computer's machine-id), mode 0600, atomic replace, no token in logs or errors, the owner gate unchanged. 58 account tests pass on Linux (playhorizon); verify and launcher green on 0de577b. Thanks @JulesB40.
🤖 Agent: native_camera_distance hook: a no-op fragment hook until a mod fills it, finite 0..=1024 overrides only, collision clipping kept; unit tests cover it.
🤖 Agent: Review by Ludovic's AI agent (Claude). The hook skips the screen the server pack prompt hides, as hud_layers draws Playing then; arismoko's in-game check confirms. I merged main; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). Docs only: bow PvP between players works since 5763854b0 (lan_e2e arrows, #5785's dedicated-server check). I merged main; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). EnderDragonRenderer has no dimension check; dragon_geometry returns at once with no dragon, and the dragon is cleared on a dimension change. I merged main; verify green.
🤖 Agent: Review by Ludovic's AI agent (Claude). Far tiles sample an explicit mip, only on tile faces (UVs past a block); block faces keep sprite_grad. A few ALU ops per far-tile fragment. I merged main; verify green.
🤖 Agent: Translucent batching 1/3: off by default (only horizon-graphics.txt turns it on), so players see no change; with it off the frame adds one bool check (follow_translucent_layout) and a branch per draw run, uploads and compaction keep their old paths. Measured gains in the body are for the opt-in path.
🤖 Agent: Review by Ludovic's AI agent (Claude). levzzz5154's four points are addressed (overlay gate, client-only guard, gamepad note, #5617 merged in); API fragment. I merged main (merges with #5731 cleanly); verify green.
The split saves Apple's tiled GPUs their hidden surface removal; elsewhere
the second pass doubled the solid draw calls (one per section without
multi-draw) and the vertex work for no gain that was measured. Other
backends keep the single fs_solid pipeline, the flat one included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Management protocol serversettings/difficulty get/set as MinecraftServerSettingsServiceImpl, saved to server.properties and server.toml; the management harness covers it.
🤖 Agent: Boat paddle flags reset to false (AbstractBoat.DATA_ID_PADDLE_*) when a stroke stops or the rider dismounts; new harness boat_paddle_reset compares against a vanilla server.
🤖 Agent: Thrown potions hit players with vanilla's pick margin and leftOwner rule (Projectile.canHitEntity / checkLeftOwner); costs nothing when no potion is in flight; unit test covers the thrower case.
🤖 Agent: Placing a painting swings the hand as vanilla's HangingEntityItem.useOn (InteractionResult.SUCCESS) does; tests pass, parity runs on the head.
🤖 Agent: OptiFine series step verified (licence gate, gating to packs that ship the files, per-mob cost measured, verify green on 64d0b1a4e). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gating to packs that ship the files, per-mob cost measured, verify green on b88a1aad1). Thanks @some-du6e.
🤖 Agent: OptiFine series step verified (licence gate, gating to packs that ship the files, per-mob cost measured, verify green on d454a1fcb). Thanks @some-du6e.
🤖 Agent: Shader packs: a custom variable/uniform expression may end with one ';' (trimmed), extra statements and empty expressions still rejected; unit tests.
🤖 Agent: Shader packs: custom image dimensions expand pack option macros (default and chosen), the option table is still built once; literals unchanged, unknown names still rejected; unit tests.
🤖 Agent: Shader options: a declared default missing from its authored range stays selectable (inserted first), empty ranges still create no option; unit tests.
🤖 Agent: Shader packs: a one-literal vector constant (ivec3(1)) in workGroups/workGroupsRender fills every component (GLSL 4.60 §5.4.2); negative or nested expressions still rejected; regressions fail before the fix.
🤖 Agent: Shader packs: isRightHanded, is_on_ground and the dimension's bedrockLevel/heightLimit/logicalHeightLimit/hasSkylight/hasCeiling get real values (black-box probe of the uniform values, no source read); set per frame only while a pack is on.
🤖 Agent: Client hook native_mob_submit as an API fragment; I fixed arismoko's two review points (walk state from mobs::anim::state: no walk while riding/dead, babies x3; hook asked before the sulfur cube's block). Verify green on 62e9cf2ba; the hook is a no-op stub without mods.
🤖 Agent: Reviewed lod.rs (seam stand-in rule in select and evaluate, coarse backdrop in the wanted queue, load order only). fmt/clippy/lod tests green in verify on this head. Thanks @some-du6e.
🤖 Agent: Requeued after merging main (conflict beside native_data_pack_files, both hooks kept, 3 files +24 vs main). Server hook native_server_data_loaded(reload) as an API fragment; empty stub, no cost.
A fluid hit always snapped to the top of the water cell, so a boat landed
above a submerged player. In a full water cell the hit now stays on the
ray, as vanilla's VoxelShape.clip has it, so the boat collides with the
player and nothing is placed.
Fixes#5967
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: Client hook native_biome_colors + native_refresh_biome_colors as an API fragment: runs only when a mod asks for a refresh (rebuilds sections like a Biome Blend change), nothing per frame; Biomes::with_colors unit-tested.
🤖 Agent: Harness diagnostics only: a JVM oracle that dies without an exception reports its exit/signal and last output (chunk_generation, datafix); no change to what is compared.
🤖 Agent: World border wall invalidated when the camera changes SectionPos (LevelRenderer.repositionCamera); an i32 compare per frame; world_border_render passes; verify green.
🤖 Agent: One line: rope_rot_old (yRotO/xRotO) set at the start of the server-client tick, as the local tick does, so the held lead end stops easing toward yaw 0; verify green.
🤖 Agent: A full dynamic glyph region no longer caches the missing glyph forever: overflow is retried after begin_frame clears the region (one bool check per frame); font unit tests cover overflow and no-provider chars; verify green.
🤖 Agent: Pointer menu mode: the on-screen keyboard auto-opens over chat/signs and A on a text field opens it, as focus mode does; pure helpers unit-tested; pad UI only, verify green.
🤖 Agent: Toast label colour packed with hud::rgba (yellow 0xFFFF00 / pink 0xFF88FF as AdvancementToast), unit test on the packed bytes; client UI only, verify green.
🤖 Agent: queued with my fix (9ec5db50d, see above): the remote arrow's flight rotation as vanilla's `AbstractArrow.tick` has it, without the two hunks that went against 26.3. Thanks @Lostpold88.
🤖 Agent: verify was green before main's splitscreen extra conflicted. Main is merged in (40704247c), and the extra stays off by default. Queued for merge, thanks @Stink-O.
Resolves docs/native-mods.md with main's native_player_command and
native_perform_console_command entries. levzzz5154's review:
- native_player_respawned runs through as_guest, so the helpers that act on
the player (native_player, native_give_items...) reach the respawned one;
- it fires at the end of guest_respawn, after the bed, anchor and world-spawn
placement and the hardcore switch, as PlayerRespawnEvent ends
PlayerList.respawn; the docs name the later-tick trips and the End exit;
- the fragment carries neoforge PlayerEvent.PlayerRespawnEvent, side server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the client collision snapshot in sync with the animated lid and apply its existing swept-volume push, including while a container menu is open. Cover lift, support and closing for both players in survival and creative on the integrated server.
Co-Authored-By: Codex GPT-6 <[email protected]>
levzzz5154's review: only logs/ and crash-reports/ follow --player-dir (crash
telemetry stays on --home, whose consent it reads); a dedicated server never
takes it. The launch reference says what stays shared (command history, Play
with Friends' relay) and that horizon-ctl logs and the launcher read --home.
Tests: shared_dir_of, servers_file_of.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla's DataPackCommand ends in ReloadCommand.reloadPacks, which
reloads the server's resources: recipes, tags, loot and advancements
with the functions. Here enable and disable rebuilt only the function
store, so a pack's recipes stayed (or stayed away) until the next
/reload. They now reload the content tables too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reject unsupported negative and nested scalar expressions rather than dropping their operators when expanding a vector. Add a regression for the overbroad expansion.
Co-Authored-By: Codex GPT-6 <[email protected]>
Far tiles marked only leaves as opaque, so every stone, sand or ice face
of the far terrain went through the cutout pass and its discard. They are
marked as the mesher marks section quads now: opaque leaves and faces
over opaque texels.
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
🤖 Agent: verify is green and the branch merges cleanly with main. It also built and passed the Python tests in a combined branch with ~40 other open PRs. Read through: it looks right. Queued for merge (parity runs before it lands). Thanks!
A splash, lingering or XP bottle stopped at blocks and mobs only, so it
fell through any player. The step now meets players too. The thrower and
their vehicle group are skipped until the potion has left them, as
Projectile.canHitEntity and checkLeftOwner do.
Fixes#5876
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Send the default false values when paddle entries disappear from tracked metadata, including dismounting mid-stroke. Compare 18 transitions with actual vanilla servers.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
- NativeMobRender takes the walk cycle, head turn and age from mobs::anim::state,
the state the vanilla models read: no walking while riding or dead, a baby's
legs three times as far (LivingEntityRenderer.extractRenderState,
WalkAnimationState.positionScale).
- The hook is asked before a sulfur cube's swallowed block is drawn, so a
mob a mod draws carries none of the vanilla model.
Review points from arismoko's review.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack of Horizon's own with [shadow] draws from the shadow camera's lists
(d36550e82), which makes frames flat where the device can, and flat draws
read translucent quads in the section arena. translucent_split now counts
that pass with a shaders/ pack's shadow programs, and a renderer made with
such a pack picked starts without the split (picked_pack_draws_flat, which
reads the pack's manifest; it was shaders_pack_picked).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: reviewed by Ludovic's AI agent (Claude): rdbtCVS's #5595 finished (main merged, "Built on {base}" in all 10 launcher languages); remove_version refuses a running build; verify and launcher checks green on e918209, merges cleanly with main. Thanks @rdbtCVS.
The file kept the Microsoft refresh token and the Minecraft access token in
plain JSON. It is now sealed with a key derived from /etc/machine-id
(/etc/hostid on FreeBSD), so no token is on disk in plain text and a copy
taken off the computer can't be read. A file that doesn't open is refused
without quoting it and never written over, as a broken one was.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Windows F3/crash-report CPU name from the registry's ProcessorNameString with the old fallback; read once (OnceLock) so no per-frame cost; the launcher starts the game with CREATE_NO_WINDOW so reg.exe opens no console. Parser unit-tested.
🤖 Agent: MCP initialize now negotiates the protocol version (echo only a supported one), same 12-line helper in horizon-ctl, horizon-server and the launcher, with unit tests; verify and launcher CI green.
🤖 Agent: Docs only (mods.md, modpacks.md, template comment, manifest doc comment); checked the facts against the content loader (sounds key names a vanilla block, menu/entity_type kinds, mismatch refusal). The failing 'check' job is horizon-mod-registry's lock on main, unrelated.
Packs read these Iris uniforms as zero: isRightHanded (the Main Hand option), is_on_ground, and
the dimension type's bedrockLevel, heightLimit, logicalHeightLimit, hasSkylight and hasCeiling
(from the level's shape; hasCeiling in the Nether). A pack reading bedrockLevel 0 and heightLimit
0 in the overworld puts its fog or its sky's horizon at the wrong height.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GLSL 1.20's gl_FogFragCoord is a built-in varying: a vertex stage writes the fog distance and the
fragment stage reads it. naga knows no such name, so every program using it was skipped with
"Unknown variable: gl_FogFragCoord" (26 in MakeUp UltraFast, 26 in E-LITE, 18 in VanillAA, 1 in
Potato). It is now hz_FogFragCoord, a float output of the vertex stage and input of the
fragment stage, like gl_FrontColor and gl_TexCoord[n].
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify is green and the branch merges cleanly with main. The saved switch stays off by default, so nothing changes for a player who doesn't enable it. Queued for merge, thanks @martipops.
🤖 Agent: verify is green, the branch merges cleanly with main, and levzzz5154's review found it ready (load order below the world's packs, both first open and /reload covered). The hook's API number comes from its fragment when it lands. Queued for merge, thanks @Sylviromi.
Read and change the dedicated server difficulty through vanilla management
requests, broadcasting the change and saving it for restart.
Part of #5147
Co-Authored-By: Codex (GPT-6) <[email protected]>
Translucent quads (water, stained glass, ice) sat right after their
section's solid quads in the 256 MiB arena pages, so the back-to-front list
changed page every few sections and only short runs merged: 3,832 draw calls
a frame over the ocean at render distance 64. With the new
translucentBatching setting on, they get a vertex arena of their own whose
pages may grow to about 2 GiB (LARGE_PAGE_BYTES: DirectX 12 and OpenGL cap a
buffer at i32::MAX), and the sorted indices the same cap, so the list goes
out as a few multi-draws. Vertex data, sorted indices and draw order are
unchanged.
The split holds only while no picked shaders/ pack can draw terrain or its
shadow pass flat (translucent_split); a pack that changes that drops the
sections and far tiles, which the game meshes again. compact_arena moves
what a section holds in the section arena: its solid range when split,
skipping sections without solid quads. LOD tiles and their fade stand-ins
draw and release from the translucent arena too. Batching is offered with
indirect multi-draws, not OpenGL's, and not where vanilla frames draw flat
(translucent_batchable). Every build_renderer caller passes the setting,
splitscreen and resource reloads included.
Off by default. RTX 4090, 1080p, off -> on: +4 to +16 % FPS at render
distance 32, up to +43 % at 64 (ocean 284 -> 406 FPS, 3,832 -> 354 draw
calls); arena memory +1 to +15 %.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Projectile.shootFromRotation adds the shooter's known movement and drops
only its y on the ground, and sets no rotation after it: an arrow shot
while strafing drifts with the player in vanilla too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As serde_json Values, vanilla's ~1,450 loot tables (0.6 MB of JSON,
~10,450 objects, a B-tree node each) held ~7 MB of a server's idle heap.
LootTables keeps each table's text and reads it into a Value the first
time it is rolled; every table is still read whole when it loads (a bad
file fails the load as before) and the native hook still runs on it, in
id order, before anything rolls it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified at e241409 on the Mac mini: fmt, clippy -D warnings (horizon-client-render), mesh_pool tests pass; the new assert fails without the fix. Thanks @Teiwazik.
The solid terrain shader could discard every pixel (the fade dither and
the alpha cutout), and one discard anywhere in a pipeline turns off the
hidden surface removal of tiled GPUs (Apple's) for everything it draws.
The world pass now draws each solid list twice: first the quads over
opaque texels that are not fading, through fs_opaque, which has no
discard, then the rest through fs_solid. Each vertex shader decides its
pass before any other work; a quad of the other pass lies outside the
clip volume. Shader packs and native materials keep the one pipeline.
M1 Pro, 1280x720, same commit before and after: GPU 10.4 -> 9.6 ms in a
flight over new ground, 12.9 -> 11.0 ms in rain, 9.5 -> 8.1 ms with 300
mobs in view. Screenshots are the same.
On the server-thread client, Game::tick returns tick_remote() before the
rope_rot_old write, so the first-person lead end eased toward the initial
(0, 10) every tick. Write it in tick_remote_level, after the unlit-chunk
early return, as the local tick does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The wall mesh only holds the stretch inside render distance at build time
and was rebuilt only when the border itself changed, so after a respawn or
a long walk it stayed on the old stretch. Invalidate it when the camera's
section (floor >> 4 on X, Y, Z) changes, as LevelRenderer.repositionCamera.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: reviewed (the replay takes the shared flags' sprint bit the server sends, as vanilla's client); rebased on main; pr_ready --quick green on 2a1dd55 (1711 tests); the new test reproduces the live 0.0294/0.0455 signature before the fix; a +0.05 speed lie still counts from its first tick.
Read ProcessorNameString from the Windows registry instead of displaying the raw processor identifier.
Co-Authored-By: OpenAI Codex <[email protected]>
A client whose STOP_SPRINTING and START_SPRINTING reach the server in two
server ticks gets the STOP's shared flags and movement speed (no sprint
modifier) after it sprints again: its flag cleared, LocalPlayer.aiStep
starts the sprint again, modifier included, and sends nothing. The
replay took the attributes but not the flag, so it sprinted on without
the modifier until the START's attributes came: 0.029 then 0.045 off,
the official server's "ground sprinting" flags (#5935).
Record the sprinting bit of the shared flags the server sends the player
(Change::Sprinting) and apply it to the replay's flag alone, as
SynchedEntityData.assignValues does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: rebased onto main (conflict with #5856's frozen test helper, both kept); pr_ready --quick green on 30dfdc3 (1689 horizon-client tests, 19 prediction tests on vanilla data).
For 10 ticks after the server's item use ends, the replay also tries the
client still slowed by it: the client finishes a use on its own ticks and the
server on its own (food eaten one after another with the key held), and the
server stops one for its own reasons. Slower than the keys allow is no
advantage; a use the server has and the client may not still counts at once.
tools/anticheat_e2e.py: prediction-items (a shield, food and a bow used on
the move) and prediction-water (a deep pool, a current, a bubble column, a
ledge) with a real Horizon client.
Fixes#5860.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proof: pr_ready --quick green (1680 horizon-client tests); write timeout = read timeout; local frozen-player run ends both threads; ships in the next batched restart for the live measurement.
#5915's last revision made the shared pairs references, so the sort's
comparison borrowed a reference: main's verify (clippy -D warnings)
fails on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proof: pr_ready --quick green on 98e49e3 (1680 horizon-client tests); live on the official server since 12:22Z as dd4b209 (this head minus the test commit), status pings ok every minute, 99 joins in 15 min; the deadlock of the first deploy is reproduced by the test without the fix.
A peer that stops reading (a frozen game, a dropped network) held its
writer in a full send buffer until TCP gave up, minutes later, and the
connection's place with it: play() joins the writer. Writes now time out
like reads. The turned-away report also says how many places are before
play and how many wait for a writer to finish.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: main moved after the queue (#5875 and #5755 merged), so I merged main in again (clean, d635bc57f). `cargo check -p horizon-client --lib --bins --tests` passes. Requeued.
Review on #5909: native versions published as .hmod would reach
launchers that drop their content or refuse them. The registry keeps
.hnmod until launchers and the game read native .hmods.
docs/api/mod-files.md holds every rule of a .hmod/.hnmod in one
place: zip and path rules, top-level entries, the horizon-native.json
schema, what mixins/<id>/ may hold and the size limits.
The registry's Cargo.lock catches up with horizon-mods' png
dependency so its --locked check passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: proven on a union with main c1f4a7c19 (+ #5875, #5755, #5679): `pr_ready.py --quick` passed (fmt, clippy -D warnings, horizon-client tests, doc tests). I added a lan_e2e check, `attack_over_slab` (97c18d712): a NoAI zombie stands behind a bottom slab, the player aims at its head over the slab and left-clicks. It passes for the host (20 → 19.06) and for the guest (19 → 18). Queued for merge. Thanks @repositorykreml1n!
🤖 Agent: verified by Ludovic's AI agent (Claude). ItemStack.isBarVisible needs isDamageableItem, which an unbreakable stack isn't, and the unbreakable unit component now reaches clients. pr_ready --quick passed on main c1f4a7c19 with this PR, #5875, #5903 and #5679 merged in (fmt, clippy, horizon-client tests incl. the new sync test).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against ShulkerBoxBlock.playerWillDestroy and Block.popResource (offsets drawn before the block_drops check, as vanilla). pr_ready --quick passed on main c1f4a7c19 with this PR, #5903, #5755 and #5679 merged in (fmt, clippy -D warnings, horizon-client tests, Python tests). CI parity covers the new command_exec cases.
🤖 Agent: playhorizon 4 CPUs: idle RSS −17 MB with THP off (241.7 → 224.7, every start) and −18 MB median with THP on (4/4), live heap −13.6 MB, Done 2.06 → 1.955 s (4/4); 20-bot MSPT unchanged (3 rounds). pr_ready tests 2066 + content roots pass; merge with main checks; fmt/clippy/Python clean on the Mac.
The deadlock fixed in the previous commit only shows with a log writer;
the test hangs without the fix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
They live in their own lists, so a plate's entity count never saw them.
They now count, as vanilla's getEntitiesOfClass does. A rocket boosting a
player and a hook on an entity count but skip entityInside, whose step
their ticks skip too. A guest's hook ends when the guest changes level, so
it can't act on the blocks of the level it arrives in.
Fixes#5896
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
loot_table_load_stays_hookable searched for "\n}\n", which a CRLF checkout
(core.autocrlf=true on Windows) never contains. Normalize as the other
source-reading test in this file does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
make_room logs turned-away connections, and the log reads by_address:
count called it with that lock held, so the accept loop deadlocked the
first time the server filled with a log writer set.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A .hmod's contents decide what it is: content only, native code
(horizon-native.json + mixins/<id>/), or both in one file. Native
bundle reading skips a .hmod's content entries, checks both
manifests name one mod, and refuses mod.wasm with a clear error.
The registry publishes native-mod versions as .hmod and still
accepts .hnmod; .hnmod keeps loading unchanged.
Launcher install, game content loading of native .hmods and the
GitHub install path are not done yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
decorated_region (through chunk_generation's oracle) failed in the nightly audit with
"oracle failed: " and nothing after it, and datafix with a CalledProcessError that
names the command; both Java oracles stopped with no exception to print. Show the exit
status (and the signal that killed it) and the last lines of its output.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A file data/<ns>/recipe_filter/<name>.json lists filters that match
recipes by id or type (with *), by an input or an output item, and
remove them or swap one input or output for another. They run on each
recipe's JSON after the packs are merged and its load conditions held,
so they reach every recipe type, mods' own included, in every loader:
crafting, the stations, brewing, the recipe book and the recipes the
players are sent. A filter with an unknown key or not exactly one
action is skipped with a warning, so a typo never matches every recipe.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When every place is taken, the oldest connection still before play
(handshake, status, login, configuration) that has waited more than 2 s
is closed to admit the new one. A crowd of sockets that never finish can
no longer keep players out; players in the game are never closed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The connection cap (#5776, players + 64) filled up on the official server
within minutes of a restart and every new join was reset. Now one public
address holds at most 10 connections (local and private addresses, where a
proxy or a port forward hands everyone over, stay unlimited), the handshake,
status and login phases wait 10 s for the client instead of 30 s, and once a
minute the log says how many connections were turned away and which
addresses held the most places.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: fixes the Windows-only account test flakes that blocked launcher 0.2.33 (per-store account lock). Verified: windows-latest green twice on this head (runs 41300356019 push, 41300386770 dry run: 352 passed), Mac mini account tests + clippy clean.
In single-player the player's attack goes through the integrated server as a guest attack. guest_attack calls
look_at_entity, which aimed at the centre of the target mob; Game::attack then drops the hit when a block outline is
nearer than the mob along that ray. The attack packet names only the entity, so a mob that the player's own crosshair
hits through a gap (a top slab, a closed top-half trapdoor, ...) had its centre hidden and the hit was dropped.
For mobs, look_at_entity now aims at the centre when it is visible, else at the visible point of a grid of points in
the mob's box nearest to the centre (nearer the eyes on ties), found with a clip_ray from the eyes. A mob whose whole
box is hidden keeps the centre, so a really blocked hit is still refused. Other entity kinds are unchanged.
Fixes#5772
native_server_data_loaded(reload) fires once the server's data tables are
built: on a world's first open, before its chunks and players, and after
every /reload, once the players were sent the new data. NeoForge's
TagsUpdatedEvent with SERVER_DATA_LOAD. A mod drops caches derived from
the data and rebinds what depends on it there instead of polling.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
block_texture, block_texture_level, block_texture_size and block_texture_lod read the surface's
sprite anywhere on it. The LabPBR _level functions now count the texture's mip levels.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Cloning a registry state bumped one Arc per name and value; world
generation threads clone the same states, and 20 bots cost 0.25 ms of
streaming. Shared strings are now interned once for the process (the
registries' names and values, never a player's) and copied as pointers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/summon minecraft:tnt {Passengers:[...]} dropped them. Load them as riders
(EntityType.loadEntityRecursive) and seat them on the TNT's top.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
SectionMesh::clear kept `lights`, so a mesh taken from the pool still listed the sources of the
section it last held: a broken torch stayed in the colour grid, and sources piled up elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Keep native hook targets and source paths stable. Run gameplay tests in the gameplay library, and build ctl/probes from horizon-client-lib without gameplay. Update build/test selection and the fixed mixin-tool pin.
Co-Authored-By: Codex <[email protected]>
Every unit test shared the process-wide account operations lock. On Windows a connection
to the tests' closed port (127.0.0.1:9) takes about 2 s to be refused, and tests that hold
the lock meanwhile made another test's status read answer "busy" after its 3 s wait
(Retry instead of SignInRequired), or starved a 5 s lock_within behind blocked waiters.
The in-process lock now comes from SecretStore::operations: the system keychain keeps the
one process-wide lock (one keychain per computer), a MemoryStore has its own, as its
secrets are its own. The launcher uses a single store, so nothing changes for players.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
So a flag can be found again in the world and played there (#5879: water
flags that don't reproduce in the harness).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
The HUD batch takes colours packed as hud::rgba (red in the low byte);
0xFFFF_FF00 there is cyan. AdvancementToast draws the label -256 (yellow),
or -30465 (pink) for a challenge.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
🤖 Agent: Shader series stacked in order (#5590 → … → #5757), each head clippy/test/fmt-clean on the Mac mini, the union checked in game with an all-keys pack and without a pack (vanilla). Merging once CI and parity are green.
So it no longer sits on the lines #5731 adds its field to: the two merge in either order without a conflict.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every merge cancels the parity-main run before it. In a busy hour of merges, the
last finished run fell out of the 10-run window, green_main_head found nothing,
and no Nightly went out for 14 h (2026-11-01) although parity-main had passed
on bd8c53bfa.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve the throw origin and inherited movement by preventing queued movement from overtaking a deferred UseItem. Compare standing and gliding throws against a real vanilla server.
Co-Authored-By: Codex GPT-6.1 <[email protected]>
Preserve collected components when breaking filled shulker boxes in Creative,
including with block drops disabled. Check all variants against vanilla through
the integrated server player-break path.
Fixes#5821
Co-Authored-By: Codex (GPT-6) <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5830 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5819 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5753 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5681 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5663 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5652 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5658 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5824 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5814 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5827 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5619 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The PRs before this one in the series, merged in order with their conflicts resolved,
so this branch merges cleanly once #5590 has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pr_ready --quick all green (1671 horizon-client tests, clippy, fmt); new replay test + 16 existing pass; anticheat_e2e --check prediction: honest client never flagged, cheating bot flagged 13x and sent back 12x.
When charging a bow while strafing on the ground, arrows drifted sideways
because player ground movement was added to the projectile velocity and
rotation was not aligned to the resultant vector. In addition, client-side
remote arrows did not update their pitch and yaw during flight, causing
them to fly horizontally like static lasers without following their
parabolic trajectory.
Align bow release with vanilla behavior by not injecting ground strafe
velocity and updating rotation in `shoot_from_rotation`. In
`remote_arrow_tick`, update pitch and yaw every tick using `lerp_rotation`
matching `AbstractArrow.tick()`.
Fixes#5855.
Co-Authored-By: Antigravity <[email protected]>
`native_biome_colors(biome, colors)` changes a biome's grass, foliage,
dry foliage and water tints. It is asked for every biome when a mod calls
`native_refresh_biome_colors()`, which swaps the mesh workers' biome table
and rebuilds the loaded sections and far terrain when the colours differ
(LevelRenderer.allChanged, as a Biome Blend change does).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QRKvdg3SMVP1onGqS3
native_mod_screen_open refuses what screen_hooks_open would: an overlay that leaves the screen at Playing (a
book, a sign editor, a dialog, a prompt), the world loading, no player, and anywhere but the window's own client
(no renderer, or a LAN guest's swap on the server thread), so a screen never opens that nothing could draw or
close. The docs and fragment say so, and that a gamepad can close it but not click on it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client mod is asked once a frame for each mob in view (players aside)
with the mob's render state: id, kind, custom name, baby, partial tick,
age, body and head turns, pitch, walk cycle, hurt and death time. It draws
with the block entity drawing helpers under a pose at the mob's feet,
turned with its body, and sets `drawn` to leave out the vanilla model
(EntityRenderer.submit; NeoForge RenderLivingEvent.Pre, cancelled).
Name tag, shadow, fire and glowing outline still draw.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QRKvdg3SMVP1onGqS3
The registry's states were read into a BTreeMap of owned strings, then
shared: two allocations and a hash per string, 50 ms of the start. The
facts.json decoder now shares each name and value as it reads it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The renderer's own pass times were not reachable from the client. The F3
overlay now shows them under their own heading, next to the shader pack
group. A new gpu-passes control command times them without opening F3,
and the stats and bench summaries report them as gpu_passes.
The per-pass GPU timer only covered shader-pack passes, so the F3
overlay could not say which of Horizon's own passes cost the frame.
Give the same timer to the frame's own passes (world, occlusion test,
OIT, HUD, post, HDR, hand, outline, menu blur, native shadow) and add
compute_writes for compute passes.
Passes that already carry timestamps (shader packs, the frame timer),
screenshots, one-off clears and test code are left as they were. The
timer begins at the top of the frame so that passes recorded before
the first draw, such as sprite animation, are timed too.
Until the client says it loaded the level (at join and after a respawn), the
ticks it ends with no move are excused: it is on its loading screen, where it
doesn't move, even once the server stops waiting for it after 60 ticks. Its
first move counts as any other.
Fixes#5849.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
One bad file from a mod would fail its whole table and drop the world's
pack content with it; it is now skipped with a warning. The hook's docs
list every kind the tables read, trade sets, villager trades and dialogs
included.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
parity-main fails on dedicated_server now and then: "+play-start
minecraft:update_attributes". #5597 held the join tick's dirty attribute
instances for one tick, because vanilla syncs them on the next tick, after
the first chunk batch. But this server sends no batch before placed_at + 2,
and only once the player's chunks are ready: on a loaded runner the first
batch came after that next tick, so the attributes went out before it.
They now wait until a batch has been sent (unacknowledged > 0, or the
client already acknowledged one), which keeps vanilla's order however long
the chunks take.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
So #5675 (splitscreen), which edits the same two arms, merges with this in either order; only the one-line
serialize format string still meets it (keep both keys).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block state's properties were a BTreeMap<String, String>: a 544-byte
leaf and two allocations per property for each of the registry's 35,723
states (about 30 MB of a dedicated server's live heap), for 237 distinct
strings. BlockProperties keeps them as a sorted slice of pairs, read like
the map (get, insert, iter, indexing, order, equality and hashing pair by
pair); the registry shares its states' strings through one Arc each.
States made elsewhere (chunk palettes, commands) own theirs, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The gameplay facts load parses its 56 MB file twice: a validation pass that
only keeps the JSON errors, then the typed decode. The validation now runs
on a scoped thread while this one decodes, and is joined before the load
returns, so its error still wins. With a single worker (`parallel` false)
the two passes run one after the other as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The window now hands a key's characters to the character hook instead of
the key, so `world_options_key` must not read the missing text as a
backspace: only a key that carries text types, and a `None` stays the
control socket's `\b`.
The character hook fires after the key's screen effects, as GLFW's
separate char callback (`KeyboardHandler.charTyped`) comes after its key
callback, and its characters survive a cancelled press, as NeoForge's
`CharacterTyped.Pre` comes after a cancelled `KeyPressed.Pre`.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Code <[email protected]>
GLFW's char callback comes after its key callback, so the character
hook now fires once the screen has acted on the key, on the screen open
then: a key that closes the screen reports no character, and one that
moves the focus types into the box it moved to. Options keeps its text
on the key, as its key path routes it between boxes and key binds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntityUseItemEvent.Start may change a use's duration, which
start_using_item keeps in use_duration (vanilla's useItemRemaining). The
first person pose asked getUseDuration again each frame for the remaining
ticks, and a server's client counted a food's crumbs from its consumable
time: both now count from use_duration, as getUseItemRemainingTicks does.
A use the server's flags start on the client takes getUseDuration, as
onSyncedDataUpdated does.
The fragment carries the catalog's neoforge, side and cancellable keys,
which the stamp tool now copies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The content root appended each mod type's entity-types.json row by parsing
and rewriting the table, which needed serde_json's float_roundtrip to keep
a zombie's movement_speed to the bit. Cargo unifies that feature over every
crate in the game build (worldgen JSON included), while horizon-world's
parity probes, built alone, would parse without it. The rows are now
appended as raw text copied from the template's row, so no vanilla float
is reparsed and the feature goes.
The save test compared p.kind by value, then borrowed p: ProjectileKind
isn't Copy.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While PackConfirmScreen is open the frame draws no screen under it, but
the render hook still reported the hidden one, so a mod drew over a
screen nobody saw. It now skips that frame, as the input hooks already
do through screen_over_playing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stub moves next to native_item_bar: #5804's block highlight took the
spot after native_tick. The docs said the batch uses screen coordinates;
it takes the same GUI units as x and y (draw_slot_item fills there too).
They now also list the other screens that draw item icons.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stub moves next to native_camera_shake: #5804's block highlight took
the spot after native_tick. Its docs no longer say mods can draw their own
hand geometry: the hook passes no vertex buffer, pose or light.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla saves Abilities.flyingSpeed with the player, so a speed a server
mod set now goes into abilities.flySpeed and comes back on the next join;
a file's own speed is read. Every abilities packet comes from one
Guest::abilities, so the join, a game mode change, a respawn and a level
change all carry it (tested with the save round trip). The fragment is
renamed to native_motion for both helpers, FLYING_MAX says why 50 and what
the movement checks do with a fast non-owner, the guest comment names
the server mod, the fork wording goes, and docs/native-mods.md gets a section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A key press a mod cancelled hands the characters no mod cancelled to the
text box, as vanilla's separate charTyped does; the control socket's
type \b no longer offers its backspace sentinel as two characters, and
its cancelled tap sends the release the docs promise. The key and
character hooks' docs name the control socket and say a key that closes
the screen reports its character there first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_perform_console_command hears a command a mod registered when it is typed on the
dedicated server's console (level 4, no player), before vanilla's dispatcher; the answer goes
to the console through native_feedback. The single player owner's console is not covered.
Since #5100, turn_to_base_block (a dirt path or farmland turning to dirt)
emits a push_entities_up block event, which the block-update probe printed
as "X push_entities_up". Vanilla's Block.pushEntitiesUp only moves the
entities in the new block's box, and these cases have none, so the oracle
logs nothing: the dirt_path case differed and parity-main failed. The probe
now skips it, as it skips the other events that need someone there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_quad draws through HudBatch::fill_quad (the HUD atlas's white, as native_fill) instead of borrowing the
lightning renderer's white_uv. The rules for opening a mod's own screen move into mod_screen_opens, with a test:
only over the world, never over another screen, the title screen or another mod's, and again a no-op. The docs
say every mod's own screen shares the id `mod` (check native_mod_screen() is yours) and that it doesn't pause a
single-player game.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Renamed from native_get_clock (no other helper has a get_ prefix). Both fields are i64 ticks as
Level.getGameTime and getDayTime return them, without the clamp to 0: the day time counts on past 24000
and /time set can make it negative, and the docs say so. Documented as the shown world's clock, for
client mods.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With it off the static pool is vanilla's again (247 channels to whatever starts first), so the sound_engine
parity harness is unchanged; `uiSoundReserve` (Extras > Sound, "Keep channels for UI sounds") turns on the 16
channels positional sounds leave for the ones played to the listener. The test checks both, and its
`use test_backend::Null` moves into it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: playhorizon A/B, 20 bots, 3 rounds: tick_lan 4.22→3.96 ms (−6 %), MSPT 25.65→25.32. Same bytes (new test vs from_values). fmt + clippy -D warnings pass (Mac mini); workspace tests 3982 pass (playhorizon); the 2 Python failures there are its VSCREEN env, they pass on the Mac mini.
A native mod can now send a particle of its own type, drawn by every client
from the sprite set the mod's pack ships, instead of borrowing a vanilla one.
The type is the mod's namespaced name in ParticleOptions::named ("mymod:spark");
the client builds it as vanilla's SimpleParticleType with a sprite set: a
plain billboard, no tint, 20 to 40 ticks, on the engine's own random. The set
comes from assets/<namespace>/particles/<name>.json, the file vanilla's
SimpleParticleType reads; its textures resolve as particle/<texture> in the
atlas, the same rule vanilla's own particle JSONs use.
On the wire a mod particle is Horizon's own extension: the sentinel id
MOD_PARTICLE_ID (-1, never a vanilla registry id) followed by the string,
which vanilla's Particle codec has no room for. A client that does not know
the mod ignores it like any unknown particle, and native_send_particles
refuses an unnamespaced name.
native_send_particles keeps its signature; a mod builds the options itself,
so no hook point moved. NATIVE_HOOKS_API is 22 for the new capability and
docs/native-mods.md says what is supported.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
The dragon was drawn only while the player stood in the End, so a
dragon summoned in the Overworld (/summon ender_dragon, which the
server sends to the client as an AddEntity) was heard, flapping and
growling, but never shown. Vanilla's EnderDragonRenderer draws an
EnderDragon in any dimension like any other entity; the gate was
added with the fight, when the dragon only ever existed in the End.
The dragon fireballs share the same block and are now drawn in every
dimension too.
LevelTicks and ChunkTicks hash a (type, position) key at every schedule,
poll and dedup check, and the chunk map at every lookup, with std's SipHash.
They are only looked up, never iterated in order (the keys of an area are
sorted), so they now use the Fx hasher, which moves from
horizon-client-render to horizon-core (re-exported under the old path).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The server doc still listed bow PvP between guests as missing; it works since 5763854b0 (lan_e2e 'arrows'), and a dedicated server run with two protocol clients agrees.
🤖 Agent: #5786. Auto-jump now in ServerboundPlayerInputPacket as vanilla's keyPresses. Staircase walk with autoJump on, same server: 75 prediction flags with main's client, 0 with this one. pr_ready --quick all green on 7a8a97dd5.
LocalPlayer.sendChanges sends input.keyPresses, which an auto-jump's makeJump
sets to jump for that tick. Horizon sent the keys as held, so with Auto-Jump on
the server saw jumps its keys didn't allow: the anticheat's prediction flagged
every auto-jump and the 0.4 a tick it left (75 flags on a staircase walk, none
with this change). The official server's log has the same lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5725. One prediction flag per slip across instead of 10-20 (87% of live lines continue an earlier slip). Strict cheat tests unchanged; new tests for a missed push (counts once) and a kept-up 0.05 speed lie (35 of 40 ticks count). pr_ready --quick all green on 7a8a97dd5.
Leads dropped through break_leash without telling native_loot_drops,
so a mod couldn't see or change the lead a player gets back, nor the
one a leashed mob drops when a player kills it.
Untying a lead, cutting it with shears (on the mob or its fence
knot) or re-leashing a mob from another holder now passes it through
the hook as a use (break_leash_used: the leashed mob, the acting player).
A mob a player kills while leashed records its killer (lead_killers);
the corpse drops its lead on its next tick as vanilla does
(Leashable.tickLeash), as a call of its own naming that killer. A lead
that snaps or loses its holder names no player and drops as before.
The API number comes from the lead_drops fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
87% of the official server's prediction flags (6,002 of 6,900 lines on
2026-11-01) continue a divergence that started on an earlier tick: once the
replay's velocity is off (a hit it missed), the offset dies out by the air drag
(0.91) or ground friction each tick, and each of those ticks flagged again.
The first tick off after ten that agreed now has its follow-up tick try the
velocity the client was left with (across only, at most 0.5), as another
candidate. A single slip then counts once; a blink leaves the velocity alone; a
tick that agrees only thanks to it never becomes the setback point. Not up and
down: the vertical drag (0.98) would let a slow fly pass for a slip.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5775. Official server crash 05:15Z (spawn worker WouldBlock, no save). Worker spawn failure non-fatal; connection threads capped at max_players+64. New flood test fails without the cap, passes with it; pr_ready --quick green apart from one ENOSPC test failure that passes alone.
A dedicated server crashed on the official server (2026-11-01 05:15Z) when the
system refused a new thread: Streaming::new's .expect("spawn worker") panicked
and the process died without saving. Worker spawn failures are now logged and
the pool runs with the workers that started.
The server starts a thread per accepted socket (vanilla's Netty uses a few for
all): past max_players + 64 live connection threads, a new socket is closed at
once, so a flood of silent sockets can't use up a container's thread limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A section's first encoding unpacked its 4,096 block states, then built a
palette from them again (Paletted::from_values). For a list palette the
stored ids are now renumbered in first-seen order instead: the same palette,
indices and bytes, without the values. Global palettes keep the old path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The integrated server retained minecraft:unbreakable, but network_stack
never emitted its unit component. The client therefore still drew a bar
even with the rendering guard in the previous commit. Send UNBREAKABLE
as Component::Unit; the client decoder already preserves unit components.
Add a regression test through stack_to_network, Slot write/read and
from_network, checking both ordinary and unbreakable damaged hoes.
It fails without the encoder entry and passes with it. Verify the actual
server-thread client retains the component and loses only the unbreakable
hoe's bar in the hotbar screenshot.
Written with Claude Opus 5.5 in OpenCode.
tick_creature_entities takes the mob list, ticks it, and appended it back
into the emptied list: a new buffer and a copy of every Mob each tick (and
the old buffer freed). With no mob added meanwhile the list now goes back as
it is. The dead-mob set and the guest bindings, looked up per mob each tick
and never iterated, use FxHash instead of SipHash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: #5725 step 2. Guest item use (eating, bow, shield) now slows the replay as it slows the client. Vanilla 26.3 client walking/sprinting while drawing a bow 3 min: 503 prediction flags → 2 (0.045). pr_ready --quick all green on 3bc251ea5.
`native_mod_screen_open(mod_id)` opens a full-screen menu of a client mod's own over the world (`Screen::Mod`, its
`NativeScreenView.id` `mod`), `native_mod_screen_close` closes it, `native_mod_screen()` says whose is open, and Escape
closes it as any screen. It draws nothing itself: the screen hooks fire over it as over any other, so the mod draws it
in `native_screen_render_post` and takes its clicks, scroll, keys and typing from the mouse, scroll, key and
character hooks (#5543). Two drawing helpers beside `native_fill` for that: `native_quad` (a wedge or a slanted bar)
and `native_item_model` (an icon drawn with another `item_model`).
Ported from Stink-O/horizon ae531730c (`ui.show`) and 848a0686c, reshaped onto the screen hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An item in use (food, a bow, a shield) slows the client to its
use_effects speed (0.2 for most) and stops its sprint. The replay plays a
guest on the server's copy of its body, which takes the item use from
Game.sync_player_body, and that runs only for the window's own player: a
guest's copy never had one. So the replay walked a guest who was eating
or drawing a bow at full speed. That put it 0.8 of a walk (0.173 blocks a
tick) or of a sprint (0.224) off, every tick of it. 0.173 was the official
server's most common prediction flag.
guest_replay now sets the copy's item use speed and its no-sprint flag
from the item the guest is using, as it already did for the glider. A
vanilla 26.3 client sprinting and walking while drawing a bow for 3
minutes: 503 flags before, 2 after (0.045 blocks).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mimalloc backs the game's memory with transparent huge pages where Linux
allows them; many are only partly used. low_memory = true starts the game
with MIMALLOC_ALLOW_THP=0: 60 MB less idle and 190 MB less at 20 players,
for ~1.2 % more tick time (playhorizon, 20 bots, 5 rounds). Off by default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_loot_drops heard block breaks and mob deaths only, so a mod
couldn't tell, or change, what a player's shears, brush or hit
dropped, nor who caused it. An auto-pickup or a global loot modifier
needs those too.
The player-use drops now pass through it, with the acting player
named and the new NativeLootDrops::used set: shears on a sheep, a
mooshroom, a snow golem or a bogged; a brush on an armadillo or a
suspicious block; shears carving a pumpkin; picking sweet berries or
cave vines; and a player's hit taking an item frame or its item. One
helper, native_use_drops, builds the context. The berry and cave vine
drops are popped inside the block behaviour, so their run of spill
spawns is tagged and retold when the spill flushes (retell_runs),
keeping each stack's spot and the random draws when no mod changes it.
The API number comes from the use_drops fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemStack.isBarVisible is isDamaged, which needs isDamageableItem: a
max_damage and a damage, and no minecraft:unbreakable. draw_item_decorations
only looked at the item's max_damage and the stack's damage, so a damaged
unbreakable stack (/give @s diamond_hoe[damage=58,unbreakable={}]) showed a
green bar. It now asks stations::is_damageable, the port of
isDamageableItem the anvil and the grindstone use.
Fixes#5721
Written with Claude Opus 5.5 in OpenCode.
Block light has a brightness but no colour, so a pack can't make a soul torch blue. Section meshes now
list their light sources, and a 3D grid around the camera holds the colour reaching each cell, read
through block_light_color. Block light keeps its own brightness, which walls stop, so the grid only
tints it and needs no occlusion of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Since #5641 a saved snowball, egg or ender pearl loads as a live projectile,
but NoGravity was dropped: the scene's floating snowball and pearl fell into
the floor and were gone before the save. Entity.getGravity is 0 with
NoGravity; the projectile now reads it, honours it and writes it back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod could cancel an experience orb in native_entity_join_level but
not read its value first, nor give experience to anyone. An
auto-pickup or a machine that pays out experience needs both.
native_experience_orbs(dimension, min, max) reads the orbs in a box,
as native_item_entities does for items. native_give_experience(uuid,
points) gives points as an orb a player touches does: Mending repairs
first (repairPlayerItems), then giveExperiencePoints with the level-up
sound. It reaches any player from any server hook, through as_guest
for a guest, and refuses one swapped out by another player's hook.
native_give_levels(uuid, levels) adds levels or, negative, takes them
off (giveExperienceLevels), as the anvil and enchanting table charge.
Both go through one dispatch, native_as_player.
The API number comes from the give_experience fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Follows the .jem reader's review changes: files over the model's byte
budget are refused by size, and the reader's warnings reach the log.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: log-only detail for prediction lines (#5725 step 1); unit tests for the worst tick's offset/state; vanilla client on a dedicated server logs the new detail; pr_ready --quick all green on 21a12582c.
- Strings read or copied out of the files (part names, ids, animation
targets and expressions, axes, numbers written as text) cost one per
started 64 bytes, each time; animations pay theirs before the copy.
- A submodel costs its keys, a part the layers its keys go through.
- by_id no longer copies an id it already has.
- Tests: shared .jpm with a 1 MB expression and with 200k submodel keys,
the .jem's bytes in the byte budget, non-object animation items, junk
boxes that only exhaust when keys are charged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main rebuilds a heap of wanted tiles when the view moves and selects
incrementally. The backdrop becomes a class in `Wanted`'s order (seam,
backdrop, the rest), and `evaluate` keeps split tiles at the seam from
standing in, as `Plan::select` does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: launcher on GPUI Fast (Apache-2.0; fixes the NVIDIA/blade crash #3812); macOS text fixed (font-kit) and checked against main by screenshot; launcher tests + clippy pass on macOS; approved as code owner.
- pow follows Java's Math.pow: a NaN exponent is NaN, and 1 or -1 to an
infinite exponent is NaN; x to 0 stays 1, NaN included
- Test clamp with a NaN upper bound
- The too-long test checks the error lands inside the expression instead
of pinning an offset that depends on when nodes are counted
- Boundary test: a 512-term sum (1023 nodes) compiles and runs, 513 terms
are too long
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without the feature gpui-fast-platform has no font source on macOS: the
launcher drew no text at all and half its home page was blank. With it,
the home page matches main's GPUI build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A prediction line said only how far off a move was. On the official server
it logs about 15,000 lines an hour, from ordinary players (mostly 0.1 to
0.2 blocks) and cheaters (1 to 10 blocks) alike, and nothing in a line
tells a glide, water, a ladder or a lag from a cheat, so the check can't
be trusted to act.
Each line now ends with its worst tick as the replay played it: where the
client said it went less where the replay ended (x, y, z), how far the
client said it moved, and what the replayed body was doing (ground or air,
water, gliding, sprinting, sneaking, swimming, flying, riding). Only
flagged ticks pay for the text; the state is a few bits per played tick.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- An entry the budget cuts down to no quads is left out, not drawn empty
- Animations are all or nothing per entry, checked and charged before baking
- A box costs max(faces, keys, 1); non-object items of boxes, submodels and
animations cost 1, and so do non-scalar animation values
- MAX_BYTES (now 4 MiB, pub) bounds the .jem and all its .jpm files together;
a cached .jpm counts once
- Boxes whose corners or UVs overflow to non-finite numbers are skipped
- baseId dedup compares only against the entry's own .jpm layer
- Tests: a real lone-surrogate escape case, valid oversized JSON for the byte
limit, the shared byte budget, repeated keys, non-finite boxes, 1 unit left
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- clamp keeps a NaN value NaN, like Java's x < lo ? lo : Math.min(x, hi)
- round returns Java's int: NaN is 0, results saturate at i32::MIN/MAX
- calls take up to u16::MAX arguments, so long keyframe tracks compile
- tests: laziness values, an expression just under the node cap, the
offset of the too-long error, a 300-frame keyframe, clamp/round NaN
and infinities
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_push_player([x, y, z])` on a client, added to the local player's velocity as an explosion's knockback is (the window's player moves itself, as vanilla's client does), and `native_set_flying_speed(player, speed)` for server mods, a player's creative flying speed kept as vanilla keeps its abilities (a respawn or a game mode change keeps it). A test for the push. Ported from Stink-O/horizon 63ea5c582 and 848a0686c.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_get_clock()` gives a client mod the world's game time and time of day in ticks (none on the title screen), as `Level.getGameTime` and `getDayTime`: for anything timed to the day, the sun or the world's own clock. Ported from Stink-O/horizon's `game.get-clock`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A light job for a column of the last ring reads its neighbours past the
area as views of their decorated proto chunks. The server thread built
those views itself while it scheduled light (~8 columns per tick at 20
players). The proto chunk is now checked out to a worker, like a save,
which builds the view; the light job is scheduled once it is back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- keyframe: no usize overflow for infinite, huge or NaN k; past the end is the last frame
- parser caps nesting at 64 and nodes at 1024 ("expression too deep/long" with an offset),
so no pack can overflow the stack while parsing, folding or dropping
- programs deeper than the 64-value stack (long keyframe tracks) run on a per-thread buffer
- ifb returns 1/0; nbt(key, exists:false) compiles (raw test skipped, value 0)
- catch falls back on NaN only; min/max return NaN like Java's; round is Java's, exact
- unseeded random() starts each thread somewhere else; unary + parses
- !, && and || lower to comparisons and ifs (Not/And/Or/Bool gone); easings derived from
ease-in and moved with the splines to cem/curves.rs; Expr::slots removed
- bench prints CPU time; Error documented; tests for each of the above
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Past the node budget, later parts are left out instead of replacing their vanilla part
with an empty bone.
- Budget: every box costs at least one, the last box keeps the faces that fit, and the
"left out" warning fires only when something was cut (Bake { left, cut, unread }).
- Animation pairs count against the same budget.
- Layers carry a key index and a base's layers already present are not added twice.
- Per-part textures and sprites are noted in submodels too.
- JSON: a high surrogate before a non-low escape gives U+FFFD and the escape is read on its
own; one shared `get`.
- Tests for byte limits, a missing .jpm named twice, own keys over a .jpm's (also in a
base), the budget's edges, surrogates and comment marks in strings.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main. Fox trust list, save/load, breeding and egg-on-fox trust (rdbtCVS's fix, approved). pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
🤖 Agent: merged main. Canceled hits forget their attackers on every damage path (PvP, /damage, thorns, dragon, blast, potion); test. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
🤖 Agent: merged main. ResolvableProfile texture patch drawn for heads, read from the active resource root, reloaded on pack change. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
The fallback for roots no command Target stands for checked only orbs after
the canceled-orb fix; end crystals and cushions also take an id at once
(rdbtCVS's re-review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The native-mods PRs each add a section to docs/native-mods.md at the same
place; holding the one ahead lets the series merge in one pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
# crates/horizon-client/src/game/native_hooks.rs
# docs/native-mods.md
The native-mods PRs each add a section to docs/native-mods.md at the same
place; holding the one ahead lets the series merge in one pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
# crates/horizon-client/src/game/native_hooks.rs
# docs/native-mods.md
The native-mods PRs each add a section to docs/native-mods.md at the same
place; holding the one ahead lets the series merge in one pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts:
# docs/native-mods.md
🤖 Agent: merged main. BucketItem.emptyContents in the Nether: sendParticles(LARGE_SMOKE, corner, 8, 1,1,1, 0, ALTERNATIVE) now reaches clients; ALTERNATIVE spread added. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
🤖 Agent: merged main. MinecartSpawner: BaseSpawner ticks at the cart, saved/loaded, drops and picks as a minecart, entity event broadcast. pr_ready --quick on a union of #5414#5418#5433#5441#5442#5450 merged into main f99f8feaa: fmt, clippy -D warnings, 2403 client/render tests, 510 Python tests pass.
Second review round of the .jem reader:
- an entry's keys are layers (own, .jpm, baseId's) over shared objects,
so a big base or .jpm costs nothing per entry; the first entry with an
id is the one baseId finds
- the 1024-entry cap counts every entry, bones count against the face
budget, and a .jem or .jpm over 8 MiB is refused
- the JSON reader takes Gson's = and => for colons and ; between items,
quoted empty keys, and \u surrogate pairs; \u needs four hex digits
- per-part textures and sprites, not drawn yet, get one warning
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Windows a backslash separates folders, so a pack's ..\x would leave
the resource root once joined onto it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Include GitHub and local native bundles in both installed-mod views, retaining source/update actions and the existing build confirmation.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Review fixes for the .jem reader:
- a small JSON reader keeps keys in file order (animations read what
earlier ones wrote) and accepts what Gson does: a BOM, comments,
trailing commas, single quotes, bare keys
- an entry's own .jpm wins over what it inherits through baseId; each
.jpm is read once
- .jpm paths go through paths::resolve, which refuses ones that leave
the pack; a missing or broken .jpm, or a baseId naming nothing, leaves
that entry out with a warning instead of the whole model
- rotations through an odd number of inverted axes turn the right way
- numbers must be finite; attach and animation values may be strings or
numbers; a file builds at most 1024 entries and 65536 faces
- shadowSize, per-bone textures and attachments are no longer parsed
until something draws them
- CLAUDE.md lists the crate
Co-Authored-By: Claude Opus 5.5 <[email protected]>
shaders.properties' `vignette` key (on unless `false`) says whether the game draws its vignette
over the pack's image; most packs turn it off (BSL, Complementary, Bliss, Bloop, FastPBR,
Hysteria...) because they draw their own. Horizon ignored the key, so with the Vignette option on
those packs got both. The game's vignette now draws only when the option is on and the loaded
pack doesn't turn it off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ShelfRenderer passes its block entity as the ItemOwner (at the block's
centre, turned by its facing), so a compass on it points to its target;
without one it always points the same way. The client's copy of a block
entity has no item slots: the docs say to send the stacks drawn in the
update tag.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dropped_item_geometry resolved and meshed every item within 64 blocks,
even behind the camera. Apply EntityRenderer.shouldRender's frustum test
(box grown by 0.5) first, as mobs already do: ~0.2 ms -> ~0.01 ms per
frame with a few dozen items around.
draw_sections left a translucent sorted-index page bound, so the cloud
draw after it indexed past its end and wgpu aborted on the first frame.
Rebind the shared quad indices when a sorted page was used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
draw.keep() in native_block_entity_submit keeps what that call drew,
relative to the block as a sign's text is kept, and the block is drawn the
same again without calling the mod until its state, its block entity, its
light or the resource packs change. Idle machines then cost a copy moved to
the camera, not a call into the mod, each frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Enderman.teleport(x, y, z) fails for a passenger, repeatedlyTryToTeleport
returns before drawing, and EndermanLookForPlayerGoal.tick skips its
teleports while the enderman rides something. Horizon had none of these
checks, so an enderman in a boat "teleported" every tick (sound and
portal particles) and was snapped back to its seat, which also made its
held block jitter. Fixes#4865.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server's client keeps thrown tridents apart from its other projectiles,
so TakeItemEntity never found them and the trident just vanished instead
of playing vanilla's ItemPickupParticle. Look the trident up there too,
turn it into a pickup particle and draw it on its way to the collector.
The host also sent the pickup only for tridents that gave an item, so a
creative player's trident vanished on other clients; it now always sends
it, as Player.take does in AbstractArrow.playerTouch.
lan_e2e: trident_pickup throws a trident at each player's feet and checks
that it comes back and flies to them on both clients (fails before this).
Fixes#5577
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Java-format packs read both names as zero today. isElytraFlying is the
player's fall-flying state; cloudHeight is the cloud_height environment
attribute (default 192.33, dimension_type visual/cloud_height) when the
dimension's cloud colour alpha is above 0, as in vanilla's LevelExtractor,
and NaN otherwise ("no clouds"). The 192.33 literal of clouds.rs becomes
a shared constant.
Part of #3172.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
#5656's falling motion (-0.3015 against vanilla's -0.2277) is a tick of
gravity more, not a change in the game: main, and the exact tree CI
failed on (#5502's head merged into 76dc5e3bd), pass every time on a
quiet machine. On a loaded one the server catches up with two ticks for
one of the jump's moves (seen: "the server ran 2 ticks for move 7"), and
the server's copy of the player falls in the tick with no move. Before
the jab that changes the motion the jab sends.
LocatorBarClient's arc now records an error when that happens up to the
jab, so the failure says why, and lunge_motion joins known_flaky so the
runner gives it its one retry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The camera's motion vectors see mobs and dropped items as still, so a made frame
would show them where the real one does. The game now tags each mob's and
dropped item's vertices with its entity id (Frame::entity_motion); one whose
model has the same shape as last frame gets each vertex's own motion, drawn into
a texture at the render size where the world shows it (its depth, or a
translucent item's texture). The interpolation takes that texture as
object_motion and uses it over the camera's motion vectors. Nothing runs it yet:
the Frame Generation setting comes next.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Build button moved from Settings > Native mods to the Mods page, but the
message after installing a mod from GitHub still sent players to the old
setting (reported on Discord, 2026-11-01). Updated in English and the ten
translations.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mob_attributes asked the attribute catalog for movement speed (by name)
and recomputed its base value up to three times per mob and tick, on
top of the registry id built with format! per attribute (Attr::id, the
previous commit). The speed instance, the baby and the sprinting
modifiers now share one lookup.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The type's default of an attribute (base_value, read by mob movement and
every tick's entity tracking) and the LAN attribute sync built the id
"minecraft:<name>" with format! on each call: for a server's 2000 mobs that
is several allocations per mob per tick. Attr::id returns the id as a
static string.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep entity portal state with its level so another ticking dimension cannot
clear it. Handle falling-block portals after movement and impact damage but
before placement, preserving cooldowns, saved data and vanilla's End transfer
duplication branch. Extend integrated-server command parity with direct
falling-entity probes and remove its stale in-frame runner override.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Noise, lookup tables and the like had to be computed in WGSL. A pack can now ship up to eight PNGs,
which the check decodes and every stage reads as <id> and <id>_sampler.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A fill visits up to 32768 blocks of a handful of states. It now skips
blocks that are already the block it would place before matching the
filter (place_state would change nothing there), and matches each block
state against the filter once per fill instead of once per block.
Results, counts and side effects are unchanged. Ported from
Stink-O/horizon 242b45037 (its fill part, the same code).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Positional sounds leave 16 static channels free for sounds played to the
listener. Ported from Stink-O/horizon a72b65ea3 (sound_engine.rs, now in
horizon-client-render); that commit's other half, block destroy events
only within 64 blocks, is already here (lan/containers.rs level_event).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An item stack drawn under a block entity's pose as a campfire's, and a block
state's property (its facing) read without a world lookup. Helper-only, with a
fragment of its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: unit tests for the split choice and resplit; HORIZON_SPRITE_SPLIT=512 renders the same world as the default on the Mac mini; pr_ready --quick green. Merging after verify + parity.
Give the pause action its own translated Splitscreen Settings label. Check the transient test override through the visible saved switch rather than waiting for an untouched settings file to be written.
Co-Authored-By: OpenAI Codex <[email protected]>
🤖 Agent: merger STOP was a conflict with main after #5571 landed; main merged (clean). clippy -D warnings, the native hook tests (55) and rustfmt pass on it; the rest was proven in union3 with #5571.
A GPU with 512 layers per texture array refused to start ("GPU supports 512
texture layers; 1291 required") although terrain binds 8 sprite arrays. The
split is now the smallest of SPRITE_SPLIT, HORIZON_SPRITE_SPLIT and the
adapter's max_texture_array_layers (one less on OpenGL when a full array would
be padded past the limit), computed once when the renderer is made and used
by the sprite arrays, the shader constants and the shader pack atlas. Mip
chains prepared before the adapter was known are cut again for it. Startup
only fails when the sprites need more than 8 arrays of that split.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rebuild the profiles and saved switch increment on current main after the compositor, controller, windows and profile-page model landed. Preserve pause dialogs and keyboard navigation, keep test overrides transient, and validate profile folder names.
Co-Authored-By: OpenAI Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: vanilla's Pig/Strider getControllingPassenger use player.isHolding, which checks both hands; the server path (lan_vehicles → tick_mount_riding) shares this check. Main merged; its test passes. union3 = main ba8b5b414 + #5516#5535#5566#5571#5578#5573#5569: pr_ready --quick passes (python 511, rustfmt, workspace tests 2856, content root); clippy -D warnings clean after the #5573 borrow fix.
Vanilla 26.3's PhantomSweepAttackGoal.tick bites on a plain box check
(getBoundingBox().inflate(0.2).intersects(target)) with no line-of-sight
or block test, and its targeting gate (TargetingConditions' eye-ray) is
what keeps a pane-hidden player safe. Horizon ports both, so:
- a phantom pressed against the far side of a foot-high pane wall, whose
eye-ray passes over the wall, bites the player pressed behind it, in
Horizon exactly as in vanilla;
- behind a window-height pane wall the eye-ray is blocked and the
phantom never takes the player as a target, also as in vanilla.
Two parity cases pin both sides of that behaviour, mirroring the live
geometry (player and phantom each pressed against their face of the
pane, 0.125 apart, bite reach 0.2). Refs #4928.
🤖 Agent: Review points from some-du6e fixed and confirmed (seat_own_stands ordering, the cancel() test, port-ledger citations); local lan_e2e proof posted by the author's agent; CI green on 6f941a5e9. Queued for merge.
🤖 Agent: launcher account status, profile heads, refresh and targeted sign-in (Microsoft only); rdbtCVS's points fixed earlier; main merged here (lang union, both sides' account tests, a string main dropped removed). Proven: union of main 956e35469 with 19 PRs, launcher clippy + tests green (the i18n tests after the string fix), game clippy/tests green.
🤖 Agent: native loot table and drop hooks. Fixed here: NativeLootDrops.player only while the hurt memory lasts (dropFromLootTable's playerKilled). Proven: union of main 956e35469 with 19 PRs had this PR's code (clippy, workspace + content-root tests green); the author's later commits are docs, a test and rustfmt, and CI verify passes on this head.
🤖 Agent: native consume effect kinds; fixed here: an empty namespace is minecraft's. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native block contact / landing hooks; fixed here: no item/projectile sweep without mod blocks (hot path), vanilla stepOn-then-inside order, getOnPosLegacy. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native bone meal hooks; fixed here: Some(false) keeps the meal as growCrop does, None leaves the template's. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
"Microsoft account · signed in" came back with this branch's tables in the
main merge; the code lost it in 86149f7da (i18n tests).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: fence knot clicks and orb smoothing; fixed here: the orb draws its correction step (previous kept), knot hit only within entity range; main merged. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: #5521 by rdbtCVS finished (main merged: serialize keeps screenshotQuality and main's shadowCache). Screenshot Quality defaults to 1x, so at 1x and 100% scale F2 saves the window's frame as before; the scaled frame draws offscreen inside OOM/validation error scopes. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green (clippy -D warnings, workspace tests).
🤖 Agent: native block entity renderers; main merged here. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: OptiFine .properties reader (new crate, no dependents); fixed here: no .. paths, linear glob, no link loops; skip-release. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: parallel horizon-prepare (same output digest, release check covers both lane layouts); fixed here: the extraction test on case-insensitive disks. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native can_survive / may_place_on hooks; fixed here: a piston head's neighbour change asks can_survive. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: native advancement criteria triggers (+ the author's on_fire fix); main merged here. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: biome modifiers place mod features; fixed here: unread modifier types and unknown names are skipped with a warning instead of dropping every content mod. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 9 (on #3801, testing only); main carried up the stack. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 8 (on #3794, testing only); main carried up the stack. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 7 (on #3777, testing only); main carried up the stack. Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
🤖 Agent: splitscreen part 6 (HORIZON_SPLIT testing only, off by default); main merged here (main's headless loop runs before the split's event loop). Proven on a union of main 956e35469 with 19 open PRs including this head: pr_ready --quick green for it (clippy -D warnings incl. the server build, workspace tests, content-root tests, Python tests). CI parity covers the harnesses.
A native pack had no way to light or tint weather, so vanilla's streaks stood out against a pack's
own lighting. Horizon still places and animates the streaks; the pack only returns their colour.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
origin/main's identity hashes mod components' defaults so peers that
disagree are told apart; it read items.json raw, so a native mod's change
slipped past it. It reads them through item_defaults::read now. The read
and hook docs mention the stack-size range.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Server mods could only give plain items to the acting player
(native_give_items). An auto-pickup or a machine's output needs to hand
over the stack it has, with its damage, name and enchantments, from any
server hook. native_give_stack(uuid, stack) places it as Inventory.add
does and returns what did not fit.
Game::give's slot loop moves into inventory_add, shared by both, so
Inventory.add stays one port. The API number comes from the
give_stack fragment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
Each optifine/cem (or EMF's emf/cem) .jem replaces or joins the parts of
the model layer it names, with OptiFine's part names mapped to the game's
and baby models falling back to the adult's. Built when the packs load
(and on every pack reload, on the reload worker); a mob's draw only swaps
the layer's part tree and, when the .jem names one under textures/, its
texture.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: stop riding a remote vehicle that is gone (Entity.baseTick); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests). Stacked on #5630, tested together.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests). Stacked on #5630, tested together.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff. pr_ready --quick passed on main c4a65832e with this PR merged with 16 others from the same sweep (fmt, clippy -D warnings, horizon-client, -core and -render tests, Python tests). The image follows the text and the bundle component, the rule Horizon's bundle tooltip already uses; moving both under the title, as vanilla's index 1, is a separate parity fix.
🤖 Agent: tadpole AbstractFish goals and TadpoleAi brain, mob_ai scenarios; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: fancy leaves sorted front to back on the mesh workers (no render-thread cost); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: six-face panorama check in horizon-prepare check.rs, rustfmt fix; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
A pack's sky stage only replaced the dome: Minecraft's clouds, sun and stars still drew over it, so a
pack with its own had two of each. Each key defaults to true, so existing packs don't change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
🤖 Agent: reproduced #5352 with launcher 0.2.31 and Nightly d91cab6 in scratch homes; fixed launcher builds Zoom, Minimap, Recipes and X To Xray on d91cab6 (all ON in Options > Mods). pr_ready --quick green.
🤖 Agent: online_mode harness (finished from #5508): the services' key fetch stays out of the log. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: vaults' display item spins on remote clients (VaultBlockEntity.Client.tick); verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: a stack's own blocks_attacks component is read (Item.use, getItemBlockingWith); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
The clamp moves from the item table into item_defaults::read, after the
hook, so loot and item models read the same 1 to 99 (a negative size too).
The durability tooltip no longer underflows when a mod lowers max_damage
below a saved stack's damage (getDamageValue clamps to the max).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: enderman's carried block culls back faces (ITEM_CUTOUT); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head (pr_ready --quick green for it). Parity rerun: the failed run was only online_mode (harness bug, #5627) / dedicated_server (flake #5560).
🤖 Agent: backup before restoring level.dat_old, other-series worlds refused on every open path; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head (pr_ready --quick green for it). Parity rerun: the failed run was only online_mode (harness bug, #5627) / dedicated_server (flake #5560).
🤖 Agent: reconnect decided by the disconnect's translation key; author's main merge leaves the diff unchanged; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head (pr_ready --quick green for it). Parity rerun: the failed run was only online_mode (harness bug, #5627) / dedicated_server (flake #5560).
🤖 Agent: 16 port-map rows say port; both review points fixed; data only. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: content mods declare thrown entity types (snowball template); rdbtCVS found no defects; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: server tag hook fragment fixed for the stamp tool (no game code); verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: lan_e2e punches vehicles fully charged so they break; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: horizon-mixins pin a2af5c0 (only horizon-mixins#15); approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: keybind-opened screens release the item use in the same tick (Gui.setScreen releaseAll), with the use-screen e2e scenario; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: the AppImage path test runs on Unix only (fixes the Windows launcher build); code-owner path, approved here. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: superflat structure sets (FlatLevelSource.createState) with every review point fixed (ring seed 0, sea level/min Y, null layers, no creature packs, generator min Y); verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: launcher strings through tr()/trf(), Turkish table completed; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: verified by Ludovic's AI agent (Claude). pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others: launcher fmt, clippy -D warnings, and launcher tests (one failure in backend::tests::prepared_versions_get_what_the_asset_store_adds_without_preparing_again was a prepare-lock race; it passes alone with the account tests). The owner gate runs as that signed-in account; no offline name. I fixed the review's doc nit.
🤖 Agent: boats in bubble columns as vanilla, countdown test added by rdbtCVS; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: opt-in shader-pack shadow cache (off by default), rdbtCVS's custom-image redraw fix read; approved; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
🤖 Agent: VRAM arena compaction, rdbtCVS's last fix (LOD stand-ins off the closing page) read; approved by rdbtCVS; verify green. Proven on a union of main f99f8feaa with 37 open PRs including this head: pr_ready --quick green for it (fmt, clippy -D warnings incl. the server build, 3077 workspace tests, 20 content-root tests, launcher clippy + 340 tests, 510 Python tests; the one failure is #5570's own new test, not in this PR). CI parity covers the harnesses.
A pack with dozens of options needs them in categories. The first page is the one the button opens,
"page:<id>" opens another, "" leaves a slot empty and columns = 1 gives full-width buttons. Every page
has to be reached from the first and every option has to be on a page, or the check refuses the pack.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
The skip compared Paths, which compare case-sensitively, so it never fired
and the extraction test failed on macOS's case-insensitive file system (the
later member, tag.json, wins there). It now checks whether a case-folded
path exists.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each copying, hashing or compiling step spreads over that many threads; the lanes run theirs side by side, so it is no total limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The objects check summoned its stands on the floor, where a guest that
never moved a remote stand still matched the host. This one starts 3
blocks up and turned to 200 degrees, so where it lands and its yaw are
compared between host and guest.
Written with Claude Opus 5.5 in OpenCode.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests). I set vanilla's forced-translucent colour (alpha 38).
The CEM expression language from OptiFine's cem_animation.txt with EMF's
documented additions (keyframe, keyframeloop, easings, splines, wrapdeg,
degdiff, catch, randomb, global constants e and nan), compiled to a flat
stack program with constants folded; if, && and || only evaluate the
branch they take. Variables are caller-resolved slots. The example
cem_expr_bench times an animation pack's frame of 20 expressions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests). I stopped the narrator on headless exit and moved idle's doc back.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests). I added the server-only gate for the stuck push and one shared moveTowardsClosestSpace.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests). I made the answer carry its level event, added API markers, and the stamp tool now keeps side.
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests).
🤖 Agent: verified by Ludovic's AI agent (Claude). I read the diff against vanilla 26.3. pr_ready --quick passed on main bfc2a2798 with this PR merged with 17 others from the same sweep (fmt, clippy -D warnings, horizon-client and horizon-client-render tests, Python tests). Ctrl+Q on the Survival Inventory tab uses the item's max stack size, like every other Horizon menu (none reads a max_stack_size component yet).
A mod setting minecraft:max_stack_size to 0 made merchant.rs clamp(1, 0)
panic on the server thread. Items now keeps it to ExtraCodecs.intRange(1, 99),
and the docs say what else a malformed value does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A rebuild stays on the Horizon its native build was made on, and mods pinning an
older commit (eight registry mods pin 142388e) set the build's revision too. When
a mod needs newer hook points than that revision has (recipes v2, xtoxray v7),
and the hook mods were tested on different commits, the build was refused with
"Horizon 142388e doesn't have the native hook points these mods use", even
though the installed Nightly has them all and no Update button was offered
(#5352). The build now moves to the revision of the release or Nightly it
starts from when that one has them; pinned mods then wait for Build anyway,
as for any version mismatch. The refusal names only the mods that don't fit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fix for the secrets file (#5095): when the keychain took a
secret but another process had written a newer copy to the file
meanwhile, the newer copy rightly stayed, yet the run stopped counting
it as seen in the file. Once that run stopped asking the keychain and
another process handed the copy over, the run couldn't read it.
The clean-up now says whether the file still has the entry, and the run
keeps counting it while it does.
Test: a launcher on the file renews A during a start's hand-over of A,
the keychain refuses B; after another start hands both over, the first
start reads the renewed A and B.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
ArmorStand.interactAt ignored `DisabledSlots` (not even read or saved), so
locked stands could be emptied and filled. The stand now keeps the bitmask:
bit `filterFlag` disables a slot (an empty hand then takes from the main
hand), `+8` stops taking from it and `+16` putting into it
(`isDisabled`, `swapItem`), and the save writes it back.
A lead tied to another player was untied by anyone who clicked the mob;
only the player it is tied to unties it (`getLeashHolder() == player`).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Main samples only the planned tiles, nearest first: the seam with the
near chunks is fine at once, but the far terrain ends in a hard edge
until the last of ~3000 tiles (Full) is in, minutes on a slow machine.
After the seam, sample a coarse backdrop first: each far tile's
ancestor two levels up (one where that reaches the seam), ~230 tiles,
about a tenth more work. Ready ancestors already stand in for missing
children; now no tile stands in within 3 chunks of the render distance,
so broad boxes never sit beside the near chunks. The horizon is covered
after under 1000 jobs instead of the last one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.dropFromLootTable supplies LAST_DAMAGE_PLAYER only when
playerKilled (lastHurtByPlayerMemoryTime > 0), so NativeLootDrops.player is
None for a mob that dies to the environment after the memory ran out
(arismoko's review). Vanilla's own loot is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- swept_inside returns at once when no loaded block is outside minecraft:,
so a server's items and projectiles no longer sweep their blocks every tick
with no content mod (rdbtCVS's hot-path note).
- An item's step_on comes before its inside contacts, as
Entity.applyEffectsFromBlocks calls stepOn before checkInsideBlocks.
- A mob's step_on (and the magma floor, turtle egg checks with it) reads
getOnPosLegacy through block_on_legacy, so a mob over the edge of its
supporting block still steps on it (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GameRenderer.extract only renders the level when there is one, so vanilla
never draws the debug crosshair on the title screen. The F3 overlay stays
on when a world closes, and the crosshair was built without looking at
whether the frame showed a level, so its axes stayed over the panorama.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mob equipment was an item id plus enchantments and a trim, so a picked-up
sword or armour piece lost its damage, name and components, a hand took one
item of a stack, a worn piece always compared as fresh, and death or
replacement dropped it repaired and unnamed.
`Gear.stacks` (the main hand keeps `main_hand_stack`) now holds the stack of
a slot that is more than one plain item. `set_item_stack` stores it,
`stored_stack`/`item_stack` read it back, replacing the item forgets it.
`equipItemIfPossible` takes the whole stack into a hand (one into an armour
slot) and drops the replaced stack as it was; `canReplaceCurrentItem` sees
the worn piece's damage and name; death drops the stack itself
(`Drop::Stack`), with the rolled damage when the drop is not preserved; the
save writes and reads it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The summary charged a group with every mismatch whose command text also
appears in it, so two groups that probe the same thing (the orb groups'
`data get entity @e[type=minecraft:experience_orb,...]` lines) each
showed the other's failures. Positions are what the per-group ranges
already are; count by those.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Freeze flight-history interpolation after death and render death rays with
vanilla's depth writes, back-face culling and distance fog. Add a frozen-pose
regression and a vanilla oracle comparison for ray geometry and colours.
Co-Authored-By: Codex (GPT-6) <[email protected]>
The review of #5601 noted the new "experience orb fall" group never
reaches the one path of ExperienceOrb.tick that draws a random: the
push out of a block. This group pins it — a 3x3 block placed over a
settled orb leaves it colliding with only its top face free, so the
server nudges it up (Entity.moveTowardsClosestSpace) with gravity
skipped while its box is inside blocks, and it comes to rest on top of
the block. The draw's magnitude only decides how many ticks the escape
takes, so the checkpoints after it are the same either way; with the
push disabled the orb stays boxed in at y 215 and the group fails.
Corpus with both orb groups: 973/973 commands match.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
A modifier of a type not read yet (add_spawns, add_carvers, a mod's own
serializer) or naming a biome, feature or tag the root doesn't have (another
mod's ids, #c: convention tags) dropped every content mod. Real mods ship
those beside their feature modifiers, so that modifier is now skipped with a
warning naming the mod and file, as NeoForge skips an unresolvable holder
set (reservedbytes's review). A malformed modifier of a read type (unknown
step) still stops the build. The docs say #tag in features reads vanilla's
feature tags only for now (arismoko's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fix for the secrets file (#5095): a start counted what its failed
hand-over left in the file only afterwards, so a hand-over by another
process in between left it reporting the keychain away for those
accounts. The start now counts the file's keys first, and drops each
one it hands over itself.
Test: another process hands the account over right as this start's
hand-over fails; the start still reads it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
The whole matrix (7 mounts x 2 levels x 2 players + in_nether) takes about 10
minutes, and the nightly LAN suite already runs ~2,400 s of its 3,000 s
timeout (rdbtCVS's review). The default keeps the regression signal;
LAN_E2E_MOUNTS=all and LAN_E2E_LEVELS=all run everything.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Entity.commonTick sets the old position before the interpolation step, so
the frame lerps from the pre-step position: previous keeps it now, where
ExperienceOrb::tick had overwritten it and the orb still hopped (rdbtCVS).
- The knot is an entity, so a click counts as one on it only within the
entity interaction range (filterHitResult), not the fence's block range
(Joansitoh).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rdbtCVS's review points 1-3:
- resolve returns None for a path with a .., . or empty segment, so a pack
can never name a file outside the overlay root (vanilla refuses them).
- glob backtracks only to the last star: O(pattern x name), where every
star tried every split before (a player-chosen item name, per frame).
- properties_files reads each real folder once and at most 16 deep, so a
folder pack linking to its own parent no longer recurses to PATH_MAX.
Tests for each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
winit's MouseButton::Other(n) is the raw X11/evdev number on Linux, not GLFW's
index, so the hook docs and the fragment say a button past 4 is
platform-defined (rdbtCVS's review).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The player-file save in `save_game_metadata` only ran for a dedicated-mode
integrated server (the fixture), so a real `--dedicated` server (the
`DedicatedServer` role) skipped it on autosave and `save-all`: players were
only written when they left or the server stopped. It now runs for every
server role, as PlayerList.saveAll does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Identifier.bySeparator reads ":clear_all_effects" as minecraft:clear_all_effects
(Consumable.CODEC decodes both), so it is vanilla's clear, not a mod kind for
the native hook (arismoko's review). Test added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A chest boat or chest/hopper minecart open in a menu keeps its slots in a
stand-in under a placeholder key. Removing the vehicle (broken, killed,
exploded, a rider's death, a portal) dropped the vehicle's stale items and
left the stand-in with a full copy a stale menu could still take from.
`release_vehicle_container` writes the menu's live slots back to the vehicle
first (so they are what drops or travels), then removes the stand-in, before
each removal.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
DirectionalPlaceContext.getRotation is get2DDataValue() * 90, so a vertical
facing gives -90, not toYRot's 270. Both name East for vanilla's blocks
(from_y_rot, rotation segments), but the placement hook shows the raw value
(arismoko's review on #5516). Armor stands and boats keep toYRot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- native_dispense_item answers (remainder, event): 1000 click, 1001 fail
(OptionalDispenseItemBehavior without success), 0 nothing (NOOP), so a
mod can make a silent or failing behaviour.
- native_dispense_item and native_dispense_default carry the
API @API:dispenser_behaviour@ marker; the fragment lists dispenser.rs.
- stamp_native_hooks.py copies a hook's neoforge, side and cancellable keys
into native-hooks.json instead of dropping them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A menu was only validated once per tick, after the packet phase, so a click
could still land on a vehicle that was gone, a mount that died or left reach,
a lectern out of reach or a station block that was replaced. The click
packets (container click, button, slot state, rename, beacon, trade select,
bundle select, place recipe) now run one shared check first: the open menu,
whichever kind, must still be valid, else it closes like ContainerClose and
the click lands on nothing. The per-tick checks use the same predicates.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Leaving the server (a disconnect, a LAN close, a dedicated stop) kept the
guest's cursor stack, trade payment, grid and station inputs in the menu, a
trade unwritten on the villager and a vehicle's stand-in in the level.
`guest_close_all` runs the same close code as ContainerClose for every kind
of menu, in the guest's own level, and runs from `HostEvent::Left` and
`save_leaving_guests` (LAN close, dedicated stop, integrated stop) before the
playerdata is written.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review fix for the secrets file (#5095): a launcher that started while
the keychain was away, on a file a previous run left, asked the
keychain again only for secrets it had written itself. Once another
process handed the file over, it reported the keychain away for
accounts the keychain now held.
It now also counts what it read from the file and what its own start
left there. Test: two launchers started on an old file while the
keychain is away (one read the account, one didn't) both read it after
a third hands it over.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
native_is_valid_bonemeal_target answers Option<bool>: Some(false) refuses
the meal even where the block's template takes it (BoneMealItem.growCrop
returns false when isValidBonemealTarget is false), None leaves the
template's BonemealableBlock (arismoko's review). The client prediction
falls through to growWaterPlant on a refusal, as vanilla's useOn does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each opener closed a different subset of the menus the guest could have
open (the trading screen left a mount or crafting state behind, a vehicle's
stand-in stayed, the old window id still took clicks). They now all start
with `guest_close_all`, as `ServerPlayer.openMenu` closes the open menu:
containers, vehicles, mounts, merchants, stations, lecterns, the crafting
table and native menus.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
position_riders ran before the player moved and before previous_position
was saved, so a name hologram on the local player was drawn a full tick
behind it. Vanilla ticks the player (tickNonPassenger) before its
passengers (positionRider), so seat_own_stands now runs after the move,
from the previous_position just saved to the moved position.
The snap test now walks toward x 5 when the far sync comes and checks no
step back toward it is left, so it fails if a snap stops clearing the
handler. The ledger citations move off the first doc lines they were
read as ports from.
Written with Claude Opus 5.5 in OpenCode.
Leaving the server (a disconnect, a LAN close, a dedicated stop) kept the
guest's cursor stack, trade payment, grid and station inputs in the menu, a
trade unwritten on the villager and a vehicle's stand-in in the level.
`guest_close_all` runs the same close code as ContainerClose for every kind
of menu, in the guest's own level, and runs from `HostEvent::Left` and
`save_leaving_guests` (LAN close, dedicated stop, integrated stop) before the
playerdata is written.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: proven on 8e5ab9ec6: pr_ready --quick green (fmt, clippy -D warnings, 1577 horizon-client tests, 510 Python tests); playhorizon A/B 3 alternated rounds 20 bots: streaming span 4.82 → 4.64 ms median (lower in every round), MSPT within noise. The 34 affected harnesses run in CI (run-parity).
Leaving the server (a disconnect, a LAN close, a dedicated stop) kept the
guest's cursor stack, trade payment, grid and station inputs in the menu, a
trade unwritten on the villager and a vehicle's stand-in in the level.
`guest_close_all` runs the same close code as ContainerClose for every kind
of menu, in the guest's own level, and runs from `HostEvent::Left` and
`save_leaving_guests` (LAN close, dedicated stop, integrated stop) before the
playerdata is written.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PistonHeadBlock.neighborChanged calls state.canSurvive; the direct
piston_head_can_survive call skipped native_can_survive (arismoko's review).
can_survive keeps the piston rule as its default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: native_explode: server-only wrapper over server_explode with the TNT/anchor drop-decay rules, guarded on dimension, loaded chunk and radius; verify and parity green on this head.
ExperienceOrb.tick gates the stuck push with !level().isClientSide(): a
server client (remote.rs) no longer nudges stuck orbs and draws from its own
random between server updates. Items and orbs share one
towards_closest_space port instead of two copies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
stamp_matches compared a missing outputs key with the digest of a missing output as None == None. The docs and comments said HORIZON_PREPARE_JOBS is how many things run at once; it is how many threads copy and hash, the lanes all run at once unless it is 1, and it counts logical processors. With the split there are five lanes. java.rs's memory figure now says what it compared.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A held two-sided model carries a quad per direction (a calibrated sculk
sensor's tendrils and amethyst planes, big dripleaf, block/cross), and the
item render types vanilla picks in ItemBlockRenderTypes -- solidBlockSheet
(entitySolid), cutoutBlockSheet (entityCutout), translucentItemSheet
(entityTranslucentCull) -- all keep the composite state's CULL. Horizon
culled only quads whose sprite has translucent pixels, so held blocks drew
both sides: the coplanar pairs striped (#4856, #4855) and a hay block's far
faces bled through the near ones (#4801).
Two-sidedness in vanilla is geometry, not culling: ItemModelGenerator's
bakeExtrudedSprite bakes a South and a North quad per layer, which
horizon-assets already mirrors.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
The namespaced-id check is Identifier's character rules, five lines; the
dependency pulled horizon-world (and horizon-core, postcard...) into
horizon-mods, the mod registry and the launcher builds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The owner loads the file level.dat names, but when it left (save_guest,
PlayerList.remove) only its own file was written; the named one waited for
the world's save that follows (stop_integrated: free_player_beds, the
chunks, then save_game_metadata). A game stopped or failing in between
left the named file older than the own one, and the owner then loaded the
older state. `playerdata::save_owner` writes the named file
(`Level::write_player`, also the player half of `Level::write`) before the
own one, so the file the owner loads is never the older.
`playerdata::load` (PlayerDataStorage.load) now falls back to the player's
`.dat_old`, as vanilla does: `write_dat` moves the old file aside before it
renames the new one in, so a save cut off there leaves only the
`.dat_old`, which the named file's read already used.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlayerList.loadPlayerData gives the single-player owner the file level.dat
names (`singleplayer_uuid`), not their own. Since #5069 the integrated
server saves the owner to both files, `players/data/<singleplayer_uuid>.dat`
(offline `Player`) and `<owner uuid>.dat`, but loaded the owner's own file
first. A build that saves the owner to the named file alone (0.2.93 and the
stable releases before #5069) leaves the own file behind, so after
switching back the owner got an older inventory and position, and the next
save wrote them over the newer ones.
`Level::read` now says whether its player came from the named file, and
`playerdata::load_owner` applies it before the owner's own file; the own
file stays the fallback when the named file is missing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both the vanilla and the Horizon server fetch getPublicKeys in the background, at any moment of a
scenario, so ('unknown', '/publickeys') turned up in four checks (parity run 41243825430 on #5508).
It now stays out of the log, like a bare GET /.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes for the secrets file (#5095):
- A start's hand-over wrote a snapshot of the file to the keychain, so
a token another process signed out or renewed meanwhile could come
back or be overwritten. Every keychain write now holds
account-secrets.keychain.lock, and the hand-over reads each entry
again under it. The file's own lock still never waits on a prompt.
- MCP secrets_file is true only while the file holds a sign-in: not
for sign-out marks alone, nor for a file that can't be read.
- Tests: a hand-over held on a prompt while a running launcher signs
out or renews the same account; what counts as a sign-in in the file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
Parses OptiFine's custom entity model files: models, part, attach, scale,
baseId, .jpm part files, invertAxis, translate, rotate, mirrorTexture,
textureSize, texture, boxes (textureOffset box UVs or per-face uvNorth..
with their aliases, sizeAdd, sizesAdd, EMF's sizeAddX/Y/Z), submodel(s),
attachments and the animation expressions as text. Boxes bake into the
quads ModelPart draws, on vanilla's axes around the vanilla part's pivot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The repeat run's file check skipped horizon-resources.json, so a manifest the repeat rewrote differently passed. The comments that said at most three JVMs run at once now say four with the second server JVM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Horizon pack declares options, but they stayed at their defaults: the player had no way to set them.
The picked pack now gets the same Shader Options... button as a shaders/ pack, and its values live in
shaderpacks/<pack>.txt, the file that kind already uses. A saved value counts as a change to the pack,
so it reloads with it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
player_entity left on_fire false, so a criterion's player predicate with
flags.is_on_fire true never matched, a mod's trigger (the reviewer's
case) or a vanilla one. It is Entity.isOnFire on a server now:
remainingFireTicks above 0 (players aren't fire immune).
Written with Claude Opus 5.5 in OpenCode.
Review fix for the secrets file (#5095): Layered asked the keychain
while holding account-secrets.lock, so an unlock prompt held up every
other launcher and CLI process.
- Each call reads its file entry under the lock, asks the keychain
with the lock released, then removes the entry only if it still
holds what was read: another process's newer write stays. The start's
hand-over works the same way, from a snapshot.
- Once the keychain refuses or doesn't answer (a dismissed prompt),
the run stops asking it and parks new secrets in the file, until it
finds that another process handed the keychain one it parked.
- Tests: a keychain waiting on a prompt while another process reads and
writes the file, a dismissed prompt asked once a run, and a half
refused hand-over followed by a sign-out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
🤖 Agent: fixes#5572. ground never sends back and a dropped claim never feeds the floating kick. anticheat_e2e ground (allow-flight off) passes; vanilla 26.3 client soak 25 min never flagged; vanilla client at the live-flagged spots on the server's seed 42 min / 7 spots, 0 flags (18 min of it on this branch, ground acting). pr_ready --quick all green on main 74698584e.
player_model_shown() no longer blocks spectators, so their model
geometry generates in third person. The render path uses render_with
so the poses' visibility hides body parts, leaving only the head/hat.
body_color returns ~12.5% alpha for spectator players; OIT marks the
body already, so the ghost head looks ghostly.
Co-Authored-By: Hermes Agent <combo1> <[email protected]>
Clarify that only physical_keys is cleared directly in heal_hold_latches, leaving logical sneak/sprint keys to the toggle loop.
Co-Authored-By: Antigravity <[email protected]>
When ground acted, a false claim sent the player back to where it was:
into the air it claimed to stand in. A player there claims it again, is
sent back again (+0.5 VL twice a second at the same spot) and hovers until
vanilla's "floating too long" kick: in 11 minutes on the official server,
four players got it 20 times. Only the anticheat's own kick (logged, told
to the operators, counted toward the ban) should end a false claim.
ground's correction is now the dropped claim alone (vanilla's fall damage
follows), logged as "claim dropped"; it never sends the player back, and a
move whose claim was dropped never counts toward clientIsFloating.
setback = 0 still makes the check log only. The ground e2e runs with
allow-flight off and fails on a setback or a floating kick.
Fixes#5572
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pack treats hundreds of kinds of surface differently, where [[tags]] hold 32 sets. Each sprite takes
the id of the first [[materials]] entry that names it, as MaterialInput.material, through the same
generated switch as the tags. [shadow] solid_materials draws those ids into the map without their
cutouts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Review fixes for the secrets file (#5095):
- The Tailscale access token administers the player's whole tailnet:
it goes through account::keychain(), the keychain or memory for the
run, never the secrets file.
- MCP account_list says secrets_file: true while sign-ins sit in the
file, beside keychain (sign-ins outlast the launcher).
- launcher/README.md and the sign-out help (docs/launcher-commands.md,
regenerated) no longer say tokens are never in a file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
self.keys tracks the logical gameplay keys, and sync_toggle(0) uses KeyCode::ShiftLeft to represent active sneak regardless of the physical key binding. Calling self.keys.remove(&key) when physical Shift is up cleared active sneak every frame for players who rebound sneak away from Shift (e.g. to Left Control or C).
Fixes#5608
Co-Authored-By: Antigravity <[email protected]>
Review fixes for the secrets file (#5095):
- No store is picked once per run any more: on Linux and FreeBSD every
read takes the file's copy first and the keychain's otherwise, and
every write goes to the keychain, to the file only when it refuses.
A launcher that wrote to the file keeps reading, renewing and signing
out after another process hands the file to the keychain.
- Each start hands the keychain the file's secrets one at a time, each
leaving the file once the keychain took it, so a move it half refuses
never leaves a secret in both places, and signing out removes it.
- Signing out while the keychain is away leaves an empty entry that
hides the keychain's copy until it can be deleted.
- macOS and Windows keep the keychain or memory, as before.
- The concurrency test now runs real child processes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
RenderPipelines.TEXT_POLYGON_OFFSET and TEXT_SEE_THROUGH (and the
TEXT_SNIPPET they build on) never call withCull, so
RenderPipeline.Builder.build keeps its default, cull.orElse(true). Horizon
drew both with cull_mode None, so a text display showed its mirrored
text from behind (#3833).
Culling alone would have hidden the readable side instead: the text
display quads were taken in the wrong corner order and wound clockwise
toward the reader. They now use Font's glyph order (top left, bottom
left, bottom right, top right), the one name tags already use, through
text_display::CORNERS. A test projects fixed and billboard displays
through the real view and projection matrices and checks that the side
the text reads from is the counter-clockwise one.
The improved-transparency text pipeline is culled the same way.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: players can /msg, /tell and /w a player by their nickname (shown as ~name); server guests included. SMP proof and pr_ready --quick passed on this head (comment above).
A finished column at the edge of the generated area is lit against its
decorated proto neighbours, each copied into a column on the server
thread (light_view). The light engine reads only their blocks, yet the
copy also rebuilt every section's biomes (64 biome lookups and a
palette per section), the scheduled ticks, post-processing marks and
structure references. The view now copies the blocks alone; the final
column (finish) still gets everything.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes for the secrets file (#5095):
- Every read-modify-replace holds account-secrets.lock (std File::lock),
so launcher and CLI processes signing in or out at once no longer
overwrite each other; each write uses a temporary file of its own.
- The file no longer sticks once made: at the first start the keychain
answers, its secrets move into the keychain and the file is removed,
so an account kept in the keychain before a start it missed comes
back beside the ones signed in meanwhile. If the keychain refuses any,
the file stays whole and in use.
- A broken file's error names only the error kind, line and column:
serde's own message can quote a string the file holds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
LivingEntity.applyItemBlocking passes `damageBlocked >= damage` to
blockUsingItem with the damage Player.hurtServer already scaled by the
difficulty; Horizon compared against the raw damage, so on Easy a full
block counted as partial and blockedByItem's knockback pulled the
defender toward the mob. The hurt now records whether it was fully
blocked and the shield reads that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.setRemoved makes a removed vehicle's passengers stopRiding, and
LivingEntity.dismountVehicle puts them at the higher of their own and
the vehicle's heights, at the free spot nearest there (findFreePosition).
break_cushion only cleared the riding id, and a server-thread client kept
riding a cushion that was gone, so the rider stayed at the seat, 0.35
below the cushion, and dropped into the block under it.
Both the server (break_cushion) and the client (the cushion's removal)
now dismount the rider that way, with no get-up sound
(Cushion.removePassenger plays none once the cushion is removed).
Fixes#4571
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_data_pack_files asks a native mod, while the server assembles its data
tables, for the pack files it contributes: (path, bytes) in the overlay's form.
It fires once per load — when a world opens, and again on every `/reload` — so
the ask is the mod's chance to recompute them from its config or from the
world. A mod that turns configuration into recipes, tags or loot needs no
content mod beside it and no data pack installed by hand: today neither can
recompute those files for a load.
The files join the load where the world's packs already do:
Game::build_world_tables is both a world's first open and reload_world_tables
(`/reload`, `/datapack`), and the hook is asked there and nowhere else. They
sit below those packs: PackOverlay::top is a path's last file, so a player's
data pack still overrides a mod's for the same path, and a later contribution
overrides an earlier one, as packs layer. The advancement prefetch takes the
same files, so a contributed advancement is there on the first open and not
only after a reload.
With no mod hooking it nothing is contributed: a world with no packs still
uses Resources.rest, and a world with packs builds exactly the tables it did
before.
The hook is API next (tools/native-hook-fragments/data_pack_files.json);
native-hooks.json and NATIVE_HOOKS_API are untouched.
Co-Authored-By: DeepSeek <[email protected]>
The local player's model isn't drawn in first person, so a native pack's shadow pass gets it on its
own through the frame's shadow_only quads. The round blob under entities is left out while a pack
draws a shadow map, since the sun's shadow is already there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Vanilla's first ServerEntity.sendDirtyEntityData for a player runs before
its first ServerPlayer.tick, whose updatePlayerAttributes (and doTick's
removeFrost) create the interaction range and movement speed instances.
Those go out on the next tick, after the first chunk batch. Horizon's guest
tick created them before sync_player_state sent them, so a vanilla client
got a default-valued update_attributes before the first chunk batch.
Fixes#5560
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A surface wgpu never configured has no error sink, so acquiring from it
panics instead of reporting Validation (#5353). recreate_surface now checks
configure through error scopes and a device-lost flag, and frames configure
such a surface again before acquiring, skipping until it takes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityHitboxDebugRenderer.emitGizmos draws every visible entity's box,
the Interaction too (its renderer is a NoopRenderer, the hitbox is not
the renderer's). Its box is EntityDimensions.scalable(width, height):
eyes at 0.85 of the height, not a LivingEntity.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Native mod builds only showed in the version picker, so there was no way to delete one by
hand. The Versions page now lists them above the releases, with what each was built on, its
size on disk, and the same Use and Remove (asked twice) as a release. Removing a version a
program still runs from is refused.
Fixes#5326
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LoadingOverlay.extractRenderState blits textures/gui/title/mojangstudios.png
as two halves side by side over BRAND_BACKGROUND and draws its bordered bar
at 83.25% of the height; Horizon only filled the background. The logo comes
from the player's own prepared resources (the GUI atlas already packs it).
Fixes#5115
Co-Authored-By: Claude Opus 5.5 <[email protected]>
remote_open_merchant left the menu title unset, so NativeScreenView.title was
None on that window although the docs promise the server's title.
Co-Authored-By: Claude Code <[email protected]>
Adds screen_key_pressed_pre and screen_char_typed_pre (ScreenEvent.KeyPressed.Pre
and CharacterTyped.Pre) on the open screen, fired from the window's key event,
the control socket's key and type commands, and the input method's commit.
Co-Authored-By: DeepSeek V4.1 Flash <[email protected]>
Co-Authored-By: Claude Code <[email protected]>
The server simulated its players' bodies with no world border (only the
window's own player got one, in player_border), so a server player's
re-simulated movement went through the border's walls. enter_guest now
gives the guest's body the level's border each turn, as vanilla's
Entity.collide collides every player with the level's WorldBorder.
Fixes#2743
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Many Linux systems run no Secret Service (GNOME Keyring, KWallet), and
the launcher then kept sign-ins in memory only: players signed in again
at every start (#5095).
Where the keychain doesn't answer, the secrets now go to
account-secrets.json in the launcher's data folder, readable by the
player only (0600 on Unix) and replaced whole through a temporary file.
The keychain stays first; a secrets file from an earlier start stays in
use so a flaky keychain doesn't sign players in and out, and signing the
last account out removes it. HORIZON_NO_KEYCHAIN still keeps secrets in
memory for the launcher's tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DG1mYGWXNxQzmhMnjB36p1
The renderer's opaque entity quads now go into shadow_map through the shadow camera, with their
texture's cutout. They draw after the translucent map is copied, since where one casts the solid
map is the darker and decides. Unlit overlays, as entity.wgsl lists them, cast nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
#5559 let the dialog registry carry data for a client that knows the
core pack; with this PR the server follows vanilla's packRegistry again,
so every registry, the dialogs too, goes without data to that client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The classic path drew weather before translucent terrain with depth writes
on, so the water behind the rain failed the depth test and the rain columns
cut holes in it. Vanilla 26.3's RenderPipelines.WEATHER is
DepthStencilState(GREATER_THAN_OR_EQUAL, false), and executeClassicTransparency
draws translucent terrain, clouds, then weather, then the world border.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps this pull request near the size limit; the block state stays in the
hooks, whose signatures can't change once merged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_screen_mouse_scrolled_pre` (`ScreenEvent.MouseScrolled.Pre`) hands a
mod the open screen, the wheel's movement in x and y as the game's scroll
handler already scales it (vanilla's doubles, the sign only when discrete
scrolling is on, a touchpad's pixels over 30) and GLFW's modifier bits, plus
`canceled`.
It fires in the `MouseWheel` arm once `scaled` is defined, before the dialog,
title, hotbar, bundle and every screen scroll, so a cancelled turn scrolls
nothing and leaves nothing half-applied; the hotbar reads the wheel only
while playing, which the hook's gate excludes. The pad's and the keyboard
focus's made-up wheel is not the mouse and fires nothing.
The hook doc names the screens that never fire, as the mouse button hooks
do, and the fragment is pending, so this is "API next".
Co-Authored-By: DeepSeek <[email protected]>
tick_remote_spawners ticked spawners and trial spawners on a remote client
but not vaults, so VaultBlockEntity.Client.tick (display item spin, idle
particles and sounds) never ran on the server-thread client.
Fixes#3108
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both hooks get the block's state, as a renderer reads
blockEntity.getBlockState(); native_block_state_property reads its facing
without a world lookup, and native_draw_item draws an item stack under the
pose stack as a campfire does (resolve_item, submit_item_world).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`horizon-launcher play --account WHO` plays this launch as a signed-in account (name or UUID)
without changing the one `account select` picked, so several games of one instance can play
as several accounts at the same time (#4952). The owner gate runs as for any launch, and
what the services renew (tokens, names, skins) is kept.
account::session_for_game_as runs the gate as that account on the loaded accounts and puts
their choice of player back before saving them (launch_as). A Java instance refuses the
flag, as it does --world.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/native-mods.md gets an "Items' default components" section beside the
render hook points: the target path, both sides, threads, vanilla clients,
creative search. The catalog test reads free-function hooks with one helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A failed exporter compile fails every lane at once instead of being
retried by each lane that waits for it.
- The gameplay facts exporter streams to a .partial file and moves it into
place: a run stopped halfway no longer leaves a short file the next run
would keep.
- pool::map takes no per-thread state (a jar clone costs two Arc counts);
pool::lanes and map are tested at 1 job and several. With 1 job the first
failing lane stops the rest, as documented now.
- The lanes' log and stage messages go through relay(), which a test now
checks; block behaviour reuses export_on; the stamp inputs are worked out
once; the fifth lane exists only when it splits; dead checks are gone.
- check_prepare_exporters.py also prepares with 2 jobs (the default path
below 6 cores, 5 JVMs) and repeats that run, which must start no JVM and
change no file.
- The docs say what HORIZON_PREPARE_JOBS does, that the split is not yet
timed on 6 cores, and the split's measured ~400 MB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_block_entity_render_bounding_box lets a mod widen the box its block
entity is culled with (NeoForge's getRenderBoundingBox), or make it
infinite, so a tall part stays drawn while its block is out of view. The
view distance check is strict, as Vec3.closerThan.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: pr_ready on the Mac mini (head 26748d7c1, base a4873dec4): fmt, clippy -D warnings, horizon-command tests 27/27 (new a_dedicated_server_keeps_seed_and_version_for_game_masters), Python 510/510, public tree ok; parity 41/44 affected harnesses pass incl. command_parse, command_exec, chat_screen, server_ops_feedback. The 2 failures predate this PR and don't involve commands: online_mode = this Mac's known IndexError (stray GET /, fails on main too), dedicated_server = config-phase minecraft:dialog registry data from #4698 (same root cause as #5559), the stage before any command. CI verify passed.
On a server where a mod set nicknames (native_set_nickname), a player
argument naming no player matches the player whose nickname it is (any
case, without the ~ chat shows, or with it quoted), so /msg, /tell and /w
reach a nicknamed player by the name chat shows. Real names win; no
nickname, no change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The other way round from a force-loaded chunk: the player and a horse in the
Nether while the other player keeps the Overworld ticking; the Overworld's pass
must leave the menu alone (in_nether, --server-thread). The player lands in
creative and is put back on the platform once it stands, so a cave below the
spot doesn't drop it out of reach.
A killed mount lies at the spot for 20 ticks (LivingEntity.tickDeath), and the
next one, summoned there 10 ticks later, took the clicks meant for it on a
slow run: wait until the client no longer sees it. LAN_E2E_LEVELS takes a
comma list (the_nether, the_end, in_nether), as LAN_E2E_MOUNTS does; the
force-load is removed even when the wait after adding it fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ExperienceOrb.tick diverged from vanilla 26.3 in the way the dragon's
death XP shows: a landing orb kept its pre-collision speed through the
friction step and bounced back at 0.9 of it on every ground contact, so
the orbs rained onto the exit portal hopped high and for a long time —
the report reads them as floating in mid-air and spreading out.
The tick now follows the vanilla method: gravity 0.03 is skipped while
the orb's box is inside blocks (`!level.noCollision`), a stuck orb with
no player within 8 blocks gets Entity.moveTowardsClosestSpace (already
ported for items, now on the orb too, its own random drawing the
speed), the collided axes' velocities are zeroed the way
Entity.move's restituteMovementAfterCollisions does for a bounciness 0
orb (x and z by Mth.equal's 1.0E-5, y exactly), one friction of
getAirDrag() times the ground's friction — computed as a float, as
vanilla does — scales every axis including y, and the landing bounce
is `-fallSpeed * 0.4` gated on `fallSpeed < -getGravity()` instead of
a fixed -0.9. The friction anchor below the orb is getOnPos(0.999999)
as the item's already was, not the block one below.
tools/test_command_exec_parity.py gains an "experience orb fall"
group: an orb falling 45 blocks onto a stone platform, out of the 8
block follow radius, matches vanilla's Pos and Motion to the digit at
every checkpoint, the settled state included (969/969 with it).
Fixes#4781.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
MineshaftStructure and StrongholdStructure call moveBelowSeaLevel with
ChunkGenerator.getMinY(), which is 0 for FlatLevelSource (the noise settings'
minimum Y otherwise), not the level's -64. The Classic Flat stronghold sat
64 blocks lower than vanilla's.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
`native_screen_mouse_button_pressed_pre` and
`native_screen_mouse_button_released_pre` (`ScreenEvent.MouseButtonPressed.Pre`
and `.MouseButtonReleased.Pre`) hand a mod the open screen, the button's GLFW
index (0 left, 1 right, 2 middle, 3 back, 4 forward, `Other(n)` = n) and
GLFW's modifier bits (shift 1, control 2, alt 4, super 8), plus `canceled`
for a press the screen never sees.
They fire first in `handle_window_event`'s closure, before the screen or the
container hotkey, sort and header buttons act, so a cancelled press leaves
nothing half-applied and a cancelled release keeps a drag held. The pad's and
the keyboard focus's made-up clicks are not the mouse and fire nothing.
The gate and the view two more input hook families need are here: the pure
`glfw_modifiers` and `glfw_button`, `native_screen_open` (a screen of the
game's over `Screen::Playing`, no connect or load) and `screen_view_now`,
which builds only the `HudBatch` its rect needs. The hook docs and the
fragment name the screens that never fire: the title screen, the connect and
loading screens, the prompts, books and sign editors drawn over gameplay, and
any screen while a world loads or saves.
The fragment is pending, so this is "API next".
Co-Authored-By: DeepSeek <[email protected]>
New crate horizon-optifine, the layer every OptiFine-format feature (CTM,
CIT, random and emissive entities, CEM, sky, colours) reads its files
through: Java .properties with escapes and continuations, name matchers
(pattern:, ipattern:, regex:, iregex:), range lists with parenthesised
negatives, id lists with ! exclusions, block lists with states, OptiFine
path resolution (~/, ./, namespace:) and the optifine/ and mcpatcher/
folders of every namespace. Written from OptiFine's public format docs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CreativeModeInventoryScreen.slotClicked drops one item (the stack up to its
max size with Control) from a slot of the Survival Inventory tab whatever the
hand carries; the menu's THROW needs an empty hand, so with a carried stack Q
did nothing there. The screen's carrying branch now covers that tab as well as
an item tab's hotbar, and sets the slot as the menu path does (equipment
changes included).
lan_e2e creative_throw also checks Control+Q with an empty hand on an item
tab's hotbar, Q and Control+Q over a display slot, and Q, Control+Q and Q
while carrying on the Survival Inventory tab.
Part of #3684.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dropped item can be picked up again after 40 ticks; on a slow runner a
pickup between steps would change the counts the check compares.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a creative item tab, Q over a hotbar slot took the stack out through the
menu's THROW, but on a client of a server (single player's server thread, LAN,
dedicated) the drop only went to the server on the Survival Inventory tab, so
the slot was synced empty and nothing dropped. Send it on every creative tab,
as AbstractContainerMenu.doClick calls Player.handleCreativeModeItemDrop.
Also port CreativeModeInventoryScreen.slotClicked's own branch for a THROW on
an item tab's hotbar slot while a stack is carried: one item (the whole stack
with Control) drops from the slot and the carried stack stays.
lan_e2e: new check creative_throw, on the host and the guest.
Part of #3684.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mod could not change a default component of an item it didn't add (a
stack size, a food, a tool rule), as NeoForge's ModifyDefaultComponentsEvent
lets it. native_modify_default_components hands a mod each item's defaults
in items.json's form as they are read, to set or remove components.
Every reader of items.json (the item table, loot, enchanting, item models,
banners, pot sherds, rarity in chat) now goes through
horizon_world::item_defaults::read, so a change reaches all of them, and
stacks are saved and sent against the changed defaults like vanilla's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The depth range now reaches past the build height below the camera and shadow_position clamps z,
so a point past the far end stays lit, the blank map included. The light's box culls the native
lists instead of a section radius that lost casters at low sun. The native camera's snap is
euclidean, the resolution is clamped once, and the distortion and the camera each live in one
place. The generated reference lists the shadow helpers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
The first cut told an entry's kind before its requirements were checked, so a
failed requirement still fired, and gave the hook no way to know which mob was
hit. The kind is now told after the check, with the mob's id. A bare type name
is minecraft's own, so ignite runs as minecraft:ignite instead of reaching the
hook. The docs now say what's told: attacker on victim, a player's weapon on a
mob.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fresh preparation on a 6-core laptop finished its other JVM lanes by ~17 s,
then ran the server lane alone for ~20 s more: its last export, the world
generator's block behaviour (11.5 s there), sat behind everything else on
that JVM. From 6 cores (pool::jobs, so HORIZON_PREPARE_JOBS too) it and the
structure blocks run on a fifth lane, in their own server JVM. It pays a
second Bootstrap and ~400 MB of peak memory, so it stays off below 6 cores:
forced on this 3-core box it took 44.9 s against 40.5 s.
Same output (digest unchanged either way). check_prepare_exporters.py now
runs its batched preparation that way and expects 6 JVM launches; it passes
(23,204 files).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Measured on the pinned 26.3 jars, sampling every process's RSS: the peak came
at the end, from the server worker alone (1.25 GB). Its live heap stayed under
~250 MB; the rest was garbage the serial collector promoted and kept, mostly
from two exporters:
- The collision exporter wrote, and the worker cached, 62 MB of verification
inputs (printf'd, ~4 s) that only the parity harness reads. Inside the
prepare worker it now skips them; standalone runs still write them.
- The gameplay facts exporter built its 56 MB file as a Gson tree, then a
String, then bytes. It now streams each state to the file; the bytes are
the same (re-serializing the parsed file with Gson.toJson gives them back).
The worker now peaks at ~480 MB. In the Rust process, every thread reading a
jar parsed its member list again (~10 MB each, eight at once); they now
share one parse through clones of the archive that read at their own offsets.
Its heap peak halved (96 to 51 MB).
check_prepare_exporters.py passes (23,054 files, 5 JVM launches); the output
digest is unchanged. "Movement and attributes" takes 12-13 s instead of 16-18 s.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An enchantment's effects entry that names an effect.type in a namespace
outside minecraft: used to be silently skipped at the firing point. It
now reaches native_enchant_effect(enchantment, kind, data, level) — the
post_attack components first, where the weapon's holder is the acting
player — and the game's own damage, sounds, effects and drops are open
to the hook. An unclaimed kind does nothing.
Co-Authored-By: ZCode AI agent (GLM-5.3)
The serial collector is as fast as the parallel one for these JVMs (fresh run
63-66 s either way, pinned 26.3 jars) and keeps the three that run at once to
1.25 GB together instead of 2 GB. An export's time no longer includes waiting
for javac. A compile error still names the export it stopped.
tools/check_prepare_exporters.py passes: 23054 resource files match between two
isolated runs and a batched one, with 5 JVM launches and no compiler.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The exporters, the data generator and javac are short batch JVMs, three of them
running at once. The C2 compiler's background work and G1's concurrent threads
cost them more CPU than those win back. With -XX:TieredStopAtLevel=1
-XX:+UseParallelGC (javac gets them through -J), a fresh preparation from the
pinned 26.3 jars took 67 s instead of 102-105 s on the same machine. The output
digest (tools/prepare_output.py) is unchanged. java.rs is not part of the
exporter bundle key, so cached bundles stay valid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fresh preparation ran everything one after another. The steps now run on four
lanes: the server exporter JVM (same order as before, since its exporters share
state), the client exporter JVM then the command registries', the data generator
then the creative inventory's, and the Rust-only copying. At most three JVMs run
at once, still five JVMs in total. A worker stops when its lane is done. The two
exporter sides compile at once. Asset extraction, structure templates, worldgen
bundles, server library unpacking, client library imports, inventories and pin
checks run on several threads. Every pinned file is still hashed in full.
Repeat runs always started the server JVM for the block behaviour and
structure-block exporters, which had no skip check. They now keep a stamp.json
in .local (exporter bundle key, server jar, preparer sources, output sha256), so
an unchanged repeat run starts no JVM. The prepared files are unchanged.
Every step logs how long it took. HORIZON_PREPARE_JOBS=1 runs it all serially.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The check now opens a tamed horse, camel, llama, trader llama, nautilus
(owned), chested donkey and mule by shift + right click and by the
inventory key while riding, and again by shift + right click after getting
off, on the host and on the guest, with a chunk force-loaded in the Nether
and in the End. Every way is reported on its own and must still be open 20
ticks later.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RodDbPWCByh96WKcbiEQpB
The rect comes from the same container image the game draws, so the
merchant and the beacon report their wider, shifted window. An unknown menu
type is None rather than an empty id, and slot_count is checked against
slots() in debug builds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server names no player of its own as a mob's killer, and the drops
context's fields aren't flagged as never read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`native_screen_render_post` (`ScreenEvent.Render.Post`) hands the mod the
mouse in GUI pixels and the frame's partial tick besides the screen view,
as the event's `mouseX`, `mouseY` and `partialTick` do, so a mod can draw a
hover highlight.
The view also carries `menu`, the vanilla menu type of the menu the server
opened (`minecraft:generic_9x3`, `minecraft:furnace`, a mod's own id), and
None for a window the client opened. Every container, station and mod
window draws on the inventory screen, so `id`'s doc says they all report
`inventory` and are told apart by `menu`, `title` and `slots`.
The view is built without allocating: slots are counted the way `Game::slots`
lists them (`slot_count`, with `station_slot_count`, `mount_slot_count` and
`native_screen_slot_count` beside their list versions) instead of being
collected into a `Vec` every frame, and the id comes from the screen
`hud_layers` drew rather than from a second argument beside `self.screen`.
The hook and the fragment say which screens never fire it: the title screen,
which returns before its point, and the prompts, books and sign editors
drawn over gameplay after it, which keep `Screen::Playing`, are skipped, and
draw over what the hook drew. Vanilla fires Render.Post for them; that gap
is now written down.
With no mod installed the body stays `let _ = (...)`: the frame is vanilla's.
Co-Authored-By: DeepSeek <[email protected]>
- /summon minecraft:spawner_minecart with no NBT runs BaseSpawner.load on an
empty compound, as SummonCommand.createEntity does, so SpawnPotentials is
WeightedList.of(new SpawnData()) rather than empty.
- The save test passes no raw tag and changes the delay after loading, so it
checks the cart's own save arm.
- The cart model test covers spawner_minecart#main.
- Port ledger: MinecartSpawner's tick, save, load and display state are ported.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
More review fixes on the flat generator:
- FixedBiomeSource.findBiomeHorizontal for the stronghold rings of a fixed biome source.
- The ring cache key carries the structure_overrides, not a constant.
- A structure_overrides list naming no existing set lets every set generate; an unknown
biome is plains.
- A flat generator that cannot be built leaves the world on its flat ground; the column
queries cover the whole level; far decoration is off for a flat world.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The loader dropped an on_consume_effects entry whose type the game
doesn't know silently. It now keeps the kind and the JSON the file
wrote, and the game hands them to native_consume_effect when the item
is consumed, on the server thread, for the acting player. The game's
own effects, sounds, teleports and drops are open to the hook; an
unclaimed kind does nothing, as an unknown kind on any loader.
A bare type name without a namespace is minecraft's own — apply_effects
is minecraft:apply_effects — so only a namespaced kind outside
minecraft's becomes Native; an unknown minecraft: kind is dropped. The
hook sees the item's own consumable component, not a patch on the stack.
Co-Authored-By: ZCode AI agent (GLM-5.3)
The wall-table test's registry has every block's default, a kill by a guest
who left names no player, and the load hook's docs say two loads can overlap.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeoForge's CompoundIngredient matches if any child does — the union of
their items, not the intersection (that is IntersectionIngredient).
The ingredient_type key is neoforge:ingredient_type, type only as the
older packs' spelling; children is the field name, ingredients as the
older spelling. neoforge:fluid is not among NeoForge's item ingredient
types (its fluid ingredients are a separate registry for fluid stacks,
not items), so it is dropped until a use case arrives with a source.
Review: rdbtCVS's agent in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
spawn_loaded_entities hands out the id before native_entity_joins runs,
and a cancelled orb doesn't give it back, so the fallback returned an id
no entity holds. Take it only when an orb holds that id (root_id), and
test both cases.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QT61YPiPzVNysmVHsH
Vanilla reads the block at every drawn position of a ticking section; most
hold stone, dirt or air, and each read is a chain of cache misses (chunk map,
section, palette, words, block facts). Each ticking section now keeps a
4,096-bit mask of the blocks a random tick can do anything for (a randomly
ticking block, or lava), built at its first draw and dropped when the section
changes. The draws, their order and every tick that runs are the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Empty stacks a mod leaves don't drop, the load hook runs outside the
cache's lock, a mob's context names the table that rolled (one helper with
drops::die), the wall-variant table lookup has a test, and the docs say
which breaks the drops hook hears.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_loot_table_load (horizon_world::loot) hears each loot table's JSON as
it loads, after the data packs' (LootTableLoadEvent, LootTableEvents.MODIFY),
and native_loot_drops hears a broken block's or a dead mob's items with what
dropped them (global loot modifiers, MODIFY_DROPS).
Closes#5522
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: fixes#5505 (lan_bot 13/15 failing on main since #4698 always sends minecraft:dialog data). Test-only: the known-pack assertion allows data for the dialog registry. lan_bot 15/15 on this head with vanilla data; fmt ok.
Review fixes for the structures on a flat generator, from the 26.3 bytecode:
- createForFlat searches the stronghold rings with seed 0.
- FlatLevelSource's sea level is -63 and its generation context starts at Y 0 with depth 384.
- A null layer (not opaque to MOTION_BLOCKING) is skipped by getBaseHeight and
buildTerrain and reads as air in getBaseColumn; a FillLayerFeature fills it after the
structures (TOP_LAYER_MODIFICATION).
- spawnOriginalMobs is empty for a flat generator: no generation-time creature packs.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Since #4698 the server sends minecraft:dialog's data even to a client
that knows the core pack (Horizon's client reads the dialogs from the
packet), so every bot join panicked on the configuration step's "no data
with the known pack" assertion and 13 of 15 lan_bot tests failed. The
assertion now expects data for the dialog registry and stays strict for
every other one.
Fixes#5505.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#4698 made the minecraft:dialog registry always carry its entries' data,
on the idea that a client does not rebuild dialogs from its known pack.
Vanilla's does (RegistrySynchronization.packRegistry): an entry of a
pack the client knows goes without data, and the dedicated_server
harness saw Horizon send the dialog registry in full where vanilla sends
Optional.empty for each entry. Horizon's own client answers
SelectKnownPacks with no packs, so it keeps getting every entry in full;
a dialog a world's pack adds or replaces is marked from_pack and still
carries its data, so quick actions from a data pack reach every client.
Fixes#5525
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds `screen_render_post`, the point of NeoForge's `ScreenEvent.Render.Post`: once a frame, after the
open screen has drawn itself and its tooltips, a native mod may draw over it. It is handed a
`NativeScreenView`: the screen's id (one per `Screen` variant, from `Game::screen_id`), the open
menu's title (borrowed from the menu, not copied: no per-frame allocation), the container panel in GUI
pixels or the whole window, and the menu's slot count.
The firing site is the end of `hud_layers`'s screen match, where every screen but Win and Options is
drawn. Those two keep their early returns: folding them into the match would draw the HUD and the
prompt path behind them, so the hook fires on their own path, after their drawing, instead. It does
not fire while playing with no screen, and it is a `&self` hook on the window only, so a mod can
read and draw but not change the screen.
With no mod installed the body is `let _ = (...)`: the output stays vanilla's.
The hook is API next (`tools/native-hook-fragments/screen_render_post.json`); `native-hooks.json` and
`NATIVE_HOOKS_API` are untouched.
Co-Authored-By: DeepSeek <[email protected]>
An 8x screenshot at 200% Resolution Scale with the video memory already full
crashed: the depth buffer's allocation failed, and its view raised a
validation error that only an out-of-memory scope was watching. The target
and depth buffer, and the whole scaled frame, now run inside out-of-memory
and validation scopes; a failure writes no PNG and tries again next frame at
half the size, down to the window's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
break_vehicle's ten punches have run two ticks apart since they went
into one script (bd09f8b03). Player.attack scales a hit by its charge,
0.2 + s * s * 0.8, and a fist charges in 5 ticks, so each later punch did
0.4: 10 for the first, then 4 a hit against 2 of decay, 28 after ten of
the more than 40 a vehicle needs (vanilla would take 17). The punches
are now 5 ticks apart, fully charged, twelve of them: the vehicle breaks
on the 8th, and a tick of drift still nets at least 4 a punch.
Fixes#5526.
Written with Claude Opus 5.5 in OpenCode.
Commands(CommandSelection.DEDICATED) registers SeedCommand and
VersionCommand with checkPermissions (LEVEL_GAMEMASTERS) and leaves out
PublishCommand and UnpublishCommand (includeIntegrated). Horizon's
executor tree used the integrated rules on --dedicated, so any player
could read the seed of an SMP. The Commands packet already cut them; the
executor and /help now do too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sort solid cutout quads while the worker-side side buckets are built, so rendering uploads the canonical worker result without a render-thread permutation or duplicate allocation.
Co-Authored-By: Codex <[email protected]>
A rider kept its vehicle id after the vehicle left the level. A respawn into
another level keeps the id when KEEP_ENTITY_DATA is set, and a removed
nautilus or minecart leaves it behind. remote_ride then kept the player in
the riding pose with no vehicle under it: it did not move, sneaking did
nothing, and the seat stayed until a vehicle packet named it again.
Vanilla Entity.baseTick stops riding a vehicle that is removed
(isPassenger() && getVehicle().isRemoved() -> stopRiding(), Entity.java).
Do the same in remote_ride when no minecart, boat, cushion or mob has the
id, as tick_riding already does on the local path (boats.rs).
A tadpole is an AbstractFish with a brain: it gets panic 1.25, avoid-player and
the fish random swim goals, SmoothSwimmingMoveControl(85, 10, 0.02, 0.1, true),
SmoothSwimmingLookControl(10), water-bound navigation, and the TadpoleAi brain
(panic, look at player, follow temptation, swim/walk/stay-in-water gate).
Its travelInWater is AbstractFish's (0.01 speed, 0.9 drag, -0.005 gravity).
Adds mob_ai parity scenarios tadpole_idle, tadpole_tempted, tadpole_panic.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
agent_control_e2e's use-screen scenario opens the inventory and the chat over
a golden apple in use (survival) and checks the use is released in that tick:
the stack stays at 64 and no effect appears, while eating with no screen still
finishes. With the call sites in game/remote.rs disabled it fails with the
apple at 63, so it covers the bin wiring the interaction-input parity harness
cannot see; the parity rows stay as oracle-side coverage.
Co-Authored-By: DeepSeek 4.1 Flash (DeepSeek Harness) <[email protected]>
Adds step-on, inside and fall-on callbacks with entity and block context; landing damage is adjustable before sounds and hurt.
Co-Authored-By: GPT-6 <[email protected]>
A cross model (flower, fungus) carried by an enderman drew both of its
coplanar sides through the entity pipeline, which does not cull. The two
quads sat at the same depth and flickered against each other (#5421).
CarriedBlockLayer.submit draws the block through BlockModelRenderState,
which uses the item cutout sheet (RenderPipelines.ITEM_CUTOUT). That
pipeline keeps the default back-face culling, so only the side facing
the camera is drawn. Drop the quads whose face normal points away from
the camera after emitting the block model. CULL_BACK would also drop
them, but it turns off the normal lighting this model needs.
Refs #5421
The hardening edit put a raw newline inside the fragment's `doc_summary`, which
JSON reads as a control character: `catalog_matches_hook_points` failed to
parse it. The line break is the `\n` escape the other fragments use.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
tick_lan_mount_menus checked every open mount menu on every level pass,
not only in the player's own level. When a second level ticks (a player
or a force-loaded chunk in the Nether, a portal ticket), its pass found
no such mob and closed the menu the tick it opened. Like the station,
crafting and merchant ticks, it now skips players not in the level in
place (ServerPlayer.tick checks stillValid in the player's level).
lan_e2e mount_other_level force-loads a Nether chunk and checks that the
host's and the guest's horse inventories stay open, opened with shift +
right click and with the inventory key while riding.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The issue asks the hook to run for every placer that goes through vanilla's
path, not only the player. `the_level_chooses_the_state_a_block_is_placed_in`
now drives a dispenser's `DirectionalPlaceContext` as `dispense_shulker_box`
builds it — `directional_can_place`, then `place_block` — and asserts the
furnace lands in the hook's state, with the hook handed the dispenser's own
click (its yaw, the front block it replaces).
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Review point on #5516: an answer naming another block would place a block the
item isn't bound to, and skip that block's `setPlacedBy` and sound. Vanilla's
`getStateForPlacement` sets properties on the block's own `defaultBlockState`,
so the hook's answer is taken only for a state of the block being placed;
another block's, like an id past the state table, leaves vanilla's state.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
A mod block took no bone meal: `BoneMealItem.growCrop` looked the block's
`BonemealableBlock` up in the port's classes and stopped. The level now
answers vanilla's three questions for it, ahead of the `BonemealableBlock` a
content template would give it, so a mod block can refuse, roll and grow:
- `is_valid_bonemeal_target` — `isValidBonemealTarget`. Asked on both sides,
as vanilla asks it, so a client predicts the use with the mod's answer.
- `is_bonemeal_success` — `isBonemealSuccess`, drawing from
`native_level_random` so a mod's draws stay in vanilla's order.
- `perform_bonemeal` — `performBonemeal`, with the level's ticks.
The hooks are `native_is_valid_bonemeal_target`, `native_is_bonemeal_success`
and `native_perform_bonemeal` (a fragment, no API number taken), plus
`native_level_random` for a world hook, the stream `native_level_random
(dimension)` hands a server hook.
One insertion point serves every placer: `Sim::grow_crop` is where the
player's `BoneMealItem.useOn`, a dispenser's `BoneMealItem.growCrop` and a
villager's bone meal meet, and the meal is used up and level event 1505 fired
by the callers as before, so a mod block gets vanilla's particles and sound.
The client's copy of the level now forwards the one read-only question
through `DryLevel`, and a mod block's growth particles stay inside it, as
vanilla's `GROWER` type does, where the port knows no type for it.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
A world whose Version.Series is not main went straight to the file fixes
and the upgrade in WorldRequest::prepare, which the dedicated server and
the agent launch both reach. Vanilla refuses it: the dedicated server's
Main.java returns before DataFixers.getFileFixer().fix when
summary.isCompatible() is false, and WorldOpenFlows.
openWorldCheckVersionCompatibility shows the incompatible AlertScreen
(LevelSummary.isCompatible, DataVersion.isCompatible).
The title screen already refused such a world. The open path now does
too, so an incompatible world is never upgraded and saved in place.
The Recover screen's Attempt to Restore moved the broken level.dat aside
and put level.dat_old in its place without copying the world first.
Vanilla RecoverWorldDataScreen.attemptRestore runs
EditWorldScreen.makeBackupAndShowToast before restoreLevelDataFromOld:
a zip of the whole world folder without session.lock. The restore runs
once that finishes, whether the backup worked or not.
The button now starts the same backup through the backup screen and
runs attempt_restore when it is done, with the backup toast.
Reorder solid cutout quads front to back during GPU upload while preserving the canonical section mesh and shared index batching. Include the before and after leaf-scene captures used for validation.
Co-Authored-By: Codex <[email protected]>
F2 now saves the frame at the game's resolution (Resolution Scale above
100%) times a new Video Settings option, Screenshot Quality (1x, 2x, 4x,
8x; 1x by default). The frame is drawn once into its own offscreen
target, with the HUD zoomed to the same layout, and capped to the GPU's
texture and buffer limits; if the large targets can't be allocated the
window's frame is saved as before.
Fixes#3030
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_has_analog_output and native_analog_output answer BlockBehaviour.hasAnalogOutputSignal and
getAnalogOutputSignal for a mod's block; Level.setBlock's comparator update follows the answer, and
native_update_output_neighbors is Level.updateNeighbourForOutputSignal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.move resolves Y first, where the body starts, then X and Z. On the
tick a player walks off an edge, or lands on one and slides off it, the
client is on the ground over air at the move's end. An instrumented
25-minute vanilla client soak on generated terrain matched all 117 of
its ground flags to this (104 walk-offs at the same height, 13 landings),
one per step down any slope or staircase: enough to push a player
running down a long one toward the kick. ground now also holds a claim
when the ground holds at the move's start X and Z at the new height. A
no-fall or jesus has nothing there either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ExperienceOrb.createInterpolationHandler returns a LinearInterpolationHandler, so a client spreads each server position correction over 3 ticks (doInterpolate in Entity.commonTick) and the orb's own move carries the target along (applyPredictedMovement). The remote path put the orb at the new position at once, so it jumped on every correction; the comment claimed vanilla's NO_OP handler.
LeashFenceKnotEntity.interact runs only when the ray meets the knot (its box from recalculateBoundingBox, 0.375 wide, y + 0.375 to y + 0.875) before the post. A click elsewhere on the fence is FenceBlock.useWithoutItem or LeadItem.useOn, which only ties the player's own mobs (LeadItem.bindPlayerMobs). interact_knot ran the take-back and shears branches for any fence click, so clicking the top of a post with stone took a tied horse's lead instead of placing the stone, and shears snipped every lead.
The prepared root holds features without vanilla's old "config" wrapper and
with a block state as an id, so the zinc ore never decoded and the
content-root test failed over real data (CI skips it without .local/).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Opening the inventory (or chat, or advancements) while an item was in use
left the use running: the key event sets the screen, and the release sentence
of Minecraft.handleKeybinds never runs while a screen is open, so the serving
side kept counting and ate the food behind the GUI. Vanilla releases in the
same tick: the key opens the screen inside handleKeybinds, Gui.setScreen runs
KeyMapping.releaseAll (clearing key.use), and the same call then hits the
isUsingItem release. Add interaction_input::release_on_screen_open and call it
from the inventory/creative, chat and advancements key paths; pause, server
containers, mount inventories and dialogs keep vanilla's later release.
Fixes#4472
Co-Authored-By: DeepSeek 4.1 Flash (DeepSeek Harness) <[email protected]>
`getStateForPlacement` had no way in for a mod: a block could only be placed
in its default state. `LevelAccess::before_state_for_placement` now asks the
level, once per block whose placement state is computed, with vanilla's
`BlockPlaceContext` (the clicked face, the placer's rotation, the clicked
position, whether the clicked block is being replaced, inside, the item in
hand) and the fluid of the position the block would go in, so a block can
face the player, sit on the clicked face or come waterlogged. The answer is
`Some(Some(state))` to place that state, `Some(None)` to refuse the placement
as vanilla's `null` does, and empty to let the block's own placement run.
`native_state_for_placement` is the hook a native bundle answers with, and
`native_state_with_property` builds the state from the block's default state
and one of its properties (a content block's declared ones included).
`Ctx`, the resolved context, is now `PlacementContext`, public and documented
as what a mod block is handed. Both placers that go through vanilla's path
reach it: the player's `BlockItem.place` and a dispenser's
`DirectionalPlaceContext` (both `Sim::state_for_placement`), and the answer
still takes its survival and entity obstruction checks.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
The new cart_slots was inserted between unpack_cart_loot and its doc
comment, so rustdoc merged both onto cart_slots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's handlers take a loop handle (EventLoopCtl) and about_to_wait's body is Game::idle: exit_on_error takes the handle, the split guard stays in idle, and run_event_loop stops the narrator after the split's loop.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The dither changes vanilla's look for every cutout from 64 texels up, so it now runs only under
Distant Plant Dither (ditherCutoutMips in horizon-graphics.txt). Turning it on or off reloads the
textures, since mips are made when they load. Decoded textures don't hold mips, so the sprite cache
keeps its version.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
An advancement of a content mod or a data pack can name a trigger of
the mod's (mymod:pipe_connected); the tree keeps it, but only the game's
own events called Game::trigger, so nothing could grant it.
native_trigger_criterion(uuid, trigger, test) is vanilla's
SimpleCriterionTrigger.trigger for one player: each open criterion of
that trigger goes to the mod's test, then to its player predicate, and
those that pass are granted and awarded as the game's own are. The test
reads the criterion's conditions and can check a vanilla item predicate
in them. It reaches the acting player inside their hook, and any player,
in their own level (for_player), where no player is swapped in;
minecraft: triggers are refused.
Fixes#5481.
Written with Claude Opus 5.5 in OpenCode.
Keystroke::parse takes a 'static str; the app comes from gpui_platform::application(), and
AsyncApp::update returns nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_can_survive answers BlockState.canSurvive before the block does: as it is placed (BlockItem.canPlace)
and from the shape update of a block whose template checks it, which breaks it with its drops.
native_may_place_on answers VegetationBlock.mayPlaceOn, the blocks a mod plant can stand on, which its
template's canSurvive asks. Both through LevelAccess, on authoritative servers, as native_update_shape is.
Inbox::reset, called on Event::Reconfiguring after clear_lod: requests a proxy's previous backend held are forgotten instead of holding in_flight slots for the 30 s timeout, and the next offer's per_second makes a new bucket.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The profile's texture id comes from a server's NBT; Identifier's characters and
no '.', '..' or empty segment keep it from reading a PNG outside the resource root.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The ten lang tables conflicted where both sides added strings: main's table as the
base, plus this branch's account strings, without the "Microsoft account · signed in"
this branch removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: lod.rs (main's wanted-tile heap from #4747; a failed tile stays out of the heap until its RETRY passes, then the heap is rebuilt so it is asked again; main's MAX_LEVEL 22) and remote.rs (send_far_terrain and main's tick_chat_session both run before ClientTickEnd).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two games playing in one game folder at the same time (#4952, the launcher's
`play --account`) fought over its per-player files: one `options.txt`, one
`logs/latest.log` (the second game logged to the terminal only), one generated name in
`horizon-player.txt` (two offline players got one UUID) and one `servers.dat`.
`horizon-client --player-dir DIR` puts this player's own files in DIR: `options.txt` and the
settings files beside it, `logs/`, `crash-reports/`, `horizon-player.txt`, `servers.dat`,
`hotbar.nbt` and `screenshots/`. Its players still share what the game folder holds: saves,
resource and shader packs, mods (the enabled list included, `logging::shared_dir`) and native
mods' `config/`, which no longer follow `options.txt` but the game folder (`Game::game_dir`).
Without the option nothing moves.
Launch API 0.1.1 promises the option (docs/api/launch.md).
Tested: two games started at once in one folder with --player-dir players/Alice and
players/Bob each logged to its own players/<name>/logs/latest.log and saved their worlds in
the shared saves/; a third, without --name, kept options.txt, horizon-graphics.txt and its
generated horizon-player.txt in players/Carol/.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main added tr.json after this branch began, so it lacked the 12 keys the i18n
completeness test asks for. They hold the English source until translated, as the
other untranslated entries do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The nine lang tables conflicted where both sides added keys: main's table kept as
the base (its order and its translations), plus the keys only this branch has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both sides added an Extra field and its NBT round trip (rabbit MoreCarrotTicks here,
ocelot Trusting on main); kept both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fox.onOffspringSpawnedFromEgg's only caller is Mob.checkAndHandleImportantInteractions
(a fox egg used on an adult fox), so the trust moves from use_spawn_egg to
spawn_egg_offspring. The port map's Fox.clearTrusted and readAdditionalSaveData rows
now say what is ported.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main now clears a pack's custom images each frame before the shadow pass, which may write them; skipping that pass on a hit would leave them empty.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two documentation conflicts again, where main's management protocol lines grow at the same place as the RCON ones. Main's text is kept, and the RCON lines now sit away from them (before the management protocol's paragraph and its changelog line), so the next merge doesn't meet them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The countdown of AbstractBoat.tickBubbleColumn moves to Boat::tick_bubble_countdown,
so a unit test checks it fires on the 60th tick over a column, with the column's
direction, and resets when the boat leaves.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content mod's placed features now generate in vanilla's biomes through
NeoForge's biome modifiers (data/<ns>/neoforge/biome_modifier/, and
forge/biome_modifier/ for older mods): add_features, remove_features and
none. When the content root builds they rewrite the biomes' feature
lists, an added feature at the end of its step as NeoForge adds it; the
generator's FeatureSorter then orders them as vanilla does. Biomes no
modifier names keep their text, and a root with no modifiers keeps
vanilla's bundle. An unknown biome, tag, feature, step or type fails the
build with the mod and file named.
Part of #4953, closes#5431.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NLEqWLKW2LHgtLer35pT8m
Vanilla's Item.use, getItemBlockingWith and applyItemBlocking read
DataComponents.BLOCKS_ATTACKS from the stack, so a sword given
`[blocks_attacks={}]` blocks like a shield. Horizon only read the item's
default component. BlocksAttacks::of_stack now takes the stack's own
component first (kept in StackData::other), then the item's, and the
local player's raising, use tick, blocking, off-hand intent check and the
remote client's use prediction go through it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSY4AbxZbBtWqJLhFg
FlatLevelSource.createState: villages and strongholds (the classic flat
world's structure_overrides) and any set a world's settings allow generate on
the flat layers, with the fixed biome, and are found by /locate. The pipeline's
noise column is the layers (getBaseHeight, getBaseColumn), the terrain stage
fills them, and the structure steps run with no placed features.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A tile's previous mesh (kept while near chunks fade in) lives in the arena as well; without moving it, a closing page holding one stayed closed until the fade ended and compaction stalled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Follow-up to #5408. A summoned orb spawned but native_summon returned None,
since no command Target lookup covers orbs: return the stack's first
spawned id. Passengers without Pos were not placed and got lost: share
/summon's recursive place helper (place_summoned), which also supplies
the Fire and Rotation defaults.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QT61YPiPzVNysmVHsH
known() now checks the whole subtree, not only the immediate type:
not(not(unknown)) and or(true, not(unknown)) both failed to exclude
the file, because the outer combinator was a known kind and only the
direct child was checked. Both now fail closed, with tests.
Review: Aria's agent swarm (Dyna) in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
An ingredient that is one of NeoForge's standard object shapes now
resolves to the flat item list it matches, where the string parser
dropped the recipe file whole: neoforge:compound and
neoforge:intersection keep the items every sub-ingredient names,
neoforge:difference the base's without the subtracted's, and
neoforge:fluid a fluid's item form, its full bucket (a water or lava
tag names the same still fluid). A kind outside these still fails the
file - a mod's own ingredient types come with their own hook, not the
loader.
Co-Authored-By: ZCode AI agent (GLM-5.3)
Two conflicts, both documentation: docs/api/server.md's changelog and docs/server.md section 8, where main added the management protocol's allowlist and bans. Main's text is kept and the RCON lines follow it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
docs/native-mods.md keeps main's API 58 notes, then the API mods section.
With the game module in the horizon-client library, its unit tests ran in the
library's test process and shared process-wide state with the library's tests
(health::HURT_SERVER, the sulfur cube archetypes): six tests failed. The game
module now builds into the library only outside tests; its unit tests stay in the
executable's test build, as before the move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Translucent terrain is drawn over a copy of the solid shadow map, so a pack can tell a point under
water or stained glass from one in shadow: shadow_map_translucent holds that depth, and shadow_color
the texture colour times the tint of the nearest such surface, with alpha 1 on a fluid.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Called from native_explosion_detonate, native_explode took the outer
blast's source, cause, owner and hive/rail flags. Save and clear them
around server_explode, then restore them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QT61YPiPzVNysmVHsH
`/item replace`, `/item modify` and `execute if items` answered "The
target does not have slot container.0" for a chest or hopper minecart.
Vanilla's AbstractMinecartContainer.getSlot is ContainerEntity's
getChestVehicleSlot, which takes any index below the container's size
(27 for a chest minecart, 5 for a hopper minecart), and reading or
writing it unpacks the cart's loot table with no player first.
Slot::Cart gives commands those slots; cart_slots drops the loot table
the same way, so no player_generates_container_loot fires later.
The command execution corpus checks both carts against vanilla: 66/74 of
the items and loot group matched before, 74/74 now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
setValuesFromPositionPacket measures tooBigToInterpolate from
PositionMoveRotation.of(entity), which is getClientPositionAndRotation:
the interpolation target while one runs. handleEntityPositionSync
measures from the entity's position. MovePacket keeps the two apart.
The mob and armor stand branches of move_entity now share stepped_move.
A mob put somewhere at once also resets its previous yaw and pitch
(snapTo's setOldPosAndRot), so it doesn't spin for a frame, and every
spawn starts with a fresh interpolation handler, a stand's included.
Written with Claude Opus 5.5 in OpenCode.
mod_loaded now asks which mods are loaded (the mod-content root's new
horizon-mod-ids.json marker), not which namespaces they own — a mod that
also owns a namespace doesn't make that namespace another mod. An
unknown condition kind fails the file through not and or as well, not
just on its own: `not` of an unknown kind is not a pass, and an `or`
over one doesn't short-circuit past it. And the stations, the recipe
book and brewing apply the world's packs first and gate the merged
file, as the crafting loader already did: a false-conditioned pack file
leaves the lower recipe gone too, not still active underneath.
Review: Aria's agent swarm (Dyna) in the pull request.
Co-Authored-By: ZCode AI agent (GLM-5.3)
Conflicts: game.rs keeps the api_mods export beside main's narrator module;
horizon-threads takes main's windows-sys features (the same Windows fix);
native-mods.md keeps main's API 47-57 notes, then the API mods section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An armor stand is a LivingEntity, so a vanilla client moves it with the
SteppedInterpolationHandler over its update interval (3 ticks), position
and yaw together, and ArmorStand.tickHeadTurn has the body follow yRot.
Horizon eased stands with the generic three-step lerp and set the yaw at
once from each packet. Stands now share the mobs' stepped handler:
ArmorStand::interpolate takes its step, keeps the yaw continuous and
saves yBodyRotO, and the renderer and the F3+B hitbox turn the body by
Mth.rotLerp(partialTick, yBodyRotO, yBodyRot).
A move of more than 64 blocks now snaps (tooBigToInterpolate), with
nothing left to blend from (Entity.snapTo). TeleportEntity snaps too, as
setValuesFromPositionPacket does; before, only EntityPositionSync did,
so a far teleported mob also slid there over three ticks.
Written with Claude Opus 5.5 in OpenCode.
A pack asks for a shadow map in its manifest and the renderer draws the solid terrain the light
sees into it each frame, from the shadow camera's lists shaders/ packs already use. The material
and post stages read it as shadow_map with shadow_sampler and the prelude's shadow_position, and
the frame gains light_direction, has_shadow, shadow_view_proj, shadow_distance, shadow_distortion,
shadow_resolution and shadow_center_texel. A pack without [shadow] gets a 1x1 map that lights
everything. Translucent terrain and entities don't cast yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
DebugLevelSource: barriers at y 60 and the block states in registry order on
a grid at y 70, one every other block. A Debug world's blocks never change
(Level.setBlock fails; setblock, fill and clone report it), nothing ticks
(scheduled, random, natural spawning), the login packet says isDebug, and
level-type minecraft:debug_all_block_states creates it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Two conflicts, both documentation: docs/api/server.md's changelog and docs/server.md section 8, where main added the management protocol's TLS and reworded its list. Main's text is kept and the RCON lines follow it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Upstream now sends custom_model_data itself; keep its codec and drop
ours, so this branch only adds custom_data to the LAN item sync.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EntityRenderer draws an entity at Mth.lerp(partialTick, xo, x), but armor
stands were drawn at their tick position, so a falling stand jumped once
a tick (20 times a second) while boats, TNT and mobs slid. ArmorStand now
keeps its previous position: ArmorStand::interpolate saves it before each
interpolation step on a server's client, a stand riding a mob or the
player takes it from its vehicle's previous seat (as the mob riders do,
so it stays right while the level is frozen), and the in-frame tick
saves it too. The body, its light, the culling box, the name tag, the
flames and the F3+B hitbox all use the blended position.
Fixes#4416
Written with Claude Opus 5.5 in OpenCode.
Rabbit.RaidGardenGoal: a MoveToBlockGoal (0.7, 16) to carrots at their maximum
age on #supports_crops ground, which takes one age off them (or the crop when
it had none) and leaves the rabbit full for 40 ticks (moreCarrotTicks, saved
as MoreCarrotTicks, running down by 0 to 2 a tick).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A native bundle with [api] (crate and entry) and no [[injection]] is an API mod: its
crate implements horizon_mod_api::Mod and the game asks it right after the item and
block hook points it is named after, once the mixin mods there are done.
- horizon-mod-api: the Mod trait (use_item, use_item_on, right_click_block,
left_click_block, destroy_block, place_block, tooltip), Action and Host.
- horizon-client: the game module moves into the library and the executable only adds
the allocator, so a build can link the game from another package; game/api_mods.rs
dispatches, and a mod turned off in Options > Mods is skipped with no build.
- launcher: API mods skip horizon-mixins; their crate and a generated native-game
package build the executable, so horizon-client never depends on a mod.
- horizon-mods: the manifest's optional api field and its checks.
- docs: native-mods.md "API mods (no mixins)", stability.md; example in
launcher/tests/fixtures/native-api-demo.
`feed_animal` takes the player who feeds an animal into love since this
branch; the probe passes none, so `--examples` builds again.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
Main's #5397 translated the same 17 strings into ja, pt-br and tr that dd722ee7a did, with
other wording. Git merges both without a conflict, which left each of those tables with 17
duplicate keys. Main's wording is kept and this branch's dropped, so the tables differ from
main only by this branch's 26 account strings and the key it removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82d7UpxGuCncnLpJsq
`Fox.FoxBreedGoal.breed` hands the breeding to the baby as trust, which needs
the player who fed each parent into love: the animals kept the love counter
but not its cause. `Animal.LoveCause` is now kept (`Extra.love_cause`, fed
from the player who hands over the food, saved and read), and
`fox::trust_breeders` gives the baby both players, once each.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
`Fox.getTrustedEntities` and `Fox.addTrustedEntity` were modelled as "no
trusted players yet": the save wrote an empty `Trusted` list, nothing read it
back, and the avoid-player goal had no trust test, so a fox fled the player
who spawned it and nobody could become trusted (two rows of #3512).
- `Extra.trusted` holds the two `DATA_TRUSTED_ID` slots; `fox::trusted_entities`,
`add_trusted_entity` (first free slot, then replacing the second),
`clear_trusted` and `trusts` are the entity's own methods by name.
- The save writes the real list (`Fox.TRUSTED_LIST_CODEC`: int arrays) and
`mob_from_nbt` clears and rebuilds it, as `Fox.readAdditionalSaveData` does.
- `AVOID_PLAYERS` no longer flees a player the fox trusts, which is what the
row meant by a bred fox still running from you; `AvoidEntity::player_when`
takes the avoider so the predicate can ask. A fox spawned from a spawn egg
trusts the player who used it (`Fox.onOffspringSpawnedFromEgg`).
Tested without prepared game data:
`mobs::fox::trust_tests::a_fox_trusts_two_players_and_the_third_replaces_the_second`.
Still missing from the row: the breeding hand-off (needs a saved love cause,
which the mobs do not keep) and `DefendTrustedTargetGoal`; both are in the
note of the map rows.
Co-Authored-By: DeepSeek Harness (deepseek-v4.1-flash) <[email protected]>
A data file with neoforge:conditions loads only when every condition
holds, and is silently absent otherwise, as NeoForge's ICondition leaves
it out of the manager. The gate covers the recipe loaders - crafting's
three kinds and the mods' own types, the stations, brewing, and the
recipe book - and the packs' overlays over them; mod_loaded asks which
mods' data is in the root (minecraft, always, and the content mods
that are on), and neoforge:true, not, and and or combine. A condition
the game does not know leaves the file out: a future shape fails
closed. Ported modpacks already carry such recipes verbatim (the carry
step keeps them), so cross-mod recipes stop loading blind.
Co-Authored-By: ZCode AI agent (GLM-5.3)
The one conflict was docs/api/server.md's changelog: main's longer management protocol line and the RCON line are both kept.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
BucketItem.emptyContents ends an evaporation with ServerLevel.sendParticles
(LARGE_SMOKE, the block's corner, 8, spread 1, speed 0, ALTERNATIVE).
Horizon played the hiss but sent no smoke for a player's bucket, and the
dispenser drew its own smoke one block too wide (nextDouble * 2 - 1 around
the corner instead of nextDouble from it) and sent none to other players.
evaporation_smoke does both: the window's puffs and the packet, now with
its randomization type (QueuedParticle.randomization). A client receiving
LevelParticles now honors ALTERNATIVE and ALTERNATIVE_WITH_SPEED, as
ClientPacketListener.handleParticleEvent does: positions spread over
[0, dist), speeds kept or scaled by nextDouble.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A sampler named like a function (Bloop's `uniform sampler2D texture;` read as
`texture(texture, uv)`) is the function where it is called, and the sampler elsewhere.
- A local named like a sampler, or like a sampler parameter, is in scope only after its
initializer (GLSL 4.60 4.2.2): `float depth = texture(depth, c).x` still samples the parameter
`depth` (Potato's depth-aware blur).
- A parameter's `const` (`const in float x`), which naga's parser refuses, is dropped: it only
promises the function doesn't write the parameter.
- naga's frontend nests arrays of arrays the other way round (`float x[3][2]` comes out as 2
arrays of 3; also `float[3][2]`, struct members and parameters), so indexing reads the wrong
element or fails validation. Two or more dimensions in a type are written reversed, so the
frontend's reversal restores GL's order; accesses are untouched.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
enable-rcon, rcon.port and rcon.password were read from server.properties but
nothing listened. The supervisor now serves vanilla's RCON (RconThread and
RconClient in 26.3): it listens on server-ip (every address when empty) and
rcon.port, logs in with the password, and runs each command through the same
console request as `horizon-server cmd`, so it answers while the game restarts.
Settings follow RconThread.create (no password or a port outside 1 to 65535
leaves it off with vanilla's warning; a port that can't be bound is a warning).
The packets follow RconClient: login 3, command 2, replies in packets of 4096
characters, -1 for a refused login, "Unknown request <hex>", and "Error
executing: <command> (<reason>)". Differences: packets are read from the
stream, the password is compared in constant time, a connection that hasn't
logged in within 30 seconds is closed, and at most 16 are open.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Grass blades a few texels wide at 2048 average to about the same alpha everywhere by 64, and no scale of that alpha keeps the coverage, so the alpha test passes the whole quad or none of it. From 64 texels up, each level below the base takes the mean alpha through an ordered dither, about as many texels in as the base has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
A new cart kind (spawner_minecart, made by /summon: there is no item) that
shows a spawner block, drops and is picked as a plain minecart, and saves and
loads the BaseSpawner fields. Its serverTick is the spawner block's, with
broadcastEvent as an entity event on the cart.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
longbridge/gpui-fast: Zed's GPUI with retained views and cached scroll layers.
The API changes are mechanical: focus and blur take the App, the application
comes from gpui-fast-platform (wgpu on Linux), menus say whether they're
disabled, shadows whether they're inset. oo7 follows to 0.6.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client helper that plays a sound event, vanilla's or the mod's own
from its assets' sounds.json, for this player only and up to a minute
later (SoundEngine.playDelayed), so a mod can time a blast's sound to
its shock wave. Audio's distance delay now goes through the same
play_after.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
A server mod's helper that throws a falling block at a velocity: it
flies, lands and places itself as sand does, or drops as an item, and
can hurt what it lands on as an anvil does. For explosion debris and
collapsing builds. Refused on a client, for another dimension, air, an
unloaded chunk, values that aren't finite, or past 1024 falling blocks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
A client hook asked once a frame for eight vec4s that every stage of a
Horizon shader pack reads as frame.mods, so a native mod can tell the
pack it bundles where and when something happened (an explosion's
centre and age for a cloud ray marched in a post pass). Values that
aren't finite read as 0; all are 0 when no mod sets them. The
reference is regenerated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
boot_paint.rs draws the boot screen with GDI (Win32_Graphics_Gdi) and reads the
window's size (Win32_UI_WindowsAndMessaging), which horizon-threads didn't turn on.
A client hook asked each frame for turns in degrees and an offset in
blocks, which add up across mods. Horizon scales the sum by Distortion
Effects (0% turns it off), bounds it to 10 degrees and half a block on
each axis, and multiplies it into the view after the hurt tilt and view
bobbing. It moves what the player sees, not where they aim.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
Main's 31c1212bf added 17 strings to seven tables but not to ja, pt-br or the new tr, which
fails the every-language test on main. They are translated here as in the PR to main that
fixes it, so the lines are the same. tr.json also gets this branch's 26 account strings and
drops "Microsoft account · signed in", as ja and pt-br did.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82d7UpxGuCncnLpJsq
A ResolvableProfile can carry a `texture` id (PlayerSkin.Patch body, a
ClientAsset.ResourceTexture). Vanilla draws a player head with that
resource-pack texture instead of the downloaded or default skin; Horizon
ignored it and drew the profile's default skin. Placed heads and head
items now read the patch, loading non-vanilla namespaces' textures from
the resource packs into a skin slot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01P1ZTfB3AfwKgGnv7UFKYVD
A server mod's helper for Level.explode with no source entity: blocks
break as NativeBlast::Keep, Block or Tnt say (the drop decay rules),
entities are hurt and pushed, fire is optional, and players near hear
and see it. Refused on a client, for another dimension, an unloaded
chunk, or a radius outside 0 < r <= 32.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XnU6QSxLfzUbudHhcvZtgy
boot_paint.rs imports Win32_Graphics_Gdi and Win32_UI_WindowsAndMessaging,
which stopped resolving after #4441's Cargo.lock pass; main does not
compile. Also the test's language map resolves the keyed refusal.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
refresh_account and renew_for_game_with take the accounts.json a renewed profile is saved to.
The commands pass the launcher's own (Accounts::path()); the tests pass one that is never
there, so they no longer read or write the developer's launcher data. A new test checks that
a refresh saves the services' newer profile and leaves an unchanged one unwritten.
Main added pt-br.json and ja.json after this branch's strings: both get its 26 account
strings, and lose "Microsoft account · signed in", which the code no longer asks for (the
other tables dropped it already).
With main merged: profile_heads' test uses Accent::VIOLET (main made Accent a hue), and
docs/launcher-commands.md is regenerated for main's `completions` command, which main had
not written into it (the reference test failed on main too).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JLHp82d7UpxGuCncnLpJsq
native_block_entity_submit runs once a frame for each mod block entity in
view, as vanilla's block entity renderers do (64 blocks, frustum, chunk
fade-in), with a pose stack and helpers that add to the entity batch:
quads, model-element boxes baked as FaceBakery does, and block models
(submitBlockModel). Content mods' textures/entity join the atlas.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main moved the launcher's completion checks into horizon_prepare::missing_extras, which
--check and COMPLETIONS share, so lacks_panorama and its test live there now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main added Japanese and Portuguese (Brazil) since this PR's keys were
written; the i18n test wants every key in every language.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
rdbtCVS's two review points on #4250:
- The launcher's own status workers (off the UI thread, showing "Waiting for
account access…") take the lock without the 3 s bound, so a worker queued
behind another account's renewal no longer shows a false "Could not check
sign-in". account list and MCP keep the bounded wait.
- refresh_account saves a renewed profile (new name or skin) to accounts.json
as launch() does, and the UI reloads accounts and profile heads when a
status arrives and the saved list changed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Only the shadow passes write the shadow targets, so while the key matches they
still hold the map (and the A/B side shadowcomp left current): no cache
textures, no copies, and no copy into shadowcolor buffers made without
COPY_SRC/COPY_DST (a wgpu validation panic with any pack using shadowcolor).
pass-timing leaves this PR (unrelated).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- audio.rs: main's shared-output fields (the device tracker's, f80c0360d).
- A guest in Window 1 leaves its F11 to P1: the game's window follows P1's option.
- disable() shows Window 1 again and says so (window1_shown), so a later
split into Window 2 hides it again.
- Clicks, key presses and the wheel from a window the focused player isn't
in no longer reach that player; releases still pass, so no key sticks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The page stays closed after its last mesh moves, so the frame's uploads can't
refill it before trim returns it (trim clears closing). Pages record whether
their buffer went to system memory: moves only go to video-memory pages
(alloc_moved), and sparsest empties a system-memory page first. Tests for both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The forged reply was rejected on its empty biome palette and out-of-range
spans before the cap; it now names one biome and keeps every span inside
HEIGHTS, and asserts the cap's error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: main's lan login fallback (an auth failure only warns on LAN) kept
with this branch's keyed reasons; reconnect tests take both the lan flag and
the reason's key.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
network_stack (integrated-server and LAN inventory sync) encoded custom_name
and item_name from StackData.other but skipped minecraft:custom_data and
minecraft:custom_model_data, so item models' tints/selectors and client-side
custom_data reads saw nothing for a synced stack. Push both components, built
from the saved tag the same way vanilla's DataComponentPatch carries them;
item_sync's decode side already mapped both back.
Co-Authored-By: Claude Sonnet <[email protected]>
Kept PadRouter's state_of with main's stick_axes, both Flags fields, and filled
main's audio device-tracking fields in Audio::share.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts: integrated.rs keeps both Flags fields (split_players, load);
pad/hardware.rs keeps the PadRouter split and moves main's stick_axes
mapping into state_of. A shared Audio gets main's device-tracking fields
and leaves reopening the output to the game's own Audio.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
main's audio device tracking (#default_seen, devices_seen, device_ticks) added fields
the shared Audio built by share() now fills.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The auto-reconnect's `allowed` matched English keywords ("ban", "server
closed", "online mode"...) against the disconnect's resolved text. The
texts arrive resolved in the player's language — a vanilla server's
"Server closed" is `服务器已关闭` to a zh_cn client, so a legitimate retry
was refused, and Horizon's own refusals (NOT_SIGNED_IN, the invalid
address) could only stay English for the matcher to see them.
The disconnect's translation key now travels with the text: the net
thread's reasons carry it (`Reason { text, key }`) from the server's
component's `translate` field, the session-service refusals' inner keys,
and Horizon's own keys (new `horizon.multiplayer.not_signed_in`,
`invalid_address`, `connected_friend_direct/relay` in all six bundles;
the friend connection's status texts and the local world-open progress
line move to keys too). `allowed` decides from a key list first — the
keyword scan stays as the fallback for plain reasons (OS errors,
servers' literal kicks in the server's own language), whose behavior is
unchanged.
NOT_SIGNED_IN is localized now that its classification is key-based, and
`load_language`'s no-language fallback picks Horizon's bundled strings up
so the net thread's keys resolve there too.
Co-Authored-By: GLM 5.3 (ZCode) <[email protected]>
Review fixes (kacpherek on #4947):
- a refused, lost, timed-out or unreadable tile comes back as no columns,
and Lod asks for it again 2 s later instead of keeping it empty;
- sampling threads queue their requests and the game thread sends them
each tick with its other packets, so they no longer take the
connection's writer; requests queued when the far terrain ends are
dropped with their slots;
- docs and the e2e: horizon-lod-distance is off unless set.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The last step: a Horizon client of a dedicated server or of a LAN world
(not of its own server thread, which hands its generator over directly)
draws far terrain when the server offers it. Its far terrain's samplers
each send one tile request and wait for the reply, which the network
thread hands straight to them (lod_net::Inbox), keeping to the server's
limits (requests waiting, tiles a second) so a well-behaved client is never
refused. LOD Distance stops at the server's distance. A new dimension, a
reconfiguration or a disconnect cancels the waiting samplers at once.
tools/server_lod_e2e.py checks it on a real dedicated server: with
horizon-lod-distance=16, a client with LOD Distance 32 gets the server's
far terrain, capped at 16 chunks, and draws far tiles; with 0, none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes (kacpherek, dimaniojk on #4946):
- the worker hands finished replies back to the game thread, which sends
them before the tick's flush_all, so they keep their place among the
game's packets;
- a slot frees when the reply leaves, and replies wait while the player's
connection has more than 1 MiB pending;
- every request takes a token, refusals included; unreadable or over-rate
requests are strikes, and 64 strikes disconnect the player;
- a panic while sampling drops that level's sampler and refuses the tile,
so every job still gets a reply;
- only clients with the horizon brand (the ones offered it) are served;
- a level with no far terrain refuses instead of leaving the client waiting.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second step: a dedicated server, or a LAN world, offers far terrain to
Horizon clients while they configure (vanilla clients hear nothing) and
answers their tile requests. `horizon-lod-distance` in server.properties
(Horizon's own key, 64 chunks by default, 0 off) bounds how far; each
player may have 8 tiles waiting and ask for 64 a second (a burst of 256),
and a tile past the distance, over the limits or of the wrong shape is
refused at once. One "Far terrain server" thread, started at the first
request and behind the ticks (horizon_threads::behind_server), samples
them from the player's level for every player in turn, and sends each
reply to its player itself. Clients don't ask yet (the next step).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
horizon-lod-distance now defaults to 0: far tiles reveal the generator's
terrain beyond view distance, which helps crack a seed. The offer starts
with a protocol version. A reply over 512 KiB or 16 384 spans, with spans
out of order, overlapping, empty or out of the height range, is an error
before it allocates; the server refuses such a tile instead of sending it.
(Real tiles measure 4-6 spans a column.)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first of three steps that let a Horizon client get its far terrain
(LOD) from a dedicated server or a LAN world, whose generator and seed it
doesn't have: the offer a server sends Horizon clients in configuration,
the tile request, the reply (columns with biomes by name, block states by
id, or a refusal), the per-player check of a request's distance and shape,
the token bucket both sides pace with, and the `horizon-lod-distance`
property's reading (64 chunks by default, 1024 at most, 0 off). Nothing
sends or reads them yet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
"thickness" (2 to 14 pixels) gives a connecting block its own outline and
collision, built as PipeBlock builds its shapes (pipe_shape, checked against
the chorus plant's 64 states). The shapes go through the same per-state path
as a block's "shapes", so a block sets one or the other.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A block declared with template minecraft:chorus_plant and connects_to: a
block tag is vanilla's PipeBlock without the chorus plant's rules: no
support needed, and each of its six sides joins the same block or a block
in the tag, on placement and on every neighbour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Like a server's streaming, a headless game's doesn't mesh sections it can't draw. Measured
on a single-player world after loading: 0 sections meshed instead of 21287, 853 MB resident
instead of 1117 MB, and about 15% of a core in steady state instead of 20%.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`horizon-client --headless` (`horizon-ctl launch --headless`) runs where no display server
is: winit's event loop needs one even for --offscreen, so the game runs its own loop
(game/headless.rs) through the EventLoopCtl handle, one frame a tick. Each step is
Game::idle, which runs the frame through the RedrawRequested path with WindowId::dummy();
handle_window_event takes that id when there is no window. No renderer is made and audio
is silent; the loading screen is skipped (synchronous startup). It counts as offscreen for
everything else. Quitting saves the world as a window's game does (Game::shut_down, now
shared with winit's `exiting`).
Measured on a Ryzen 7 3700X with no DISPLAY: a single-player world opens through the
server thread, the control socket moves, commands and menus work, no GPU device is opened,
about 1.1 GB resident, and in steady state the client's own thread uses 1.6% of a core.
Screenshots and clicks at GUI points answer "no renderer yet" until vision (#4951).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Right past the render distance, far terrain showed as big flat-topped
boxes with grey stone sides: coarse ancestors standing in while the
sampling order filled a ring ahead first and refined broad tiles every
other job. Main now samples only the plan's leaves, nearest first; this
pulls that order into `sample_order` (no change) and tests it:
- At render distance 12, cells are 1 block wide to 256 blocks out and
2 to 512 (Reduced: 1 to the near terrain's edge, 2 to 256).
- Loading one job at a time, the ring within two chunks of the near
terrain reaches that detail all around after 270-301 jobs, all fine
tiles (7500-8500 density lattice points). The old order took
54000-119000 for the part in view alone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A window that failed to open was tried again every frame, each time
re-laying out every player. Its players now play in Window 1 and the
window is forgotten.
- Vsync and the frame pacing's refresh rate follow the first shown
window, not a hidden Window 1; Window 1's visibility is kept in a
field, so Wayland (which can't report it) isn't told every frame.
- Only free windows remember where they were: a monitor's rect made a
later free window with its number open monitor-sized.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
route_pads released P1's pad hardware every frame, so with P1 alone the
reader lent for P1's turn lost its rumble and DualSense link a frame
after they started. It now releases only when nothing is lent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A controller first pressed with a trigger got a player, and the held
trigger then attacked or used: only buttons were held back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A LAN player who logged in under a registered seat's name (P2) passed
the integrated server's gate and the full-server check and held the
owner's permissions given to that seat. The gate now admits a seat's
name only from the memory connection (MEMORY_ADDRESS, port 0).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A refresh or a launch check holds the account lock across network calls.
Status reads (account list, the account page) now wait at most 3 s for
it, then say another operation is running, instead of waiting out a
network timeout. Writers still wait.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Translate the account status messages, print a failed `account refresh`
once, fetch a profile head again a minute after its skin download failed,
and check every account's sign-in at most once a minute on page switches
(an account added meanwhile at once), so switching pages no longer reads
the keychain each time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Default off in horizon-graphics.txt. When on, reuse the last shadow map only if
sun/view, world revision, day time, ticks, pack uniforms and settings match,
and skip the cache while entity shadows are drawn.
Co-authored-by: Cursor <[email protected]>
The shadow-cache branch was cut from an older tree and looked like it removed
the published benchmarks folder; put those files back from current main.
Co-authored-by: Cursor <[email protected]>
`account session WHO` renews an account and hands the running game a new
read-once session file, or the public status when it needs a browser
sign-in or a retry. `account sign-in --for WHO` runs the UUID-bound
re-sign-in from the command line. The launcher passes its own path to the
game as HORIZON_LAUNCHER_EXE. `account session` is kept out of MCP: its
file holds a token.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fetch public skin heads on workers with a bounded, URL-keyed cache and UUID default fallback, report each account operation's actual phase, and bind Sign in again to the account's UUID so a different Microsoft account is rejected before any credentials are stored.
Co-Authored-By: Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Show ready, refreshable, and sign-in-required states using the theme's success, warning, and danger colors, retaining icons and full labels.
Co-Authored-By: Codex <[email protected]>
Compare sign-in states directly instead of comparing a state to an Option's debug output, which continually restarted every account's status checks.
Co-Authored-By: Codex <[email protected]>
Inspect and renew due sessions on workers, expose Refresh and Sign in again actions, and share the operation through CLI and MCP for future launcher-to-game integration.
Co-Authored-By: Codex <[email protected]>
Twenty-nine literals were drawn straight into the window, so the tables never
had them: the game arguments and proxy rows with their explanations, every
text field's placeholder (one place now), Manage versions, the launcher's own
update lines, the release and patch-note empty states and the sponsors line.
Russian in ru.json; the other six tables carry the English source.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
Review: device_event turned P1's GUI scale on for every mouse motion even with no other
player; it now hands the event to P1 directly while there is no compositor.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Upstream moved the title actions into title_action (the split guest's Quit guard goes there),
added the server's online mode beside our memory-seat limit bypass, the audio output's device
name (a shared output carries it), and dropped code mods' key hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A camel's legs used the default walk: they kept swinging while it sat or dashed and eased in at 0.4. Camel.updateWalkAnimation aims at 6 times the distance moved (at most 1) only in the standing pose with no dash animation running, otherwise at zero, and eases at 0.2. The remote client's own walk update uses it for camels too.
A leashed sitting camel stayed down however hard the lead pulled. Camel.onElasticLeashPull stands it up when it is sitting, not changing pose, and the standing box fits where it is (canCamelChangePose, wouldNotSuffocateAtTargetPose). AbstractHorse.onElasticLeashPull, which it calls first, also ends a horse's grazing.
A horse that landed from a small hop while still rearing ignored the keys until the rear ended, and a donkey or mule never reared on a jump. AbstractHorse.onPlayerJump sets allowStandSliding and calls standIfPossible (every horse that can rear), getRiddenInput only zeroes the input while the horse is standing on the ground without a jump pending and without allowStandSliding, and tick clears it once the horse stands no more.
A horse, donkey, mule or undead horse that reared kept its rider at the fixed seat. AbstractHorse.getPassengerAttachmentPoint adds (0, 0.15, -0.7) times the previous tick's stand animation (standAnimO) to the seat, turned with the horse. The player and a mob rider both slide.
The rider's seat on a camel was a fixed height for sitting and standing, built from the old full-height box, and a mob rider sat at the top of the box when the camel sat. Camel.getPassengerAttachmentPoint takes the body anchor from the current pose box and lerps it through the sit and stand transitions (getBodyAnchorAnimationYOffset), at the camel's age scale. The player and a mob rider both use it now.
A camel that sat down kept its standing box, so it blocked and was hit as if it still stood. Camel.getDefaultDimensions gives Pose.SITTING an adult box 1.43 lower with eyes at 0.845, and a baby box 0.425 tall with eyes at 0.41. Mob::awake_dims now follows the camel's synced pose, on the server and on a client.
A llama landed with the generic fall sound and hurt on any damaging fall. Llama.causeFallDamage makes no fall sound, and hurts the llama and hands the fall to its riders only when the fall is six blocks or more.
A rider took no damage when the mob under it landed hard, and a mob on a mount's back never felt it either. Entity.propagateFallToPassengers hands the fall to every passenger (LivingEntity.causeFallDamage first, AbstractHorse.causeFallDamage after its own hurt and only when hurt), and each passenger runs its own causeFallDamage with its own safe distance. A mount now raises a fall event the game turns into Player.causeFallDamage for the local player and server players, and the mob causeFallDamage for a mob rider.
AbstractBoatRenderer.submit turns a boat that is not under water about the axis (1, 0, 1) by the bubble angle when it is not 0 (Mth.equal, 1e-5), after the hurt tilt and before the model's flip, so a boat wobbles while a column works on it.
AbstractBoatRenderer.extractRenderState copies AbstractBoat.getBubbleAngle(partialTicks), the wobble lerped between the last two ticks, into the render state. boat_geometry now reads it the same way (the drawing follows in the next commit).
The server decides when a column throws or pulls a boat, but the boat its player drives moves on that player's client, so the server's own change does nothing there. AbstractBoat.handleEntityEvent now takes entity events 71 and 72 and runs handleBubbleColumnEffect, which the client applies to the boat it drives.
Boats never rose or sank in a column and a down column let every rider stay. AbstractBoat.tickBubbleColumn now counts the bubble time down on the server while the boat stays above the column, then ejects the riders (down only), broadcasts entity event 71 (down) or 72 (up) and applies the throw or pull. A server's player is let out through guest_vehicle_tick. Clients run the other half: the wobble multiplier grows by 0.05 a tick while the bubble time runs and fades by 0.1 otherwise, and the angle is 10 * sin(0.5 * tickCount) * multiplier.
A boat in an up or down column never changed its vertical speed. AbstractBoat.handleBubbleColumnEffect sets it to 0.6 for an up column and subtracts 0.7 for a down one, on the side that moves the boat (canSimulateMovement).
A boat above a bubble column, with nothing over it, had no bubble state and never splashed. AbstractBoat.onAboveBubbleColumn now marks the boat as above the column with its direction, starts the 60 tick bubble time when it is 0 and, one time in a hundred while the boat is not under water, posts the splash game event on the server and plays the splash sound and particle on the client (it does not push the boat, unlike Entity.onAboveBubbleColumn).
A boat inside a bubble column, with water above it, kept its speed. Entity.handleOnInsideBubbleColumn now moves it up to 0.7 or down to -0.3 (AbstractBoat does not override it), twice a tick as AbstractBoat.tick calls applyEffectsFromBlocks twice. Only the side that moves the boat applies it (canSimulateMovement): the server for a boat nobody drives, the client for the boat its player drives. Boats the window does not tick also get their status refreshed, as getStatus runs on every boat.
Boats had no AbstractBoat.DATA_ID_BUBBLE_TIME (entity data entry 13). The server now sends it with the other synched boat data, and sends 0 again once it drops back, and a client keeps it on the boat.
bubble_column_push found the columns a box touches and pushed in one go, so nothing else could ask which column an entity is above or inside. bubble_columns_touching now returns each touched column with its drag and whether nothing sits above it (BubbleColumnBlock.entityInside); bubble_column_push uses it and behaves as before.
Part 9 of the splitscreen series (#3541), on #3801. Each player plays in a window: Window 1
(the game's), Window 2, 3... opened for the players put there, or a borderless window filling
a monitor (over the menu bar on macOS, the system's borderless fullscreen elsewhere). Players
in one window share it. Closing a player's own window drops its players; the game's window
hides while nobody plays in it; titles say who plays where; a window reopens where it was.
Another player's F11 toggles its own window. HORIZON_SPLIT_WINDOWS (testing, until the Setup
screen) says each seat's window: `1,1,2`, or `m2` for monitor 2.
Live: tools/splitscreen_e2e.py adds `windows`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 8 of the splitscreen series (#3541), on #3794. While splitscreen watches the controllers
(HORIZON_SPLIT for now; the switch comes later), one reader serves every player, alone too:
the first controller used is P1's (its rumble and DualSense output lent to P1's turn). In P1's
world another controller pressing a button is offered to join on every view: A joins (as
P<seat>), Y drives P1 when P1 has none, B says no, 10 s and it goes. A controller that
disconnects leaves its player dimmed and asked: the same controller back takes it again, A on
another takes over, B lets it leave. Each view gets a short badge (who, and why: "Keyboard
and mouse" when it takes them).
Live: tools/splitscreen_e2e.py adds `alone` (watching, nobody joins).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Windows the game horizon-ctl launch starts inherits horizon-ctl's stdout pipe, which then
never closes while the game runs: subprocess.run waited forever. No pkill there either.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 7 of the splitscreen series (#3541), on #3777. While several play, one controller reader
(PadRouter) serves every player's game: a controller nobody has goes, when pressed, to the
first player without one (P1 first); its first press is not passed on; a controller that
disconnects leaves its player without one. Each player's glyphs follow its controller.
Alone, P1 reads the controllers itself, exactly as without splitscreen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Part 6 of the splitscreen series (#3541), on parts 1 (compositor), 2 (audio), 4 (pointer
ownership) and 5 (layout). With HORIZON_SPLIT=N (testing only, no switch yet), N-1 more
players join P1's world through memory as P2, P3..., each its own Game in this process,
rendered offscreen and composited into P1's window. Keyboard and mouse go to the view
clicked last. A player who quits or disconnects drops out; alone again, P1 draws on its
window as before. Guests never open the controllers (pad.feed); routing comes next.
Live: tools/splitscreen_e2e.py (join, four).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Add ShadowCache struct to store rendered shadow maps between frames
- Cache keyed by sun/moon angle, camera position, and shadow settings
- On cache hit: copy cached textures instead of re-rendering shadow pass
- On cache miss: render normally and store result in cache
- Add pass-timing control command for shader pack GPU profiling
- Expose ShadowTargets fresh/cleared fields for cache access
Benchmarks (seed 1234, render distance 16, time 6000):
- Avg FPS: +23-45% across biomes
- 1% low FPS: +85-167% improvement
- Shadow pass time: ~90% reduction on cache hit
The terrain vertex arena only freed a page once every range in it was free, so after a render distance change or far tile churn it kept pages that were mostly empty. On a 2 GiB card the next allocations then spilled to system memory and frames slowed to a crawl. When at least 128 MiB and half of the arena is free ranges, copy the meshes off the sparsest page into the other pages (32 MiB of GPU copies a frame) and let trim free it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-28 21:50:14 -03:00
669 changed files with 51970 additions and 6382 deletions
@@ -39,7 +39,8 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| Crate | Owns |
|---|---|
| `horizon-client` | The game: a lib (`src/lib.rs`, ~100 flat modules: gameplay, networking, title screens) and the bin (`src/main.rs`→`game.rs` + `game/`), plus `horizon-ctl` (`src/bin/horizon-ctl/`). Also the dedicated server (`--dedicated`) |
| `horizon-client` | Gameplay library (`src/lib.rs`: `game.rs`+`game/`, `entry.rs`) and thin executable (`src/main.rs`). Also the dedicated server (`--dedicated`). Re-exports `horizon-client-lib` for existing mod paths. |
| `horizon-client-lib` | Reusable client systems, `horizon-ctl`, probes and their tests. Its manifest points to the existing sources under `crates/horizon-client/`; the library root is `src/client_lib.rs`. It never depends on gameplay. |
| `horizon-command` | Command parsing (Brigadier): the vanilla tree, argument types, selectors, suggestions, the tree's network form. Re-exported as `horizon_client::command` |
| `horizon-client-core` | The client lib's bottom layer: `text.rs`, `lang.rs`, `snbt.rs`, `nbt_path.rs`, `component.rs`, `scoreboard.rs`, `bossbar.rs`, `gamerules.rs`, `block_entity_nbt.rs`, registry/particle/sound tables. Re-exported under the old paths (`horizon_client::text`...) |
@@ -56,6 +57,7 @@ Game workspace (root `Cargo.toml`); `launcher/` and `crates/horizon-telemetry-se
| `horizon-friends`, `horizon-relay` | Play with Friends: invite codes, encrypted tunnel, the relay ([docs/play-with-friends.md](docs/play-with-friends.md)) |
| `horizon-mods`, `cargo-horizon-mod` | Content mods (`.hmod`: manifest, content fence, no code), the registry index and `.hpack`, and `cargo horizon-mod` (`new`, `build`, `run`, `check`, `registry`) ([docs/mods.md](docs/mods.md)). Code mods are native, built by the launcher ([docs/native-mods.md](docs/native-mods.md)) |
| `horizon-optifine` | OptiFine-format pack files below every feature that reads them: `.properties`, the name/range/block matchers, paths and `optifine/` folders, and custom entity models (`cem/`: `.jem`/`.jpm` parsing and baking) |
| `horizon-shader-api` | Horizon's own WGSL shader packs ([docs/api/shaders.md](docs/api/shaders.md)): `horizon-shaders.toml`, the `.hshader` package; shared by the game, the launcher and the registry |
| `horizon-shaders` | Shader packs (the `shaders/` folder format), rebuilt clean-room from a written spec of the format; no wgpu |
| `horizon-theme` | Design tokens and fonts for the launcher, the game's own menus and the website |
@@ -83,8 +85,8 @@ reads `README.md`, `launcher/links.json`, `docs/api/`, `roadmap/roadmap.json` an
```sh
python3 tools/pr_ready.py # the self-check: fmt, clippy -D warnings, touched crates' tests, harnesses; paste its block in the PR
python3 tools/pr_ready.py --quick # same without harnesses
cargo test -p horizon-client --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo test -p horizon-client -p horizon-client-lib --lib --bins --tests # skips the 104 examples (pr_ready does this); `--doc` separately
cargo test -p horizon-client-lib --lib mod_content::tests:: -- --ignored # the 5 slow content-root tests, #[ignore]d; pr_ready runs them when mod content, assets, blocks or items change
cargo build --profile fast -p horizon-client # optimized, quick to link: use it to play or run lan_e2e
python3 tools/parity.py --changed origin/main # the harnesses your branch can affect (CI's parity job)
python3 tools/parity.py --changed-files paths.txt --list # which harnesses cover these files, without running them
@@ -101,8 +103,11 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
or the game binary with a fixture env var (`game/dispense_fixture.rs`, `game/sculk_fixture.rs`,
`game/command_harness.rs`, `game/net_harness.rs`). Copy a nearby harness for a new one.
- Probes build into `.local/parity-target/`; harnesses find binaries via `lab.cargo_target()`, never `target/`.
-What a change reruns (of 329 harnesses, measured 2026-10-23): a file of the game binary (`game.rs`, `game/`)
~25, a file of the clientlib ~103, of `horizon-world` ~103–109, of `horizon-core` ~119. Plan for it.
-Harness selection uses Cargo dep-info and reachable-code narrowing (`tools/parity_reach.py`).
`horizon-client-lib` builds the control tool and client probes without gameplay; their dep-info
excludes `game.rs` and `game/`, so gameplay edits do not rebuild or select those probes.
Source paths remain in `crates/horizon-client/`. Measure actual harness selection on refreshed
records with `tools/parity.py --changed-files paths.txt --list`; historical counts are not a guarantee.
- Linux needs `libasound2-dev libudev-dev`; `--no-default-features` drops audio and controllers (the server build).
- Run and drive the game (starts the `horizon-client` beside `horizon-ctl`; other arguments go to the game):
@@ -127,8 +132,11 @@ python3 tools/agent_control_e2e.py # horizon-ctl / MCP end to end
## horizon-client: where things live
`L:` = lib (`crates/horizon-client/src/`, or `crates/horizon-client-render/src/` for the modules that crate holds, under
the same `horizon_client::<module>` paths), `G:` = bin (`crates/horizon-client/src/game/`). The lib can't see
`Game`; bin code reaches the lib as `horizon_client::<module>`.
the same `horizon_client::<module>` paths), `G:` = gameplay (`crates/horizon-client/src/game/`), also in the lib in normal builds.
Gameplay keeps its `horizon_client::<module>` imports through a self-crate alias. The executable
contains only the allocator, mod API marker and a call to `entry::main`. For unit tests, the lib
excludes `entry` and `game`, and the executable test target owns `game` so its process-wide test
state stays separate from the library tests. This compiles gameplay twice for executable tests.
| Subsystem | Files |
|---|---|
@@ -159,7 +167,7 @@ the same `horizon_client::<module>` paths), `G:` = bin (`crates/horizon-client/s
- `struct Game` (game.rs, ~290 fields) holds everything. Its methods are spread over ~180 files: game.rs keeps
the core (tick, render, input, inventory, world open/close) and each `game/<feature>.rs` adds an
`impl Game<'_>` block for one feature, with its own state types. Fields are private to the bin.
`impl Game<'_>` block for one feature, with its own state types. Fields stay private to the gameplay module and its descendants. Gameplay tests run in the gameplay library, separate from reusable-library tests.
"Shows your name above your head in third person, as other players see it. Hidden when the server hides your team's names or you're invisible."
}
Item::HideBlockParticles=>{
"Hides the debris particles that burst from a broken block and crumble off the one you mine. Other particles stay."
}
Item::AutoReconnect=>{
"Rejoins a server after a lost connection or a restart, never after a kick or ban."
}
Item::UiSoundReserve=>{
"Sounds in the world leave 16 of the 247 sound channels free, so a flood of them (a big explosion) can't silence menu clicks."
}
Item::MouseTweaks=>{
"Right-drag places one per slot, left-drag gathers the held item, Shift + left-drag moves every slot."
}
@@ -471,6 +505,9 @@ impl Item {
Item::DiscordPresence=>{
"Shows Playing Horizon on your Discord profile while Discord runs: menus, single player or multiplayer, the dimension and the time played. Never a world's name or a server's address."
}
Item::Splitscreen=>{
"In a world, another controller can join as a new player: press A when it asks, then pick a profile and where to play."
}
Item::AgentControl=>{
"Your own AI agent can play this game through horizon-ctl or an MCP client, on this computer only."
"Frame rate and frame times over the game, with a graph.":"Fotogramas por segundo y tiempos de fotograma sobre el juego, con un gráfico.",
"horizon.multiplayer.finding_friend":"Buscando el mundo de tu amigo…",
"horizon.loading.opening_world":"Abriendo el mundo",
"horizon.loading.cancelling_world":"Cancelando"
"horizon.loading.cancelling_world":"Cancelando",
"horizon.multiplayer.not_signed_in":"Este servidor está en modo en línea. Inicia sesión con tu cuenta de Microsoft en el launcher de Horizon para unirte.",
"horizon.multiplayer.invalid_address":"Dirección de servidor no válida: %s",
"horizon.multiplayer.connected_friend_direct":"Conectado directamente con tu amigo…",
"horizon.multiplayer.connected_friend_relay":"Conectado con tu amigo a través del relé…"
"Frame rate and frame times over the game, with a graph.":"Частота кадров и их время поверх игры, с графиком.",
"horizon.multiplayer.finding_friend":"Ищем мир друга…",
"horizon.loading.opening_world":"Открытие мира",
"horizon.loading.cancelling_world":"Отмена"
"horizon.loading.cancelling_world":"Отмена",
"horizon.multiplayer.not_signed_in":"Этот сервер работает в онлайн-режиме. Войдите с помощью аккаунта Microsoft в лаунчере Horizon, чтобы присоединиться.",
"horizon.multiplayer.invalid_address":"Недействительный адрес сервера: %s",
"horizon.multiplayer.connected_friend_direct":"Установлено прямое соединение с другом…",
"horizon.multiplayer.connected_friend_relay":"Соединение с другом через релей…"
//! Selection cost only (not block simulation). Run with prepared client resources.
//! cargo run --profile fast -p horizon-client --example random_tick_eligibility_bench -- RESOURCES
externcratehorizon_client_libashorizon_client;
usehorizon_client::{
blocks::BlockFacts,
chunk::{Chunk,MIN_Y,Section},
Loaded 100 of 669 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.