The mockup's instance dialog names a new instance before the player types (defaultName): "My
Horizon" for Horizon, as the launcher already did with "Vanilla 26.3" or "Fabric 26.3". A new
Horizon instance opened with an empty name and "New instance" as its title. The dialog now
starts with the default name for every kind, and it follows the kind until the player types
one. The Horizon version rows say "Nov 1" as the mockup's, not "2026-11-01" (the Versions
page's day format). One new string, "My Horizon", in the 16 languages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
What the launcher just did shows for 4.2 s, as in the mockup: in the top
bar between the pages and the account on a window 1280 px wide or more,
right-aligned at 70 %; narrower, a glass toast along the bottom.
It says: Opened {folder} (every Open folder button), Copied {text},
Created / Saved / Deleted {instance}, a mod installed or removed, and
"Launcher settings are back to their defaults" after Reset to defaults.
Any handler can say something (glass_toast::say), even with only &mut App.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
⌘K (Ctrl+K elsewhere) opens the mockup's Palette over the dimmed
launcher: a 560 px glass card at 14 % of the height, a search line and
up to eight commands with their hints. Play each instance (only when
Play would; otherwise its page), Open each page and Settings, New
instance, Open the game folder, and from two letters, Install or Remove
the registry's matching mods in the selected instance. Arrows move,
Enter runs, Esc or a click outside closes.
Pages the top bar hides stay out of the palette (Page::in_top_bar).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The glass look's Mods page follows the mockup's ModsView: the title with
its tabs, the search and results in one plate, the picked project's card
(360 px) beside it. Registry rows end with downloads, Modrinth's with how
they run (Works, Partly, Doesn't run); the card shows Install in or
Remove from the instance, and keeps the last project when a search finds
nothing. The tabs are the page's kinds, CurseForge after them as soon.
Show everything, the source switch, filters, sort, the install target,
ratings, the support reason, GitHub native mods, imported modpacks and
the installed list stay, in the mockup's patterns.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Proxy and Game arguments are the mockup's stacked rows: the name and
note, then a 44 px field up to 560 px wide. The Game card gains the
mockup's Native mods row; native mods are always on, so it picks the
profile they build with, as the Mods page does. About opens with the
mockup's three tiles: Horizon (the version in use and whether it is up
to date, with Install when it isn't), the launcher (Check now, Restart)
and the data folder (Open); the logs folder and the applications menu
stay as rows under them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Settings > Launcher in the glass look gains the mockup's Glass (how much
white the plates take, 0 to 16 %) and Blur (0 to 48 px) sliders: the
look changes while dragging, settings.json keeps them on release, and the
plates' backdrop is blurred again when the blur changes. "Reset to
defaults" puts the card's options back as Horizon ships them: accent,
glass, blur, language, while playing and the opening, and here the
appearance and Home's background too, being in the same card.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Any accent but the violet tints the whole scene as the mockup does: a
layer of the accent mixed with grey (58 % in OKLab) over it with CSS's
`color` blend, so each colour keeps its luminance and takes the tint's
hue and chroma. The blend is the same sum on every colour, so the scene
applies it where each colour is painted: the baked sky, halo and ridges,
the runs, stars and fireflies of each frame, and the plates' backdrop.
The violet keeps the website's picture untouched.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Settings > Launcher's accent row in the glass look is the mockup's: its
seven swatches (the named accents, now in the mockup's colours), the
any-colour swatch, which shows the hue strip, and a hex field that takes
a colour as soon as one is typed. The glass look now wears the player's
accent: buttons, switches, ticks and the plates' tint, where it always
drew the violet. An accent can be any #rrggbb (settings.json, the CLI);
the light look turns its tokens to that colour's hue as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Who plays now (an 80 px head, the name in the display font, how they
play), the offline names as chips with a field to add one, and the
Microsoft card beside them with its white Sign in with Microsoft button.
The Microsoft accounts already signed in, the game's own name, each
name's skin and the sign-in's code and progress stay, in the same chips
and flat rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As the mockup's SettingsView: the list of sections on the left (its icons, the
current one in the accent), the sections as glass cards in a pane that scrolls on
its own under a still title, the list following the scroll (its scroll spy) and
taking the pane to a section. Rows inside the cards are its SettingRows: index_row
and section_title draw them while glass_ui::flat_rows builds a card. Data collection,
not in the mockup, is its own entry in the list. The Account card keeps today's rows
until the next PR.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each release on its own glass card as in the mockup's NotesView: its name and the
day it came out (the day at 55 %), its notes in 14 px at 75 % with square bullets.
The sponsor card stays above the notes (not in the mockup), on a glass card with a
quiet button.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each version on its own glass card as in the mockup: its name, Newest on the first,
the day and time it came out and the instances that play it, Install or the bin on
the right, and Install new versions automatically beside the page's title. Kept in
its patterns: Use and In use, the space versions take, Import a game build, native
and imported builds, install progress and errors, the confirmation before a removal.
Releases keep their publishing time (HH:MM UTC) for the line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These tabs aren't in the mockup; the glass look fits them into its patterns: settings
as its SettingRow (15 px semibold name, 13 px note at 60 %, the control 40 px away),
the switches with their names as SettingRows too, small card headings, player and
backup rows as its ROW text with 28 px heads, the Create dialog's stepper, and no rules
between rows. The light look keeps its rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The glass look's Create dialog follows the mockup's server modal: an aside with the
mode's picture (or the copied world's icon), the server's name and its rules, and a
main column with Name, World (new with a seed, or one of the player's worlds with
its icon, mode and size), Game mode cards, Difficulty, Most players, Who can join
(friends as chips, the first an operator) and Check Minecraft accounts, above the
EULA line and Create server. A focused empty glass field keeps its placeholder after
the caret, as the mockup's inputs do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mockup's ServersView in the glass look: the open server's glass (state,
name, numbers, Start or Restart and Stop, the console and its command line),
beside it the servers with Create a server, and how friends reach the open
one (on this network, over the internet through Tailscale, who is online).
The mockup always shows a server, so the first one opens.
Kept from the launcher, fitted into the mockup's parts: the web panel and
folder as small icon buttons in the glass's empty corner; Add a server
folder as a row of the list; crash count, the action under way, problems
with their fix, notes and settings waiting for a restart as lines in the
glass; Players, Settings and Backups on a glass under it, with Tailscale
invitations under Players; Sculk's banner (sponsor) on a glass card with
"Partner" and Hide. Every field is a glass field in dark.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In the dark appearance, the launcher's shared controls draw as the mockup's
parts, so the pages not rebuilt yet look like the rest: button() is PLAY,
QUIET or a text button; switch() the Toggle; segments() the server dialog's
difficulty strip; page_header() the PageHead; panels and night plates are
glass plates round at 16; the progress bar is the mockup's Bar; caps() labels
are the small 13 px headings. The dark theme's tokens become white at a few
opacities with no borders or bevels. The light appearance is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued (urgent: every native mod build fails on Nightly f42ef42, #8169). verify green, and a real native build of smp + voicechat-server passes the mixins step at this head. The only code change besides the attribute is control_mobs_at's json! split in two, same output.
Without horizon-client's #![recursion_limit], the one json! with every
key of control_mobs_at hit the macro recursion limit. Same output.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The launcher's horizon-mixins (ddd9700) treats an inner attribute it
doesn't know on the crate root as an unexpanded macro, so every native
mod targeting horizon_client failed with E_UNSUPPORTED_MACRO on Nightly
2026-11-03 (#8169). 59fe490 added the attribute; CI tells whether the
crate still needs it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. The run-parity job passed on this head (61 harnesses, management_protocol included: live get/set, invalid input, saved properties, restart all match vanilla). The `lan == None` nit can follow separately.
🤖 Agent: FlyingMoveControl now reads the flying_speed attribute, not the type's 0.4, so #7822's summoned parrot with flying_speed base 0 stays put (Horizon's drift was exactly 0.4 × 0.02 per tick). New head = main merged in, run-parity dropped: the old head's only failure was pack_screen on playhorizon (its .local has no lang files). parity-main checks command_exec.
🤖 Agent: random_tick's 2 oracle errors (failing since #7084) are stage-1 saplings that reach TreeGrower.growTree with no FEATURE registry; the probe reports X grow_tree for them. Only that exact pair is set apart; any other oracle error still fails. run-parity: random_tick, block_update, dispenser and 9 more pass.
Since #7084 an oracle error fails the block update harnesses. Two of
random_tick's 40,000 cases are stage-1 saplings whose random tick reaches
TreeGrower.growTree: the oracle's level has no Registries.FEATURE, so
lookupOrThrow throws, and the probe, which has no feature table, reports
X grow_tree. Neither side grows the tree, so the pair is counted apart and
not compared; any other oracle error still fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FlyingMoveControl.tick reads getAttributeValue(FLYING_SPEED). Horizon read
the type's default (0.4 for a parrot), so a parrot summoned with
attributes:[{id:"minecraft:flying_speed",base:0d}] still flew off. #7822's
command_exec "bird flap sensors" scenario summons exactly that and failed
30 Pos lines on parity-main. flying_speed is now a mob attribute like the
others: loaded, saved and read by the move control.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued after merging main. The conflict was in the extras switch list: main's resource pack browser and this PR's auras switch are both kept, and the three-state Others setting keeps its own value match. Cosmetics stay off by default.
🤖 Agent: queued. Grindstone and smithing menus have no data slot, so the cost snapshot now reads `data.first()`; the anvil-only `data[0]` below stays because the anvil always has its cost. The official server has run this fix (r6.1) since 05:45 UTC.
🤖 Agent: requeued after the pack_uniforms.rs test conflict with #8066 (both tests kept); the smoothing still uses OptiFine's tick half-lives at the 0.1 s a tick that Iris was measured to use.
Keeps both sides: main's resource pack browser switch and this branch's
auras switch and three-state Others setting.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: requeued after the generated_host.rs conflict. The file is gone again; build.rs generates it from native_living_refs.rs, which already carries #7965's doc change.
🤖 Agent: the biome uniform and BIOME_* constants number vanilla's biomes in Biomes' declaration order (forest 8, ocean 46), as measured in Iris with a probe pack, instead of by identifier; modded biomes follow after vanilla's 67.
🤖 Agent: a shader pack's centreDepth readback now also runs for frames handed to the submit thread, so centerDepthSmooth tracks the current frame instead of a value several scenes old; without a pack the after-submit hook is unchanged.
🤖 Agent: ambientLight now reads the dimension type's ambient_light (Nether 0.1, End 0.25, as in 26.3's dimension_type data), so shader packs stop seeing 0 outside the Overworld; the Nether's worldTime and playerMood follow too.
🤖 Agent: joining 26.1 servers behind HORIZON_OLDER_SERVERS=1 (off by default): packet, registry and entity-data translation one release at a time; a 26.3 join never runs any of it.
🤖 Agent: shader packs whose shadow program has a geometry stage now draw entities and block entities through it (Arc, Shrimple, Rethinking Voxels); shader packs only, off by default.
🤖 Agent: a minecart's rolling sound now loops (MixerBackend's attach always got looping=false) and a silent cart keeps its instance; the remote displacement-velocity hunk was dropped.
Only the anvil menu has a data slot (its cost). lan_stations read data[0] for every station with a result slot, so a click in a grindstone or smithing table panicked the server thread (index out of bounds: the len is 0), which crash-looped the official server on 2026-11-03 at 05:11Z (r6).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: leaving a world no longer waits for unvisited chunks' generation and decoration (only in-flight saves), so the server drop goes from ~3.6 s to ~20 ms. The dropped jobs are proto chunks that regenerate identically. abort_generation runs only on the exit paths, never on an autosave.
🤖 Agent: a player's lead now saves its real holder UUID, so after Save and Quit the owner (or a LAN guest) can untie it again, and the mob only follows and measures its rope against that player, not whoever is nearest. Next time, please /claim the issue first: #6876 was claimed by glitchsnake half an hour before this opened.
🤖 Agent: launcher-built native games compile again: the generated main.rs uses horizon_threads::ALLOCATOR like the game's own entry point, and a test pins the two lines together.
🤖 Agent: a shader array declared with an empty [] takes its size from its initializer, so packs with such arrays translate; main's .length() rewriting still runs for sized groups.
🤖 Agent: /tag and /data on rockets, tridents and falling blocks now write to the same fields tags_of reads, instead of reporting success and dropping the change; main's Cloud arm kept.
🤖 Agent: horses and llamas keep a head item and a body item at once, as vanilla's separate equipment slots do; #7844's /item and native-mod callers now use the plain head/body indices.
🤖 Agent: a dying player's XP now goes to a sculk catalyst in hearing range, as for mobs (no orbs then), and the killer gets kill_mob_near_sculk_catalyst; main's ServerPlayer.die stats still follow.
🤖 Agent: every entity kind keeps a join sequence, so ticking-order checks (the wind charge's push) compare join order instead of list position; main's new wind test TNT got its join slot.
🤖 Agent: test-only: the wither skeleton test now also matches MobEvent::BowShot, which perform_ranged_attack emits for a bow shot since #7395, so it observes the arrow again.
Measured frame by frame in Iris 1.11.7 with a probe pack that logs each
frame's values into a buffer it never clears: centerDepthSmooth halves its
distance to the centre depth every 0.1 s, eyeBrightnessSmooth every 1 s and
is rounded down, wetness every 60 s rising and 20 s falling. Each is the
default half-life in ticks at 0.1 s a tick, twice what Horizon used.
Fixes#8062
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Iris 1.11.7 numbers vanilla's biomes (the `biome` uniform and the `BIOME_*`
constants of custom uniforms) in the order vanilla's `Biomes` class declares
them: read back with a probe pack, all 67 constants and the uniform in four
scenes (forest 8, ocean 46, soul_sand_valley 60, the_end 62). Horizon gave
the biome's registry id, which follows identifier order (forest 22).
Biomes vanilla doesn't have come after vanilla's, in registry order.
Fixes#8063
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`centerDepthSmooth` follows `depthtex0`'s centre texel, copied each frame into
one of three small buffers and read back later. The readback only started in
`PackPost::submitted`, which the renderer called after an in-place submission;
frames that go to the submit thread returned before it. The three buffers then
stayed copied and unread until a frame took the in-place path (a screenshot),
so the uniform kept its first value (1) or one from the last screenshot.
The readback now comes back as a closure, run right after the submission on
whichever thread submits, as culling's readback already is.
Fixes#8059
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Read back exactly with a probe pack in Iris 1.11.7 and Horizon, same scenes:
- ambientLight is the dimension type's (0.1 in the Nether, 0.25 in the End);
Horizon gave 0. The synchronized dimension type now carries its
ambient_light (vanilla's defaults when the client knows the pack).
- playerMood and constantMood are the cave mood (BiomeAmbientSoundsHandler's
moodiness); Horizon gave 0.
- worldTime reads 0 in a dimension type with no default clock (the Nether's;
Level.getDefaultClockTime); Horizon gave the Overworld's time.
Fixes#8061
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: a salmon now starts MEDIUM in Mob::new like Salmon.defineSynchedData (fixes mob_attributes and mob_ai since #7651); the dispense fixture prints the 1501 lava-fizz S line again (use_item_on since #7652). mob_ai 272/272 and use_item_on 4000/4000 on the Mac mini.
When shutting down or exiting a world, unvisited chunk generation,
decoration, and meshing jobs remained queued and actively running across
worker threads. In Streaming::save, the server stalled waiting up to
30 seconds for background decoration and finishing to drain, and when
dropping Streaming, Registration::drop blocked joining all worker
threads that were actively computing unvisited chunk generation.
Match vanilla Minecraft's deactivateTicketsOnClosing() behavior by
introducing Streaming::abort_generation(), which drains pending
non-save jobs and marks the streaming pool aborted so worker threads
do not start new generation passes. In addition, only wait for durable
chunk writes (saving > 0) during shutdown and skip the artificial
guest disconnect sleep when no remote LAN guests are connected.
Use a fixed persistent cow UUID, verify an empty hand and a closer
bystander, and record every stage before checking serialized UUIDs so
the baseline reaches the actual post-reopen release attempt.
Co-Authored-By: Codex (GPT-6) <[email protected]>
Restrict legacy owner recovery to the known offline Player sentinel.
Do not substitute a nearby player when the saved holder is absent, in
network synchronization or rope physics. Cover real holder selection,
arbitrary saved UUIDs and the physical pull outcome.
Co-Authored-By: Codex (GPT-6) <[email protected]>
🤖 Agent: fixes main's three red checks: an item the loaded registry lacks now falls back to vanilla's villager food values (mod content tests no longer zero the bread), client_lib.rs is formatted, and six launcher tables get the Wardrobe strings.
- Salmon.defineSynchedData starts DATA_TYPE at Variant.DEFAULT
(MEDIUM). Horizon's Mob::new left the variant at 0 (small), so since
#7651 sized the box by variant, a salmon finalizeSpawn never sized
(probes, any constructor path) was half size (mob_attributes,
mob_ai salmon scenarios).
- #7652 plays level event 1501's sound unshared (clients play it from
the event); the dispense fixture now adds its S line from the event,
as VanillaDispenserOracle does (use_item_on respawn anchor cases).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging: save_roundtrip compared EggLayTime, a timer that advances with every tick like the Age it already ignores; since #7881 the scene's NoAI chickens count it, so the two saves differed by the ticks between them. Only that field is added to the timing rule.
🤖 Agent: you were right on point 3 and our review was wrong: BabyTurtleModel passes RenderTypes.entityCutoutCull (AdultTurtleModel passes entityCutout), and the entity_cutout_cull pipeline never calls withCull(false), so vanilla culls the whole baby. Culling the whole baby model through the bat's path matches that exactly. I merged main in; main's Option model needed the culls_back_faces check moved inside its map_or_else.
The villager_food table is process-wide and set by the last item registry
loaded. horizon-client-lib's mod content tests load content roots without
the vanilla items, which made bread worth 0 for the villager tests running
beside them (eating_fills_the_food_level_then_digests and two others failed
on main's verify). The table now holds every registry item, and an item the
registry doesn't know falls back to vanilla's four values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #7881 a NoAI chicken counts its EggLayTime down as vanilla's
does, so the scene's chickens are ticks apart between the two saves
(vanilla 329, Horizon 320; 7021 and 7012 for the one riding a boat),
the same timing difference Age and Time already have.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Those four tables lack the Wardrobe page's 39 strings, so main's launcher
test fails on ar.json once Korean and Croatian are complete.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the browser is now an Extras switch (off = the game never contacts Modrinth), and fetched icons go through decode_image_bytes with the 4096² cap from #7856 instead of the PNG-only decoder; the add-failed line is translated (horizon.pack.addFailed). Also dropped an unused SEARCH_Y constant that failed clippy -D warnings.
BabyTurtleModel's render type is RenderTypes.entityCutoutCull, so vanilla culls the whole baby model. Mark turtle_baby#main with set_cull_back at load, the same path the bat takes (#7139), instead of detecting coincident opposite faces in every model.
The bat's cull path already writes SHADED_CULL_W, so the baby's feet keep their directional shading and only gain the cull. The other 202 coincident opposite faces in no-cull models stay as vanilla draws them.
Resolve the callee named by the catalog rather than deriving it from the public helper name. Keep native-method and module validation, and cover a helper that shares a hook name but targets its distinct ref method.
The Extras page gets the Resource pack browser switch, which nothing could turn on
before. Fetched icons go through decode_image_bytes, now that decode_bytes stays
PNG-only. A failed install shows horizon.pack.addFailed with the reason, not a
bare English line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: I fixed four things. (1) The hand-rolled SHA-1 copied the whole download (up to about 1 GiB for a 512 MiB pack); it now uses skins::sha1 (the sha1 crate), and the FIPS vectors still pass. (2) Only https URLs from a reply are fetched, and an icon may be at most 2 MiB, not a pack's 512 MiB. (3) free_path returned the taken name after 999 numbers, which would overwrite a pack; it now reports that no name is free. (4) Windows device names (CON, AUX, COM1…) get a '_' prefix (JustKarkat's points). Clippy is clean and all 8 pack_browser tests pass.
🤖 Agent: the planks hint now clears on the craft, through the slot packets vanilla's Tutorial.onGetItem listens to; the ContainerSetContent hunk is gone, so opening a chest of planks no longer clears it.
🤖 Agent: I made three changes. (1) decode_bytes stays PNG-only, since it also reads resource packs and vanilla's NativeImage.read rejects a JPEG or WebP texture; the new decoders sit behind decode_image_bytes, for fetched images. (2) The header alone sizes the buffer, so a fetched WebP or JPEG may claim at most 4096 x 4096 pixels (a 16k-square one would have asked for about 1 GB). (3) hud.rs had been converted to CRLF, which made the diff 7,956 lines; it's back to LF and the real change is about 150 lines. The new crates (image-webp, zune-jpeg, zune-core, byteorder-lite, quick-error) are all MIT/Apache/Zlib/Unlicense.
🤖 Agent: I added the other half: emptying a bucket now sets from_bucket on fish and axolotls (Bucketable.setFromBucket), and requires_custom_persistence reads it (AbstractFish and Axolotl: super || fromBucket()). A released fish now saves FromBucket:1b instead of the 0b this PR alone would have written, and a fish loaded with FromBucket:1b no longer despawns. Main is merged again.
🤖 Agent: an unclean start now resets only fullscreen and its resolution, as 26.3's Minecraft.<init> does, and keeps the resource packs; the dedicated server never writes startedCleanly.
The Korean and Croatian tables landed beside the Wardrobe and its cosmetics
errors, so main's launcher test finds 47 strings missing in both tables.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hand-rolled SHA-1 copied the whole download for padding; skins::sha1 (the
sha1 crate) hashes in place. A reply names the URLs fetched next, so only https
ones are fetched, and an icon may be 2 MiB, not a pack's 512. free_path returned
the taken name after 999 numbers; it now says none is free. Windows device names
(CON, AUX, COM1...) get a prefix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
decode_bytes reads resource packs too, where vanilla's NativeImage.read takes PNG
only; the WebP and JPEG decoders now sit behind decode_image_bytes for images
fetched from the network. Their headers alone size the pixel buffer, so a fetched
image may claim at most 4096 x 4096 pixels. hud.rs had been converted to CRLF.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: /setidletimeout now changes the live timeout. Lists holds the only copy, and the tick, the management API and the command all read it, while both setters persist player-idle-timeout (DedicatedServer.setPlayerIdleTimeout). I resolved the same main merge independently, and it passed clippy and horizon-command's tests on the ThinkPad, so I'm taking your head as it is.
🤖 Agent: summoned and loaded evoker fangs now join the live fang list: they bite, save with their live Warmup, unload with their chunk, and /data and selectors see them. They also move with their level in levels.rs's swap now, so a dimension's fangs no longer tick in another one. I merged main (corpus union: all 55 cases kept).
🤖 Agent: a creepy enderman's shake is now 0.02 * state.scale, as EndermanRenderer.getRenderOffset has it (the old code hardcoded scale 1), so a scaled enderman shakes in proportion. This sits on #7868, which I merged in.
The server tracks minecart deltas (ServerEntity trackDelta, lan/tracking.rs)
and remote_motion sets the cart's velocity from ClientboundSetEntityMotion,
which MinecartSoundInstance reads as getDeltaMovement. Replacing it with the
lerped displacement changed the volume against vanilla; the silence came from
the dropped looping flag and the start that was never retried.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: this pins vanilla's repeated quickMoveStack: one move_to pass leaves the remainder doClick's loop leaves (40 snowballs into two free slots keeps 8 in the chest, a full inventory moves nothing). I merged main beside #7840 and #7845's tests, and all 15 lan::menu tests pass.
🤖 Agent: format_fixed rounds the shortest decimal half-up, as Java's Formatter does, and the float yaw, pitch and difficulty are widened with as f64 first. That matches Formatter.print(float), which formats (double) value, so 0.125 → 0.13 holds for them too. It only runs while F3 or /data get is shown.
/item's worn-armour index and native_living_set_equipment still asked
head_is_body: with head and body kept apart, a mob's head is always armour
slot 0, and set_equipment no longer refuses a horse's head or a zombie's
body (vanilla's EntityEquipment has every slot). API docs follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: wear_banner now records head = 2.0 through set_drop_chance (Raid.setLeader and PatrollingMonster.finalizeSpawn), so a captain's drop_chances save like vanilla's. The other slots' explicit chances survive, and drop_chance still takes one value per slot, so the banner can't drop twice.
Main keeps the live minutes for the management protocol (DedicatedServer.
setPlayerIdleTimeout, which also writes player-idle-timeout). /setidletimeout
and the management call now share Lists.player_idle_timeout through
set_player_idle_timeout, so both persist, as vanilla's command does through
the same method.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: backup, copy and delete now take the same File::try_lock on session.lock that SessionLock::acquire takes, so a world open in a running game can't be copied mid-write. The external .mcc payload is now synced before its rename. Follow-up (JustKarkat's point): refusing a locked world shows the raw OS error, and it should be a translated launcher message.
🤖 Agent: the source is fine: the matrix names come from the MIT shaders.properties docs at 98f84cbe456e, the same commit the clean-room spec cites as [IR]. The translator change binds only undeclared or mat4-declared projectionMatrix/modelViewMatrix, so a pack's own local or global of that name keeps its value; the GPU test covers both shadowing forms. It costs nothing per frame, since the binding happens at translate time.
🤖 Agent: chickens and parrots now emit minecraft:flap after onFlap (strict flyDist > nextFlap), silent ones included, as Entity.processFlappingMovement does, so sculk sensors hear birds. The species re-check only scans mobs on flap events, not on steps.
🤖 Agent: the order matches vanilla's PrepareSpawnTask (loadedPosition.rotation().orElse(respawnData yaw/pitch)): face_world_spawn returns for a known player, so only a first join takes the angle, and head_yaw follows yaw as snapTo sets it.
🤖 Agent: every living entity's box and eye height now take getScale() through sanitizeScale (shulker capped at 3, happy ghast at 1), as LivingEntity.getDimensions does. The rider seat height now divides the scale back out for every mob, not only avatars, so seats on a scaled mob don't double-count it. This builds on #7827.
AbstractFish and Axolotl.requiresCustomPersistence are super || fromBucket():
the bucket's setFromBucket(true) is what keeps them, and what a save writes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: this matches Shulker.getDefaultDimensions for the floor face (scale(1, 1 + currentPeekAmount)), so the eye height is now exact. Remaining gap: vanilla's collision box is makeBoundingBox's progress box (1 + getPhysicalPeek in the open direction, on every face), while ours is built from these dimensions. A floor shell a quarter open is 1.25 tall here and 1.15 in vanilla, and the side and ceiling faces don't grow at all. Worth a follow-up. I merged main and fixed the wording: attach face DOWN is a shell sitting on the floor, not hanging under a block.
🤖 Agent: everything here runs in adapter::translate, which only a 26.2 session calls; the 26.3 path is untouched. The global-palette repack goes from 15 to 16 bits because 26.3 has 35723 states against 26.2's 32366 (ceillog2 crosses 2^15).
🤖 Agent: this one touches the shared readers (item stacks, components, particles, entity data) for every connection, so I checked their cost on 26.3: ids::item and friends read a const thread_local Cell that is None unless a 26.2 session set it, one TLS load per id. DelimitedSlot re-encodes its stored patch only while renumbering, so a plain re-send still copies the bytes.
🤖 Agent: a villager's food now comes from the villager_food component in the loaded item registry, so data-pack and mod items count. I moved use_speed's doc line back onto use_speed (the new field had taken it) and merged main. Note: the table is process-wide, so the last registry loaded wins.
🤖 Agent: this matches Creaking.getHurtSound (isHeartBound() ? creaking.sway : super): an unbound creaking now gets Monster's entity.hostile.hurt, and a giant gets the hostile hurt and death pair. I merged main and kept its can_use_portal beside the new tests.
🤖 Agent: a guest's pickup, quick move, swap, throw and double-click now ask Slot.mayPickup (a cursed helmet stays on outside creative), and drops ask Player.canDropItems. I merged main: pickup_all keeps doClick's 'clicked slot holds a stack it may take' early return and main's button order (0 from the first slot, 1 from the last).
🤖 Agent: off unless HORIZON_OLDER_SERVERS=1, and with it off the reader and writer pay one None check per packet, nothing else. When on, older_server costs one extra status ping (5 s timeout) per join, and it skips friend codes. The session's state switches follow Connection's codec changes in each direction separately, which the second test pins.
🤖 Agent: the footer order matches AccessibilityOptionsScreen.addFooter (link button, then Done), and the in-game path goes through ConfirmLinkScreen back to Options as vanilla's confirmLink does. The startup menu opens the link without confirmation, a known gap shared with that menu's other links.
🤖 Agent: checked the output against 26.3's plains.json: spawners land in attributes[minecraft:gameplay/natural_mob_spawns].argument.spawns_by_category with count as an int or a minecraft:uniform object, the shape biome.rs already reads. One edge: an entity type missing from entity-types.json falls back to the misc category, but the entity_reg check skips such a spawner first, so that fallback never runs.
🤖 Agent: checked the server side of the handshake: lan/conn.rs keeps the last minecraft:brand it receives before SelectKnownPacks, and the client's follow-up "horizon" goes out before its known-packs reply, so Horizon servers still turn on LOD, mod checks and horizon_clients_only. The test covers vanilla, horizon, Paper and a malformed brand.
🤖 Agent: take(MAX_OVERRIDE + 1) is what tells an over-size entry from an exact 64 MiB one, and the running total now counts inflated bytes, not the zip's declared sizes. On failure the written files are removed, though directories create_dir_all made stay behind (harmless, empty).
🤖 Agent: every lock in the relay now goes through locked(), including the per-byte total bucket in copy(). A panic can still leave inner.hosts or pending half-updated, but those entries go away on their own disconnect paths, which is better than every later connection panicking.
🤖 Agent: the token is still 32 hex digits, so same_token and stored tokens don't change. getrandom 0.3 was already in the lockfile through horizon-relay (MIT/Apache), so only a dependency edge is new.
🤖 Agent: the wanted filter in block_edits.rs and replace_block_entities now test the same condition (name changed, or the entity there doesn't fit), so make_block_entities' order still lines up with the positions that take them. fits is an exhaustive match, so a new BlockEntity kind has to name its blocks. A same-name write over a missing entity now creates one, as LevelChunk.setBlockState's createBlockEntity does.
In vanilla Minecraft, minecart sounds (both riding and ambient cart rolling)
are continuously active sound instances whose volume scales with horizontal
speed squared, silencing when stopped or stationary.
In Horizon:
1. tick_remote_objects interpolated cart position when !lerp_cart(c)
without deriving displacement velocity. Derive velocity from position delta
so remote minecarts calculate speed for sound attenuation.
2. In start_cart_sounds, avoid permanently discarding minecart sound
tracking if PlayResult::NotStarted is returned (e.g., audio engine busy
or channels initializing), allowing retries.
3. In crates/horizon-client-render/src/audio.rs, ensure looping parameter
is properly forwarded when caching/queuing sound decodes.
4. Added unit tests for minecart sound volume scaling, stopping on removal/
dismount, and surface/underwater sound switching.
Fixes part of #7833 (minecart sounds).
🤖 Agent: the freeze is right to scope to content mods: mod_content.rs appends their block states in id order, so a mid-world toggle on the next stage_pack would shift every later mod's ids under loaded chunks. Native mods still toggle live through native_on, and the test covers both rows.
🤖 Agent: checked that each of the six options has a reader on main: chunkFade feeds gfx fade_millis, improvedTransparency the OIT path, chatDelay remote.rs chat_delay_ms, saveChatDrafts chat.rs should_discard_draft, musicToast the now-playing toast, ctrlClickEmulatesRightClick the macOS-only click in game.rs. ChunkBuilder and the chat/Realms ones stay greyed: nothing reads them yet.
🤖 Agent: this matches InventoryMenu.quickMoveStack: slot 45 now reaches the equipment check, so off-hand armour is worn, and anything else takes the final else (9..45, main first). The fix is in the server's menu, so single player, LAN and dedicated servers all get it.
Vanilla's extractContent draws HIDDEN_ADDRESS_TEXT (selectServer.hiddenAddress)
in the address line while hideServerAddress is on; it never leaves it blank.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla reads lastStartWasClean to reset fullscreen (and the graphics API);
resource packs roll back only when their load throws (rollbackResourcePacks),
not after an unclean start. The flag is a client's: a dedicated server writes
no options.txt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: mob_split runs after vanilla's count draw, so the slime's random sequence is unchanged with or without mods. baby_entity_spawn is gated on a non-empty mod list, so breeding pays nothing without mods. I merged main and regenerated the hook API (both hooks stay pending, mod-api --check clean).
🤖 Agent: Checked that nothing on the server reads mob geometry: every `kind.model`/`layer` user in `game/` and `mobs/` draws (item-in-hand mobs, perspective, CEM), so an empty `Part` there changes no gameplay. Single player is unaffected: the window loads with `draws` true.
🤖 Agent: /time's argument and /compute round now use one exact Math.round(float) (jmath::round_f32), so 0.49999997t gives 0 ticks, not the 1 that x + 0.5 in f32 gave. I merged main and kept its f64 jmath::round beside it.
Vanilla calls Tutorial.onGetItem only from handleContainerSetSlot,
handleSetCursorItem and handleSetPlayerInventory: a full content sync (opening a
chest that holds planks) must not end the CraftPlanks step. Drop the two tests
that only re-ran their own loop over a local tag set.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: plain and NBT summons now both go through allowed_in_peaceful (EntityType.isAllowedInPeaceful), not the monster category. Peaceful now accepts what vanilla accepts and rejects what it rejects, on the dedicated server too. The new harness compares this against a real vanilla server.
🤖 Agent: when an orb, falling block or TNT joined before the wind charge, its push is now held until its own tick (after tick_tnt), as vanilla's join-order tick list does. The per-tick cost is one empty-Vec check when no charge burst. Nothing else found.
🤖 Agent: merging: fixes parity-main's sound_engine and music, both broken by #7479. A replayed sound instance resolves again as vanilla's getOrResolve does (it kept its first variant's volume), and with no track turned off the music manager runs vanilla's startPlaying as it is (it left no current music on an empty pick). Hot-path cost: none.
🤖 Agent: the drowned's go-to-water goal now picks only water blocks (block_name == minecraft:water), as vanilla's getWaterPos does (Blocks.WATER), so kelp, seagrass and waterlogged blocks no longer count. Nothing else found.
🤖 Agent: the container hotbar check now asks about the raw key, so with off hand on Left Alt and hotbar 7 on F, pressing Left Alt no longer swaps slot 7. I fixed the key-release fixture to use the new 4-argument mapped_key and merged main.
🤖 Agent: Wardrobe page in the top bar, flag off; the one conflict with main (extras.rs, Music page) was resolved keeping both sides. The risk is a missing tr() string; the launcher's i18n check covers it.
🤖 Agent: merged main beside #7552's climbing: tick_settle now takes a StandMedium (water or air) so a stand sinks in water and falls in air; clippy and the entity tests pass on ThinkPad.
🤖 Agent: Lands the whole stack (#7718 to #7789). Kept only real translations: game tables without English copies; launcher tables with every string main asks for, English where untranslated. A script check removed 45 entries with letters from another script, 43 of them in ur_pk (Kannada, Bengali and Devanagari letters inside Urdu).
- Game tables (hi_in, id_id, pt_pt, ur_pk): English copies dropped (the
game falls back to en_us per key).
- Launcher tables (ar, hi, id, pt): every string main asks for, English
where untranslated (the launcher test needs them all).
- Entries with letters from another script dropped, or reset to English
in the launcher: 43 in ur_pk (Kannada, Bengali and Devanagari letters
inside Urdu), one each in hi_in and the launcher's hi (Arabic letters).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Removed the server-side half of the random-model rules: `living.rs` read the rule-species lock and the block name for each mob each tick on the server thread, whose mobs never draw. The window's remote tick already keeps `standing_on` fresh. Also dropped the unrelated `/resourcepacks/` .gitignore line. Without a CEM pack, `posed_body` and the hand anchors fall back to the vanilla model and anchor.
In vanilla Minecraft Java Edition 26.3, numbers displayed in F3 debug overlay lines and `/data get ... <scale>` are formatted with `String.format(Locale.ROOT, "%.Nf", value)`, which rounds the shortest decimal string representation half-up. Rust's standard `{:.N}` rounds the exact binary IEEE-754 value (ties to even), causing discrepancies on half-way ties.
Use `java_text::format_fixed` in `DebugEntryPosition`, `local_difficulty_line`, `DebugEntryPlayerSpeed`, and `/data get` `scale_text`.
Co-Authored-By: Antigravity <[email protected]>
🤖 Agent: Only packs with `gbuffers_line` or `gbuffers_basic` take the new path; without one the game draws its own outline, as before. Each edge is widened in screen space after the pack's transform, so a pack that scales its world buffers (SEUS PTGI HRR 3) no longer leaves a stray box in the corner. Merged main beside `clouds=off` and the depth copies.
1.21.11 (protocol 774) gets its packet and registry tables and a step to
26.1's layout: chunk sections gain 26.1's fluid count, set_time's day
time becomes the overworld's world clock (with a world_clock registry
added at the end of configuration), entity data of AgeableMob types moves
past the entry 26.1 inserted, and entity data serializers are renumbered.
Serverbound, interact goes out as INTERACT_AT then INTERACT, attack as
interact's ATTACK, and punch as 1.21.11's swing. The session can now
queue packets for Horizon to read next and bodies to send after the
current one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
🤖 Agent: Moving `stop`/`autosave` into the shared `Lists` lets the console, the management protocol and an operator's command flip the same switch. A guest's `/save-all` waits for the end of their turn, so their swapped-in state never gets written to their own file. Merged main beside `/transfer`, #7279's live settings and the save-failed reply; the tree order is vanilla's: save-all, save-off, save-on, stop, transfer.
🤖 Agent: Merged main, where `/experience add … levels` now goes through `give_levels`, so only points pass `xp_change`. That matches NeoForge, where `PlayerXpEvent.XpChange` fires in `giveExperiencePoints` and levels have their own LevelChange. `try_sleep` reports vanilla's first problem in vanilla's order before the hook can overrule it.
🤖 Agent: `living_death` fires once health is gone and before loot, exp and the death message, as NeoForge's `LivingDeathEvent` does, for mobs (`mob_died`) and server players (`guest_die`) alike. A mod that cancels has to restore health itself, as on NeoForge. Merged main and regenerated the Host files.
Since #7479 the music manager picked the track itself and, when the
pick was empty, left no current music and drew a new delay; vanilla
sets the instance as current whatever it resolves to and the delay to
its maximum (music parity: warped_forest current, delay MAX). The Music
page's own pick now runs only for a row's play-now or when tracks are
off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The spill uses `BlockItem.onDestroyed`'s order: stacks come out in slot order, not the order they're stored in (the test feeds 20, 0, 3), and the box is left with an empty `container`. Merged main after #7645: the bundle path and the container path now share `ItemEntity::hurt`'s one dispatch.
🤖 Agent: Carries #7588 unchanged plus merges with main. Free without mods: `spawn_hooks()` is false, so `mobs_at` keeps borrowing the biome's list. Regenerated the Host files after #8003's note block hook.
#7479 made SoundEngine.resolve skip any instance that already had a
sound and an event, so a replayed instance kept its first variant and
volume (sound_engine parity: gain 1.0 where vanilla's replay picked a
0.4 variant). Vanilla's getOrResolve picks again every time; only the
Music page, which chooses the track itself, now keeps its sound
(SoundInstance.chosen).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Holding `fire_ticks` at ≥1 while the server's flag 0 is set matches `Entity.isOnFire`, which reads the synched flag on a client. Clearing on a flag drop and on respawn/login keeps a stale flag from burning the window forever.
🤖 Agent: merging: boat_submerged, command_exec's piglin barter and fishing_hook_entities depended on timing or vanilla's unseeded random. Each now checks the same rule deterministically, and a vanilla run the harness itself rejects is redone. Horizon's side is never retried.
🤖 Agent: merging: fixes parity-main's mob_ai (272/272 scenarios with it, 267 on main). A NoAI horse now runs AbstractHorse.aiStep's draws as vanilla does, and the breeze's inhaling/sliding particle arms are back in vanilla's order (#7573 had swapped them). Hot-path cost: none, only NoAI horses do the extra work.
🤖 Agent: The byte-level `is_ascii_hexdigit` check removes the panic from slicing multi-byte text at byte 32, and it rejects the `+` that `from_str_radix` accepted. JustKarkat's note stands: undashed 32-hex input is still accepted, unlike `UUID.fromString`, and that's left for another PR.
The server's mobs never draw, so the server thread no longer reads the
rule species lock and the block name for each mob each tick; the
window's remote tick already keeps standing_on fresh. Also drops an
unrelated .gitignore line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: The salmon scale (0.5 / 1.0 / 1.5) is applied in `dims()`, so collision, the hitbox and the client's box all follow the variant, as `Salmon.getDefaultDimensions` does.
🤖 Agent: Fixed verify: `tests/test_world_input_capture.py` compiles `set_capture` against a stub `Game`, which needed the new `capture_mode` field (and a `Copy` `CursorGrabMode`). The re-grab is Linux-only and runs on clicks and wheel events while playing, never per frame. Merged main.
`AccessibilityOptionsScreen.addFooter`: vanilla''s footer has an
"Accessibility Help" button (`options.accessibility.link`) opening
`CommonLinks.ACCESSIBILITY_HELP` through `ConfirmLinkScreen`. The page
takes the left footer slot the Mods and Shader Packs pages use; the
event drives the same `confirm_trusted_link` path as the pause
screen''s bugs and feedback links, returning to the options screen.
The pre-game Loading menu has no screen stack for the confirm, so it
opens the link directly, as its other links do.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Agent: The proof is a real-protocol comparison against a vanilla 26.3 dedicated server, and the arrival speed is clamped to ±0.4 like `OldMinecartBehavior.getKnownMovement`. The change sits in `server_travel.rs`, so single player, LAN and dedicated all get it.
🤖 Agent: This now reads every world's saved level stem, not just single-biome ones. That is safe for default worlds: their stems are references (`minecraft:overworld` noise settings, the multi_noise preset) that resolve as the preset did, and a data pack's stem still wins first. One effect to know: a Horizon world created with a single biome before this fix gets that biome only in chunks generated from now on.
🤖 Agent: The one-line clamp removal is right: `Animal.addAdditionalSaveData` writes `inLove` as is, and the reader already kept the signed value, so a save round-trips -32769.
🤖 Agent: An unemployed villager gets `entity.minecraft.villager.none`, which en_us reads as "Villager", so only employed villagers change name; CustomName still wins. Folding the Mob arm into `command_name` also removes the double scan of `mobs` for a named mob.
🤖 Agent: The new `set_damage` arm matches horizon-world's `loot.rs` formula (float + add base, clamp, floor of `(1 - remaining) * max`). It also checks unbreakable/max_damage before drawing, so an ineligible stack leaves the random sequence untouched, as `SetItemDamageFunction.run` does.
🤖 Agent: Carries #7597 unchanged plus a merge with main. A replacement outside 0–24 keeps vanilla's proposed note, so a buggy mod can't write an invalid NOTE state.
🤖 Agent: launcher-only client for the founder set (claim, equip, Show my cosmetics) with an offline change queue in <data>/wardrobe/<uuid>.json; the Minecraft token goes to Mojang only, the service's session is kept in memory for the call. pr_ready --quick passes on this head with main merged in.
🤖 Agent: worn.json is written only by the fetch thread when an answer changes it and read only after a failed request, so the frame path is untouched; the new test fails if the restore step is removed. pr_ready --quick passes on this head.
26.1, 26.1.1 and 26.1.2 (protocol 775) get their packet and registry
tables, and translation goes a release at a time: a 26.1 packet is
brought to 26.2's layout first, then on through 26.2's translation, with
ids mapped straight to 26.3's. The 26.1 step adds login_finished's
session id and login's account check, reorders team parameters (an
optional TeamColor in 26.2), and tells a 26.1 server the entity a
spectator picked by its UUID.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
🤖 Agent: Closed a path escape: an .obj's `mtllib` names its .mtl as raw text, so `mtllib ../..:x.mtl` reached files outside the content root, because `RootAssets` checked the path and not the namespace. Both parts must now be resource-location characters with no `.`/`..` step; there's a test for it. Merged main after #7567.
A shadow program with a geometry stage loaded for terrain only: shadow_entities
and shadow_block (or `shadow` as their fallback) were skipped with "uses a
geometry stage (supported for terrain's shadow only)". Iris 1.11.7 draws
entities through it too (observed with a probe pack of our own).
Entities now get geometry inputs of their own: the vertex stage pulls each
corner of the run's quads from the entity vertices it already reads at set 1,
walking them as the quad indices do, and the run draws without indices, one
instance per triangle the stage can emit. Set 5 (terrain's draw indices) is
bound only for programs that read it.
Part of #6812
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merging. The Nightly pick now takes the newest commit a parity-main run passed on (whose checks passed), so a later failed or timed-out batch holds only its own merges instead of every Nightly. Risk: none for players: a failing merge still can't reach a Nightly, it only stops blocking the green commit before it.
Minecraft.<init> arms `startedCleanly` false while it builds and
onGameLoadFinished writes it back true; a start that still reads
false died mid-load, so rollbackResourcePacks drops the configured
packs instead of crashing the same way forever. Horizon read the key
but never wrote it, so a pack set that broke startup broke every
launch. Arm the flag after Settings::load, clear it on both success
paths, and roll the selection back only on an explicit false: an
options file that never had the key is a clean start, as the field
defaults true.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
An .obj names its .mtl as raw text (mtllib), so a namespace such as
../.. reached files outside the content root. Both parts of the location
now need a resource location's characters and no . or .. step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Works on the server thread too: a server's client ticks thrown projectiles through the same `ProjWorld` (`remote_projectile_tick`), so the window ignites its own copy and draws the flame without a synced flag. Not covered: a vanilla client joining a Horizon server, because the tracker sends no on-fire flag (bit 0) for projectiles yet. Merged main: `fire_tick` now runs before main's new first-tick shoot event.
🤖 Agent: The guard sits where `LivingEntity.hurtServer` checks `IS_FIRE` + Fire Resistance, after the species callbacks. Returning `reflected` keeps Ghast.hurtServer's true for a reflected fireball, and the test checks that a refused fire hit leaves no hurt cooldown behind.
🤖 Agent: Fixed both of Dyna's points. `add_tag`/`remove_tag` now return false for kinds with no tag storage yet (ominous item spawners, falling blocks, rockets) instead of a false success. An item entity's name is now its CustomName, else the stack's `item_name` (`ItemEntity.getName`), which also fixes `/say @e[type=item]`. Merged main again after #7559 and #7563.
🤖 Agent: No cost without mods: vanilla's `minecraft:` properties never reach the hook, and with no trait mod loaded the custom condition reads false without a call. Merged main and updated #7509's test, which expected a mod's condition to collapse to `on_false` at decode time; now it stays a condition its mod answers per stack.
🤖 Agent: Kept as `living_incoming_damage_v2` (fold reverted), so the shipped hook's contract is unchanged. v2 now fires after the victim's invulnerability guard (`Mob::refuses_damage`), so a mob in its hurt cooldown no longer reports a hit. Contains #7582.
🤖 Agent: Kept as `living_knockback_v2` (fold reverted) so `living_knockback`'s shipped contract and mixin target stay unchanged, per the docs' versioning rule. With the attacker scope opened for mob hits and mob projectiles (`f98a0c6c1`), a zombie's sword now reaches `attacker_main`.
FishingHook's throw is randomised: on the loaded runner vanilla's own
cast missed its target three times in a row once. one_player now
reruns a rejected vanilla run up to 5 times, and the harness gets the
known-flake retry (#2451).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#7509's test expected a mod's condition property to collapse to on_false
at decode; with the item_model_property hook it is kept and on_false is
what draws when no mod answers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reverting the fold keeps living_incoming_damage's contract unchanged. The
contract rule in docs/native-mods.md says a contract never changes and an
incompatible change adds a version. The invulnerability guard from
f152b155a stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Fixed both of Dyna's points. An air stack now empties the slot (`ItemStack.isEmpty`, as `/item` filters it). Setting a mob's head, chest, legs or feet now also replaces the worn armour stack `equipment_stacks` prefers, so a damaged iron helmet no longer reads back after a golden one is set; this covers `/item` too. Merged main and regenerated the Host files.
Reverting the fold keeps living_knockback's contract unchanged. The
contract rule in docs/native-mods.md says a contract never changes and an
incompatible change adds a version, as menu_button_v2 did, so a mixin
injection that targets native_living_knockback keeps working. The attacker
fix from f98a0c6c1 stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Delegate launcher trait, event and helper generation to horizon-mod-api-gen. Keep the old-source compatibility path, move dispatch fixtures out of xtask into an unpublished workspace member, and select launcher checks when shared generator code changes.
🤖 Agent: The i64 field is wider than vanilla's int, but saving clamps it to a short, so the legacy `u32::MAX` sentinel still saves as 32767 and loads back infinite. Players' `== 0` pickup check keeps refusing a -1 delay, as `ItemEntity.playerTouch` does.
🤖 Agent: Vanilla models carry no `loader` key, so their resolve path is the old one plus an empty fallback map; only `neoforge:composite` adds work. The nesting cap (`MAX_LOADER_DEPTH` 8) together with the per-chain cycle check stops a composite that names itself through a child's parent.
Vanilla's LanServerEntry hides the host:port while the option is on; the key was persisted but never read.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- set_equipment treats an air stack as empty (ItemStack.isEmpty), as /item does.
- Setting a mob's head, chest, legs or feet also replaces the worn armour
stack it keeps, so the previous piece (a damaged helmet) no longer reads
back after the write, through this API and /item alike.
Both from Dyna's review on #7563.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- add_tag/remove_tag return false when the entity's kind keeps no compound
for its tags (ominous item spawners, falling blocks, rockets) instead of
reporting a write that never happened.
- An item entity's name is its CustomName, else its stack's item_name,
else the item's name (ItemEntity.getName), for /say and the handle alike.
Both from Dyna's review on #7561.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A 26.2 server's entity moves (three shorts and on-ground), position
syncs (with motion), and hand swings (an animate action in 26.2, a
swing_animation in 26.3) are read in 26.2's layout; spawned entities,
falling block states and attributes are renumbered. Particles, explosions
and advancements (whose tab position 26.2 keeps inside the display) are
rewritten. Entity data, inventories, equipment, trades, sounds and
recipes are decoded with 26.2's ids renumbered as they are read, and the
creative slot and container clicks go back in 26.2's numbering.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
🤖 Agent: Fixed Dyna's finding: `is_loaded` now returns false outside the level's height, as vanilla's `Level.isLoaded` does through `isInValidBounds`, so it agrees with `block_state` at y=400. Merged main and regenerated the Host files (`xtask mod-api --check` clean).
Drop the shared HANDLES registration list. Reuse the source export walk, infer snake_case receiver keys, and keep unknown-receiver errors and duplicate-key diagnostics.
is_loaded now returns false outside the level's height, as vanilla's
Level.isLoaded does through isInValidBounds (review on #7559). Host files
regenerated with xtask mod-api.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Off by default and free while off: records, uploads and the frame pass all sit behind `gpu_culling.is_some()`. One thing to watch: `open_backend` now requests `MULTI_DRAW_INDIRECT_COUNT` on every adapter that offers it, so a driver bug in that feature would reach players who never turn GPU culling on. (Merged main: a comment conflict beside the new FLOAT32_FILTERABLE feature.)
Build every profile from the catalog, fragments and handwritten signatures. Keep xtask for filling handwritten placeholders, and stamping for catalog data. Stage launcher-owned declarations as build input, retaining shared injection validation for older source trees with committed generated traits.
🤖 Agent: Matches `DimensionOriginStructurePlacement.isStructureChunk`; merged with main's new spacing/frequency validation (`dimension_origin` sits beside it in the placement match). The spawn search in `spawn_column` runs at generator setup only when a set uses `dimension_origin`.
🤖 Agent: Matches `LivingEntity.handleOnClimbable` (±0.15f clamp, no slide stop for a non-Player) and the post-collision 0.2 lift. Per tick it adds one `on_climbable` lookup per stand before the move, plus one more after a horizontal collision.
available() predated the features: ChunkFade, ImprovedTransparency, ChatDelay, SaveChatDrafts, MusicToast and CtrlClickEmulatesRightClick all have readers now.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Main split ArmorStand::tick into tick_move and tick_settle around the
bubble-column push (#7669). The climbable lift stays at the end of
tick_move, before that push, and tick_move takes the climbing registry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Merged with main's UUID bookkeeping carried into the moved loaders; the every-kind attachment reload test passes. Static dispatch through `load_lists!` costs one string compare per listed kind per loaded entity, the same as the old match.
Main added remember_uuid to the orb, arrow, fireball, throwable, falling
block, dragon and crystal loaders (#7398, #7688); those bodies now live in
the moved Game::load_* methods, which take the saved UUID for it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- one_player: vanilla's run is judged by the harness's check before the
comparison, and run again (up to 3 times) when it is no proof, so a
vanilla bobber that missed its armor stand on a loaded runner no
longer reads as a Horizon diff (fishing_hook_entities). Horizon's run
is never retried.
- boat_submerged: the short dip and the re-submersion are observed at
the end of their own 20-tick step, not a step later; at ~40 ticks plus
the client's syncs a loaded host pushed them past the 60 that eject.
- command_exec piglin pickups: what an adult's barter throws comes from
vanilla's unseeded level random and the piglin picks some of it back
up; check that none of the gold lies on the ground instead of the
item count.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: Requeued after merging main; the only conflict was the corpus file. `creeper_cloud_selectors` waits 330 ticks for the generated cloud to expire, which adds that much to the corpus run.
A pack reload rebuilds the mod block-state table from the live enabled set; a mid-world toggle remaps ids under loaded chunks.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- A NoAI horse still runs AbstractHorse.aiStep: tail swish, healing,
grazing (nextInt 200/900/300) and its animations (#7263's new
scenarios diverged at tick 1).
- Breeze.tick: shooting, inhaling and standing draw 1 + nextInt(1)
crumbs, sliding emits twenty without a draw; #7573 had inhaling and
sliding swapped, which shifted every breeze's random.
- mob_ai probe: a baby starts at its species' getBabyStartAge (a
sniffer's -48000), as AgeableMob.setBaby does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: `MeshTables::tint_source` reads the same `info.tints` table as `default_tint`, so JustKarkat's drift worry only applies if a new `Tint` variant is added without a `TintSource` arm, and the exhaustive match will refuse to compile then. Game-side cost is one extra sampler and keeping the sprite views.
🤖 Agent: Opt-in commit budget; off, the allocator wrapper is one relaxed load per call. Off Windows the budget counts mimalloc's commit only (now in the docs).
🤖 Agent: Korean and Croatian land together: game files hold only translated entries (37 English copies dropped), launcher tables follow main's 914 strings so the completeness test passes.
🤖 Agent: Forge's default build is the promotions' recommended one (66.1.0 for 26.3, not the newer 66.1.3), so a fresh Forge instance starts on the build Forge vouches for; installing Forge/NeoForge still answers "can't install yet" until #7740 lands.
🤖 Agent: the name tag kind is format-only plus one match arm in the game (Slot::NameTag never drawn on the body); a name tag item with parts or a mark over 16×16 is refused at parse, so nothing new reaches the mesh path. pr_ready --quick passes on this head with main merged in.
A later failed or timed-out parity-main batch held every Nightly, even
though an earlier run had passed on a commit without the failing merges
(2026-11-01 19:09 to 2026-11-03: no Nightly). green_main_head(parity)
now takes the newest successful parity-main run whose checks passed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The table starts from main's (every string the launcher asks for today) and
takes this branch's text only where main still had the English.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The table starts from main's (every string the launcher asks for today) and
takes this branch's text only where main still had the English.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The game files drop the entries that were still the English text: the game
falls back to en_us for a missing key. The launcher tables take today's
string list (27 strings were added since the branch started) and keep the
English text only where no translation exists, because the launcher test
requires every string in every table.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The fetch thread keeps the last answers in worn.json beside the item files
and reads them only when a request fails, so players it saw before keep
their outfits until the service answers again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve Horizon extension negotiation by announcing the Horizon brand only after the server identifies itself as Horizon.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The obj loader reads the model's .obj and .mtl through a new AssetReader on
ModelLibrary and bakes unculled quads: triangles repeat their last vertex,
`flip_v` and `shade_quads` are honoured, and `map_Kd #slot` picks a texture slot.
A face whose `o`/`g` object the model chain's `visibility` marks false is skipped;
the model closest to the leaf wins. A missing or broken file warns once and bakes
nothing. ModelQuad gains `shade`, which the mesher honours.
Part of #5551
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A model may name a `loader`, inherited through `parent` (a model's own loader
wins). The composite loader bakes the model's elements, then each shown child as
its own model in document order, with the composite's textures as slot fallback.
`visibility` hides children by name, and an inheriting model's map overrides the
loader's. A loader the decoder does not know adds no geometry. Children's parents
are queued when block models load.
Part of #5551
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Read NeoForge's `add_spawns`, `remove_spawns`, `add_spawn_costs` and
`remove_spawn_costs` biome modifiers alongside `add_features` and
`remove_features` when building the content root:
- `add_spawns` appends its spawners (a single spawner object or a list)
to each entity type's spawn category in `spawns_by_category` under
`attributes[minecraft:gameplay/natural_mob_spawns]`, converting
`minCount` and `maxCount` to 26.3's `count` (an integer when equal,
`minecraft:uniform` otherwise) and taking the category from
`entity-types.json`;
- `remove_spawns` removes the resolved `entity_types` (an id, `#tag`,
list, or `any`/`or`/`and`/`not` holder set) from every category;
- `add_spawn_costs` sets `spawn_costs[type]` (`energy_budget`,
`charge`) and `remove_spawn_costs` removes them;
- additions run before removals in mod and file order, unknown entity
types or tags are skipped with a warning, malformed modifiers fail the
build naming the mod and file, and changed biomes also update
`data/<ns>/worldgen/biome/<path>.json` in the overlay when present.
Fixes#7478
Co-Authored-By: Claude Opus 4.6 <[email protected]>
LevelChunk.setBlockState keeps the entity at a position only while BlockEntity.isValidBlockState holds for the state now there; a mismatched or missing one is dropped for the block's own. Horizon kept whatever entity sat there on a same-name write, so a sculk or creaking heart entity spilled by a removal could end up under a chest. BlockEntity::fits maps each kind to the blocks for_block gives it; replace_block_entities and the entity-wanted list now swap on a name change or a bad fit.
Fixes#7880.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
LivingEntity.tick runs checkBedExists for every sleeping living entity before aiStep; Horizon kept it inside villager::ai_step, which the no_ai gate skips. The bed check now runs ahead of the gate; ai_step keeps the hurt wake and the bed-facing pose.
Fixes#7875.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Every kind in entity_lists! stores when it joined: a counter that rises with
each join. A JoinList replaces the Vec for the eleven lists and numbers an
entity on push, insert or extend; one taken out and put back keeps its number.
A chunk's entities are saved in join order. A checked walk over all lists in
join order is added, with unit tests; nothing ticks with it yet.
Part of #6509.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The session now hands each packet to adapter::translate. A 26.2 server's
chunks have their block states renumbered and, on the global palette,
repacked from 15 to 16 bits, and its light masks turn from long arrays
into 26.3's byte arrays. Block, section and block entity updates, level
events with a state, and tags are renumbered; login and respawn read
26.2's byte game types. Serverbound, the teleport confirmation drops
26.3's position, player actions skip 26.3's destroy direction change and
the punch becomes 26.2's swing. A packet the translation can't read goes
on as it came, with a warning.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
The body species (horses, donkeys, mules, llamas, camels, wolves, happy
ghasts, nautili, sulfur cubes) kept their body item (horse armour, a
llama's carpet, a harness, a wolf's armor, a swallowed block) in the same
field as the head item, so a helmet replaced the armour and vice versa;
vanilla holds one in each slot (#7883).
Give Extra a separate body field, route every site that read or wrote
equipment through it — the equipment packet, /item, death drops and their
chances, item breaks and sun protection, the horse's hurtArmor, the wolf's
armor interactions, the mount, harness and nautilus equip paths, the
sulfur cube swallow, the conversion carry-over, the save and the load —
and keep the saved NBT compatible: equipment.body and equipment.head keep
their vanilla keys on every species.
random_token built the control API token, the panel's login codes and its
session cookies from SipHash hasher seeds, the clock and the process ID.
Use getrandom (already in the lockfile through the relay) for 128 random
bits instead.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Every lock on the relay's shared state was lock().unwrap(), so one thread
that panicked holding a lock poisoned it and made every later connection
panic too. Take the state back from a poisoned lock instead, as the
telemetry server already does.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Modpack::read totalled the sizes a zip declares for each entry, which a
hostile .hpack controls, so the 256 MiB overrides limit did not bind what
extract_overrides wrote. Count the bytes actually inflated, fail instead of
silently truncating a file over 64 MiB, and remove what was already written
when a limit is hit.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
LivingEntity.tickEffects ticks each effect with the mob's list in place, so a Poison or Wither tick's damage reads Resistance and Infested and a totem's granted effects stay. Wither ticks hurt with wither, not magic.
Fixes#7948.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
tools/generate_registry_id_adapter.py writes, from two versions'
registries and blocks reports, how an older release numbers block states
and the registries the protocol sends by id, as runs both ways (26.2's
here). adapter::ids holds them and a per-thread context: while a packet
from such a server is decoded (or one for it encoded), the item stack,
data component, particle, entity data, sound and slot display readers
renumber the ids they meet, since those ids decide how the bytes after
them read.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
Exercise distinct and shadowing parameter names with a genuinely runtime-lowered global array and preserve constant parameter length 3.
Co-Authored-By: OpenAI Codex <[email protected]>
`AbstractHorse.handleStartJump` rears the horse, lets it slide on the
rider's keys and plays its jump sound; `Camel.handleStartJump` dashes with
the dash sound. The rider's own client charges the bar and drives a
saddled horse itself (`isLocalInstanceAuthoritative`), so that is the
whole of the server's share — and a guest's had no server side at all.
The mount's reaction is one function over the vehicle
(`mounts::start_riding_jump`), and the jump sound has one definition
(`equine::jump_sound`), used by the host's own ride and the guest's.
ServerGamePacketListenerImpl.handlePlayerCommand sends START_RIDING_JUMP
and STOP_RIDING_JUMP (3 and 4) to the vehicle the player rides, which
takes its own handleStartJump/handleStopJump. The hub's action match only
knew sprinting, waking and (through the arm above it) the inventory and
the elytra, so a guest's released jump bar was dropped on the floor: the
horse never reared, and nobody heard its jump sound.
The connection to a server of an older release follows each direction's
state from the packets themselves and renumbers every packet through the
#6703 tables, both ways, rewriting the handshake to the server's
protocol. The client pings first and adapts the connection when the
server speaks a known older protocol; the server list shows it joinable.
Behind HORIZON_OLDER_SERVERS=1 until packet contents are translated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NbhTr6rUbVKx6iB2X73DNG
The oracle builds the real block entity for the case's state and items and ticks
it over a level that records every `addParticle` and `addAlwaysVisibleParticle`
by registry name with all six doubles as bits; the douse cases call the real
`CampfireBlock.makeParticles` directly.
The probe reads the same requests from the engine: `ParticleEngine.create` now
records them in `created_requests` instead of making a particle, because a
particle's own sprite draw comes off the engine random - the same generator the
tick draws from - and would move the level random the case is compared on.
`CampfireBlockEntity.particleTick` writes the smoke loop as
`for (int i = 0; i < random.nextInt(2) + 2; i++)`, so the iteration bound comes
from a fresh draw on every pass: a tick with smoke in it keeps asking the random
whether to make one more puff, and spends one more draw when it stops. Horizon
drew that count once and looped it as a fixed number, so the puffs after the
first landed elsewhere and the level random was left in a different place.
With the new cases the old code differs in 148 of the 1,104 `particleTick`
cases. The first is an empty lit campfire facing north, ticked six times from
seed 7: both make two puffs and the second is already at
`3fe0e33ecc7f720d 40500e6bba76d26e 3fe7ea5686e766ea` against
`3fe76e1f3d8e2508 40506480d4539aee 3fe85eeae1fa95a2`, and the next draw is
-1720867674 against -1758292495.
The marker suite drives the whole `animateTick` loop, but its world is barriers
only and no case ever made a campfire lit, so the campfire half of the same
module - `CampfireBlockEntity.particleTick` and `CampfireBlock.makeParticles`,
which a shovel's douse also runs twenty times - had no comparison anywhere. Its
scope said "barrier marker positions, velocities and random draws", and the
block entity tick was never reached.
1,104 `particleTick` cases and 144 `makeParticles` cases now tick a campfire at
[0, 64, 0] from a seeded level random: both campfire blocks, lit and unlit,
signal fire on and off, all four facings, each of the four cooking slots held
alone and all four held, and a douse's one or twenty puffs. Both sides print
each particle's type, position and velocity as exact bits, in the order the
level was asked for them, and the level random's next draw after the tick.
TransferCommand.transfer sends every target the transfer packet and
replies with commands.transfer.success.single (the player's name) or
.multiple (the count), both with the hostname and the port; with no
players argument the source's own player goes, and a source without one
is `permissions.requires.player`, as getPlayerOrException is.
TransferCommand.transfer hands each target's client to another server
with ClientboundTransferPacket; the client closes and logs in there. The
hub could receive that packet but had no way to send one, so a
`/transfer` had nowhere to land.
TransferCommand.register registers on a dedicated server only
(Commands.CommandSelection.includeDedicated) at LEVEL_ADMINS, as
`<hostname> [<port> [<players>]]` with
IntegerArgumentType.integer(1, 65535) for the port. The tree had no such
node, so the command was unknown to the game and to the console.
Keep an inferred outer dimension in the existing dimension reversal.
Scope the local-array fixture to length use; named local array bounds
and comma-array declarators have separate pre-existing parser failures.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
SaveAllCommand.saveAll sends `commands.save.saving`, then either
`commands.save.success` or the failure its ERROR_FAILED carries,
`commands.save.failed`. Horizon's console handler logged the error and
said nothing, so the source saw "Saving…" followed by silence and the
failure only reached whoever read the log.
Ready.spawn snaps a newcomer to the respawn data's yaw and pitch; for a player without a bed or anchor that is the world spawn's angle (setworldspawn <pos> <rotation>, the level's saved spawn angle). The !known join branch set the position but left yaw, pitch and head yaw at 0, so every new player faced south whatever /setworldspawn's angle said.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
SetPlayerIdleTimeoutCommand.setIdleTimeout sets the timeout and replies
with commands.setidletimeout.success and the minutes, or
commands.setidletimeout.success.disabled for 0; the command's result is
the number of minutes. The handler runs where the other dedicated
commands do, so an operator in game and the console reach it through the
same dispatcher.
`MinecraftServer.playerIdleTimeout` is a field of the server that
`PlayerList.tick` reads every tick, seeded from `player-idle-timeout` at
start-up. Horizon copied the property into a local of the tick loop, so
nothing a running server did could change it.
The minutes now live beside the other dedicated-server state (`Lists`):
seeded from the property, read from the tick loop, and writable by the
command.
SetPlayerIdleTimeoutCommand registers on a dedicated server only
(Commands.CommandSelection.includeDedicated) at LEVEL_ADMINS, with
IntegerArgumentType.integer(0) minutes. The tree had neither the node nor
the argument, so an operator in game got "Unknown or incomplete command"
and the timeout could only be changed in server.properties.
The dedicated tree now registers it in vanilla's place in the root order
(after pardon-ip), and the dedicated-commands test names it.
Reuse geometry and shadow matrix state for the core projectionMatrix and modelViewMatrix names. Preserve authored names and local hiding, and verify actual provider values through an opt-in GPU pixel control.
Co-Authored-By: OpenAI GPT-6.1 Sol <[email protected]>
MobEffects' Speed, Slowness, Haste and Mining Fatigue amounts are float literals widened into a double modifier, so the low digits differ from a double literal: a zombie with Speed I syncs 0.2760000056922436 in vanilla, not 0.27600000500679017. effect_modifier now writes them as f64::from of the f32, as mobs/finalize.rs already does for the mob table.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Infer the outer array size from top-level initializer-list elements,
including nested aggregates and an optional trailing comma.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
ServerPlayer.die sent the ENTITY_DIE game event before dropAllDeathLoot, and a SculkCatalystBlockEntity within 8 blocks converts getExperienceReward into a sculk charge and calls skipDropExperience. guest_die went straight to spawn_orbs, so a player dying next to a catalyst still dropped orbs. The guest death path now runs catalyst_takes_experience like mob_died does: the reward becomes a charge, no orbs drop, and the credited killer gets kill_mob_near_sculk_catalyst with the death's killing_blow.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The hub stops reading a guest's packets after an attack, dig, use or
interaction when a move is queued behind it, so the move waits for the
next tick (0b179e13d). It also stopped before the arm swing the client
sends right after the action (Minecraft.startAttack: the attack, then
swing). The swing starts the attack strength again, so a swing handled a
tick late took a tick off the next hit: punches 5 ticks apart landed at
4 ticks of charge, 8.48 cart damage instead of 10, about 10 punches to
break a minecart instead of 8. On a loaded machine the guest's 12 in
lan_e2e's minecarts check were no longer enough.
The Punch right behind the action now goes with it; the move still waits.
Fixes#7817.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
chunk_set_block_state ran MovingPistonBlock.destroy's extended-base removal on every state change, so /setblock, /fill, explosions and decay over a moving piston also deleted the extended base behind it. finalTick (removing the moving-piston block entity) still runs there; destroy is ServerPlayerGameMode.destroyBlock's alone, so it now runs only from player_destroy.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The oracle builds a stand-in level holding the case's block, hands the real
useOn a player with an empty hand and records the sound the level is asked
for; the probe reads the case's item and asks the matching branch for the
sound and the pitch.
The corpus held level random seeds only, so the probe always asked for the
fire charge's sound: FlintAndSteelItem's branch, its pitch formula and the one
draw it makes were never compared, and the charge's own useOn was never called
at all.
Each of the 20,006 seeds now runs both items against a campfire, a candle and
stone: 120,036 cases through the real useOn of each item.
submit's sleeping translate is `getEyeHeight(Pose.STANDING) - 0.1`, and
`LivingEntity.getEyeHeight` goes through `getDimensions`, so the standing eye
height carries `minecraft:scale`. The avatar branch used the type's 1.62 and
put a scale-2 sleeper at the scale-1 distance from the pillow; multiply by
`Mob::model_scale()`, as the other branch's `awake_dims` already does.
Test: a scale-2 sleeping avatar's bed-relative pose is twice the unscaled
pose; before, the final z reads -4.4343753 where -2.8143752 is expected.
GeyserParticleOptions and GeyserBaseParticleOptions are constructed from the
case's water_blocks and burst_impulse_base, so the four types reach their real
providers instead of the assertion that had no options for them.
Six types the client implements never reached the comparison: the corpus drew
its kinds from a list without geyser, geyser_plume, geyser_base, geyser_poof,
noxious_gas or noxious_gas_cloud, and the probe's option reader has asked for
water_blocks and burst_impulse_base all along.
The geyser, its plume, base and poof now run with water columns of 1, 2, 3, 5
and 8 blocks and burst impulses of 0, 0.25, 0.5 and 1; the gas and its cloud
run dry and in the still water the cloud needs for a spawn point.
`tags_of` reads the Tags of these three (the rocket's and the trident's
compound by entity id, the falling block's saved compound), but `raw_mut`, the
writer behind `/tag`, had no arm for them, so `/tag ... add` wrote nothing and
still reported success, and `@e[tag=]` never found them. Rockets and tridents
now write to their compound by entity id; a falling block writes to its saved
compound and makes one when it has none.
tools/lan_e2e.py --check entity-tags.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The probe asks can_auto_jump for the case's passenger flag before it drives
MotionWorld, which moves an unmounted player; the oracle answers isPassenger()
from the field its Jumper already carried.
canAutoJump refuses a passenger before it looks ahead, with the on-ground,
staying-on-surface, moving and block-jump-factor tests. The corpus never made
the player a passenger, so that guard ran with the flag false everywhere.
A rider now walks at each of the 39 blocks in the scene from 0.6 of a block
away, at the two step lengths the on-foot walk family jumps from, and one case
in five of the random family rides too.
`LivingEntity.getPassengerRidingPosition`'s row still said the attachments
were not scaled by `getScale`; every box and attachment carries the attribute
now, and only the age scale remains a gap. `EndermanRenderer.getRenderOffset`'s
note names the `0.02 * state.scale` it takes.
`getRenderOffset` adds a gaussian `0.02 * state.scale` per horizontal axis for
a creepy enderman. Horizon used a fixed 0.02, so a `minecraft:scale` 2
enderman's random side offset stayed at the scale-1 distance while its model
and box doubled. Multiply by `Mob::model_scale()`.
The test asserts the same seeded gaussian offset doubles at scale 2, with no
window.
getDimensions had no row; it now points at mobs/living.rs::dims, which is
getDefaultDimensions(pose).scale(getScale()) with the sleeping box ahead of
the scale. getScale moves from riders::scale (now a thin f64 wrapper) to
Mob::model_scale, and sanitizeScale, which was absent, names that function's
two caps.
EntityType.COW is sized(0.9f, 1.4f).eyeHeight(1.3f) in 26.3, so
getDimensions(pose).scale(2) is 1.8 x 2.8 with its eyes at 2.6, the box
Mob::bounds hands the hitbox raycast, the attack range and the leash pins.
The attribute's floor, 0.0625, gives 0.05625 x 0.0875 and eyes at 0.08125.
Without the scale in awake_dims_in the scale-2 case reads the type's own
0.8999999761581421 x 1.399999976158142 x 1.2999999523162842.
The other two pin what the scale must not do: a sleeping villager keeps
SLEEPING_DIMENSIONS's 0.2 at scale 4, and sanitizeScale caps a shulker at 3
(EntityType.SHULKER's 1 x 1 becomes 3 x 3, eyes at 1.5 where 4 would give
2.0) and a happy ghast at 1, where a cow takes its scale as it came.
LivingEntity.getDimensions is getDefaultDimensions(pose).scale(getScale())
for every living entity, and Horizon ran that scale only on players and
mannequins: a cow or zombie with a minecraft:scale attribute rendered (and
cast its shadow) at the attribute's size while its box, eye height, name
tag, leash pins, seat and the hitbox the attack range picks stayed at the
type's. A datapack's giant zombie could be walked through.
getScale is sanitizeScale(attributes.getValue(SCALE)), SCALE being
RangedAttribute("attribute.name.scale", 1.0, 0.0625, 16.0). Mob::model_scale
is now that one port, with the two species that cap it (Shulker 3,
HappyGhast 1), and awake_dims_in multiplies width, height and eye height by
it after the baby, species and size dims, as EntityDimensions.scale(f) does.
The sleeping box stays SLEEPING_DIMENSIONS's 0.2, which getDimensions
returns before it ever reaches getScale.
riders::base_attachment divided the scale out of height() for avatars only,
since a player's box was the only one that carried it; every box does now, so
the default attachment divides it out for all of them (a scaled cow's seat
still rides at 1.4, 2.8 at scale 2). riders::scale, the shadow radius and the
avatar model scale read the same helper instead of repeating the 0-means-unset
check.
`LivingEntityRenderer.submit` scales the pose stack by
`LivingEntityRenderState.scale` (`getScale()`) for every living entity, and
does it after the sleeping offset and before `setupRotations`. Horizon applied
the attribute only in the avatar branch of `species_scale`, so a scaled cow or
zombie kept its model at the type's size while every box already followed the
scale (#7844).
Move the uniform scale into `base_transform_at`, between the bed offset and
`setupRotations`, and leave only `AvatarRenderer.scale` (0.9375) in the avatar
branch. The sleeping translate stays outside the scale: vanilla does not scale
it, and a uniform scale does not commute with it.
Tests are matrix assertions, no window: a sleeping villager at scale 2 (the
bed offset stays put while the pose after it doubles) and an avatar at scale 2
(1.875, not 3.75).
getDimensions had no row; it now points at mobs/living.rs::dims, which is
getDefaultDimensions(pose).scale(getScale()) with the sleeping box ahead of
the scale. getScale moves from riders::scale (now a thin f64 wrapper) to
Mob::model_scale, and sanitizeScale, which was absent, names that function's
two caps.
EntityType.COW is sized(0.9f, 1.4f).eyeHeight(1.3f) in 26.3, so
getDimensions(pose).scale(2) is 1.8 x 2.8 with its eyes at 2.6, the box
Mob::bounds hands the hitbox raycast, the attack range and the leash pins.
The attribute's floor, 0.0625, gives 0.05625 x 0.0875 and eyes at 0.08125.
Without the scale in awake_dims_in the scale-2 case reads the type's own
0.8999999761581421 x 1.399999976158142 x 1.2999999523162842.
The other two pin what the scale must not do: a sleeping villager keeps
SLEEPING_DIMENSIONS's 0.2 at scale 4, and sanitizeScale caps a shulker at 3
(EntityType.SHULKER's 1 x 1 becomes 3 x 3, eyes at 1.5 where 4 would give
2.0) and a happy ghast at 1, where a cow takes its scale as it came.
LivingEntity.getDimensions is getDefaultDimensions(pose).scale(getScale())
for every living entity, and Horizon ran that scale only on players and
mannequins: a cow or zombie with a minecraft:scale attribute rendered (and
cast its shadow) at the attribute's size while its box, eye height, name
tag, leash pins, seat and the hitbox the attack range picks stayed at the
type's. A datapack's giant zombie could be walked through.
getScale is sanitizeScale(attributes.getValue(SCALE)), SCALE being
RangedAttribute("attribute.name.scale", 1.0, 0.0625, 16.0). Mob::model_scale
is now that one port, with the two species that cap it (Shulker 3,
HappyGhast 1), and awake_dims_in multiplies width, height and eye height by
it after the baby, species and size dims, as EntityDimensions.scale(f) does.
The sleeping box stays SLEEPING_DIMENSIONS's 0.2, which getDimensions
returns before it ever reaches getScale.
riders::base_attachment divided the scale out of height() for avatars only,
since a player's box was the only one that carried it; every box does now, so
the default attachment divides it out for all of them (a scaled cow's seat
still rides at 1.4, 2.8 at scale 2). riders::scale, the shadow radius and the
avatar model scale read the same helper instead of repeating the 0-means-unset
check.
GPU frames draw the near solid sections from lists a compute pass builds
(gfx/gpu_culling.rs, gpu_culling.wgsl), one multi_draw_indexed_indirect_count
per arena page, instead of the CPU listing a few draws per section by the
sides it shows the eye and grouping them by page. Every resident section
with solid quads has a record under an id it keeps while it stays: upload,
release, remove_columns and clear_sections keep the records in step, and
its copy. The pass applies the changed records, then writes each page's
draws in id order, so where faces of two sections tie in depth the result
is the same every frame.
The CPU walk still decides: in a GPU frame its solid loop marks the whole
sections it keeps within the render distance that have a record, and the
pass draws exactly those. Sections fading in or out, columns past the render
distance, far terrain and translucent quads stay on the CPU's lists, so GPU
frames show the CPU frames' pixels. Frames with the title, a pack's terrain,
a shadow view or the water mirror stay CPU frames; GPU frames turn cull.rs's
occlusion test off.
It is offered where the device draws a count the GPU wrote (DirectX 12, or
Vulkan 1.2 or VK_KHR_draw_indirect_count) and runs compute shaders, and
where vanilla frames never draw flat; for now the control word gpu-culling
on turns it on. gpu_culling_requested, which means cull.rs's pass, becomes
occlusion_culling_requested.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Boats were no target for any projectile and minecarts only for arrows, so a
trident or a snowball flew through them and an arrow flew through a boat.
Vanilla's VehicleEntity is pickable: its hurtServer takes the damage (shake,
+10 per point, destroyed past 40), and the projectile is spent (arrow,
snowball) or bounces (trident).
- Boats and minecarts join the targets of arrows (tridents included) and
snowballs, in the tick, the mod-visible tick-through and the thrown trident.
- The thrown trident's bounce is half what it was: ProjectileDeflection.REVERSE
scales by -0.5 before the (0.02, 0.2, 0.02); it now stops where it met the
box, which grows by ProjectileUtil.computeMargin, and its drag is the float
0.99F.
- tools/lan_e2e.py --check projectile-vehicles.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Share the existing adult egg-laying step with the NoAI path and cover
countdown, laying, baby and jockey controls on real dedicated servers.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
🤖 Agent: queued at b17870dbf5. Port map rows only. Every cited file and function exists on main and does what the row says (checked the ones moved from missing to port against the code).
🤖 Agent: queued at b3475f8769. Port map rows only. Every cited file and function exists on main and does what the row says (checked the ones moved from missing to port against the code).
🤖 Agent: queued at 70b60c488d. Port map rows only. Every cited file and function exists on main and does what the row says (checked the ones moved from missing to port against the code).
🤖 Agent: queued at 76f1dc4a66. Port map rows only. Every cited file and function exists on main and does what the row says (checked the ones moved from missing to port against the code).
doClick only starts the collect when the carried stack is not empty and the
clicked slot has no item or may not be taken; the test clicked a slot holding
the carried item, which vanilla refuses.
🤖 Agent: queued at dbf5ea1396. I added 90acfe8039 and 9e555b5303: the new commit showed the page's headings (Overworld, Nether biomes...) as raw English through Text::literal; they are now keys under horizon.pages.music in en_us.json, shown with tr, so they can be translated. I also merged main (dbf5ea1396): the cosmetics extras and the music ones sit side by side in extras.rs. Clippy -D warnings and the extras and music tests pass on playhorizon.
Keep sized array length constant under local size-name shadows and avoid generated binding collisions with authored identifiers.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
🤖 Agent: queued at ef6cc9fa38. CI parity passes use_item_on against vanilla at this head (built on #7425, main merged): the openable, dragon egg and block state cases match.
🤖 Agent: queued at 66f5d2ed9b. CI parity on the stacked #7449 (which contains this PR, merged with main) passes use_item_on against vanilla. I merged main here (66f5d2ed9b): the harness conflict was two new case groups, both kept.
An Add button beside the search box on Select Resource Packs opens a screen
that searches Modrinth, shows what it found and installs one into the packs
folder. The available list refreshes without the screen closing, so the pack
can be turned on straight away.
Off by default, behind a new `packBrowser` extra. The pack screen is
otherwise vanilla's and tools/test_pack_screen_parity.py compares it with
vanilla, so nothing is drawn at all unless a player turns it on: with the
extra off the screen's draw output is unchanged.
The screen carries a sidebar of category and resolution filters, its own
search box, and one card a pack: icon, name, author, description, and an
Install button that reads Installing and then Installed. Two columns where
the window has room for them, one where it does not. The layout is pinned by
a test rather than by eye: the name column ends before Install begins and the
description starts below the icon, at both widths.
`Backdrop` is new in screen_draw: a screen says there which background it
wants, and `Game::shows_panorama` honours it. The panorama is a cube the
renderer draws before the HUD, so a draw list cannot hold one; a screen asks
instead. The two cases that always had a panorama are untouched, and a
screen that leaves the backdrop at None behaves exactly as before. Only in a
menu: in a world it would replace the world the player is standing in.
Downloaded icons are freed when the browser closes. Each is 64 KiB and a
search brings twenty, so left in the pack screen's icon map they accumulated
for the rest of the session.
Co-Authored-By: Claude Opus 5 <[email protected]>
🤖 Agent: queued at 1dfb29c7f0. Tested in a union of 8 PRs on playhorizon, then again on current main: clippy and the lib tests pass, and command_exec matches vanilla. /bossbar list now prints in Java HashMap order. I merged main (1dfb29c7f0): the corpus conflict was two new groups side by side, both kept.
🤖 Agent: queued at 828237afa7. Tested in a union of 8 PRs on playhorizon, then again on current main: clippy -D warnings and the full horizon-client/-lib/-items lib tests pass, and command_exec and save_roundtrip match vanilla. The save round trip now covers leads, and passes.
🤖 Agent: queued at 79ae7cfe19. Tested in a union of 8 PRs on playhorizon, then again on current main: clippy -D warnings and the full horizon-client/-lib/-items lib tests pass, and command_exec and save_roundtrip match vanilla. /loot rolls loot_table entries through the nested table, as the new corpus cases check.
🤖 Agent: queued at a65b941358. Tested in a union of 8 PRs on playhorizon, then again on current main: clippy -D warnings and the full horizon-client/-lib/-items lib tests pass, and command_exec and save_roundtrip match vanilla. A DeathLootTable on a mob now replaces its type's table when it dies, and the save round trip keeps it.
🤖 Agent: queued at d6bb00a14d. Tested in a union of 8 PRs on playhorizon, then again on current main: clippy -D warnings and the full horizon-client/-lib/-items lib tests pass, and command_exec and save_roundtrip match vanilla. I fixed the new test (d6bb00a14d): ExperienceOrb.tick's air drag is the float 0.98F, so the expected velocity is 0.014 * 0.99 * (double) 0.98F; the code was right.
The pieces a screen needs to search Modrinth for resource packs and put one
in the packs folder, with no UI of its own yet. The launcher has its own
browser under launcher/src/browser/, but the launcher is a separate
workspace and nothing there is reachable from the game.
Search and download both run on a worker thread and report over a channel,
so a caller only ever polls. Filters map to Modrinth facets, grouped so that
terms within a group widen the search and separate groups narrow it: asking
for 16x and 32x gives both rather than nothing.
What a reply says is treated as data, not as instruction:
- a served file name cannot leave the packs folder (separators and parent
references are stripped, and the name always ends in .zip)
- a download is checked against the SHA-1 the index states for it
- a reply larger than 512 MiB is refused rather than read into memory
- the file is written beside its destination and renamed, so a pack list
never sees a half-written zip
- an existing pack of the same name is never overwritten; the new one is
numbered
SHA-1 is implemented here rather than taking a dependency for it, and is
checked against the published FIPS 180-4 vectors.
Co-Authored-By: Claude Opus 5 <[email protected]>
`decode_bytes` read PNG only, so any image that was not one silently failed
to load. The game's own resources are PNG throughout, but images fetched
over the network are commonly neither: Modrinth serves many project icons
as WebP.
The format is told apart by the bytes themselves rather than by a file
name, because a name is not always there and would not be trustworthy
anyway. A `RIFF` container is only WebP when the tag at byte 8 says so, so a
WAV is not mistaken for one. Anything unrecognised still falls through to
the PNG reader, so its error stays the one reported.
Both decoders are small and pure Rust, taken instead of the whole `image`
stack: `image-webp` and `zune-jpeg`. WebP animations give their first frame,
as an animated PNG already does; JPEG has no alpha, so every pixel is
opaque, and greyscale JPEGs expand to RGBA like colour ones.
Co-Authored-By: Claude Opus 5 <[email protected]>
🤖 Agent: requeued after merging main: main made mobs::skin::Skin's fields private, so the cosmetics draw now builds it with Skin::new (same walk, no per-frame change); stress_scenes keeps both stress_avatars and main's stress_markings. Clippy -D warnings and test_perf_bench pass on this head.
🤖 Agent: queued at 35210289dd. Tested in a union of the five on playhorizon: clippy -D warnings and the recipe and mod lib tests pass. Main merged here (35210289dd): xtask mod-api --check and the stamp tests pass. The categories are read once and cached until the book rebuilds, so opening the book costs nothing more without a mod.
🤖 Agent: queued at 3abeb4f845. Tested in a union of the five on playhorizon: clippy -D warnings and the content and mod lib tests pass. Main merged here (3abeb4f845): xtask mod-api --check and the stamp tests pass. Shapes are resolved when the content loads; the per-block collision lookup is unchanged.
doClick calls quickMoveStack again while the clicked slot keeps the item it
moved, because moveItemStackTo fills one empty slot per call. This engine
calls quick_move once, and move_to fills every empty slot of its range in
one pass, which leaves the same remainder as vanilla's loop: 40 snowballs at
16 a slot land 16/16/8, two slots of room leave 8 in the chest, and a full
inventory moves nothing.
The QUICK_MOVE arm says so, so the invariant is not lost to a later
"one slot per call" change to move_to.
Entity.move has no bubble column push in vanilla. LivingEntity.aiStep
calls travel and then applyEffectsFromBlocks, so the lift is added after
the fluid drag of that tick. The mob port pushed at the end of do_move,
inside travel, so water drag scaled the lift by 0.8 (an iron golem in an
up column: 0.139 on the first tick, vanilla 0.175). The push now follows
travel in the mob tick.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Extras > Cosmetics: My cosmetics (the account's: the game signs in for one call through Mojang's
join and PUT /v1/visible, notes what the service confirmed per account, retries from 15 s to
10 minutes), Other players' cosmetics (Shown, Name tags only, Hidden; the old on/off line keeps
its meaning) and Others' auras and trails (off by default). horizon_cosmetics::prefs holds the
lines the launcher writes too. A player's name tag style draws its mark before the name and its
line under it, in the name tag's three passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Respect device attachment count and format byte limits when batching the required main-image clears.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
getDimensions had no row; it now points at mobs/living.rs::dims, which is
getDefaultDimensions(pose).scale(getScale()) with the sleeping box ahead of
the scale. getScale moves from riders::scale (now a thin f64 wrapper) to
Mob::model_scale, and sanitizeScale, which was absent, names that function's
two caps.
EntityType.COW is sized(0.9f, 1.4f).eyeHeight(1.3f) in 26.3, so
getDimensions(pose).scale(2) is 1.8 x 2.8 with its eyes at 2.6, the box
Mob::bounds hands the hitbox raycast, the attack range and the leash pins.
The attribute's floor, 0.0625, gives 0.05625 x 0.0875 and eyes at 0.08125.
Without the scale in awake_dims_in the scale-2 case reads the type's own
0.8999999761581421 x 1.399999976158142 x 1.2999999523162842.
The other two pin what the scale must not do: a sleeping villager keeps
SLEEPING_DIMENSIONS's 0.2 at scale 4, and sanitizeScale caps a shulker at 3
(EntityType.SHULKER's 1 x 1 becomes 3 x 3, eyes at 1.5 where 4 would give
2.0) and a happy ghast at 1, where a cow takes its scale as it came.
LivingEntity.getDimensions is getDefaultDimensions(pose).scale(getScale())
for every living entity, and Horizon ran that scale only on players and
mannequins: a cow or zombie with a minecraft:scale attribute rendered (and
cast its shadow) at the attribute's size while its box, eye height, name
tag, leash pins, seat and the hitbox the attack range picks stayed at the
type's. A datapack's giant zombie could be walked through.
getScale is sanitizeScale(attributes.getValue(SCALE)), SCALE being
RangedAttribute("attribute.name.scale", 1.0, 0.0625, 16.0). Mob::model_scale
is now that one port, with the two species that cap it (Shulker 3,
HappyGhast 1), and awake_dims_in multiplies width, height and eye height by
it after the baby, species and size dims, as EntityDimensions.scale(f) does.
The sleeping box stays SLEEPING_DIMENSIONS's 0.2, which getDimensions
returns before it ever reaches getScale.
riders::base_attachment divided the scale out of height() for avatars only,
since a player's box was the only one that carried it; every box does now, so
the default attachment divides it out for all of them (a scaled cow's seat
still rides at 1.4, 2.8 at scale 2). riders::scale, the shadow radius and the
avatar model scale read the same helper instead of repeating the 0-means-unset
check.
`debugGuiScale` 0 (the default, Auto) means half the automatic GUI scale,
and the halving truncated. An even automatic scale is exactly half either
way, but an odd one lost: a 1280x720 window has an automatic scale of 3, so
the overlay drew at 1, a third of the GUI scale, while a maximised 1920x1080
window has 4 and drew at 2, a half. The overlay therefore looked smaller,
relative to everything else on screen, on a restored-down window than on a
maximised one.
Rounded up instead, so an odd scale takes the nearer usable half. Every even
automatic scale is unchanged, which is why the existing expectations for
854x709 and 1920x1080 still hold; a 1280x720 case is added beside them.
Co-Authored-By: Claude Opus 5 <[email protected]>
A refused slot keeps its stack against a click and a shift-click while the
slot next to it gives its own up; a collect starts from an empty or a
refusing slot only, and skips the slots that refuse; THROW drops nothing
while can_drop_items is false; and a survival guest cannot take a cursed
chestplate off, while a creative one can.
doClick guards four takes that Horizon's guest menu engine had no way to
refuse: a PICKUP or a QUICK_MOVE out of a slot whose mayPickup says no, the
clicked slot of a PICKUP_ALL (a collect starts from an empty slot or from
one that refuses), each slot that collect sweeps, and THROW while
Player.canDropItems is false.
Rules gains may_pickup and can_drop_items, both defaulting to true, so a
host's plain container slots behave as before and a menu with stricter
slots can say so. View::may_pickup answers with the one rule the player's
own slots have in vanilla, ArmorSlot.mayPickup: a survival player cannot
take off armour carrying the binding curse (PREVENT_ARMOR_CHANGE), which
the local menu engine already refuses and the guest path did not.
An iron helmet shift-clicked from menu slot 45 lands in the helmet slot;
with that slot taken the next helmet fills the main inventory, and three
diamonds from the off hand fill it too.
InventoryMenu.quickMoveStack asks for the item's equipment slot before it
looks at the clicked index, so the armour branch also runs for menu slot
45, the off-hand slot: an armour piece held there is put on when its armour
slot is empty. Horizon's first arm took every off-hand stack straight to
the inventory, so a helmet in the off hand stayed unworn.
The chain now keeps vanilla's order above the armour slots: the equipment
slot first, then the main-inventory and hotbar ranges, then the final else
that an off-hand stack takes into the inventory (which is where that index
already went).
🤖 Agent: queued at 86929d2ebe. The points of my earlier review are addressed (UPDATE_ALL kept). I added 7f5ff72503: the debris pick now draws the weighted random for every particle, as vanilla's does, and the unused explosion_blocks is gone. Clippy and the particle lib tests pass on playhorizon with main merged.
First Light, the founder set's theme, replaces the sky's six stops and the accent's hue while the
selected account wears it, read from the outfit kept on disk (Saved::theme) so it holds offline.
The look is worked out again after each change of the outfit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A PICKUP or a QUICK_MOVE with button 2 leaves the stack in its slot, the
cursor alone and drops nothing outside the window; button 1 still takes
half of the stack, and THROW's button 2 still means the whole stack.
AbstractContainerMenu.isContainerClickButton refuses PICKUP and QUICK_MOVE
for any button other than 0 and 1 before doClick does anything else, so
such a click moves nothing at all. Horizon read every button except 1 as
the primary action, so a modified client could take a stack out of a slot,
shift-click it into the hotbar, or drop the cursor with a button the
vanilla screen never sends.
THROW is not gated by that check, but its amount reads the button the same
way: doClick takes one item for button 0 and the whole stack for every
other button, where Horizon took one for anything but button 1.
🤖 Agent: queued at a43709317e. Tested in a union on playhorizon: clippy -D warnings, the datapack_biome_sync test and lan_e2e chest (host and guest) pass. The new worker context is built only when a world opens or resources reload, and reuses the pack's when the biome table is the same, so nothing changes per frame or per chunk.
The founder set replaces the early-player set in words. Entry::theme (a launcher theme's sky and
accent), Me::visible and Service::set_visible (Show my cosmetics), and Saved: the last catalogue
and answer plus the changes still to send, per account, synced in order once the service answers.
Item files are kept by hash.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Between Servers and Versions, with the cosmetics flag on: a row per slot, the player on their own
skin with their name tag style, what they own for the chosen slot, the founder set's claim (and,
once its window closes, that it was free until then and isn't offered any more), and three
switches: Show my cosmetics (on the service), Hide other players' cosmetics and Show other
players' auras and trails (horizon_cosmetics::prefs, the game's horizon-extras.txt lines). It opens
on the outfit kept on disk: offline it keeps working and counts what waits; a failure keeps the
outfit with Try again and Copy details. Settings > Account no longer has the Wardrobe panel.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Settings > Account gets 'Wardrobe…' on a Microsoft account when
horizon_cosmetics::enabled(): a picture of the account's skin wearing its
items (turnable), the early-player set's claim while its window is open,
and a row per slot to choose what to wear. HORIZON_LAUNCHER_PAGE=wardrobe
(development builds) shows it with a made-up account, for screenshots.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A content block always drew as its template does, so a block whose moving parts its block entity
draws every frame (a turning shaft, a machine's arm) still had its model baked into the chunk mesh.
`properties.render_shape` takes vanilla's `RenderShape` by name: `MODEL`, `ENTITYBLOCK_ANIMATED`
(the model is left out of the chunk mesh) or `INVISIBLE`. It sets the state's `render_shape`, which
the mesher already reads. Outline, collision, light and occlusion are unchanged.
Item 1 of #5550.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 1a8c48a62f. I added 1a8c48a62f: the charge's particles were spawned only by the window that ran the wither, so players on a server (the default single player too) still saw none. remote.rs now spawns them for a server's clients from the synced charge, as vanilla's client tick does. Clippy and the wither lib tests pass in a union on playhorizon.
🤖 Agent: queued at 418cca25b5. Tested in a union on playhorizon: clippy -D warnings passes, and command_exec matches vanilla on every case this PR adds (the only failing cases were the trial spawner ones that #7689 fixed on main since).
The creaking had no sound row at all. Its hurt sound is the sway while it is
bound to a heart and `Monster.getHurtSound` otherwise, and the giant, which
overrides nothing, keeps the same hostile pair.
Three hits through `Mob::damage`, the `LivingEntity.hurtServer` the game runs
for a landed blow, read back from the mob's outbox. A creaking with no heart
takes the blow through `Monster.hurtServer` and plays `entity.hostile.hurt`;
a heart-bound one only takes `#bypasses_invulnerability`, whose hurt sound is
`entity.creaking.sway`. A giant is hurt and then killed for the hostile pair.
With the arm removed all three read what the table gives today:
`["entity.generic.hurt"]` for the unbound creaking, the sway case and the
giant's hurt, and the giant's death is `["entity.generic.death"]`.
`Creaking.getHurtSound` answers a blow with `SoundEvents.CREAKING_SWAY` while
the creaking is bound to a heart and with `super.getHurtSound` otherwise, which
is `Monster.getHurtSound`: `entity.hostile.hurt`. `Monster.getDeathSound` is
`entity.hostile.death`, and `Giant` overrides none of the three, so it keeps
the hostile pair too.
`ai::voice_sounds` had no arm for either, so both fell through to the type's
own sounds: the creaking's table row carries `entity.generic.hurt`, the
giant's `entity.generic.hurt` and `entity.generic.death`.
Refs tools/port_map/game2-003.tsv (Creaking.getHurtSound, new row).
🤖 Agent: queued at 68c6f12e92. Tested in a union on playhorizon: clippy -D warnings and the full horizon-assets lib tests pass, foreign_tests included. Everything happens when item models load; the per-frame item render path is unchanged. A definition that still fails now logs why instead of disappearing without a word, which helps pack authors.
🤖 Agent: queued at b56ec105e0. Tested in a union on playhorizon: clippy -D warnings, the crafting, native and music lib tests pass. Main merged here (b56ec105e0, hook conflicts resolved, generated.rs regenerated): xtask mod-api --check, the markers and the stamp tests pass. With no mod, a recipe ingredient has no custom kind, so the craft test never reaches the hook.
775981f9a8 made the biome headings at run time and brought back raw English
labels shown with Text::literal. The built-in and biome group labels are now
keys under horizon.pages.music, and Row::Heading shows tr(key).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Apply the ConversionTracker affliction predicate for Zombie-derived types when writing their saved counter. Add real-server wet, dry, feet-only and water-removal controls and mark the positive counter codec fixture as afflicted.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The service checks the account the way an online-mode server does: the
launcher tells Mojang's session server it joins a one-time id the service
chose, and the service asks Mojang. The Minecraft token goes to Mojang only.
Catalogue, the account's items, claiming the early-player set while its
window is open, and what each slot wears. Unused until the Wardrobe panel.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 8987ae75eb. Tested in a union of 8 PRs on playhorizon: clippy and the full horizon-client/-lib/-items lib tests pass, the new off-hand quick-move test included. One nit for later: the comment says a totem goes to the off hand, but a totem has no equippable slot in 26.3, so only items whose component says offhand (the shield) take that branch, which is what the code does.
ExperienceOrb.tick multiplies by 0.98F, so the velocity after one tick is
0.014 * 0.99 * (double) 0.98F, about 2.6e-10 away from the double 0.98.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A name_tag item has no parts: its texture is a square mark of at most 16x16 drawn before the
player's name and tag.line the colour of a line under it. bone defaults to head. The game's
body pass never draws one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep depth2 before the opaque hand and depth1 after it. Include Deferred
post readers and attached compute stages in depth requirements, and
populate requested copies for water-only shader programs.
Fixes#7815
Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
Every fourth case clicks with the pick-item button instead of the left or the
right one: on the menu slot, outside the window and over neither, with a
carried stack or an empty hand, with and without infinite materials, and
sometimes with another button's click between the press and the release.
`AbstractContainerScreen.mouseClicked` asks keyPickItem.matchesMouse(event)
&& player.hasInfiniteMaterials() before anything else, and then answers CLONE
for the hovered stack (or slot id -999 outside the window) instead of PICKUP,
QUICK_MOVE or THROW; a carried stack starts a quick craft drag of type 2
instead of a plain one; and mouseReleased answers CLONE for the carried stack
whenever the event is the pick-item button, so a Survival player's middle
release clones too (and the server refuses it). Horizon answers all of it on
main (`container_screen::Clicks`'s press and release, reached from the game's
inventory_pick_press and inventory_pick_release) and no case pressed the third
button.
The row said inline at `shulker::progress_aabb` and that the down-face height
scale was not seen; the scale is `shulker::peek_height_scale`, applied by
`Mob::awake_dims_in` over the plain box.
The test ticks the client path a remote shulker takes: `Shulker.tick` eases
`currentPeekAmount` 0.05 a tick towards the synced raw peek, 100 while
`ShulkerAttackGoal` holds the lid open, so ten ticks leave 0.5 and
`Shulker.getDefaultDimensions` scales the 1 x 1 x 0.5 box by `(1, 1.5)`.
With the arm removed it reads "a half open shell is 1 x 1.5 with its eyes at
0.75: 1 x 1 x 0.5". A second test keeps the other five attach faces at
1 x 1 x 0.5 with the lid fully open, and a closed shell under a block at 1 x 1.
`Shulker.getDefaultDimensions` scales the plain box by `(1, 1 + currentPeekAmount)`
when the shell is attached to `Direction.DOWN` and the lid has started to open,
so a shulker fixed under a block stands up to twice as tall while it fires, and
its eyes rise with the box. Horizon kept the 1 x 1 box for every peek amount.
`Mob::awake_dims_in` applies that scale last, over every other arm, through
`shulker::peek_height_scale`: it reads the eased peek in `anim.v[PEEK]`, the
`currentPeekAmount` a client ticks towards the synced raw peek.
Refs tools/port_map/game2-002.tsv:121 (Shulker.getDefaultDimensions, inline).
🤖 Agent: queued at 3042ed59ab. Built and ran with #7407/#7431: the host's half of the extended minecarts check passes; the guest's punches fail exactly as on main 3fc4c4ca0 (#7817), which this PR doesn't touch.
🤖 Agent: queued at 61c54f727b. lan_e2e frames passes for host and guest (diamond turned in the frame, helmet on and off, name tag consumed). minecarts fails the same way on main (#7817).
🤖 Agent: queued at 32779e1a20. lan_e2e on playhorizon (with #7412 and #7431 merged on top): shulker_lift and containers pass; the guest stands on an opening shulker box like the host. minecarts fails the same way on main 3fc4c4ca0 (#7817), not this PR.
Use the iterator spelling required by current Clippy for constant RGBA
chunks. Preserve every pixel predicate, count and assertion.
Co-Authored-By: Codex (GPT-6.1-sol) <[email protected]>
Share the existing allowed-in-Peaceful check between plain and NBT summons instead of rejecting the monster category. Add fresh-server admission controls for camel husks, camels, husks and zombie horses.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The byte comparison is ambiguous with serde_json's numeric PartialEq impls.
Make the cast output explicit without changing production skinning behavior.
Co-Authored-By: Codex (GPT-6.1-sol) <[email protected]>
Measures, tick by tick against the vanilla server, 82 cells of two kinds
joined in both orders (wind, TNT blast, projectile hits, rider and vehicle,
pushes and merges). Horizon's 38 known differences are recorded in KNOWN,
printed every run and not failing CI. No game behaviour changes (#6509).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at b72413d1de. Nothing calls it yet; the point to watch is that the frame never blocks: Frame::worn only reads the cache under one lock, and the worker posts at most 64 UUIDs per request with a 15 s→10 min backoff when offline, so a dead service costs the game nothing.
For the launcher's Wardrobe: the player model at rest with its skin and
each item on its bone, from any angle, with a depth buffer and cutout
alpha. Tests check which face shows from the front and from behind, and
that a cap sits above the head without covering the face.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the loader grid reads Prism's list through loaders::Kind::parse, so it shows only Fabric and Quilt until the Forge/NeoForge backends land; their logos and notes are already in.
RedstoneWireBlock.useWithoutItem turns a wire that is a cross or a dot into
the other shape and passes anything else on; Horizon's wire_use already does
it and nothing in the suite clicked a wire.
All three turn their own property by hand on a right click
(RepeaterBlock.useWithoutItem, ComparatorBlock.useWithoutItem,
DaylightDetectorBlock.useWithoutItem) and Horizon already does each; the
suite clicked none of them.
🤖 Agent: the dialog now pins a new Horizon instance's version when a non-newest Nightly is picked (Instance::version, same as the Home picker); picking Newest keeps it following updates.
It pinned the source text of container_sound and the guest open path
rather than what a player hears; lan_e2e's containers and shulker_lift
checks run both through two real games.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep the merged pose reuse and tagged-layer contracts. Resolve the sole
renderer method rename conflict with the standard-pack CPU-layer guard,
and update the original fixture's capability calls. No unmerged decoder
implementation is imported.
Co-Authored-By: Codex (GPT-6.1-sol) <[email protected]>
Use the standard array chunk iterator for known RGBA pixels, retaining the
same original GPU outputs and assertions while satisfying current Clippy.
Co-Authored-By: OpenAI Codex (GPT-6.1 Sol) <[email protected]>
Retain shader identities, overlays and hidden shadow casters in GPU output
space while preserving conservative CPU fallbacks and separate glowing outlines.
Use each current culling query's result instead of historic CPU-span markers.
Include original draw/offset/overlay regressions and an asset-free offscreen
CPU/GPU comparison fixture. Validation remains in progress for draft review.
Co-Authored-By: Codex (GPT-6.1-sol) <[email protected]>
Average all four mapped UV corners while retaining the non-flat tile-centre fallback. Add original terrain/shadow GPU regressions for partial, rotated, clamped and four-corner UVs.
Co-Authored-By: Codex GPT-6.1 Sol <[email protected]>
🤖 Agent: queued at d9b0388195. Checked: clippy, the bow and native lib tests and the arrow harness pass on the branch before fa876269f3. That commit only puts the crossbow mob's attack-delay draw back before the pitch draw, as main has it. Its head builds and passes xtask mod-api --check and the stamp tests on today's main.
🤖 Agent: horse markings and the slime outer cube now take the GPU skin path when the renderer draws tagged layers in order (improved transparency off); the every-mob byte test covers a whitefield horse and a slime, and translucent bodies stay on the CPU.
Mojang's versions (Snapshots, Old, search, the list) and the loader grid,
one card per loader loaders::Kind knows, with its logo and the mockup's
note, four across from 1280 px.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mockup's InstanceModal: kind cards and a live preview on the left;
Version (pin) and Start from for Horizon, name and group, icons (loader
logos, grass block, blocks, colours) on the right. All four mockup loader
logos ship now; the loader list follows loaders::Kind.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 5494d7fc5e. Your main merge changes only context lines, so the code is what I checked: clippy -D warnings plus the explosion and native lib tests pass. On today's main it passes xtask mod-api --check and the stamp tests. The hook fires only with a native mod loaded.
🤖 Agent: map_vertices now borrows the map's decorations instead of cloning them; the borrow ends before any mutable use of self.maps, so nothing changes in what is drawn.
🤖 Agent: a layer on the body's own model now copies the body's poses and Skin::draw reuses the previous walk when model, base matrix and pose version match; the CPU vertex digest of all 90 mob types × ages × variants is byte-identical to main.
Brigadier's StringReader.skipWhitespace uses Character.isWhitespace;
Reader::skip_whitespace used Rust's char::is_whitespace. Rust skips U+0085
and the no-break spaces U+00A0, U+2007 and U+202F, which Java doesn't, and
Java skips U+001C to U+001F, which Rust doesn't. Reader::is_java_whitespace
is Java's set, checked against Java 25 over all of Unicode.
Fixes#7672
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dispatch projectile block hits to the dripleaf behavior, including powered leaves, and schedule the existing full-tilt reset after 100 ticks. Add fresh-server projectile and no-hit timing controls.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
ServerLevel ticks entities in join order, so primed TNT, a falling block
or an experience orb that joined before a wind charge has already moved
when the charge bursts, and the push is for its next tick. Horizon ticks
per kind and these three tick after the projectiles, so the push landed
a tick early (6 of 99 wind_walk values off the real server). The push
now waits for the entity's own tick when it joined before the charge.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Use the running pack's compiled non-player entity caster capability to
avoid adding vanilla mob blobs beside its shadows. Preserve fallback
for player-only, disabled, absent and failed paths and native dimensions.
Add original compiled-pipeline and production sprite-selection controls.
Co-Authored-By: Codex GPT-6.1-sol <[email protected]>
A lid pushing this client's own player wrote the position straight into the
body. Vanilla's moveCollidedEntities calls Entity.move(MoverType.SHULKER_BOX,
...), an ordinary move: the collision scan resolves it and its end writes the
ground state. A direct write leaves the body's on_ground stale, so a body the
lid lifts off the ground keeps a ground flag gravity has to fight, and the lift
reads as steps rather than a move. The local player now goes through
MotionWorld::shulker_box_push -> entity_move::move_entity, and only the player:
the box entities the server pass already pushed are left to it, so a hosted
world no longer pushes its guests and mobs twice a tick.
The pushed distance stays vanilla's (box size + 0.01):
Shulker.getProgressDeltaAabb has no halving in 26.3, so a lid rising 0.05 per
tick pushes 0.11.
The container open and close sounds never reached everyone. A guest opening a
container told nobody: the host's copy changed its opener count only when the
menu closed, so the box opened silently for every player around, the opener
included. Vanilla counts the opener (ContainerOpenersCounter.incrementOpeners)
and plays the sound with a null source (level.playSound(null, ...)), so the
host now counts a guest's open and, on the first opener, plays the sound where
the capture relay (lan_broadcast_effects) carries it to every guest, the opener
included. Nothing is queued separately: the relay already sends every play of
the tick, and a second queue made a hosted world play the sound twice.
Fixes#6657🤖 Generated with Codebuff
Co-Authored-By: Codebuff <[email protected]>
300 avatars with three items each: frame p50 +0.63 ms over none, +0.48 ms
after (stress_avatars, alternating A/B on the Mac mini).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Register Croatian (hr / Hrvatski) as a launcher language and Horizon game
language, falling back to English for untranslated strings.
Co-Authored-By: Claude Code <[email protected]>
Track the actual source views so equal-sized world-target replacements do
not leave post-processing sampling the previous frame. Add original GPU
regressions for independent replacements, retained inputs, resize and the
no-final fallback.
Fixes#7744
Co-Authored-By: OpenAI Codex (GPT-6.1 Sol) <[email protected]>
🤖 Agent: queued at 0e6e918a75. The native template builds a trait mod against the current hooks and ships with the rest of cargo horizon-mod new. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests and datapack_worldgen parity pass.
Vanilla's LeverBlock.useWithoutItem pulls the lever on either state and
ButtonBlock.useWithoutItem presses a button that is not already pressed,
both of which Horizon already does; the suite had no case for either.
Entity.push(Entity) moves an entity only when it is not a vehicle
(!isVehicle() && isPushable(), for both). push_entities pushed a ridden
horse anyway, and the server sent its new motion to the rider, whose
client drives the horse and took it over the jump: the horse jumped
lower, or not at all, against a mob on Horizon servers.
A vehicle is no longer pushed by the mobs it touches nor moved by its own
push; the mobs around it are still pushed away. Seen gains is_vehicle so
a pusher knows its target is one.
Fixes#7647
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 2111ead65d. Pure Rust crate, nothing calls it yet; the risk is the format itself: unknown fields are refused and the 512-vertex / 64×64 / 24 px reach limits are checked at parse, so later PRs can't draw an item past them. Box UV face order follows ModelPart.Cube (tested on the down face).
Load carried TileEntityData over the placed block entity defaults so falling chests retain their inventory. Add a dedicated-server regression with direct placement, empty chest, supplied inventory and falling sand controls.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
ServerPlayer.die: DEATH_COUNT objectives, awardStat(DEATHS), resetStat of
TIME_SINCE_DEATH and TIME_SINCE_REST. Part of #6885.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The skin path poses each item on its bone and draws it with the player,
textures in the skin atlas's slots; Extras gets 'My cosmetics' and
'Other players' cosmetics'. Off by default (horizon_cosmetics::ON_BY_DEFAULT).
The perf bench gains stress_avatars, 300 mannequins, to measure the cost.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeoForge's MobSplitEvent at Slime.remove (children count changeable, cancel
spawns none) and BabyEntitySpawnEvent at Animal.spawnChildFromBreeding (cancel
makes no baby; parents still rest, as NeoForge resets them). Part of #4953.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Fold living_incoming_damage_v2 into living_incoming_damage: it gains attacker,
weapon, source_position, victim_position and victim_view; the v2 hook, its
fragment and the mace-only dispatch are gone. The hook has no Host.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at ec8cebc6a8. A scaled pass loads its attachments instead of clearing them and draws into a whole-pixel viewport, so its buffers keep what lies outside it. Unscaled programs keep [1,0,0] and draw as before. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests and datapack_worldgen parity pass.
🤖 Agent: queued at 9d20f0cc84. Found by black-box probing of Iris with your own packs, which is fine; the two tests pin the nine smooth forms and the dropped four- and five-argument calls. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests and datapack_worldgen parity pass.
🤖 Agent: queued at 6a6ef2de28. The chunk oracle now loads a pack's level stem, so the biome data-pack fixture tests the pack's biomes rather than vanilla's. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests and datapack_worldgen parity pass.
🤖 Agent: queued at 713582bbdd. A mob's melee hit now applies its weapon's post-attack effects to the mob it hit, not only to players. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests and datapack_worldgen parity pass.
Fold living_knockback_v2 into living_knockback: it gains attacker, attacker_main
and attacker_off, and an armor stand's sweep fires it. The v2 hook and fragment are
gone. A mixin injection naming native_living_knockback as a raw target must take
the new arguments.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
#7434 copied them with cp -n, which exits 1 on macOS when it skips a
file that is already there: on every runner with a preparation that has
some languages, set -e ended vanilla-data.sh and every parity job failed
in its setup step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the Console keeps its search, Follow, Copy, Share log (with its mclo.gs confirmation) and logs folder; only the look changes, and the panel loses its now-unused button row since every Home tab draws its own head.
An allow entry like '!*' normalised to empty runs and matched every word,
disabling the whole filter.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- Structure starts: the start cache keeps two generations per shard
instead of clearing a full shard, so starts still being asked for are
not rebuilt (jigsaw assembly on the tick thread).
- Streaming: Ranges::distance asks ExtraCover's buckets for the nearest
area instead of scanning every player; the buckets also answer when
players are too far apart for the dense grid.
- Minecarts: id lookups take the next index as a hint, cart pushes and
mob/player pushes look only at carts in nearby chunk cells, and the
entity boxes a cart collides with are filtered to its reach.
- Natural spawning: players are bucketed by 8-chunk cells for the
chunk-in-range checks, and mobs find their nearest player through a
64-block grid (ties to the lowest index, as the scan).
- Protocol: an interrupted socket read is retried instead of dropping
the connection.
Refs #7713
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mapped_key_event translated the physical key into the vanilla default of
its mapping before key_pressed ran, and the container arm then asked
container_hotbar_key about that default again. A default can itself be
another mapping's rebound key: with key.hotbar.7 on F and key.swapOffhand
on Left Alt, the off-hand press arrived as KeyF and swapped the hovered
item into hotbar slot 7.
Vanilla's AbstractContainerScreen.checkHotbarKeyPressed works in
Screen.keyPressed's keyspace: the raw key. mapped_key, key_pressed,
control_key_down now carry the raw key through (the same value where the
caller had no window key), and the container arm rebinding asks about it.
Fixes#7707
mob_hits_mob opened no knockback scope and projectile_hit only did for a player
owner, so living_knockback_v2 saw attacker_main as None for a mob attacker.
knockback_scope_for opens the scope for the attacker a damage source names.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Registers Arabic (ar, "العربية") as a launcher language using the
provided full Arabic translation (launcher/lang/ar.json).
Co-Authored-By: Claude Code <[email protected]>
NeoForge's LivingDeathEvent and LivingConversionEvent.Pre/Post. Death is
cancellable for mobs and server players (the entity stays at 1 health).
Conversion Pre is cancellable and fires in the shared convert_to, the
dispenser's mooshroom shear and lightning on pigs and villagers.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
loaders::Kind gains Forge and NeoForge (and Kind::ALL, the order a picker
shows them). Their versions come from Forge's maven-metadata.xml with its
promotions (stable up to the recommended build) and NeoForge's version list
API (21.1.x plays 1.21.1, 26.3.0.x plays 26.3, its forge artifact for
1.20.1). Imports from Prism/MultiMC keep a Forge or NeoForge loader.
Installing them comes in the next PR; until then install says so.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeoForge's CanPlayerSleepEvent at ServerPlayer.startSleepInBed: a mod may
clear or replace the first problem vanilla found, or add one. NeoForge's
PlayerXpEvent.XpChange at Player.giveExperiencePoints (orb pickups,
advancement rewards, /xp add points): a mod may change the amount or cancel.
Part of #4953.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: a mod's on/off still runs switch_mod (the browser's mods_switch, or the jar rename for Java instances) and the rows stay culled to the view for modpacks; native mods keep their old build/GitHub controls at the row's end until the Mods page is ported.
🤖 Agent: a pack's on/off still goes through packs::set_enabled (options.txt) and Remove through packs::remove (the trash), unchanged; only the rows, the Toggle and the screenshot grid are new, and the grid still stops at 8 until Show all.
🤖 Agent: queued at 06de8714b7. Requeued after its land conflict: I merged main (06de8714b7). The only conflict was the corpus, where your creeper_custom_fuse group now sits beside #7463's forceload and #7418's max-health groups, all three kept. Fuse is read into max_swell and saved back from it.
docs/native-mods.md: the world_drawn entry keeps both the block sprite arrays and main's motion paragraph.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at b7ca31b03a. a8cdcce9d keeps the no-mod path free (no player-id set, no dispatch without a native mod). Its head merged with today's main passes xtask mod-api --check and the stamp tests, and the native lib tests passed on that merge.
The game asks the cosmetics service what the players in view wear: one
POST for up to 64 UUIDs, on a thread of its own. Answers are cached for
the service's TTL (kept drawn while asked again), item files on disk by
their hash, and a service that doesn't answer leaves the last answers in
place and is asked again after 15 s, doubling up to 10 minutes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each records the frames after a first use: the End, rain/thunder/lightning,
53 particle kinds, and spectating the mobs with post chains.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Mod tick (size or alpha curves) and the Base tick never called sprite_from_age, so
loop and age stayed on the first sprite. A declared age or loop now ticks like
Behaviour::Animated, the curve tick walks the set too, and a particle without
animation keeps its spawn sprite as before. A test runs the real particle tick.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A new crate reads cosmetic item files: boxes with Minecraft's box UV, a
texture of at most 64x64, the player model bone the item follows, its
attachment and simple looping animations (swing, bob, spin). Items past
the vertex budget (512), the part count or a reach of 24 pixels from
the bone are refused. Nothing draws them yet; docs/cosmetics.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reassert the native cursor grab on gameplay clicks and wheel events, recovering from window-manager grab loss without consuming the event or warping the pointer.
Fixes#7654
Co-Authored-By: Codex <[email protected]>
Not in the mockup (its Overview shows a game log only while the game
starts): kept, in its patterns. The count, then the search (the small
FIELD), Follow (a Toggle), Copy and Share log… (QUIET) and the logs
folder (ICON_BUTTON); the log itself is the mockup's Logs: round at 12,
black at 25 %, Geist Mono 12 px on 1.75, white at 75 %, warnings and
errors in the look's amber and red. Share log asks first on a card as
the patch log's. Every Home tab is now in glass, so the panel's
separate button row goes.
Design: dezer-x (playhorizon.dev/launcher).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 82a73e8423. I merged main (82a73e8423): xtask mod-api --check and the stamp tests pass on it, and clippy plus the native and native_render lib tests passed on the previous merge.
Vanilla's Commands(CommandSelection.DEDICATED) registers StopCommand,
SaveAllCommand (with `flush`), SaveOffCommand and SaveOnCommand for
LEVEL_OWNERS; Horizon only had them on the console. They are now in the
dedicated tree, and the stop and autosave switches live in the shared
Lists, so the console, the management protocol and a player's command
change the same state. A guest's /save-all saves once their turn ends,
when their own state is back in their record.
Fixes#7667
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 60aa0ec37f. I merged main (60aa0ec37f, the tick_blocks order keeps native_joins_flush before lan_mark_synced). clippy, the lan lib tests and the server_entities and dedicated_server harnesses pass on it.
🤖 Agent: queued at 2a001222fc. Same diff as the branch I checked with main merged: clippy -D warnings and the horizon-world cache, snapshot and registry tests pass. The snapshot is keyed beside behaviours-v2.bin, so a stale registry rebuilds instead of loading.
uuid_from_text validates that all non-dash characters are ASCII hex digits,
preventing a panic when slicing multi-byte characters whose byte length is 32,
and rejecting leading + signs from from_str_radix.
Co-Authored-By: Antigravity <[email protected]>
Select the ambient, hurt and death event names through the same goat state used by vanilla. Add a dedicated-server packet regression with adult, baby and silent controls.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
🤖 Agent: queued at 604256c623. I pushed 27d3d3ffdb: a saved max_health base other than the type's default now goes into finalize's bases, so /attribute ... base get answers 40 for command_exec's summoned cow instead of 10. command_exec passes with it, then I merged main (the corpus conflict was #7463's forceload group beside yours; both kept).
🤖 Agent: layout only, from the window width of the last frame (window_width): at 1280 px and wider nothing moves; under it the invite code stacks under the actions and the page strip scrolls, as the mockup's xl breakpoint does.
🤖 Agent: the three spectator chains compile on their own thread from the first frame in a world; until a chain has arrived, apply leaves it out instead of compiling in the frame. Cold Metal cache: first-spectate frame 67–140 ms → under 8 ms.
🤖 Agent: queued at 81bea7acc5. Middle click selects the world under the cursor in both title-screen paths and is ignored on the Options screen. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at cd18f6c74f. Closing the inventory clears the controller cursor's focus, origin, slider edit and pending press, so nothing stays highlighted. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at cf3d1e1187. /loot mine now asks block_loot_table, which keeps a content block's namespace (loot_repro:blocks/...), instead of building a minecraft: id. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
A stand in lava now follows LivingEntity.travelInLava (halve, sink by gravity / 4)
instead of air physics, and the bubble column's push runs after the stand's
settle step, as LivingEntity.aiStep applies block effects after travel.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at 91a96f3b4b. cp -n now fills only the language files a preparation lacks, and pack.mcmeta is copied on its own when missing, so a partly prepared tree no longer skips the whole restore. Passed in the union run with the other u10 PRs.
🤖 Agent: queued at 2f32ceb119. Without strict, the state passes through updateFromNeighbourShapes before the named properties apply, and a shape that would become air keeps the parsed state. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at 72817d326e. ForcedChunksSavedData sorts by ChunkPos.pack, so negative z orders like vanilla's reply. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at a54a0a733d. loot_tables now walks every namespace under data/, prepared files included, so /loot completes a content mod's tables. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at c7a4d4bc00. The resync runs once per console command and only while the world is open to LAN. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at f395135ea5. set_age(-24000) now runs the happy ghast's own setup instead of leaving an adult's goals on the ghastling. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
🤖 Agent: queued at 7da932e807. Matches Wolf.canMate: only a wolf partner must stand, a cat sits and still breeds; the test covers both species and both sides. Checked in a union with main: clippy -D warnings, all horizon-client/client-lib/items lib tests, command_exec and use_item_on parity pass.
The mockup's InstanceMods: "N mods in {instance}" and + Find mods over
one ROW per mod, its 36 px icon round at 9, the name and "version ·
source". The launcher's own controls fit in: the filter (the small
FIELD) and Update all in the head, a mod's Update and its on/off Toggle
at the row's end. Rows are still culled to the view for modpacks.
Design: dezer-x (playhorizon.dev/launcher).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Music page becomes a Horizon extra like the others: off by default, a
switch on the Extras page (sound section), musicPage in horizon-extras.txt.
While it is off, Music & Sounds is exactly vanilla's page and no pool is
filtered; turning it on adds the Music... button and applies musicOff.
The page now covers every track the loaded sounds.json can play. After the
seven built-in situations, the tracks only a biome's pool names (the
overworld's deep dark, swamp, badlands and cave pieces, the Nether's) are
listed under Overworld biomes and Nether biomes: vanilla shares them between
several biomes, so no heading claims one. A track several pools share is
listed once, from the first pool that names it, so a row's "play now" always
has a pool that plays it and no track is left out.
That needs rows whose pool is not the group's, so MusicGroup holds
MusicTrack { event, id } and the page's headings are made at run time
(Row::Heading). track_groups is a pure function of the registry, tested with
a registry of base, biome, record and non-music events.
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
An invulnerable or dead mob fired v2 although vanilla's hurtServer returns early
and NeoForge's LivingIncomingDamageEvent comes after that check. Mob::refuses_damage
is the guard damage_with already ran, now shared.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued. The one risk is the biome cache emptied by the idle hook; released_caches_generate_the_same_chunks shows chunks come out identical after it, the 16 worldgen harnesses pass, and lan_e2e chest/light plus a dedicated server with 2 bots (20 TPS) pass on this head.
🤖 Agent: a world's Back up / Copy / Delete still run through worlds_page's world_action (trash, never rm); only the row and its ⋯ menu are new, and Delete needs a second click as the instance menu's.
The first frame through a creeper's, spider's or enderman's eyes loaded its
post chain and compiled its pipelines in that frame. With Metal's shader cache
cold (a first play, or shaders that changed) that froze the game for 0.1-0.9 s.
PostEffects::preload now loads the three chains and compiles their pipelines on
a thread of its own from the first frame in a world; apply leaves a chain out
until it has arrived instead of compiling it in the frame. Chain loading only
needs the resources root, so load/translated_pass/standin_pass/image take it
instead of &self.
Spectating with a cold vertex shader, 3 alternated pairs on the Mac mini: worst
frame 67-140 ms -> 5.7-7.8 ms, 0.1% low 89-163 -> 192-277 FPS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Packs (the mockup's `Packs`): one ROW per resource or shader pack, its
picture, "Resource pack · on" and a Toggle (40×24, the accent while
on). Open folder and + Add pack… head the list, packs dropped on it are
added, and Remove (clicked twice) shows under the pointer.
Screenshots (the mockup's `Screenshots`): the count, then a grid of
16:9 pictures round at 12, 4 across from 1280 px and 2 below, each
with its file name; the folder button where the mockup has its blur
toggle, and Show all past 8.
Design: dezer-x (playhorizon.dev/launcher).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mockup's `xl` breakpoint: under 1280 px wide, Home's invite code goes
under the actions as wide as the page (`xl:flex-row`, `w-full
xl:w-[340px]`), and the top bar's pages give way to the buttons on the
right and scroll sideways (`min-w-0 xl:shrink-0`). Before, the invite
code covered the folder button and the settings button left the window.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RenderTooltipEvent.Color was replaced by .Texture in NeoForge 1.21.5; the 26.1 and
26.2 javadocs only have Texture. The hook gives the colour half of it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: the tab strip's cut-out corner is painted as three backdrop pieces (strip, body, a 16 px inward fillet baked with a smooth alpha edge); a strip nearly as wide as the panel falls back to one rounded plate, as the mockup's clip-path does.
The mockup's `xl` breakpoint: under 1280 px wide, Home's invite code goes
under the actions as wide as the page (`xl:flex-row`, `w-full
xl:w-[340px]`), and the top bar's pages give way to the buttons on the
right and scroll sideways (`min-w-0 xl:shrink-0`). Before, the invite
code covered the folder button and the settings button left the window.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The same `ItemEntity::hurt` the item tick reads, with a box holding three
stacks in slots 20, 0 and 3: the spill comes back in slot order — oak log 8,
cobblestone 64, diamond 4 — and the box keeps no slot filled. With the
container arm taken out of `hurt`, the spill is empty:
left `[]`, right `[("minecraft:oak_log", 8), ("minecraft:cobblestone", 64),
("minecraft:diamond", 4)]`.
`BlockItem.onDestroyed` reads the item entity's stack, writes
`ItemContainerContents.EMPTY` back into `container` and streams the old
contents — the non-empty slots, in slot order — to
`ItemUtils.onContainerDestroyed`. Every block item's item entity therefore
spills a container it carries; for a shulker box that is the box's contents,
lost where the box burned up.
`ItemEntity::hurt` (the port of the health step `ItemEntity.hurtServer`
runs) now spills both arms the stack's components name: `bundle::destroyed`
for a bundle's `bundle_contents` and `item_data::destroyed_container` for a
container item's `container`, which the item-entity tick adds to the level
where the entity lay.
One test drives `ItemEntity::hurt` — the health step `environment_tick`
runs for every damage source the game applies to a dropped item — and one
the same tick at a lava block through the prepared resources (`.local`,
skipped without them, like the fire tests beside it). With the spill taken
out of `hurt`, the first fails on the empty spill: left `[]`, right
`[("minecraft:stone", 3), ("minecraft:diamond", 1)]`.
`ItemEntity.hurtServer` runs `ItemStack.onDestroyed` before it discards a
dead item, and `BundleItem.onDestroyed` hands its `bundle_contents` to
`ItemUtils.onContainerDestroyed`, which drops each copy where the entity lay.
Horizon took the health down to zero and removed the entity with nothing
inside it going anywhere.
The four damage sites in `ItemEntity::environment_tick` (fire, lava, fire
contact, cactus) now go through `ItemEntity::hurt`, the port of that health
step, which empties the stack and returns what left it; `tick_dropped_items`
adds each stack as a `new ItemEntity(level, x, y, z, stack)`: the destroyed
entity's position, no pickup delay, the constructor's 0.2-less-0.1 nudge.
#minecraft:zombies includes camel_husk and zombie_nautilus; the mob weapon
smite bonus missed them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: the Play button's ⋯ menu and the version picker reuse the launcher's existing start, update and version actions; only their look changes (dark appearance). Java instances still show no version picker.
A lamb, puppy or kitten born to two dyed parents takes the colour
`DyeColor.getMixedColor` answers, which asks the recipe manager for the pair
first (`findColorMixInRecipes`) and only then picks one of the two.
The test runs the prepared recipe data (`.local/horizon-client/26.3`,
skipped without it, like the data tests beside it) through the same
`dye_mix` the three breed call sites reach: red + yellow makes orange,
blue + green cyan, blue + red purple, green + white lime, and orange + cyan,
which no recipe mixes, keeps one of the two.
`DyeColor.getMixedColor` and `findColorMixInRecipes` were methods on `Game`,
so the only way to reach them was to build a whole game. They read the
recipe manager and a coin, so they take those two: `dye_mix(recipes, random,
a, b)` and `dye_color_mix_in_recipes(recipes, a, b)`. The three breed call
sites hand them `self.res.recipes()` and `&mut self.world.random`, as
before; nothing else changes.
🤖 Agent: only Efficient pacing changes (Precise stays vanilla's limit_display_fps); after a long frame the grid restarts from now, so no frames are rushed out to catch up. Cap 140: 139.3 → 140.0 FPS average.
Salmon.getDefaultDimensions is super.getDefaultDimensions(pose).scale(
getSalmonScale()), and getSalmonScale returns the variant's boundingBoxScale
field: SMALL 0.5, MEDIUM 1.0, LARGE 1.5 (Salmon$Variant static initialiser:
ldc 0.5f, fconst_1, ldc 1.5f). EntityDimensions.scale(float) is scale(f, f),
so the width, the height and the eye height all follow the variant.
Horizon stored the variant - finalizeSpawn picks small 30, medium 50, large
15 - and drew the right model, but Mob::awake_dims_in had no salmon arm, so
a small salmon kept the medium box (hitbox, attack range and collision) at
half its model size, and a large one had a box two thirds of its model.
LiquidBlock.fizz sends level event 1501 with no excluded player, so the
player whose water turned the lava to obsidian sees the smoke too. The
server left 1501 out for the acting player, whose bucket prediction keeps
only sounds, and the other players heard the hiss twice: from the event
and from the sound the server captured while handling it. 1501 now goes
to every client and the server plays its sound unshared.
lan_e2e's sparks check pours a bucket beside lava for the host and the
guest: smoke on both sides, one hiss each, obsidian.
Fixes#7537
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: dark look only: the glass shell replaces the sidebar, top bar and menus; light keeps today's look. The Sculk card stays in the sidebar above Discord, as before.
The item and armor-stand ticks lived in horizon-client, which cannot see the
game crate's Entity trait, so each called the fluid update and the bubble
column push from inside its own tick. Each tick is now split at the push: the
game runs push_by_fluid, the move, push_by_bubble_column, the rest, and
push_by_fluid again. Armor stands also report NoGravity through EntityRules.
TNT's inline column push uses the new push_by_bubble_column_over default.
Part of #6544.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The mockup's Worlds and Multiplayer lists: one ROW per world or server,
white at 6 % under the pointer. A world's row plays it, its size turning
into "Play ›" under the pointer; its ⋯ menu holds Back up, Copy, Data
packs, Open folder and Delete… (clicked twice). Data packs still drop
on the row. A server's row has Join, and Remove (clicked twice) under
the pointer; Add server opens a line with the glass fields.
Design: dezer-x (playhorizon.dev/launcher).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps the mob random stream's order from before this branch, so a
pillager's or piglin's attack delays don't change for the same seed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As in Iris, the block outline is drawn by the pack's gbuffers_line, else
gbuffers_basic, through the pack's own transform; the game draws its own
only when the pack has neither. Complementary Reimagined r5.9.3 and Super
Duper Vanilla 1.3.8 style the outline in gbuffers_line; SEUS PTGI HRR, which
renders the world smaller into a corner and scales it up, showed the game's
outline floating in the top right. The edges are the game's outline buffer,
widened in screen space after the pack's transform.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
load_ominous_item_spawner never called remember_uuid, so the attachments
saved on a spawner were not read back, and its next save used a new UUID.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GLSL leaves sqrt of a negative undefined. Packs written against GL drivers
compute things like sqrt(1.0 - y * y) with y that is 1 in exact arithmetic;
the GL compiler's rounding keeps the argument at zero, SPIR-V's fused
arithmetic can leave it a hair below, and the NaN spreads. SEUS PTGI HRR's
GGX sample for perfectly smooth water became NaN for a pattern of pixels:
speckled reflections at shallow angles. sqrt in function bodies now clamps
its argument at zero; global initializers keep the built-in (constants).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GL filters and mipmaps RGBA32F textures; without the feature wgpu samples
them unfiltered and Horizon skips their mipmaps ("mipmaps of unfilterable
buffers are not generated"). Requested only where the adapter has it.
Bliss 2.1.2 keeps colortex5 as RGBA32F.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
shaders.properties' clouds=off now hides the game's clouds and drops the
pack's gbuffers_clouds. A pack without a cloud program (Bliss 2.1.2) had the
game's clouds drawn into its buffers.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rule now lives in `ignite_for_wither_skeleton(m, &mut p)`, which
`perform_ranged_attack` calls just before the shot, so its value can be pinned
without a loaded world: a wither skeleton's arrow carries 2000 fire ticks (100
seconds), a plain skeleton's 0, and a Flame arrow already alight for 4000 ticks
keeps its longer burn (`Entity.igniteForTicks` only ever raises the count).
The world-based case stays beside it: it follows `perform_ranged_attack` itself
and only runs where the prepared resources are present.
texture.<stage>.colortexN replaced buffer N in every program of the stage.
As in Iris, it now does so only until an earlier program of the stage flipped
the buffer; later programs read what was written. final continues
composite's. Super Duper Vanilla 1.3.8 (texture.composite.colortex0 = its
clouds), Bliss 2.1.2 (colortex6 = blue noise) and BSL 10.1.8 (colortex7 =
dirt) override buffers they also write.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
One test drives `ItemEntity::hurt` — the health step `environment_tick`
runs for every damage source the game applies to a dropped item — and one
the same tick at a lava block through the prepared resources (`.local`,
skipped without them, like the fire tests beside it). With the spill taken
out of `hurt`, the first fails on the empty spill: left `[]`, right
`[("minecraft:stone", 3), ("minecraft:diamond", 1)]`.
`ItemEntity.hurtServer` runs `ItemStack.onDestroyed` before it discards a
dead item, and `BundleItem.onDestroyed` hands its `bundle_contents` to
`ItemUtils.onContainerDestroyed`, which drops each copy where the entity lay.
Horizon took the health down to zero and removed the entity with nothing
inside it going anywhere.
The four damage sites in `ItemEntity::environment_tick` (fire, lava, fire
contact, cactus) now go through `ItemEntity::hurt`, the port of that health
step, which empties the stack and returns what left it; `tick_dropped_items`
adds each stack as a `new ItemEntity(level, x, y, z, stack)`: the destroyed
entity's position, no pickup delay, the constructor's 0.2-less-0.1 nudge.
Block.getCloneItemStack is overridden by the blocks that are not their own
item, and Horizon's clone_item knew only the two stem overrides, so pick
block on a crop, a sapling or a vine gave nothing.
CropBlock returns new ItemStack(getBaseSeedId()) (wheat seeds, carrot,
potato, beetroot seeds, torchflower seeds), BambooSaplingBlock bamboo,
SweetBerryBushBlock sweet berries, both cave vine blocks glow berries,
BigDripleafStemBlock the big dripleaf, GrowingPlantBodyBlock the head
block (kelp, twisting and weeping vines), and CandleCakeBlock a cake, which
is what makes the seventeen coloured candle cakes pickable.
Dedicated/headless servers carry the full entity-model layer data but never
render it. Parse entity-types.json only for entity facts and skip
entity-models.json (and custom entity models) in that case, sharing one
empty Part for the render-only fields. Also skip CEM model loading on the
server.
Co-Authored-By: Claude Code <[email protected]>
Retain the flat locator while preserving disabled-world terrain streaming. Supply definitions for explicit placement independently of automatic starts and locate searches. Cover both structure flags with fresh dedicated servers and bounded loaded-coordinate readiness.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
draw_clouds and draw_weather took their pass before checking the frame had
anything for it, and returned without putting it back. The first frame with
no rain or snow dropped the pack's gbuffers_weather for good: a world loaded
in clear weather showed no rain at all once it started raining (BSL 10.1.8:
nothing drawn). Clouds the same after a frame with no cloud quads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at the maintainer's request. Read the diff: saved LOD columns now light open water like the live path, gated on sky_light so the Nether gains none; the per-span name lookup only runs on a column's top span (light_open_water_where returns early otherwise), so the save decoder's cost barely moves. Waterlogged blocks at the surface aren't counted as water, same as before.
Vanilla's FramerateLimiter starts each frame's slot when the thread wakes up,
so every late wake-up lengthens that frame. Efficient pacing (Horizon's default;
Precise stays vanilla's) now ends each slot at its deadline: the cap holds on
average. A frame that ends past its slot, or wakes up more than half a frame
late, starts the grid again from now, so no frame is rushed out to catch up.
Cap 140 (maxFps 144 loads as vanilla's 140), RD 16, 3 alternated pairs on the
Mac mini: 139.3-139.4 -> 140.0 FPS average (p50 7.18 -> 7.14 ms); lows equal
within noise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractContainerMenu.doClick's PICKUP_ALL arm starts its sweep at slot 0
with a step of +1 when the button is 0, and at the last slot with a step of
-1 when it is 1, over the same two passes (partial stacks first). The screen
only sends PICKUP_ALL from a release of button 1
(AbstractContainerScreen.mouseReleased checks event.button() == 1), so the
right double-click is the one that is meant to fill from the far end.
Horizon's pickup_all did not take the button at all and always swept
0..len, so a collect that could not take everything emptied the first
matching slot instead of the last.
A type-3 and a type-2 drag (start, two slots, end) leave the slots empty and
the cursor full; a charitable drag still splits the stack, and a creative
clone drag still fills the slots and keeps the cursor's stack.
AbstractContainerMenu.doClick takes the type out of the click's high bits,
asks isValidQuickcraftType before it sets quickcraftStatus to 1, and
resets the drag otherwise: type 2 (clone) needs
Player.hasInfiniteMaterials, and only 0 and 1 need nothing. Its status
guard above that only lets a start through while no drag is open, so a
second start resets the fields instead of taking the drag over.
Horizon started a drag for any type and let a second start replace the
type, so a survival guest's type-2 or type-3 drag placed whole stacks
where vanilla moves nothing.
MSG2
🤖 Agent: tokens only (glass colours, radii, Bricolage/Inter fonts, icons) added to horizon-theme beside the existing ones; no launcher screen changes look until the shell PR #7566 reads them.
🤖 Agent: queued at c3aac5716d. verify passes on this head. Every row main added since the branch was cut (block-entity caches, the four Leaf Culling modes, BlueMap, Frame Generation, Translucent Batching hidden on macOS) has a place, and the language test walks every page, so a missing key would fail. Vanilla's Video Settings screen is untouched.
GroundPathNavigation.canUpdatePath is onGround() || isInLiquid() ||
isPassenger() (javap: Mob.onGround ifne 30, Mob.isInLiquid ifne 30,
Mob.isPassenger ifeq 34). Horizon had the first two, so a mob sitting on a
vehicle had can_update_path() false: create_path_raw refused to make any
path for it, recompute_path only set delayed_recompute, and do_tick skipped
follow_the_path. A piglin on a hoglin, a husk on a camel or a chicken jockey
could not path at all while riding.
The world-gated pattern the other mob tests use, `let _ = with_test_world(...)`,
has no return to lint: clippy's needless_return is denied in CI and the early
return was the last statement of the test.
native_item_container gives a mod item its slots (1..=256). The contents
are kept in the stack's minecraft:container, as a shulker box item keeps
them; an empty item carries nothing, so it stacks with a new one.
native_item_container_slots reads one item of a stack;
native_item_container_insert tops up equal stacks, then the first empty
slots (SimpleContainer.addItem's order), no slot past its limit, never a
shulker box; native_item_container_extract takes from a slot. Both answer
what moved and the single item it becomes, without changing the stack
given. The hook and helpers are in the typed mod API. Fixes#6768.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
In the dark appearance, Home's tabs become the mockup's TabbedPanel
(horizon-site components/launcher/launcher-views.tsx):
- one glass plate whose top-left corner is the tab strip: the strip round
at 18 on top, the panel round at 20, joined by a 16 px inward curve. The
strip is measured as it is laid out; glass.rs bakes the curve as its own
piece with a smooth alpha edge, one texel per device pixel;
- the tabs with their icons and counts (Tabs size md);
- Overview: the three newest screenshots (the first twice as big),
See all opening the Screenshots tab, beside the newest launcher
release's patch log by section, All notes opening Patch notes; then
what Play still needs (the launcher's rows; the Instance row's actions
are in the sidebar's ⋮ menu in this look).
The other tabs' bodies are unchanged here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ThrownEnderpearl.onHit discards the pearl when `getOwner() == null ||
!isAllowedToTeleportOwner(owner, level)`: javap shows `getOwner; ifnull 116;
invokestatic isAllowedToTeleportOwner; ifne 121; discard; return`. That helper, in
the pearl's own dimension, reaches `LivingEntity.isAlive() && !isSleeping()`
(bytecode 26-45) and returns false otherwise, so a dead or sleeping owner is never
teleported and takes no damage.
`Game::projectile_hit`'s EnderPearl arm teleported whenever `p.owner ==
Some(Who::Player)` and a player existed: with `ender_pearls_vanish_on_death` off a
dead player, and any player in bed, was moved onto the pearl and took the 5
`minecraft:ender_pearl` damage.
`horizon_client::projectiles::owner_can_take_pearl` is the same-dimension half of
the vanilla rule, read by the pearl's hit.
`perform_ranged_attack` against a player four blocks away: the wither skeleton's
arrow carries 2000 fire ticks, a plain skeleton's 0. Before the change both were
0, and `on_fire()` false for the wither skeleton's arrow.
WitherSkeleton.getArrow(ItemStack, float, ItemStack) is `super.getArrow(...)` then
`AbstractArrow.igniteForSeconds(100.0f)` on the arrow it returns: javap shows the
`invokespecial AbstractSkeleton.getArrow`, then `ldc #142 // float 100.0f` and
`invokevirtual AbstractArrow.igniteForSeconds`.
`mobs/undead.rs::perform_ranged_attack` built every skeleton's arrow the same way
- the species' tipped effect (stray, bogged, parched) and the bow's Flame bonus -
and never ignited a wither skeleton's, so its arrows flew cold and did not set
alight what they hit (Entity.igniteForSeconds(100) is 2000 fire ticks).
🤖 Agent: queued at f96c062b3a. Checked on main merged in: clippy -D warnings, the sunrise lib tests and the sunrise_shader GPU test pass. The fan is drawn only while the sunrise colour is visible, so the sky pass drops its per-pixel test the rest of the day.
A node on a cauldron is raised one block; the node after it follows when it
is not above the cauldron and keeps its height when it is; a path with no
cauldron under it is not touched.
PathNavigation.trimPath walks the new path and, for every node whose block
is in BlockTags.CAULDRONS, replaces it with the same node one block up
(Node.cloneAndMove). When the node after it is not above it (node.y >=
next.y) that node is moved to the rim's height as well. PathNavigation.moveTo
runs trimPath before it checks the node count.
Horizon only had the avoid_sun half of the trim, so a route to a cauldron
- a leatherworker's job site, a cauldron next to a farm - ended one block
inside the block: the mob walked into its side and stopped instead of
standing on the rim. The raise now runs for every navigation kind, from
nav_move_to_path, before the sun trim.
Three ticks of rearing is half the animation, so half again as tall; six
ticks is twice. The flag alone moves nothing: the box follows the
animation, which is what vanilla's getDefaultDimensions reads.
PolarBear.getDefaultDimensions scales the box of a bear that is not a baby
by (1, 1 + clientSideStandAnimation / 6): the width stays, the height and
the eye height grow to twice at a full rear-up, and the box is refreshed
whenever the animation changes (PolarBear.tick). Horizon kept the type's
box for a rearing bear, so a bear standing up was clickable and hittable
only up to its resting height.
The animation lives in anim.v[STAND] on both sides in Horizon, where
vanilla only advances it on the client, so this uses it as-is.
Each passenger case killed its zombie and chicken and set the next
spawner at once. A dying mob stays 20 ticks and still obstructs a spawn
(Mob.checkSpawnObstruction), so vanilla spawned nothing in cases 1-3
and the harness reported 12 mismatches on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/attribute ... max_health base get now reads finalize's base, which
loading recorded only for horses: a summoned cow with a saved base of
40 answered its type's 10 (command_exec's attr case).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`crates/horizon-core/src/density/beardifier.rs::kernel` fills the 13824-entry
table `Beardifier.BEARD_KERNEL` holds. Nothing checked it: the table is inside
the crate, 24³ floats, and a wrong exponent, offset or index order in it would
only show up as subtly wrong terrain under every `beard_thin`/`beard_box`
structure. This adds the check, with the values read off vanilla.
## Vanilla
`Beardifier`'s static initialiser:
```java
private static final float[] BEARD_KERNEL = Util.make(new float[13824], kernel -> {
for (int zi = 0; zi < 24; zi++)
for (int xi = 0; xi < 24; xi++)
for (int yi = 0; yi < 24; yi++)
kernel[zi * 24 * 24 + xi * 24 + yi] = (float)computeBeardContribution(xi - 12, yi - 12, zi - 12);
});
private static double computeBeardContribution(final int dx, final int dy, final int dz) {
return computeBeardContribution(dx, dy + 0.5, dz);
}
private static double computeBeardContribution(final int dx, final double dy, final int dz) {
double distanceSqr = Mth.lengthSquared(dx, dy, dz);
return Math.pow(Math.E, -distanceSqr / 16.0);
}
```
So entry `zi * 576 + xi * 24 + yi` is `e^(-(dx² + (dy + 0.5)² + dz²) / 16)` as a
`float`, with the half-block lift on `y` only.
## What Horizon had
`kernel()` builds the same array with `std::array::from_fn`, mapping the index
back to `(x, y, z) = ((i / 24) % 24, i % 24, i / 576)` and adding the same 0.5
to `y` before `jmath::pow(E, -squared / 16.)`. Written that way it is right —
and it is, value for value — but nothing said so, and `jmath::pow`'s promise of
HotSpot-exact `Math.pow` was the only thing holding the numbers up.
## Proven
`cargo test -q -p horizon-core --lib the_beard_kernel`:
- every one of the 13824 entries has the bits vanilla's formula gives it: the
test hashes the whole table (`float` bits, 31-multiplier) and gets
`b44d13f8c3d98c65` from both sides, and pins seven entries by index —
`kernel[0] = 3.9172745e-12`, `kernel[7212] = 0.9844964` (the centre, where
`dy` is 0.5), `kernel[13823] = 6.9435353e-11` among them.
- the centre is asserted to be the table's maximum, so an index order that
transposes `x` and `z` (they are symmetric in the formula, but `y` is not)
cannot pass by accident.
Failing without the fix: move the falloff from 16 to 12 — the one constant a
reader is most likely to mistype — and the test stops on
`kernel[0] is 6.175119e-13 not 3.9172745e-12` (bits `640810070` vs `747230124`);
the table hash moves too. Nothing else in the suite notices.
## How the values were read
The class cannot be run here: `client-26.3.jar` holds class files of version 69
and the only JDK on this machine is 21, so `Class.forName("…Beardifier")` ends
in `UnsupportedClassVersionError` and the private field cannot be reflected out.
The values therefore come from the initialiser's own formula, transcribed
(`computeBeardContribution` above, including the `+ 0.5` in the three-argument
overload), compiled and run under java 21, printing `Float.floatToIntBits` of
each entry; the Rust side was compared against those bits entry by entry and
agrees on all 13824.
## Not proven
- Only the table. `getBeardContribution`'s `fastInvSqrt`, the `0.8`/`0.4`
weights, `getBuryContribution`'s radius of 6 and the rigid/junction loops are
untouched here and were verified elsewhere.
- No behaviour changes and no `port_map` row changes: the map already carries
`Beardifier.computeBeardContribution inline …beardifier.rs::kernel fills
the beard kernel table`, which this makes true rather than aspirational.
A saved chunk's LOD columns kept each water block's own skylight, which
fades a level per block of water, while the generator's far columns light
open water as open sky. Where the two met - the stride limit in
`SavedLodSampler::grid` - the difference drew a dark band through the sea.
`Column::light_open_water` already existed for this and the live path
(`Column::from_chunk`) applies it; `decode_lod_surfaces` never did. It has
no `BlockFacts` and so cannot reach `state_kinds`, so the rule moves into
`Column::light_open_water_where`, taking the caller's own test for water;
the decoder names water from the block registry, which leaves lava out as
the emissive test did.
Only where there is a sky: the decoder's `sky_light` gates it, so the
Nether's water gains no skylight it never had.
Co-Authored-By: Claude Opus 5 <[email protected]>
🤖 Agent: the scene is the website's HorizonScene ported draw for draw (same seeds and random order), pinned by a golden test with values from the site's own code; dark appearance only, light keeps the dawn sky, idle CPU below the old sky (0.66 s vs 0.78 s per 20 s).
The bench runs with VSync off. --vsync writes enableVsync:true, in a window
(--size): with vanilla's default 120 FPS cap on a 60 Hz display, frames wait
for the refresh as most players' do, and the swapchain's acquire waits show in
the slow frames' spans (r_acquire).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Load the saved dimension generator beneath datapack dimension overrides instead of reconstructing it only from the preset. Preserve saved noise settings and dimension type, including after reload.
Fixes#7501
Co-Authored-By: Codex <[email protected]>
`mob_can_use_portal` now splits in two: `mobs::mob_can_use_portal` is the part
the mob's own state decides (alive, not a boss that never travels,
`Creaking.canUsePortal`), and the game's version adds the rides and the
players this side carries. The test asks the shared rule, so deleting the
heart clause fails it with "a creaking bound to its heart must not leave it
behind" instead of only removing a helper nothing tested.
In the dark appearance, Home's banner becomes the mockup's HomeView head
(horizon-site components/launcher/launcher-views.tsx):
- the edition tabs (the instance's own lit; the other can't be picked, as
an instance's edition is chosen when it is made), the instance's icon and
name in Bricolage at 64 px, and how long it was played or has run now;
- Play (Update while the launcher's version has a newer one, Stop while the
game runs, the progress while preparing, updating or starting) with its
⋯ menu: Prepare (again), Play without updating, the desktop shortcut,
Copy the launch command;
- the version picker with its ticked list and Manage versions, the Mods big
icon (opens the instance's Mods tab), the folder button whose ⋯ shows
under the pointer (instance folder, screenshots, Minecraft folder);
- the invite code field with Join inside, 340 wide on the right.
The playing-now and official SMP counts stay after the played line. The
glass scrollbar lies over the page margin instead of taking a gutter. The
light appearance keeps the old banner.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A spider on a 0.4-wide chicken hangs 0.3125 below the seat and a cave spider
0.21875, where the code put both at 0; a 2.0-wide vehicle keeps the default,
and a cave spider with minecraft:scale 2 hangs 0.4375.
Spider.getVehicleAttachmentPoint returns 0.3125 of the scale, and
CaveSpider's 0.21875, when the vehicle's box is no wider than the rider's;
a wider vehicle keeps the type default. vehicle_attachment took only the
rider, so no rule could read the vehicle's box and both spiders sat at the
default 0.
It now takes the vehicle's getBbWidth, and the four callers pass it: a mob's
width, a boat's 1.375, a minecart's and primed TNT's 0.98, a text display's
0.
ServerGamePacketListenerImpl.handleRecipeBookSeenRecipePacket asks the
recipe manager for the display's recipe (getRecipeFromDisplay) and hands it
to ServerRecipeBook.removeHighlight, which drops it from toBeDisplayed: the
recipe stops being announced as new. The hub had no arm for the packet, so
a guest's book kept highlighting every recipe it had already looked at.
ServerAdvancements::recipe_of_display is the manager's lookup over the
displays the server numbered.
Opcode 48 with AC 02 reads as display 300 and writes back the same bytes, a
list cut short is refused, and an id without a variant still reads as
Unknown - which is what opcode 48 did before it had one.
The serverbound play enum had no variant for opcode 48, so a guest's
recipe-book highlight could never be cleared: the packet decoded as
Unknown and the hub's dispatch has no arm for that.
The packet is one VarInt, the RecipeDisplayId the player looked at.
The first version called `tame::die` directly, so it still passed with the
call site taken out. `drops::die` now asks `drops::death_state` first — the
state half of `LivingEntity.die`, before any loot — and the test calls that,
so deleting the wolf clause fails it with "a dead wolf is not wet".
A depth of 21, 200 or -1, and a max_distance_from_center of 0, 129, -8, a
horizontal of 0 or a vertical of 0 or 4065: each decoded as a placing jigsaw
before and places nothing now, while 0..=20 and the in-range reaches still do.
JigsawStructure's codec reads `size` with Codec.intRange(0, 20) and
`max_distance_from_center` with horizontal Codec.intRange(1, 128) beside
vertical ExtraCodecs.intRange(1, DimensionType.Y_SIZE). Horizon read both
without a bound, so a structure carrying a depth of 200 or a reach of 0 laid
out pieces vanilla would never have registered; it now decodes as
unsupported, the way vanilla leaves such a structure out of the registry,
instead of failing the whole registry.
🤖 Agent: only SystemClock::park changes (1 ms sleep steps); the FramerateLimiter logic stays vanilla's. At a 60 FPS cap on macOS, p99 19.8 → 16.75 ms and 1% low 50 → 59.5 FPS over 3 alternated pairs.
Apply set_damage in the items loot engine and honor an inline table’s optional random sequence. Cover float clamping, additive modifiers, ineligible items, stream tails and the unsequenced fallback through command execution.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
The bench runs uncapped (maxFps 260), so the frame limiter never paces a
frame. --max-fps N writes maxFps:N: at 60 it showed every frame at 17-20 ms on
macOS (the limiter's sleeps running late), fixed in fix/limiter-park-steps.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Wolf.die` clears `isWet`, `isShaking`, `shakeAnim` and `shakeAnimO` before
`super.die`, so a wolf killed while shaking the water off does not keep the
wet flag or the shake on its body. Horizon kept both (`extra.flag2`, the
shake counter and `anim.v/vo[SHAKE]`), so the corpse shook on the ground.
`mobs/tame.rs::die` is that reset; `mobs/drops.rs::die`, the ported
`LivingEntity.die`, asks it before any loot is rolled.
Refs tools/port_map/game2-001.tsv:34 (Wolf.die, missing).
A cube with a block in its body can be leashed (hasBodyItem) and a rope hangs
at half its 0.49 box, 0.245, on its centre; the default put it at the eye
height 0.175 and 0.196 in front. Without a block the cube takes no lead.
SulfurCube.canBeLeashed is hasBodyItem(), not the Enemy rule Mob.canBeLeashed
applies, so a cube with a swallowed block takes a lead. Horizon's
can_be_leashed falls through to !hostile() and refused every cube, whose
category is a monster's.
SulfurCube.getLeashOffset is (0, getBbHeight() / 2, 0): the rope hangs at
half the cube's box on its centre, not the eye-height default with
bbWidth / 2 in front.
Pins getSleepHeight's decision table (a straw bed's head goes to its foot,
a foot that is gone or is another block has no height, every other bed is
its own shape), the top of a multi-box shape, and the position: a 7/16 foot
at y 70 puts the sleeper at 70.5625 where the constant gave 70.6875.
try_sleep and tick_sleep both built the position from the constant; they
ask the bed now, and try_sleep stops before lying down when the bed has no
shape to lie on.
AbstractBedBlock.getSleepHeight answers with the top of the block's shape
(VoxelShape.max(Axis.Y), empty for a shape with nothing in it) and
StrawBedBlock.getSleepHeight walks a head over to its foot first, so a
straw bed is slept on at the foot's shape and a head whose foot is gone has
no height at all - LivingEntity.startSleeping then returns false (bytecode
34-55) and the player stays up. LivingEntity.setPosToBed puts them 0.125
above that height (0.125d at 0, pos.getY() + height + 0.125).
Horizon added a flat 0.5625, the height of an ordinary bed's head, at both
places a sleeper is placed (try_sleep and tick_sleep).
A capping limit of 0, a uniform limit from 0, a biased limit from -2, a
weighted list whose only entry is 0, and a block_rot integrity of -1, 1.01,
2 or a string: each loaded before, each is a decode error now.
CappedProcessor's limit is IntProviders.POSITIVE_CODEC and
BlockRotProcessor's integrity is Codec.floatRange(0, 1): vanilla refuses a
processor list whose limit can draw 0 or less, or whose integrity is outside
the fraction, and the pack fails to load. Horizon decoded both without a
bound, so such a list loaded and ran as a capping processor that never kept
a block or a block_rot that kept or rotted all of them.
The row still described the state clear inside `Game::tick_used_item`. The
use's end is `Game::stop_using_item` now, and it also carries the
`ITEM_INTERACT_FINISH` vibration `ItemStack.causeUseVibration` sends.
`LivingEntity.completeUsingItem` finishes the stack and then calls
`stopUsingItem`, the same tail `releaseUsingItem` has, so a use that runs its
full course vibrates exactly like a released one. `Game::complete_using_item`
cleared `using` and returned without the event, which left every finished eat,
drink and spyglass use silent while vanilla's sculk sensors heard them.
The finish path now ends with `Game::use_vibration`, after the stack's finish
(the client's use-complete packet and `LivingEntityUseItemEvent.Finish`), as
vanilla orders it.
`LivingEntity.releaseUsingItem` ends with `stopUsingItem`, and
`LivingEntity.stopUsingItem` does two things Horizon's release path skipped:
it drops the use state and, when the use is still on, calls
`ItemStack.causeUseVibration(this, GameEvent.ITEM_INTERACT_FINISH)`, which is
`Entity.gameEvent(event)` — a game event from the player at its own position.
`Game::release_used_item` (the port of `releaseUsingItem`, reached by
RELEASE_USE_ITEM, a slot change mid-use and the use running out) ran the item's
own release and left `using` to its caller, so a bow let go, a lowered shield,
an aborted spyglass or a finished eat never made the vibration a sculk sensor
hears (it is the 3-frequency `item_interact_finish` event).
The release now ends through `Game::stop_using_item`, and `Game::use_vibration`
is `ItemStack.causeUseVibration`: it sends the event only for a stack whose
`use_effects.interact_vibrations` is set (the default, which is what
`horizon_items::items::ItemInfo::interact_vibrations` carries).
macOS runs a sleep late by a share of its length (timer coalescing: 1.8-3.5 ms
on the ~14 ms sleep of a 60 FPS frame), more than the limiter's learned
overshoot (capped at 2 ms, vanilla's) covers, so a 60 FPS cap gave frames of
17-20 ms (57 FPS on average, 1% low 50). SystemClock::park now sleeps at most
1 ms at a time; the limiter's logic is vanilla's, unchanged.
In game, 60 FPS cap, RD 16, 3 alternated pairs on the Mac mini: p99 19.6-19.9
-> 16.75 ms, 1% low 50.1-50.6 -> 58.4-59.6 FPS, average 56.8-57.7 -> 59.9 FPS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rider's cart crossed but arrived at rest, so a cart that went in at
rail speed stood still in the Nether. It now arrives with the motion it
had when its portal was handled (clamped to 0.4 along x and z, as
OldMinecartBehavior.getKnownMovement), is planned from where it stood
then, and a ridden vehicle's portal is looked for after its rider's tick
has moved it, so it is found the tick it enters the portal.
New: tools/cart_portal_player_e2e.py (vanilla and Horizon dedicated
servers, a protocol client riding a cart through a portal), a ride mode
in the Java protocol client, lan_e2e check cart_portal_rider, and a unit
test.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Baby turtle feet have two coincident polygons with opposite winding: one
for the top skin, one for the sole. Both keep their own UVs, but the
shared depth made them z-fight.
Detect coincident, oppositely wound faces once while parsing a model and
mark them `cull_backface`. The CPU renderer then packs the normal's w as
`SHADED_CULL_W` (-126, byte 0x82), so the faces keep their directional
shading and only gain back-face culling. The GPU skinning path carries the
flag in the high bit of the model vertex's normal axis word and writes the
same -126 byte in `entity_skin.wgsl`, keeping the CPU and GPU outputs
byte-identical (the existing exact `bytemuck::bytes_of` comparison).
A copy of the mob's random advanced by vanilla's two `nextFloat` draws gives the
pitch to expect; a baby killer bunny keeps the plain 1.0 base; a silent one still
draws twice and says nothing; any other rabbit draws nothing and says nothing.
`Mob.doHurtTarget` ends with `playAttackSound()` once `hurtServer` returned true
(bytecode offsets 60-123: `ifeq 126` skips the call when the hit did not land).
`Rabbit.playAttackSound` plays `SoundEvents.RABBIT_ATTACK` ("entity.rabbit.attack")
at volume 1.0 and pitch `(random.nextFloat() - random.nextFloat()) * 0.2F + 1.0F`
only when `getVariant() == Rabbit.Variant.EVIL`.
Horizon's `do_hurt_target` ends with the same hook and has the creaking's, the
panda's and the axolotl's arms there, but nothing for the rabbit: a killer bunny
bit in silence.
The pitch is an argument of `playSound`, so vanilla draws its two floats before
any silence check: a silent killer bunny still pays them. The pitch base is the
plain 1.0 of the override, not the 1.5 a baby's `getVoicePitch` gives.
Three rows in `rest-017.tsv` describe the state of the code before
`ServerCommandSuggestionsProvider` landed in `lan_actions.rs`:
* `tick` is not "no per-tick budget": `guest_try_suggestions` spends a budget
that the per-guest server tick refills (`play.suggestion_budget = true`
next to the chat and command throttler decay, then one answer);
* `limitSuggestionCount` is no longer missing: `guest_suggestions` cuts the
list to its first 1000 suggestions and keeps the range;
* `request` coalesces as `lastRequest` does: the newest command text replaces
the one not answered yet (`play.suggestion = Some(payload)`).
HORIZON_MEMORY_LIMIT_MB caps the working set, and Windows meets it by moving pages out: at 512 MB the game still committed 2.2 GB here. HORIZON_MEMORY_HARD_MB (and /ramlimit hard) is a budget of the process's commit instead. The governor sheds far terrain and render distance against it, the allocator refuses what would pass it, and a game that cannot fit ends with a crash report or a message naming the limit rather than living in the page file.
Nothing could check the fire a thrown projectile carries. The test puts a
snowball in a fire block (the cell answers `ignites`), then out of it: 160
ticks at the ignite, 159 after the tick's own countdown, one fewer each tick
in open air, 0 when the fire runs out — and 0 at once when the box is in
water. A small fireball sits in the same block and stays cold, because an
AbstractHurtingProjectile does not apply the blocks it flies through.
ThrowableProjectile.tick calls Entity.applyEffectsFromBlocks, so every thrown
projectile (a snowball, an egg, a pearl, a potion, a llama's spit, a mod's own
thrown item) is ignited by BaseFireBlock.entityInside for 160 ticks, has that
fire counted down by Entity.baseTick, and is put out by water; the entity then
answers displayFireAnimation and its renderer draws the flame over the box.
The port's throwable Projectile kept no remainingFireTicks at all, so nothing
could set a snowball alight however long it sat in the flames, and the
ThrownItemRenderer arm had nothing to draw a flame for.
ProjectileWorld gains `ignites` (false by default; the game answers it with
the block's name at that cell), the tick applies the fire phase after the
move, and the throwable render arm draws the same flame_geometry the arrow arm
already used for a burning arrow.
WallHangingSignBlock.isHittingEditableSide and
shouldTryToChainAnotherHangingSign pointed at
crates/horizon-world/src/level/interact.rs::Behaviours::block_use_item_on,
which does not exist: the arm is in Sim::block_use_item_on (interact.rs, the
impl of Sim), where the two face tests and the HangingSignItem test live. The
ledger reports a row whose fn does not exist as a map gap, so the two rows said
nothing.
The shouldTryToChainAnotherHangingSign note also claimed the chain is missing
("passing on a non-editable face lets the held hanging sign place its own
block, which chains onto the wall sign"), while the real difference from
vanilla is narrower: shouldTryToChainAnotherHangingSign is
!canExecuteClickCommands(sign.getSlotPlayerIsFacing(player), player) &&
stack.getItem() instanceof HangingSignItem && !isHittingEditableSide(hit,
state), and the world reads canExecuteClickCommands as false (it has no sign
text), so a waxed wall hanging sign whose faced side runs a click command chains
where vanilla opens its editor. WallHangingSignBlock.useItemOn carried the same
wrong claim; it now names the arm and points at that condition.
The count a recheck finds and the count it replaces are a pure function of the
tracked counts and the containers open right now, so they are tested without a
level: a chest opened (0 to 1), then closed without the close path, is
recounted to (1, 0) and its entry is dropped, so the next opener counts from
zero again; a recount that agrees returns (1, 1) and changes nothing; a double
chest counts both halves and ignores other containers.
ContainerOpenersCounter.incrementOpeners schedules a recheck as soon as the
count leaves zero (bytecode 35-39: scheduleRecheck, and scheduleRecheck is
Level.scheduleTick(pos, state.getBlock(), 5), CHECK_TICK_DELAY = 5);
ContainerOpenersCounter.recheckOpeners then counts the entities that still
have the container open (getEntitiesWithContainerOpen) and, when the count
differs from openCount, calls onOpen or onClose and
level.gameEvent(null, CONTAINER_OPEN or CONTAINER_CLOSE, pos), always calls
openerCountChanged and reschedules while the count is above zero.
Horizon's world side already dispatched that recheck for Class::Chest and
Class::EnderChest, but LevelAccess::recheck_container_openers was left to the
game and GameLevel never implemented it, so it answered None: the chest tick
did nothing, and nothing scheduled it in the first place. A chest whose menu
closed without the close path (a death, a teleport, a dropped connection) kept
openCount above zero, so its lid stayed open and its trapped-chest signal
stayed high.
GameLevel now answers from the two sources that know: the local player's open
container and each guest's menu position (both halves of a double chest, as
ChestBlockEntity$1.isOwnContainer takes a CompoundContainer; spectators
counted nowhere), and Game::count_opener schedules the five-tick recheck when
the first opener arrives, as incrementOpeners does.
A spawner counts its Delay down, and a vault turns active, only while a
player is in range, and each game puts its player at a random spot near
the world spawn. Give the spawner, vault and trial spawner a range no
player reaches, so the saved values are the same in every run.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The sidebar drew instance icons through the old look's framed squares (a
solid 2 px outline, square corners). The glass look now draws them as the
mockup's InstanceIcon: a colour tile with the initial in Bricolage, or the
chosen block, round at 26 % with no frame; pictures and loader marks as
before. A row following the launcher's version says "Update available"
after an accent dot when a newer version waits, as the mockup's does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The double vanilla hands the message is `5.9999968E7`, so `Double.toString`
makes the text read `5.9999968E7`; a rounded form such as `5.999997E7` is a
different string. The test also pins the limit the check and the message now
share.
`WorldBorderCommand.ERROR_TOO_BIG` is built with the double `5.9999968E7`
(its `<clinit>` pushes `5.9999968E7d` into
`Component.translatable("commands.worldborder.set.failed.big", ...)`, the same
constant `setSize` compares `distance` against), and a `%s` substitution
prints a double with `Double.toString`. Horizon hardcoded the rounded
`5.999997E7`, which vanilla never shows.
The message now renders `worldborder::MAX_SIZE` through
`java_text::double_to_string`, the same value the size check uses.
The row said no puffs at its feet while it stands, shoots, inhales or lands and
that only the random number was drawn; `emit_ground_particles` draws them now.
The count test walks the five poses with a copy of the mob's random advanced
the way `Breeze.tick` advances it, so a pose that draws when vanilla does not
(inhaling) or fails to draw when it does (sliding) fails on the mob's next draw.
The second test builds a stone floor, checks one crumb event of the block under
the feet at the box centre with speed 0, twenty while inhaling, and none for a
passenger or a breeze over air.
`Breeze.tick` switches on the pose: inhaling calls `emitGroundParticles(20)`,
standing, shooting and sliding call `emitGroundParticles(1 + random.nextInt(1))`
after `resetJumpTrail`. `emitGroundParticles` returns early for a passenger,
takes `getInBlockState` unless it is air (then `getBlockStateOn`), returns when
that state's render shape is invisible, and otherwise draws
`new BlockParticleOption(ParticleTypes.BLOCK, state)` at
`(box centre x, position y, box centre z)` with no velocity.
Horizon drew the count's random and dropped the particle, and it drew it for
inhaling (which vanilla does not, so every later draw of that tick was off by
one) while sliding drew nothing (vanilla draws one there). `MobEvent::LandingDust`
carried no speed, so the falling-dust crumbs' 0.15 was fixed in the handler; the
breeze's crumbs are at rest.
Keep the frozen v1 hook and add v2 following menu_button_v2. Carry scoped player attacker IDs, preserve mob source IDs and known kill credit, and route armor-stand sweeps through the hook before motion. Add generated dispatch, scope restoration, resistance and source-fixture tests.
Closes#6444
Co-Authored-By: GPT-6.1 Sol <[email protected]>
`RandomSpreadStructurePlacement.validate` was filed as `skip:data` ("codec
check that spacing exceeds separation"); `StructureRegistry::new` now makes
that check, so the row is a port.
The `StructurePlacement.getLocatePos` row still said "locate offset is zero".
`StructureSet::locate_set` reads `placement.locate_offset` and
`StructureState::locate` reports through it, so the row names that offset.
`a_random_spread_needs_more_spacing_than_separation` decodes sets with
separation equal to, greater than and at 0 with spacing, spacing past the
codec's 4096 and a negative separation; every one of them fails, and 8/7 and
4096/0 decode.
`a_structure_set_frequency_is_a_fraction` decodes 1.5, -0.25, 1.0 and 0.0:
the first two fail, the last two decode.
Without the checks both tests fail on their first assertion: `Ok` where
vanilla's codec answers a `DataResult` error.
`RandomSpreadStructurePlacement.validate` is a codec check:
private static DataResult<RandomSpreadStructurePlacement> validate(RandomSpreadStructurePlacement p) {
return p.spacing() <= p.separation() ? DataResult.error(() -> "Spacing has to be larger than separation") : DataResult.success(p);
}
and both fields are `Codec.intRange(0, 4096)`; `AbstractSpreadingStructurePlacement`'s
`frequency` is `Codec.floatRange(0.0F, 1.0F)`.
212: aload_0
213: getfield #1 // Field spacing:I
216: aload_0
217: getfield #7 // Field separation:I
220: if_icmpgt 233
223: invokedynamic // DataResult.error
...
233: invokestatic #20 // DataResult.success
Horizon decoded `spacing` and `separation` as plain ints, so a set whose
separation reached its spacing reached the spread with `spacing - separation`
of 0 or less: `next_int_bound(0)` fails and the `unwrap_or(0)` fallback put a
start in every grid-corner chunk, where vanilla refuses the placement and the
set never loads. Out of range spacing, separation and frequency were accepted
too.
`StructureRegistry::new` now refuses such a set, as vanilla's codec does.
🤖 Agent: gpui-fast 0.1.13 needs macOS 12, so the launcher bundle's LSMinimumSystemVersion goes 11.0 → 12.0; launcher CI green on Linux, Windows and macOS, the bumped launcher draws the same home and servers pages as before.
ThrowableProjectile.applyInertia adds four bubbles behind a snowball,
egg, ender pearl or experience bottle in water, a quarter of its motion
back, at its motion after gravity and before the 0.8 drag. The client
now reads them from Projectile::water_bubbles before the tick and adds
them beside the other projectile trails.
Fixes#7450
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both rows said Horizon's lists have no keyboard focus. OptionsUi::key walks
the page's focusables with Tab and the arrow keys and, when the widget that
takes the focus is a list row, scrolls it into view four pixels past the edge
it crossed: the neighbour stepping is what getBorderForArrowNavigation
answers and the scrolling is setFocused's. The rows now name that code.
BarrelBlockEntity.recheckOpen was missing, with a note that nothing rechecks a
barrel because Horizon draws no barrel lid. The recheck is the open property
and the sound, not a lid: Sim::tick_block ports it now. BarrelBlock.tick points
at that port too, and keeps the menu path (barrel_openers_changed) in its note.
The level test harness answers recheck_container_openers from a map, so a test
can say what a recount finds. The test runs one scheduled tick of a barrel and
checks the whole chain: the hook is reached, a 1 to 0 recount closes the open
property, plays block.barrel.close at volume 0.5 with a pitch in 0.9 to 1.0 and
sends container_close, no block event is queued (a chest queues one), a 0 to 1
recount reopens it, the recheck repeats five ticks later while the count stays
above zero, and the chain ends once nobody is left.
Without the arm the first assertion fails with an empty opener_rechecks.
BarrelBlock.tick (bytecode: getBlockEntity, instanceof BarrelBlockEntity,
invokevirtual BarrelBlockEntity.recheckOpen) leads to
BarrelBlockEntity.recheckOpen, which calls
ContainerOpenersCounter.recheckOpeners(level, pos, state). That method counts
the entities that still have the container open, and when the count differs
from openCount it fires onOpen or onClose and then
level.gameEvent(null, CONTAINER_OPEN or CONTAINER_CLOSE, pos); it always
calls openerCountChanged and, while the count is above zero, scheduleRecheck
(five ticks). For a barrel onOpen/onClose play BARREL_OPEN or BARREL_CLOSE
(BarrelBlockEntity.playSound: volume 0.5, pitch 0.9 + random*0.1, at the
block's front face) and call updateBlockState, which is
setBlockAndUpdate(pos, state.setValue(OPEN, open)); its openerCountChanged
returns without a block event (BarrelBlockEntity$1).
Horizon dispatched the recheck only for Class::Chest and Class::EnderChest,
so a barrel's scheduled tick fell through to the catch-all arm: a barrel whose
viewers all left stayed open, played no close sound and sent no
container_close game event.
The barrel arm keeps the chest arm's five-tick reschedule and skips its block
event, as vanilla's empty openerCountChanged does.
The dark appearance now draws Anthony S.'s mockup frame (horizon-site
components/launcher/launcher-app.tsx) over the pixel sunset:
- glass.rs: the mockup's frosted plates. The scene's backdrop is blurred and
tinted once per window size into pieces cut to each plate, since GPUI Fast
has no backdrop filter.
- glass_ui.rs: the mockup's controls (row, icon button, tab, menu row,
popover, solid button, dot, bar, mark tile) at its sizes and fills.
- frame.rs: the sidebar (instances by section with their state, the update
card, Join the Discord, Sponsor/Website links, version), the top bar (tabs,
profile chip and menu, Sign in with Microsoft, settings) and each row's
menu (Edit, Duplicate, Open folder, Export, desktop shortcut, Delete asked
twice), wired to the launcher's real actions.
Kept although the mockup lacks them: the Sculk sponsor card, the servers
section and the Build log button. The light appearance keeps the old frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first version loaded `MobTypes` from the prepared `.local` data, so the
test returned early wherever that data is absent, CI included, and proved
nothing there. `sensor_scan_tests::kind()` is a plain `MobType` the other mob
tests already use, so the creaking is built from it and the test always runs.
It also pins that a home point a wolf keeps (a bed) is not a creaking heart,
so `heart_bound` does not refuse that wolf a portal.
The row said missing and named `mob_can_use_portal` as the place that has no
heart check; it now points at the override and `mob_can_use_portal` asks it.
`set_transient` binds a creaking to a heart, so the test pins both sides of
`Creaking.canUsePortal`: an unbound creaking still travels through a portal,
a heart-bound one does not, and a zombie is unaffected.
`Creaking.canUsePortal` returns false while the creaking is bound to a
creaking heart (`isHeartBound()`, i.e. `getHomePos() != null`), and only
then defers to `Monster.canUsePortal`. Horizon's `mob_can_use_portal`
checked alive, mount, rider, the guest-ride rule and the wither, so a
heart-bound creaking walked into a nether or end portal and left its heart
behind.
`mobs/creaking.rs::can_use_portal` is that override, asked last by
`Game::mob_can_use_portal`.
Refs tools/port_map/game2-003.tsv:71 (Creaking.canUsePortal, missing).
🤖 Agent: merged main in (conflict: both branches added a test at the same spot in preprocess.rs, kept both); horizon-shaders 155 tests and clippy pass. The fix itself is narrow: only a // comment whose last char is a backslash swallows the next line, block comments are untouched.
Use the player motion state, including the swapped LAN guest, rather than the motion resource table. Add the pending API stamp token.
Co-Authored-By: GPT-6.1 Sol <[email protected]>
Keeps both tests in preprocess.rs: the line-comment continuation one from this branch and main's unbalanced-conditionals one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Decode per-face light data (block_light, sky_light, color,
ambient_occlusion) on elements and faces, face-level light_emission, and
the model render_type. Baked quads carry a (block, sky) light minimum,
colour, AO override and render type; the mesher raises vertex light per
channel, multiplies colour into the tint, and draws a typed quad in that pass.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Part of #5501. First slice: validated declarations and a cached Host lookup; display, search and unlock integration remain follow-up work.
Co-Authored-By: OpenAI GPT-6.1 Sol <[email protected]>
Font::resolve now skips a provider glyph whose advance is under 0 or over 32
and moves on to the next provider, and provider_advance, which builds the
by-width table obfuscated text picks from, does the same, so the two rows
name fishy_advance instead of calling the check missing.
A font whose first provider gives 'a' a 65 pixel advance and whose second
gives it 3: the character must advance 3 (it advanced 65 before the change),
and the by-width table obfuscated text picks from must hold no 65 wide glyph,
so random_glyph for that width draws the missing glyph instead.
FontSet.computeGlyphInfo does not stop at the first provider that has a
character: it remembers that glyph for the plain glyph source and keeps
looking, and hasFishyAdvance rejects a glyph whose advance is under 0 or over
32. So a resource pack with a broken glyph cannot widen a line - vanilla
draws the next provider's glyph, or the missing glyph. Horizon's resolve took
the first provider's glyph whatever its advance, so such a pack made its text
as wide as the pack asked, and the obfuscated-text width table put that
character under the wrong width.
The same check goes into provider_advance, which builds the by-width table
that random_glyph picks from, and skips fishy space, bitmap, unihex and
TrueType glyphs alike. Unihex advances now come from hex_advance, shared with
rasterize_hex so the two cannot drift.
The row kept a gap note saying a focused language row needed a second press
to finish the page, which Entry.keyPressed's isSelection arm never did; the
row now says what OptionsUi::key does.
Enter, keypad Enter and Space on a focused LanguageRow each apply the row's
language (Settings::language and Event::Language) and pop the Language page,
which presses twice before this change: the first press only set the choice.
LanguageSelectionList.Entry.keyPressed asks the event for isSelection (Enter,
Space, keypad Enter) and, when it is one, selects the row and calls
LanguageSelectScreen.onDone: the language applies and the screen closes on
that one press. Horizon's Enter arm on a focused row only remembered the
choice, and a second press was needed to finish the page, so a keyboard user
had to press Enter twice to change language.
Part of #7465. Add validated motion parameters and linear size/alpha curves without changing default particle behaviour.
Co-Authored-By: OpenAI GPT-6.1 Sol <[email protected]>
The classes-07 row for `DimensionOriginStructurePlacement` read `missing`
because the type decoded as `Placement::Unsupported` and such a set never
placed. It now decodes and places, so the row names the arm that answers
`isStructureChunk` and a home row for the type, with the origin it reads
(`ChunkGenerator.getOrigin`, chunk 0 0 or the NoiseSpawnFinder chunk).
`dimension_origin_places_the_generators_origin_chunk_alone` pins the chunk
the set places in: chunk 0 0 for a generator with no origin of its own,
then the origin the generator reports, and no chunk beside it.
`dimension_origin_ignores_a_frequency_and_an_exclusion_zone` pins the two
checks that must not run for this placement type: a `frequency` of 0.0 and
an exclusion zone whose other set spreads over every chunk both leave the
origin chunk's start in place.
Both fail with the placement decoded as unsupported: no chunk holds a
start, including the origin chunk.
ClientExplosionTracker.tick calls WeightedRandom.getRandomItem for each
of its particles, so the level random draws nextInt(totalWeight) even
with one tracked explosion. explosion_blocks had no caller left.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`StructurePlacements` registers three placement codecs: concentric_rings,
dimension_origin and random_spread. Horizon decoded the first two and let
`minecraft:dimension_origin` fall through to `Placement::Unsupported`, which
places nothing anywhere, so a set that uses it never generated and `/locate`
never had a start to find.
`DimensionOriginStructurePlacement.isStructureChunk` names one chunk:
ChunkPos chunkPos = state.getDimensionOrigin();
return chunkPos.x() == x && chunkPos.z() == z;
`ChunkGeneratorStructureState.getDimensionOrigin` is the `origin` field its
constructor is given, which `ChunkGenerator.createState` fills from
`getOrigin(RandomState)`. `ChunkGenerator.getOrigin` is `ChunkPos.ZERO`;
`NoiseBasedChunkGenerator` overrides it to
`ChunkPos.containing(NoiseSpawnFinder.findSpawnPosition(spawnTarget, ...))`
and keeps ZERO when `spawnTarget()` is empty.
The type implements `StructurePlacement` directly, not
`AbstractSpreadingStructurePlacement`, so its `isStructureChunk` is that
comparison alone: no `applyAdditionalChunkRestrictions` (frequency) and no
`applyInteractionsWithOtherStructures` (exclusion zone) run for it.
Horizon now decodes the type, keeps the generator's origin on the registry
(chunk 0 0 until the generator reports one), and answers the chunk question
before the rings, distance, avoid, frequency and exclusion checks that
belong to the spreading placements. The origin comes from the pipeline's
cached spawn column, so the NoiseSpawnFinder search runs only when a set
actually reads the origin.
Preserve the original incoming damage hook and expose weapon and geometry snapshots for player-to-mob weapon hits. Remaining damage routes stay on v1.
Co-Authored-By: GPT-6.1 Sol <[email protected]>
Builds a save whose World/link points outside the saves folder, clicks
Play Selected World and pins the action, the mode and that the refused
world is left unlocked. Without the check in play() the click returns
Play { folder: "World" }.
LevelStorageSource.validateAndCreateAccess validates the world directory
before it makes an access (worldDirValidator.validateDirectory(path, true),
then ContentValidationException when the list is not empty), so
WorldOpenFlows never locks or loads a folder that holds a link. Horizon
opened it as usual: only backup and delete looked for links.
world_management::symlinks walks the folder without following a link and
Title::play refuses the world with the symlink warning
(symlink_warning.title.world / symlink_warning.message.world) before the
session lock is taken.
🤖 Agent: queued at 8f97c29e8b. Harness only: block_state_case pins apply_item_block_state, and the body's mutation run (call removed → mismatches) shows it has signal; CI parity is the after-check.
🤖 Agent: queued at 4ae934a8d6. Notes only: the three debug views stay missing with a concrete reason each; refreshRendererList's list matches debug_gizmos.
The window plays single player as a client of its server thread, where
the wither's server-side aiStep draws nothing anyone sees: the client's
tick (beside client_smoke) emits charging_particles from the
DATA_ID_INV ticks the server sends.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_mods is the harness's list of trait mods, and a native mod built from
mixins is not one: it runs through its injections, so the list is empty and the
1024x1024 slot was never packed. Read native-mods.json beside the executable
instead, which is what the launcher writes for a game it built with mods; a
release or Nightly has none.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
A mod can add sources to vanilla's atlases (assets/minecraft/atlases/*.json),
as a resource reload appends every pack's sources to the ones below it.
The fence allows those files, the content root writes vanilla's sources
followed by the mods', and carry keeps them.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
entity.nbt(host) and merge_nbt as /data get and /data merge entity do,
tags, add_tag and remove_tag as /tag does, and name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 9079b9d0bb. Port map only: the three ClientPacketListener rows now cite functions that exist (forget_columns, poll_remote's Reconfiguring arm).
Neither the count of the spawn cloud's particles nor their colour could be
checked while the block only rolled the random. The test pins three
`ParticleColored` entries a tick in the (0.7, 0.7, 0.9) colour, their offsets
against the wither's own random advanced in vanilla's order, the y spread over
`[0, 3.3 * getScale())` at scale 1 and at scale 2, and no cloud at all once
`getInvulnerableTicks` reaches 0.
`WitherBoss.aiStep` draws the wither's spawn cloud in the same tick it counts
the charge down: while `getInvulnerableTicks()` is above 0 it adds three
`ENTITY_EFFECT` particles a tick in the (0.7, 0.7, 0.9) colour, each a gaussian
away from the body on x and z and `random.nextFloat() * 3.3 * getScale()`
above its feet.
The port rolled exactly those three randoms and threw the offsets away, so a
wither charging for its eleven seconds rose with head smoke alone and no cloud
around it. The rolls now build the three particles, on the wither's own random
in vanilla's order (gaussian, float, gaussian).
🤖 Agent: a teleport that drops every occlusion column now clears the store (one Edit::Clear) instead of ~27k see-through edits plus a propagate into a graph dropped next frame; partial removals unchanged. Render-thread CPU 3.7 → 0.2 ms over the two frames.
Vanilla's BlockItem.place runs updateBlockStateFromTag once the click chose the
placement state: it reads the item's minecraft:block_state component, applies
every named property the placed block has and sets the block again when the
state changed. That is how a Ctrl + Pick Block of a turned stair, a top slab, a
disabled hopper or a 4-tick repeater places the block it was copied from.
The placement code already does that (Game::apply_item_block_state); the parity
cases never carried a block_state component, so a regression there would have
gone unnoticed. Add block_state_case: twelve block and property pairs whose
state differs from what the click alone chooses, plus a property the block has
no such thing as and an empty map, which BlockItemStateProperties.apply skips.
Check damageability before wear effects and use the stack maximum when breaking armor. Cover normal, unbreakable and custom-limit armor in the ticking LAN damage fixture.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
Keep progress and total at exactly zero levels, clear all three fields below zero, and retain one native level-change notification by delegating to give_levels. Add four command boundary cases with 44 observations and no ticking.
Co-Authored-By: Codex <[email protected]>
Horizon-only captions (Graphics pages, Extras, Mods, Shader packs, shader
pack options) hard-coded ": " between label and value. They now use
options.generic_value (CommonComponents.optionNameValue), and percent values
options.percent_value, so French, Chinese, Japanese and en_ud get their own
separator and order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 67cc72a2cc. Checked: interact.rs:443 handles the MovingPiston arm of use_without_item, and set_test_status exists in game_tests.rs, so the 'no runner' and 'not removed' gaps are stale. TSV-only, 3 citations exist.
🤖 Agent: queued at 3aa0691c81. Checked: fox.rs:156 and hostile.rs register ClimbOnTopOfPowderSnow, and ai.rs:3272 tests it, so the 'goal missing from registration' gap is stale. TSV-only, both citations exist.
🤖 Agent: queued at aa5b198b4c. Checked: frame.rs:68 rejects a zero length as a corrupted frame, so the Varint21FrameDecoder gap is stale. The eight object-component rows now point at inline_object and draw_inline_object, which exist on main. TSV-only, 4 columns.
entity.velocity(host) and set_velocity, on_ground, bounding_box,
eye_height, eye_position, view_vector and distance_to_sqr. The shared
entity walk's Entity trait gains velocity().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
level.block_state(host, pos), block, block_property, is_loaded, height,
game_time and day_time, for any level the server has loaded.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep armor command instances on the mob and reuse the attribute calculation with current equipment and covered-shell bonuses. Floor effective armor before CombatRules and preserve bypass behavior. Add source-backed health and bonus tests plus 28 command-to-damage cases.
Co-Authored-By: Codex <[email protected]>
Music & Sounds gains a Music... button: the page lists the built-in tracks
by situation (menu, overworld, creative, underwater, the End, boss,
credits), read from the loaded sounds.json.
- Left click plays that track now: it takes the music manager's slot
(stopping the track already playing) and the situation's own music
resumes when it ends.
- On/Off on the right of a row takes a track out of every pool it plays
in, so a situation whose tracks are all off plays nothing. Nothing is
off by default, so vanilla's random selection is unchanged; the off
tracks are musicOff in horizon-extras.txt.
The registry picks around refused sounds (SoundRegistry::sound_except, the
same weighted walk including nested "type": "event" references), the
manager resolves and hands the exact sound to the instance, and resolve
keeps a sound a caller chose (getOrResolve runs once).
Co-Authored-By: JustKarkat's AI agent (ZCode) <[email protected]>
Adds a cow tied to a fence knot and a boat tied to a pig to the scene,
a SETBLOCKS step, a Pos-aware summon position, and a check that vanilla
kept both leads, so a dropped lead in Horizon's save now fails the test.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
InventoryMenu.quickMoveStack resolves the item's equipment slot with
getEquipmentSlotForItem and, when it is EquipmentSlot.OFFHAND and menu
slot 45 is empty, moves the stack there with moveItemStackTo(45, 46,
false) before trying the hotbar. Horizon knew only the armor branch, so
a shield or a totem went to the first empty hotbar slot instead.
Rules gains equip_slot (the minecraft:equippable component's slot) and
quick_move checks it after the armor branch, in vanilla's order.
An item definition with a node type, property, special model or tint source from
another namespace decodes to the closest vanilla behaviour instead of being
dropped (minecraft: names still fail). A definition that fails to load is logged.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
facts.json's ~35k states cost a full streamed parse every launch
(~60 ms). registry-v1.bin in the same .horizon-cache dir restores the
built tables in ~24 ms when its SHA-256 fingerprint of facts.json and
the running executable matches; a miss parses and rewrites it. The
snapshot stores a string table and the built ids/defaults — no Value
trees — so restore also skips the parse-time peak.
The fingerprint's content hash now digests files in 4 MB pieces across
idle cores — a scheme of the bytes alone, identical on any machine —
which also speeds the behaviours fingerprint over the ~70 MB sources.
Assets::load goes through the new load_registry_with_flags so the
client's startup parse uses the snapshot too.
Co-Authored-By: Devin (SWE-2 High) <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When every column the occlusion store holds goes at once (a teleport drops the
whole view), Occlusion::remove_columns clears it like a world change instead of
pushing one see-through edit per removed section. At render distance 32 that was
~27 000+ edits: 1.2 ms in the removal and 2.5 ms propagating them into the old
graph on the next frame, a graph that is dropped right after because it lost the
camera. Now 0.15 ms and nothing on the next frame.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at e8afde61ab. With the hand left where it is, the pose skips hand_pose_matrix: no five Mat4 products per hand per frame without a mod. levzzz's main merge matches the one I made (both tests kept: hand_pose_rejects_invalid_components and the overlay test from #7333), and xtask mod-api --check, test_stamp_native_hooks and rustfmt pass on this head. The code before the merge passed clippy and the native tests on playhorizon.
🤖 Agent: queued at 15d058f004. tooltip_color hands mods only the background and border ARGB values, and with no mod loaded the dispatch is an empty slice, so tooltips cost the same for players without mods. I merged main twice (docs/native-mods.md conflicted with the camera section) and put your paragraph back before that heading, since your merge had moved it under it. The pushed tree is the one that passed clippy and the native tests on playhorizon; xtask mod-api --check and test_stamp_native_hooks pass.
horizon_theme::glass holds the colours, corners, text and fill opacities of Anthony S.'s
launcher mockup (horizon-site app/launcher/launcher.css). Glass::new does the mockup's
color-mix sums (OKLab mixes, the plate's premultiplied mix), checked against Chrome's own
results, so a player's accent comes out as the website's would. tokens.css carries them as
--glass-*.
Fonts: Bricolage Grotesque SemiBold at optical sizes 56 and 28, the cuts a browser picks for
the mockup's headings (tools/bake_bricolage.py), and Geist registered in the launcher.
Icons: the 38 Hugeicons (MIT) the mockup uses, served as icons/glass/<name>.svg.
docs/design-system.md gets a Glass section.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 1e3cf30442. Your latest commit has the same early return I'd made in observe_screen: with no native mod there's no title or menu snapshot to clone each frame. The three hook points now sit in the hook-points section, so catalog_matches_hook_points passes. Your head merges cleanly with main without a regen (xtask --check and test_stamp_native_hooks pass), and clippy and the native tests pass.
🤖 Agent: queued at 1b71694071. native_hand_drawn runs after the hand pass, and the render tests show the pass draws exactly as before when no mod hooks it. I merged main because the old base lacked the xtask test fix and verify would fail. With that merge, xtask mod-api --check and test_stamp_native_hooks pass, and so do clippy (render, client and client-lib) and the native_render tests on playhorizon.
Main's new rows go on the pages: Find BlueMap and BlueMap Radius under
General → Distance, Frame Generation under Quality → Image, the seven
block-entity caches as Performance's Block Entities group after Culling,
and Translucent Batching under Advanced → Frames (greyed where the device
can't, hidden on macOS). The shortcut row keeps main's one translated
"Shortcut: %s" label and shows its key apart. With "Scroll wheel changes
settings" on, a hovered stepped value takes the wheel as CycleButton does;
sliders, tick boxes and a clicked or focused row leave it to the list.
Enter does nothing on a row greyed out while focused. split_ui and
split_profiles draw their titles with graphics_style::title. Translations
drop the keys of the three old screens; main's translations win where
both sides added a key.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 78022a1ff9. The cancelled-crystal path returning ERROR_DUPLICATE_UUID-style failure and the spike crystals joining before their fire are both visible to mods, and the third change stops ids that are never announced (dragon parts, other levels' entities) from keeping the flush walking every tick, a small per-tick saving. Clippy and the native tests pass with main merged.
🤖 Agent: queued at 478ae85c20. Tridents are now reachable by @e, /kill and /data like any other entity (EntityTypeTest.forClass(Entity.class)), and NoGravity is written only when set (Entity.saveWithoutId). Clippy and the trident tests pass with main merged.
The overworld pipeline keeps the biomes of up to 16384 chunks (and the
carvers' biome lookups) until the cache is full. After a ramp to RD 32
that is ~33 MB that nothing reads again until the player moves on. When
every worker has been idle for 5 s, the streaming pool now empties them,
next to the per-worker caches it already released. Both caches are
already emptied whole at their limit, so a chunk generated afterwards
comes out the same.
horizon-ctl memory gains `server_generation`: the proto chunks the
server thread holds (bytes by part, sections by bits per entry), the
light views and the generator's caches, so the generation ring can be
measured without the alloc-census build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The items engine, which /loot rolls tables with, had no case for a
minecraft:loot_table entry, so a table that picks another table (the trial
chambers' reward tables, many data pack and mod tables) dropped nothing:
'/loot spawn ~ ~ ~ loot minecraft:chests/trial_chambers/reward' answered
'Dropped 0 items'. Chests were right: they roll through horizon_world::loot.
A chosen loot_table entry now rolls its table (by id or inline) in full and
applies its own functions to each stack (NestedLootTable); a table already
rolling further out adds nothing (LootContext.pushVisitedElement).
/loot also counted items where vanilla's CommandResponseTracker counts
stacks: two stacks of two returned 4 instead of 2.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at a8892e3a87. Checked against the 26.3 bytecode: Pig, Rabbit and Strider use 0.6f × eye height, Parrot and Ocelot 0.5f, all with the bbWidth × 0.4 forward shift, and HappyGhast returns Vec3.ZERO. That's exactly the new branches, done in f32 like vanilla. Clippy and the lead tests pass with main merged.
With improved transparency off, EntityBatch ignores the OIT_ENTITY tag:
an entityTranslucent layer (a horse's markings, a slime's outer cube) is a
plain draw with a tag, as the eyes render type's layers are (#7287). Such
mobs now take the GPU path then too, their jobs tagged as the CPU tags the
vertices. SkinModels::eyes becomes SkinModels::tagged (Renderer::skins_tagged).
The every-mob test gives the horse markings and expects the horse and the
slime posed with the tagged layers; new stress scene stress_markings (1000
marked horses and slimes).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
observe_screen cloned the open screen's title and menu every frame for
everyone; it now returns at once when no native mod is loaded (the list
is fixed at startup).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Layer's model and texture names and body()'s are Cow: fixed names are
borrowed, names built from parts (villager type, profession and level,
armour, farm animals' climates) are interned once per thread (bounded at
4096 names, owned past that). MobType::model_name gives the species' model
key without a copy.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The load-time hook only names items. Matching a concrete stack - its
components, usually - is native_ingredient_test(kind, data, stack,
accept): the ingredient's test calls it with the kind and JSON the
load-time hook saw, the stack being matched and the verdict, already
true (the list named the item). Set accept to false to refuse the
stack (ICustomIngredient.test). The hook may be called many times a
craft; the answer must be the same for the same stack.
Ingredient carries what matching needs now: the kind and its JSON for
a mod's own kind, and NeoForge's shapes' parsed children - matching a
stack asks each child its own way (any for compound, all for
intersection, base-but-not-subtracted for difference), a mod's kind
among them answering through its hook, as on NeoForge. The flat item
list the book and the network copy see is unchanged. The furnace
index holds every recipe an item names now, not just the first: a
refused stack falls through to the next recipe whose test takes it.
Part of #5367.
Co-Authored-By: ZCode AI agent <[email protected]>
🤖 Agent: queued at f0bbdbec96. All eight rows now point at world_upgrader.rs's RegionStorageUpgrader methods instead of the old upgrade_regions; the 8 citations exist on main, and tryProcessOnePosition is rightly a port now that only changed chunks are written.
🤖 Agent: queued at 21686a6019. Checked: lan_chat.rs:66 kicks with disconnect.spam, so pointing the row at detect_rate_spam instead of the creative-drop throttle is right. TSV-only, citation exists.
🤖 Agent: queued at 8de70a30b4. Checked: datapack.rs:598 reads recipe, recipe_filter, loot_table, advancement and trade_set files, so the gap claiming only functions are read was wrong. TSV-only, citation exists.
🤖 Agent: queued at 0b1c2fbb0f. Checked: trading.rs:509 sets m.dragging from on_scroller on press, so the 'click doesn't start a drag' gap is stale. TSV-only, citation exists.
🤖 Agent: queued at 7cbd9eaa76. Dropping the stale gap is right now that the ghost-slot tooltip lives in ghost_stack (#7414's row); draw_book_panel still owns the page tooltip. TSV-only, citation exists.
🤖 Agent: queued at 81b798b861. Moving from port to inline with the faces gap is right: player_list returns text lines only and draws no head sprites. TSV-only, citation exists on main.
🤖 Agent: queued at f915616c37. Checked: stations.rs:2879 asks ghost_stack before the real slot, so the tooltip comes from ghost_stack and pointing the row there is right. TSV-only, 4 columns, citation exists on main.
Track reply traversal with the existing Identifier and JavaHashMap implementations, retaining session resize/removal history and rebuilding after load. Keep the existing bar store for packet publication and persistence, with focused and command-list regressions.
Co-Authored-By: Codex <[email protected]>
Kept the hand pose hook beside main's item frame hook in native_hooks.rs
and regenerated the mod API.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at c09ee1f683. With every worker pool sized from one place, a pool can no longer pick its own count and oversubscribe the cores. The defaults keep each pool's previous size. I merged main because the old base lacked the xtask test fix and verify would fail. Clippy across render, client and client-lib plus the worker tests pass.
🤖 Agent: queued at be3977105b. Sending a guest's Interact with a knot id to use_knot closes the gap the old comment named. Knot hits are now decided by the client's entity pick, not by re-deriving the knot box from the block hit point, which is how vanilla's LeashFenceKnotEntity.interact is reached. knot_at_id scans knot_ids only per Interact packet, not per tick. Clippy and the lead tests pass with main merged.
🤖 Agent: queued at af73537958. The 64-slot cache lives only for one render call and compares the exact block before reusing a light value, so a collision just reads the light again: same light as before, with one lookup per block instead of one per particle in dense clouds. Clippy and the particle tests pass on playhorizon.
AbstractSelectionList.mouseClicked hands a click to the row under the
pointer whatever the button, and ObjectSelectionList.Entry accepts them
all. WorldSelectionList.setSelected then runs the selection callback that
SelectWorldScreen.updateButtonStatus reads, so a middle click on a row
selects the world and enables Play, Edit and Delete in vanilla 26.3,
without opening it.
Horizon's title only handled a left press, and the game's handler ignored
every other button over the title while the loading one handled the left
button alone. A middle click therefore did nothing and the footer stayed
disabled (#7384).
Title::select_world_at selects the row at the pointer without joining,
and both title mouse handlers send a middle press there. The left button
still joins, through its icon or a click on the selected row.
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
Expose live clouds through command candidates, identity and data adapters, tags and score holders, and removal. Extend the Java/Rust command corpus with default-fuse creepers, direct-cloud controls, data queries, filtering and lifecycle checks.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
A port of the website's HorizonScene (horizon-site components/horizon-scene.tsx), as
Anthony S.'s launcher mockup draws it (wideSun x 0.72, scale 1.1, rise 0.1): same seeds,
random draws in the same order, the same colour sums, so a window of the same size shows
the same picture. Its golden test holds values computed by the website's own code.
The scene is its own GPUI view: a frame of it redraws nothing else (GPUI Fast copies the
launcher's pages from the last frame). The parts that never move are baked into images per
window size; stars, the meteor, clouds, the sun and fireflies are a few hundred quads.
Dark appearance only for now; Light keeps the dawn sky.
Images are straight alpha with a one-texel edge border: GPUI Fast blends sprites by their
alpha and samples its atlas linearly, so a stretched image without one shows a seam.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
map_texture formatted the slot's sprite name (horizon/map{slot}) and
map_vertices cloned the map's decoration list for every map drawn each
frame (a lobby's wall of framed maps holds up to 256). The 256 slot names
are built once (hud::map_slot_sprite) and the decorations are borrowed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 3f636a7936. I made two fixes. The camera snapshot is now built only when a native mod is loaded: it cost two matrix products, a projection and a String every frame for everyone. And view.is_some() didn't compile, since view is a Mat4; your later commit fixed it the same way. Merged with main: clippy, the native tests and xtask --check pass.
🤖 Agent: queued at f132bcc2ba. valid_fov_modifier falling back to vanilla's base for NaN, infinity or out-of-range values keeps a broken mod from breaking the camera. Your FovPlayer stub fix passes all 19 xtask tests. Merged with main (native_hooks.rs conflicted with #7299's movement hook; I kept both), and clippy, the fov test and xtask --check pass.
🤖 Agent: queued at 237cebd62d. The item frame hook only runs for frames holding an item, and with no mod it is one dispatch over an empty slice, so frames cost the same for players without mods. You noted nothing was built: after merging main (the generated file conflicted, so I regenerated it), clippy, the native tests, xtask mod-api --check and test_stamp_native_hooks all pass on playhorizon.
🤖 Agent: the arrow's fire now travels as Entity.DATA_SHARED_FLAGS_ID bit 0x01, which vanilla's dedicated server also pairs a burning arrow with (server_entities transcript: pairing 0=1); the client stores it without counting it down, as it already does for mobs.
Match ForceLoadCommand LongStream.sorted using the existing ChunkPos packing. Extend the command corpus with mixed-sign list order and empty, singleton, point-query, and removal controls.
Co-Authored-By: Codex <[email protected]>
Gui.setScreen runs Screen.clearFocus on the screen it takes down, but
Horizon kept the keyboard focus in PadMenu after an inventory closed. The
inventory key bound to Tab moves the focus on Tab and then closes the
screen in the same press, so the screen it moved on stayed focused: a
button was left highlighted after the inventory went away. The remote
close path (single player runs on the server thread, remote.rs) never
reached close_container and cleared nothing.
Clear the focus where the container teardown starts: close_container, the
remote menu teardown (remote_close_local), and the Creative screen's two
close arms. The focus, the click origin and the slider edit go, as
ComponentPath.applyFocus(false) does.
Co-Authored-By: OpenCode GLM 5.3 <[email protected]>
OptiFine's "Composite render scale", `scale.<program>=<scale> [offsetX
offsetY]`, was not read: such a program drew its whole buffers. With a
probe pack of our own, Iris 1.11.7 draws a composite program (probed) only
into the viewport (offsetX * width, offsetY * height, scale * width,
scale * height) from GL's bottom-left corner, offsets included; its
texture coordinates span that viewport, and its buffers keep what they
held outside it. Deferred programs follow OptiFine's doc; prepare programs
are treated the same (unverified). Horizon now does the same: the pass
loads its attachments instead of clearing them and sets that viewport.
Found while investigating #6812.
Fixes#7453
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Iris 1.11.7 reads the first argument of `smooth` as the call's id when it
is written as a number and more arguments follow: `smooth(1, 5, f)` is 5
and `smooth(5, f)` is f (observed with a probe pack of our own). Any other
first argument is the value (`smooth(1.5 * 2, 6.5)` is 3). Horizon read the
first of three arguments as the value. Iris drops a uniform whose `smooth`
has four arguments and no number first, or five; Horizon now leaves those
out too, so they read 0.
Found while investigating #6812.
Fixes#7452
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DoorBlock, TrapDoorBlock and FenceGateBlock useWithoutItem turn the block to
its other open when the material's BlockSetType can be opened by hand, play
the open or close sound and send block_open or block_close; a fence gate closed
across the player turns to face them first, drawing its sound pitch from the
level random; and an iron one passes, so the click falls through to what the
hand holds. The behaviour is on main; no harness case clicked a door, a
trapdoor or a fence gate.
doHurtTarget against mob 2 must carry poison 140 (normal), hunger 140
(effective difficulty 1.5) and wither 200 with the hit; without the fix the
hit is a plain AttackMob and the mob keeps no effect (0 ticks). On easy the
cave spider's timer is 0, so the hit stays plain.
CaveSpider, Husk and WitherSkeleton.doHurtTarget add their effect to the
LivingEntity the hit landed on, not to the player alone: the bytecode tests
`target instanceof LivingEntity`. Horizon guarded all three with
`who == Who::Player` and sent a mob target a plain AttackMob, so a cave
spider's bite, a husk's bare-handed blow and a wither skeleton's hit gave
the damage and no effect at all.
A hit on a mob now goes out as MobEvent::AttackMobWithEffect, which the game
applies through Mob::add_effect once mob_hits_mob says the hit landed, the
same rule AttackPlayerWithEffect follows.
Load and save the local max-swell value instead of only preserving raw Fuse NBT. Keep the default 30 and existing remote synchronization, and add timing, serialization, fall, and rendering regressions plus command survival checks.
Co-Authored-By: Codex <[email protected]>
Mob.readAdditionalSaveData reads DeathLootTable and DeathLootTableSeed, and
getLootTable rolls that table instead of the type's. Horizon never read them:
a summoned or spawner mob with its own table (a data pack's or a mod's boss)
dropped its type's loot. They are now read, saved back, and rolled at death.
LootTable.getRandomItems also rolls a table with no random_sequence with the
level's random (or the seed's own, when set); Horizon dropped nothing for
such a table, which is most data pack and mod tables.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 2d257a0bdd. The new dialog_close check passes against a real 26.3 server on playhorizon, through the local frame proxy injecting ContainerClose (0x11). All 10 sub-checks pass: waiting screen, parents, warning, lectern, replacement dialog, key release, death. book_menu still passes all 4. Main merged in (2d257a0bd).
A villager draws its model four times (body, type, profession, level), a
spider twice (body, eyes), and each draw set up the poses again, walked the
part tree into fresh maps and recomputed every part's matrices.
- Layers over the body's own model copy the body's poses instead of running
setupAnim again (no custom model on either).
- Poses carry a version (new at each change, kept by copies); Skin::draw
reuses the last walk's part matrices when the model, root matrix and
version repeat, pushing the same jobs with the new paint.
- The body's and layers' Poses come from a per-thread pool, so their maps
keep their capacity instead of growing and rehashing per mob.
- The spider's legs use static part names (16 format! per draw before).
- MobType.layers is an FxHashMap; humanoid_armor returns before body() when
the mob wears nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 1a6ccac6de. I merged main in: #7327 had set TypedDataComponent.encodeValue to a port in the same file, so I kept main's row and your extended notes on the other four rows. pois_in_range and dye_color_mix_in_recipes resolve on main. TSV only.
🤖 Agent: the clear-everything path in forget_columns relies on pending sections only ever naming held columns (compile_started is inserted only for loaded columns, stale mesh results are dropped by the chunks check), so clearing when no column is left loses nothing.
🤖 Agent: queued at b3c0ba9c1e. Main merged in for #7337's xtask test fix. Same review as before: only natural spawns roll the jockey mounts, and the mount tests pass.
🤖 Agent: queued at a129fa4b6b. Main merged in so verify picks up #7337's xtask test fix (the red was main's xtask test, not this PR). Citations re-checked earlier against main. TSV only.
🤖 Agent: queued at 72336fa383. Main merged in so verify picks up #7337's xtask test fix (the red was main's xtask test, not this PR). Citations re-checked earlier against main. TSV only.
🤖 Agent: queued at 186ed92f7b. Main merged in so verify picks up #7337's xtask test fix (the red was main's xtask test, not this PR). Citations re-checked earlier against main. TSV only.
🤖 Agent: queued at ed08c0d9c6. Main merged in so verify picks up #7337's xtask test fix (the red was main's xtask test, not this PR). Citations re-checked earlier against main. TSV only.
🤖 Agent: queued at dc691ee6a9. Main merged in so verify picks up #7337's xtask test fix (the red was main's xtask test, not this PR). Citations re-checked earlier against main. TSV only.
🤖 Agent: queued at b1a1b582c6. Main merged in so verify picks up #7337's xtask test fix (the red was main's xtask test, not this PR). Citations re-checked earlier against main. TSV only.
Apply the custom-name component to live armor stands, consume a tag even in creative, and retain PASS for unnamed tags. Cover component and dead-target behavior locally and add host/guest inventory and name checks to the LAN frame scenario.
Co-Authored-By: Codex <[email protected]>
EntityRef (the world's open epoch and an entity id) and LevelRef (a
dimension), made from an event's id by Host::entity and Host::level. A
helper whose catalog entry names a receiver also becomes a method of that
handle, generated beside Host: entity.position(host) calls
host.entity_position(entity). First reads: position, block position,
rotation, type, UUID and level.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at a3740ffa49. Main merged in for #7337's xtask test fix. Same review as before: both hooks now hear hit.beside(), which closes the claim bypass from #6783.
DragonEggBlock.useWithoutItem teleports the egg instead of using what the hand
holds: up to 1000 random offsets within 16/8/16, the first air block inside the
build height with a block under it gets level event 2015 with the packed
difference, the egg moves there and the clicked one is removed. The behaviour is
on main; no harness case mentioned dragon eggs.
🤖 Agent: queued at 6047ef2d48. Main merged in so verify picks up #7337's xtask test fix (the red was main's, not this PR's). Options::parse as the argument loop checks out. TSV only.
🤖 Agent: the only hand-written change in the vendored mimalloc is mi_popcount on MSVC x86/x64 taking the portable count (no POPCNT). The real proof is the first nightly after this merge: game-build's new 'Baseline x86-64 CPUs only' step must pass on the cl.exe-built horizon-client.exe.
MerchantScreen.mouseClicked said clicking the scroller does not start a
drag. Game::merchant_click sets dragging inside the 139-px track past
seven offers and Game::merchant_drag follows the pointer, so the note
now describes that.
RecipeBookComponent.extractTooltip still said there is no tooltip for
ghost slots. Game::ghost_stack answers GhostSlots.extractTooltip and
draw_slot_tooltip asks it before the slot's own stack, so the note now
says where the ghost tooltip comes from.
Use the existing mob attribute base storage and computed-value path for max-health command reads, writes, and reset. Add command parity cases for bounds, raw getters, health preservation, and survival.
Co-Authored-By: Codex <[email protected]>
The row claimed a port, but vanilla's extractImage draws a 10x10 player
face per sample entry and the name beside it. Horizon's tooltip only
has the names: player_list builds the name lines and ui::tooltip draws
them as text, and no face is drawn for them.
The row is now inline, with the missing image recorded as a player gap.
The row cited Game::draw_ghost, which only builds and paints the ghosts
(GhostSlots.extractRenderState) and draws no tooltip. The stack the
vanilla method returns is Game::ghost_stack, which draw_slot_tooltip
asks before the slot's own stack.
🤖 Agent: the mod block-entity pass now runs only when BlockEntities::has_mods (the index mods_near already trusts); without any it drew nothing. The frustum scan reuses visible_block_entities' no-fade shortcut. Small gain (0.4 % main thread in the RD 32 village), exact.
🤖 Agent: queued at 02de4be732. Keeping the markings in extra.variant2 while the component sets only the coat matches Horse.setVariant's 'id & 0xFF | getTypeVariant() & -256', and the test checks the packed Variant int (6 | 2 << 8) survives a save. Trader llamas inherit the llama arm, as in vanilla. Merged with main: clippy clean, and the horse tests pass with real 26.3 data.
The sunrise/sunset fan was still a per-pixel Moller-Trumbore test over up to
six of its sixteen triangles in every sky pixel (#3143 measured 1.8 ms at
1080p on an Intel UHD 730 after the normals moved out of the shader; #3993
cut the scan but kept it per pixel). This rasterizes the same camera-space
fan as sixteen triangles: the apex vertex carries weight 1 and the rim 0,
so the rasterizer interpolates the barycentric apex weight the shader used
to compute. The fragment shader emits the sunrise colour with that weight
as alpha, blended over the fan-free sky; the destination alpha is preserved
so HDR is unchanged.
The fan's clip positions are projected once per frame with the sky's
unjittered, unposed view (the view `fs_sky`'s `view_ray` uses), and the
960-byte buffer goes through the existing staging belt. Shader packs and the
water mirror keep the per-pixel path.
On lavapipe at 2176x1224, render distance 6, time 13000 and an 8 s fly, the
GPU pass went from 44.80 to 39.56 ms and p50 frame time from 36.43 to 32.61
ms (FPS 22.0 to 24.8). The RX 9070 XT is neutral at this scene, as expected:
its fan cost is small, but the saved work scales with sky pixels on the
iGPUs #6887 reports.
Part of #6887.
Co-Authored-By: OpenCode (DeepSeek V4.1 Flash Free) <[email protected]>
DriedGhastBlock.spawnGhastling calls setBaby(true) before addFreshEntity.
AgeableMob.setBaby -> setAge crosses the age boundary there, so
HappyGhast.ageBoundaryReached runs babyGhastSetup while the mob is still
being hatched: its box, its flying navigation and its brain are a
ghastling's before it joins the level.
Horizon's hatch path wrote extra.age directly after spawn_mob had already
registered the adult's goals (HappyGhastFloat, the tempt goal,
FloatAround) with a ground navigation and the brain stopped, so the
ghastling spent its first tick flying adult goals and only its first
aiStep (ensure_setup) corrected that. age_boundary_reached also had no
HappyGhast arm, so the crossing ran nothing for it at all.
The hatch now goes through finalize::set_age, which dispatches the
crossing to happy_ghast::ensure_setup like the other ageable species'
ageBoundaryReached arms.
The port_map row for HappyGhast.ageBoundaryReached named ensure_setup
without the dispatch that reaches it; it now points at the dispatch.
/loot ... loot <id> checks the id against the loot tables listed from the
resources root, which read only data/minecraft/loot_table: a content mod's
tables (data/<ns>/loot_table) generated and filled chests but the command
answered "Can't find element". List the tables of every namespace, on the
disk and in a prepared root's data file, as the structure templates are.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ari asked for a way to mark issues nobody should claim whole. Issues
labelled tracker, proposal or not claimable now get a one-line refusal
to /claim pointing at the issues they list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bump gpui-fast and gpui-fast-platform to 0.1.13 (Zed decd2399b1: UAX #14
wrapping, cached-view opacity fixes, executor-clocked animations, macOS
frame pacing, Windows renderer recovery). Its Apple renderer needs
macOS 12, so Horizon.app's minimum goes from 11.0 to 12.0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every frame, the mod block entity pass walked every loaded block entity
(every resident section, at any distance) and asked each whether a
vanilla renderer draws it, to find the mods' among them: furnaces,
hoppers, barrels and every other unrendered one went through it, in a
world with no mod block entity at all. It now runs only when the world
holds one (`BlockEntities::has_mods`, the index `mods_near` and level
access already rely on); without any, it found none.
`visible_block_entities_in_frustum` looked up each section's upload
time and measured its age on every frame, even with no section fading
in. It now takes the shortcut `visible_block_entities` already has:
when no section was uploaded within the fade, every section is whole.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at efa9d63169. Every port/inline/home citation resolves on main (checked: hub.rs::guest_body). 1 rows changed, all four-column. TSV only.
`a_sitting_tame_mob_still_looks_for_a_mate` runs `Breed::can_use` over five
pairs of tamed cats and wolves: a sitting cat pairs with a sitting or a
standing partner, a sitting wolf pairs with a standing one, and neither
starts on a partner that is sitting.
The mobs come from the crate's own synthetic cat and wolf types, so the
cases run without the extracted vanilla data; only the world the goal looks
in needs it.
`Cat.canMate` needs both cats tamed and in love, and `Wolf.canMate` the same
for two wolves, refusing only a partner that `isInSittingPose()`: the sitting
pose sits on the partner side, and only for a wolf. `Breed::can_use` refused
any tamed cat or wolf that was sitting as the one looking for a mate, so a
cat left sitting (the state a tamed cat spends most of its time in) never
bred, and a sitting wolf never walked off to a standing partner.
The partner filter already had the rule right (`!(species == Species::Wolf &&
s.sitting)`), so this is the seeker's own check losing the pose it does not
have in vanilla.
Corrects the `Cat.canMate` row of `tools/port_map/game-011.tsv`.
🤖 Agent: queued at 7b0a0efc7d. Every port/inline/home citation resolves on main (checked: extra.rs::Game::report_resolved_profile, vanilla.rs::fill_modes, commands.rs::Game::cmd_locate_poi). 8 rows changed, all four-column. TSV only.
🤖 Agent: queued at 30d23de3b3. Every port/inline/home citation resolves on main (checked: pistons.rs::Sim::moving_piston_tick, pistons.rs::Sim::moving_piston_final_tick, piston_push.rs::extended_progress). 10 rows changed, all four-column. TSV only.
🤖 Agent: queued at a345219dad. Every port/inline/home citation resolves on main (checked: game.rs::falling_block_visible). 1 rows changed, all four-column. TSV only.
🤖 Agent: queued at 985fd6e8f0. Every port/inline/home citation resolves on main (checked: world_cmds.rs::Game::spread_spawn_y, world_cmds.rs::Game::spread_safe, world_cmds.rs::Game::cmd_stopsound). 4 rows changed, all four-column. TSV only.
🤖 Agent: queued at b15ee4a662. Every port/inline/home citation resolves on main (checked: remote.rs::Game::remote_minecart_lerp). 1 rows changed, all four-column. TSV only.
🤖 Agent: queued at 984b742830. Every port/inline/home citation resolves on main (checked: test_instances.rs::Game::test_instance_marker_geometry). 1 rows changed, all four-column. TSV only.
🤖 Agent: queued at 30b37d8748. Every port/inline/home citation resolves on main (checked: update.rs::Sim::update_neighbors_at_except_oriented). 1 rows changed, all four-column. TSV only.
🤖 Agent: queued at 0a13a0d2e2. Every port/inline/home citation resolves on main (checked: aquatic.rs, goals.rs, dolphin.rs). 6 rows changed, all four-column. TSV only.
🤖 Agent: queued at 838bf539a7. Every port/inline/home citation resolves on main (checked: containers.rs::locked_message). 1 rows changed, all four-column. TSV only.
🤖 Agent: queued at 665ff9893f. Every port/inline/home citation resolves on main (checked: projectiles.rs::clip_including_border). 1 rows changed, all four-column. TSV only.
🤖 Agent: queued at ec325b8357. Every port/inline/home citation resolves on main (checked: world_upgrader.rs::status_key, world_upgrader.rs::status_key, world_upgrader.rs::verify_chunk_pos). 6 rows changed, all four-column. TSV only.
A hub with one guest: an effect of 30 s synced before its first tick is
sent at 600 and not again as the next ticks count it down
(LivingEntity.tickEffects refreshes only at a multiple of 600 left); synced
after one tick, it goes out at 599, #7115's transcript.
Refs #7115
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A dedicated server's console now syncs the players' effects right after
its command, as a player's own commands already do (run_guest_command).
Since #6404 (354e2e632) the guests tick before the hub syncs, so an effect
the console gave was counted down once before it was sent:
`/effect give Bob minecraft:speed 30 1` reached Bob as 599 ticks, not 600.
Vanilla runs the console after the players' tick
(DedicatedServer.tickConnection: MinecraftServer.tickConnection, then
handleConsoleInputs), and ServerPlayer.onEffectAdded sends
ClientboundUpdateMobEffectPacket at once. The sync sits in console_command,
so the stdin console, RCON (rcon_line) and the control socket's `console`
request all get it. #6404's tick order is unchanged.
Fixes#7115
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 8af100b198. Supersedes #7343 and also repairs row 21's dangling boats.rs::Game::tick_riding cell; all 5 citations resolve on main. TSV only.
🤖 Agent: queued at da13f6f606. The new head adds citations, and all 7 resolve on main. The resolveAgainst follow-up (no '..' normalisation check in folder-backed pack readers) still stands. TSV only.
🤖 Agent: queued at 64faa0482a. Same review as before (the prune now waits for a successful save). I merged main in so verify picks up #7337's xtask test fix; the earlier red was main's xtask test, not the launcher.
🤖 Agent: queued at 3b40418730. TypedDataComponent.encodeValue as hash.rs Component::code checks out. Main merged in so verify picks up #7337's xtask test fix (the earlier red was main's, not this PR's). TSV only.
At render distance 32 a far teleport forgets every column (about 3,700
columns, 27,000 resident sections) in one frame, ~6 ms on an M4 Pro:
- Streaming::forget_columns forgets them together; when no held column
is left, the pending-section maps (80,000 compile_started entries) are
cleared instead of filtered (1.7 ms -> 0.1 ms).
- Resident::remove_columns empties itself when every column goes,
without a lookup and a re-insert per section (1.9 -> 0.3 ms).
- Arena::release_all resets a page every range of which comes back,
instead of sorting tens of thousands of ranges into its free list
(1.0 -> 0.3 ms).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: rebased on main fdd290b99 (conflict in spans::time with the new recording_here(): the heap tag stays first, a const None without the feature). Rechecked on a2bbcb4fe: clippy -D warnings with and without alloc-census, horizon-threads 18/18, spans 6/6, fmt clean.
A Flame bow's arrow burned on the server but no client knew: the tracker
never sent Entity.DATA_SHARED_FLAGS_ID's fire bit for projectiles, and the
client ignored index 0 for them. The server now sends it while the arrow
burns (Entity.baseTick's setSharedFlagOnFire) and the client keeps it as the
arrow's fire, which the arrow renderer already draws flames for.
server_entities now tracks a burning arrow against vanilla's dedicated
server, and lan_e2e's flame-arrows check shoots Flame and plain bows from
the host and the guest and reads both clients' view.
Fixes#7356
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at a5a3c96e4a. The three packets exist as packet! declarations in horizon-protocol. buf.rs::write is a generic citation, but the note names each packet. TSV only.
enableForcedFeaturePacks was filed missing because Horizon's packs were said
to have no feature flags; pack.mcmeta's features.enabled is read and the
last loop of datapack::enabled_ids forces such a pack on. The two rows whose
notes claimed no feature check follow.
Review findings on #7267: /summon end_crystal now checks the join like the orb branch; the spike crystals join before tick_crystals; native_joins_flush ends with joined_mark = max(joined_mark, next_entity), so ids that are never announced stop the next flushes walking.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The fetchprofile reply, the fill modes, locate poi, msg's sendMessage,
place's loaded check and feature path, publish and the reload's pack step
each have a function whose doc names them.
Nine call sites each asked the OS for the core count and sized their own
pool, so a run spread workers over every reported core with no way to
cap them. horizon_threads::worker_count() resolves the count once
(available_parallelism is the process's share: affinity, cgroup quota
and honors HORIZON_WORKER_THREADS for machines a run should take fewer
cores of. Each site keeps its own share of the count: the streaming
pool all but one, far decoration a third, save compression half, and
the rest the raw number.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
EOF
)
🤖 Agent: GPU silhouettes are written past every entity material and drawn by the outline pass with a base vertex; outline_since copies jobs as FLAT in the team colour (= mark_outline). Invisible glowing mobs stay on CPU. Screenshot diff within same-binary noise.
The same shape as the golem and fox test: each offset's height against the
share of the eye height, the forward shift against `bbWidth * 0.4`, and the
happy ghast's at zero on all three axes.
`Pig.getLeashOffset`, `Rabbit.getLeashOffset` and
`Strider.getLeashOffset` put the rope at 0.6 of the eye height,
`Parrot.getLeashOffset` and `Ocelot.getLeashOffset` at half of it, and
`HappyGhast.getLeashOffset` returns `Vec3.ZERO`, so a happy ghast's lead
hangs at its feet.
`leash::leash_offset` knew the llama, wolf, allay, camel, bee, the three
golems and the fox and gave these seven the `Leashable` default it falls
through to — the full eye height, `bbWidth * 0.4` in front — so their
ropes were drawn too high, and a happy ghast's two blocks above its feet.
Corrects the six ledger rows these gaps sat on (game-011.tsv,
game-012.tsv, game-013.tsv, game2-003.tsv).
🤖 Agent: queued at 4611b48768. Every counter changed only names temp files within one process, so 32 bits can't realistically wrap into a live name, and dropping AtomicU64 unblocks targets without 64-bit atomics. Merged with main: horizon-world clippy (all targets) is clean and the region/cache tests pass.
Main (#7267) dropped the vanished_mobs_are_reported_by_kind test beside this
PR's hand_pose_rejects_invalid_components: kept the new test. generated.rs is
regenerated (cargo run -p xtask -- mod-api) over main's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ten methods the batch skipped have ports whose docs name them: the moving
piston's tick and final tick, the push geometry in piston_push.rs, and the
save data in persist.rs.
Row 36 said a falling block inside an identical block is still drawn and
that game.rs has no such check. game.rs has it: falling_block_visible
compares the entity's own state with the state of the cell at its feet,
and the entity draw skips the block when the two match, with a unit test
either side. The row now names that fn.
getSpawnY and isSafe are the spread command's spawn search and safety test,
stopSound is the packet-per-target loop in cmd_stopsound, and
StopwatchCommand.register is tree_misc's stopwatch tree.
🤖 Agent: queued at f96c17a314. Thanks for catching this. My stamp merge (#7258) deleted native_item_damaged.json, which this test read, so main's xtask tests have been red since. Reading the stamped hook out of native-hooks.json is the durable fix: a later stamp can't break it again. Merged with main: all 19 xtask tests pass, mod-api --check passes, and the test restores the catalog afterwards.
Row 98 said experimental minecart movement is not implemented. A
NewMinecartBehavior cart queues steps and remote_minecart_lerp samples
them at the frame's partial tick, position and rotation together, with
a None that leaves the renderer on the entity's own interpolated
position and rotation as vanilla's newExtractState does. The row now
names that fn.
Row 141 cited crates/horizon-core/src/paletted.rs::locate; paletted.rs
imports locate from storage.rs (line 11) and never defines it. The cell
is crates/horizon-core/src/storage.rs::locate now: the word and bit shift
of an entry, which is the index SimpleBitStorage.cellIndex computes.
Verdict unchanged.
map-check tools/port_map/rest-022.tsv: 149 rows, no problems.
Row 76 cited crates/horizon-client/src/game.rs::run, which game.rs does
not have. The fn that runs the game to its end is run_event_loop
(game.rs:20250): the winit event loop with a window, Game::run_headless
without one, then the narrator stop and the crash report. run_with is the
process entry that reaches it and run_loading the loading screen's loop
when resources are missing, so the note says that instead of "the main
loop".
map-check tools/port_map/rest-003.tsv: 143 rows, no problems.
Both carried unsure:. Read: the defaultgamemode arm also flips the host's
creative flag (vanilla only sets the world default and enforces it when the
world forces a mode), and the loaded arm has no entity-load check where
vanilla wants ENTITY_TICKING plus areEntitiesLoaded.
Row 133 cited crates/horizon-client/src/game/lan.rs::Game::lan_host_state,
which is not a fn in that file. The spectator-and-effect invisibility that
ServerPlayer.updateInvisibilityStatus decides is lan/hub.rs::guest_body's
invisible flag, which shared_flags sends as the shared-flag bit 0x20
whenever it changes. The row stays inline; its empty note now says what
it is folded into.
map-check tools/port_map/game-002.tsv: 146 rows, no problems.
ServerScoreboard.load and store are Scoreboard::unpack and pack (their docs
name them), and updateStepTicks is the TickingStep broadcast inside
Hub::sync_ticking.
Row 183 cited Sim::update_neighbors_at_except, which the neighbour
updater does not define; the fn that skips a side is
Sim::update_neighbors_at_except_oriented (update.rs:528), the Multi
update with skip: Some(side), reached from redstone.rs:571,
redstone.rs:829 and interact.rs:511. No verdict changes, and the empty
note now says what the cell means.
map-check tools/port_map/classes-07.tsv: 189 rows, no problems.
Rows 74-76 and 82-86 are class rows carrying port or inline, which
map-check refuses (a class row takes skip:<reason>, missing or home).
Each goal they name is in the tree, so each row is a home row now:
aquatic.rs for FollowFlockLeader, goals.rs for FollowMob, dolphin.rs for
FollowPlayerRidden, golem.rs for the two named Strolls register_iron
builds (MoveBackToVillageGoal and GolemRandomStrollInVillageGoal), and
undead.rs for MoveThroughVillage. The notes keep what the old cells
claimed and gain the Stroll each golem row is and the raider goal's real
home (Raider$RaiderMoveThroughVillageGoal, raider.rs::MoveThroughVillage).
map-check tools/port_map/game-009.tsv: 125 rows, no problems.
The row still said creative players see no beacon beam or box, from before
the test instance renderer landed. Game::test_instance_marker_geometry is
submit's per-block marker loop; the beam sections come from
Game::test_instance_beam (beacon.rs) and the box from
Game::bounding_box_gizmos (structure_blocks.rs), both called per frame.
Row 55 named the class itself in a class row's where cell, so the verdict
was port and map-check refuses that: a class row takes skip:<reason>,
missing or home. BlendingData and Blender live in
crates/horizon-world/src/worldgen/blending.rs, which is what the row says
now, and its note names the fns it describes (unpack, pack, calculated)
instead of calculateData, which no fn is called.
map-check tools/port_map/classes-05.tsv: 227 rows, no problems.
Row 129 said a chest with a Lock tag should refuse to open and that
Horizon has no Lock check, only chest_menu_blocked looking at the block
above. The check is container_locked_for, which reads the lock tag and
asks can_unlock whether the held item opens it; the menu path in
lan_actions.rs refuses the container, sends the container.isLocked action
bar text built by containers.rs's locked_message and plays
block.chest.locked. The row now names that fn.
🤖 Agent: queued at 909e5907ee. Comparing [strafe, forward] with the originals before touching keys means a run with no mod leaves the input untouched, and the per-tick cost is one array compare. You mentioned nothing was built or run, so I did: after a main merge, xtask mod-api --check and test_stamp_native_hooks pass, clippy is clean, and the native lib tests plus your two mod_api_hooks write-back tests pass.
Row 121 said the border clamp of a ray was missing and that clip_ray had
none. It is not in clip_ray: it is the wrapper projectiles.rs puts around
a block clip, a ray that starts inside the border and ends outside it
stopping on the border at WorldBorder.clampVec3ToBound, facing the
nearest direction of travel, which creatures.rs calls for every
projectile clip.
The three unsure: notes asked where vanilla's field-codec range check went.
There is no wrapper; the sites that need a bound check it themselves, and
the notes now name them.
translate and Messages.forStatus are the status key pick in status_key, and
verifyChunkPos is verify_chunk_pos - warning about a misplaced chunk is what
the method does.
🤖 Agent: queued at 97fe3e6ec9. advancements.rs::item and ::damage_source exist, and the 'gap: minor' on SingleComponentItemPredicate keeps the open part visible. TSV only.
🤖 Agent: queued at d1a116192b. The Crypt writer rows are right to stay missing: only the key-pair codec's static initialiser calls them, and Horizon reads PEM. TSV only, citations checked against main.
🤖 Agent: queued at bf3a0e467e. Making the shulker bullet's target an Option, with getGravity (0.04, none under NoGravity), is what lets a target-less summoned bullet fall instead of hanging at y 110. ExperienceBottle getting its own kind with gravity 0.07 matches ThrownExperienceBottle. Merged with main; clippy and the projectile tests pass.
🤖 Agent: queued at c769fc342c. Routing the stand through resolve_in_border with the level's BorderGeometry matches Entity.collectColliders. With the border passed as Option, the tests' None keeps the old path. Merged with main: clippy clean, and the_world_border_stops_an_armor_stand passes on playhorizon with real 26.3 data (it skips without .local, so I checked it actually ran).
🤖 Agent: queued at 2d73371393. Running place_summoned over the whole stack before mob_from_nbt matches loadEntityRecursive, and normalize_id in entity_nbt::kind covers the bare 'chicken' id case for every NBT loader, not just spawners. Merged with main; the entity_nbt tests pass.
🤖 Agent: queued at c3f8675114. With no mod loaded this is cheaper than main: persist.live stays empty and native_joins_flush returns on mods().is_empty(), and the three per-tick dropped_ids/mob_ids/orb_ids snapshots are gone. Merged with main: xtask mod-api --check passes with no regen needed, and clippy plus the native tests pass.
🤖 Agent: queued at 7bd3ccc656. Falling back to fs::copy when hard_link fails (other volume, the NTFS 1023-link limit) keeps the root byte-identical. Writes are safe because Overlay::write removes the name before writing, and your writing_into_the_overlay_leaves_the_base_alone test pins exactly that. Merged with main; the overlay tests pass.
🤖 Agent: queued at 72b58bd112. Moving AgeableMob.aiStep's age clocks out of the AI gate is the right shape: they run once per mob per tick as before, so the move costs nothing, and NoAI babies now grow (the turtle scute event comes along with it). Merged with main: clippy clean, the age tests pass on playhorizon with real data. run-parity is on because the mob_ai harness changed.
Four game2-007 rows said a boat over or in a bubble column was not
handled: onAboveBubbleColumn, tickBubbleColumn, handleEntityEvent and
handleBubbleColumnEffect. boats.rs holds the column bookkeeping, the
splash, the countdown and the drag or lift, and remote_vehicles.rs
applies event 71 or 72 to the boat the client drives, so each row now
names its fn and says what that fn does.
map-check also flagged row 21: AbstractBoat.onPassengerTurned named
Game::tick_riding, which is not in the tree. The 105-degree clamp is in
Game::remote_ride.
All four clear rows were skip:server because Horizon has no JSON-RPC
management API; the port exists and is tested, and the missing caller is the
management service, which is what the notes now say.
processWithRateLimit is the thirty-second window around the activity
notification, and management.rs::activity_due is the same timestamp test
against ACTIVITY_EVERY.
FilePackResources.getResource and listResources were filed skip:data as
"pack file access"; DataPack::read and DataPack::list answer both, over a
zip's entries or a folder pack.
A client never picked a knot, so it sent the click as UseItemOn on the
fence, which vanilla's server reads as LeadItem.useOn: a tied mob could not
be taken back. The knot is pickable for a use now and the click goes as
Interact with its id. Horizon's server read a fence click inside the knot's
box as a knot click, which left a mob untied in single player; a fence click
now only ties, and Interact with a knot's id goes to use_knot.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Since #4160 (10-29) the game's allocator is mimalloc v3, from libmimalloc-sys
0.1.49. Built by MSVC, its mi_popcount is `__popcnt64`, which MSVC turns into
the POPCNT instruction whatever the target. mimalloc runs before main, so on a
Core 2 (no POPCNT) the Windows game died at once with 0xc000001d and no log
(#7264). v0.2.97 had no POPCNT at all; nightlies 2026-10-31 and 11-01 have 9.
third_party/libmimalloc-sys is the published crate with one change: on MSVC
x86/x64, mi_popcount takes mimalloc's portable count, as the GCC builds without
-mpopcnt already do. Only arena bitmaps count bits, never malloc's fast path.
[patch.crates-io] points at it until a release carries microsoft/mimalloc#1291.
tools/check_cpu_baseline.py disassembles the Windows and Linux programs in the
game build and fails on any POPCNT or LZCNT: it flags 2026-11-01's
horizon-client.exe and passes v0.2.97's and the Linux builds.
Fixes#7264
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_world_drawn runs before the hand, so a mod that works on the picture
(a motion blur) left the hand and held item untouched, and never saw the hand's
geometry or its matrix (its own field of view and the bob). native_hand_drawn
runs after the hand's pass on the real frame, with the frame's target, the
hand's camera-relative quads and its clip matrix (HandDrawn). The generator
adds RenderHooks::hand_drawn; an API fragment numbers it.
Closes#7335
Co-Authored-By: Claude Opus 5.5 <[email protected]>
reportChunkLoadFailure is chunk_io.rs::report_load_failure, which was filed
missing although its doc names the vanilla method; reportChunkSaveFailure
pointed at the local save path's warning rather than report_save_failure.
Identifier.resolveAgainst and PlayerAdvancements.setSelectedTab lose their
unsure: now that both are read.
Identifier.toLanguageKey is spelled out in the buffet world screen's display
name, and ResourceManagerRegistryLoadTask.load has its counterpart in the
registry sync's file walk, pack overlay and file order.
`PostChain.addToFrame` and `PostChain.process` point at the entity outline's
silhouette pass and the menu's own two-pass blur, which is right, but their
gaps read as if those were the only chains that run. The chain runner is
`post_chain.rs::PostEffects::apply`: it loads whatever chain id the frame's
request names, on first use, and runs it.
`PostChain.load` and `PostChain.createPass` were filed as missing ("no
data-driven post chain loader; only the blur and entity outline chains exist,
hard-coded"), and `getReferencedExternalTargets` as needing no port. All
three are `post_chain.rs`: `PostEffects::load` reads a chain's JSON, refuses
any external target but the frame with vanilla's own message and builds its
targets and passes, and `translated_pass` is `createPass`, translating the
pass's GLSL, binding its targets and images and packing its uniforms.
A glowing mob (and a glowing dropped item) went to the CPU path whole:
the entity outline (`OutlineBufferSource`) copied its CPU vertices in
the team colour with no normal (`mark_outline`). Now a glowing entity
that is not invisible is posed on the GPU like any other, and its
silhouette too: `SkinFrame::outline_since` copies the entity's skin
jobs as FLAT jobs (no normal) in the team colour. The same dispatch
writes them past every entity material in the entity buffer, and the
outline pass draws them from there after the CPU silhouettes. An
invisible glowing mob (only its outline shows) keeps the CPU path.
The shader test now dispatches outline jobs into a second region and
checks each silhouette is its draw's vertex with the colour set and the
normal cleared, as `mark_outline` makes it. Adds stress_glowing (1000
glowing mobs).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A build with the alloc-census feature runs mimalloc with a tag in front of
each block: the startup step, timed span or pool job that allocated it, else
its thread's name. horizon-ctl memory then lists the live heap by tag
(heap_by_tag_mb). heaptrack's interpreter ran out of 7 GB on the game; this
needs nothing beside it. Without the feature nothing changes: the tags are a
constant None.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SetStructureBlock is hand-written, so its write is named directly; the
(StructureBlockEntity, UpdateType) constructor is the edit screen's packet
builder. getCommandBlock's set-command and track-output write is folded into
apply_cart_command, which also carries the handler's onUpdated and note.
JigsawGenerate, SetJigsawBlock and SetCommandMinecart are packet!
definitions in packets/play.rs, built by the jigsaw and cart edit screens
and answered by their guest handlers; the rows said the client has no such
packet and no such screen.
AtomicU64 does not compile on targets without 64-bit atomics
(armv6k-nintendo-3ds, 32-bit ARMv6). These statics only pick unique
temporary file names, where 32 bits is plenty.
Closes#7340
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`Bat.isFlapping` now names the emission it misses: `mobs/living.rs` posts
`minecraft:flap` for a bee on every other tick and for a parrot, and the bat
is not in that list, so a sculk sensor hears no wing beat.
`AbstractChestedHorse.getSlot` now names the slot set Horizon resolves, the
eight names in `loot::EQUIPMENT_SLOTS`, which has no `horse.chest` in it.
`GroundPathNavigation.trimPath` was filed as missing on the reading that
nothing cuts a path at the first sunlit node. `nav::Mob::trim_path` is that
method: with `avoid_sun` on a ground or wall-climbing mob it leaves the path
alone while the mob itself is under sky, and otherwise truncates the nodes at
the first one that can see the sky.
`LandRandomPos.getPosTowards` is `goals::Mob::land_random_pos_towards`, the
direction to the point and then `land_pos_in_direction`'s ten tries within a
quarter turn. What is still missing is the caller side, and the note now says
so: the golem goals are not registered, so a golem does not drift toward the
village.
The camera snapshot (two matrix products, a projection and the
dimension's String) was rebuilt every frame with no mod to read it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`getSessionId` now names where the id comes from: `lan/conn.rs` sends the
player's uuid as `session` in login finished, where vanilla sends one random
id per server run.
`onPacketError` loses its "unsure": nothing in the tree records a failing
packet's exception for a crash report, so the gap is real and stated as such.
`setPlayerIdleTimeout` says which half is missing: `command_order.rs` lists
the command name and the dispatcher has no arm, while the idle sweep the
property drives is in `game/dedicated.rs`.
`ServerWatchdog` was filed as missing three ways ("no watchdog runs", "Horizon
has no watchdog", "watchdog only"), but `dedicated.rs` starts one from
`max-tick-time` in `DedicatedServer.initServer`'s place: `Watchdog::run`
compares the tick in progress against its due time, logs the two lines
vanilla logs, writes `crash::report_watchdog` and exits with 1.
`exit` is inline in that loop with its one deviation kept in the note (vanilla
sleeps 10 s and dumps the stuck thread first; Rust cannot read another
thread's stack), and the report's own gap — no thread dump — is named on
`createWatchdogCrashReport`.
`Main.parseArgument` cited `Options::read`, which only feeds
`std::env::args` into the loop; the loop is `Options::parse`.
`LocalPlayer.canSpawnSprintParticle`'s note now says which half of the
vanilla test is missing: `sprint_dust` runs `Entity.canSpawnSprintParticle`'s
checks (sprinting, not spectator, not crouching, alive, out of water and
lava) and not the `!abilities.flying` that `LocalPlayer` adds on top.
new_hook_placeholder_is_reported_then_filled read
tools/native-hook-fragments/native_item_damaged.json, which 48c63bc09
stamped into native-hooks.json. It now drops item_damaged's mod_api entry
from the copied catalog and expects the same error.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Three `missing` rows have ports in the tree:
- `FirstPersonHandsAndItems.shouldInstantlyReplaceVisibleItem` is
`hand::shows_held_stack`, whose doc names it and
`ItemStack.matchesIgnoringComponents`: the same stack object, or an equal
one, plays no swap animation.
- `ItemActivation.tick` and `ItemActivation.activate` are the two methods of
`item_activation.rs`, which also carries the render pose of
`ScreenEffectRenderer.renderItemActivationAnimation`. The totem pop is
drawn: `game/totem.rs` activates it and `game.rs` ticks and poses it.
`Main.hasValidUuid` and `isUuidValid` need no port: the `--uuid` argument is
the only thing that reaches them and `Options::parse` has no such flag, so
they are skip:other rather than missing.
Verbatim copy of the crates.io package (checksum 6a45a52f…b8e9 in Cargo.lock),
without c_src/mimalloc/v2 and v3's CMake, contrib and CI files, which the build
never reads. The next commit applies the fix and wires it in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`BarrelBlockEntity.recheckOpen` loses its "unsure": there is no barrel lid
in the tree at all (`containers.rs` holds its 27 slots), so nothing can
recheck one, while the golem openers a chest rechecks are rechecked.
`UpgradeData` names both halves: the datafix carries the tag
(`datafix/chunk_leaves.rs` writes its Sides bits) and no chunk load applies
the block upgrades it describes.
Four `missing` rows with "unsure" notes have answers in the tree:
- `StemBlock.getCloneItemStack`: `block_items::clone_item` returns the seed
for a pumpkin or melon stem, attached or not, and has a test for it.
- `WallHangingSignBlock.isHittingEditableSide` and
`...shouldTryToChainAnotherHangingSign`: `block_use_item_on` passes a
hanging sign used on a face other than the sign's own and its opposite,
which is the editable-side test and the chaining that follows from it.
- `RandomizableContainerBlockEntity.canOpen` is the negative of
`Game::container_locked_for`: a spectator opens any container, otherwise
the lock decides.
The four game-test rows become skip:debug, as the client2 maps already have
the game-test renderer and the highlight packet: a test block, its instance
block and their block entities are developer tooling, Horizon keeps their
NBT and runs no test runner.
The note now says what the function does and when the game skips it:
drop_frame_items calls remove_framed_map unless the drops are off and a player
caused the break, which is the null causedBy vanilla gates it on.
The row pointed at lan_map_updates and explained that a removed frame needs no
step because markers are rebuilt each tick. maps.rs::Game::remove_framed_map is
the step: its doc opens with `ItemFrame.removeFramedMap`, and it drops the
frame's decoration and its entry from the map's frames.
Nine item predicate rows were left as "`advancements.rs::item` ignores this
key". A scan of the pinned data settles what that costs: across the 1866
advancements and the 1447 loot tables, the only item predicates used are
`minecraft:enchantments` (20 in loot tables, 1 in an advancement),
`minecraft:map_id` (18, all loot tables) and `minecraft:jukebox_playable`
(1, adventure/play_jukebox_in_meadows). Eight of the nine rows name a
predicate no shipped data asks for, and the ninth is that jukebox one, where
the ignored predicate is the only test of its `match_tool`, so any item
passes it.
`DataComponentPatch.forget` now names the function its note is about,
`block_entity_item.rs::place`, which merges the item's explicit components
over the block entity's implicit ones with no strip-the-implicit pass.
Three rows were filed `missing` with "unsure". Each has an answer in the
tree:
- `SectionPos.aroundChunk` is the chunk window `pois_in_range` sweeps
(`radius/16 + 1` chunks each way); only the pre-load half of the vanilla
use is absent, since `chunk_pois` returns for a chunk that is not loaded.
- `DataComponentLookup.findAll` is how `DyeColor.findColorMixInRecipes`
reaches the two-dye recipe; `dye_color_mix_in_recipes` builds that grid and
asks the recipe manager instead of looking recipes up by their components.
- `DataComponentLookup.ComponentStorage.findMatching` has no counterpart: a
spawn egg's type is its own id with `_spawn_egg` trimmed
(`creatures.rs::Game::spawn_egg_allowed`).
The row left it undecided which registries count as networkable. It is decided
in lan/data.rs: SYNCHRONIZED holds 32 registries in
RegistryDataLoader.SYNCHRONIZED_REGISTRIES' order, and registry_packets packs
each one, so the packer and the list agree by construction.
The row filed the component's codec as Java plumbing, but hash.rs::Component::code
opens with `TypedDataComponent.encodeValue(hashOps)` and is that codec per
component kind, down to the two kinds that have none.
`Axis.rotateDegrees` said only that call sites convert; it now names what
they convert for, `math.rs::quaternion_from_axis_angle`, which takes the
angle in radians.
`MatrixUtil.mulComponentWise` keeps its `missing` but now names the one
method that reaches it, `ItemFeatureRenderer.computeFoilDecalPose` (the 0.5
GUI and 0.75 first-person foil decal matrix), and says what the tree has
instead: the SVD half of `MatrixUtil` in `math.rs`, a glint scale per context
in `item_render.rs`, and no component-wise matrix product anywhere.
`SystemReport.<init>` gains the function its rows are built in,
`crash.rs::system_details`, and with it what the report carries before the
gap: Horizon and Minecraft version, operating system, CPU, memory, then the
game's own details.
Twelve rows of `MatrixUtil` and `GivensParameters` were filed as skip:other
("SVD split helper") because Horizon was thought to keep item and display
transforms as plain matrices. `crates/horizon-assets/src/math.rs` has the
whole split: `Givens::unnormalized`, `quarter_pi`, `cos`, `sin`, `inverse`
and the per-axis `quaternion`/`matrix` builders, then `approx_givens`,
`qr_givens`, `step_jacobi` and `svd_decompose`, the last five cited against
the vanilla methods they port.
`fromPositiveAngle` is inline: only its pi/4 case is built, as
`MatrixUtil.PI_4`. `aroundX/Y/Z` are three axes of one builder;
`similarityTransform` is the `R^T * m * R` inside `step_jacobi`; and
`eigenvalueJacobi` is the five-pass loop in `svd_decompose`.
Two rows that had none: `GivensParameters.sin` (`Givens::sin`) and
`GivensParameters.inverse` (`Givens::inverse`).
🤖 Agent: queued at 63a9c0afb8. The old pre-1.7 user lists being unsupported is fine to record as missing; the dedicated server reads only the JSON lists. TSV only.
🤖 Agent: queued at 94aa0d9699. Opt::set_slider for SliderableEnum next/previous is right: the preset is a slider, so stepping goes through it. All nine citations resolve. TSV only.
🤖 Agent: queued at 5224233ca6. ime.rs ime_preedit/sync_ime and preedit.rs set_preedit all exist; 'inline' fits submitPreeditEvent since the preedit goes straight to the field. TSV only.
🤖 Agent: queued at 4e45758836. All eleven repaired citations resolve on main, including the moved render-crate paths (gfx/oit.rs, post_chain.rs). TSV only.
BanListEntry.<init> was the one method of a class this batch maps that the
file never answered, so the ledger still counted it missing. Both vanilla
constructors land in BanEntry:🆕 a missing source becomes (Unknown), and
the createdAt argument is not taken but stamped with now, with a file's stored
date kept by fields() on the read path.
`PackFormat.fullCodec` was filed as Java plumbing, but packs.rs::format_codec
is that codec: an int, or a list of 1 to 256 non-negative ints whose second is
the minor. `PackFormat.lastPreMinorVersion` pointed at the function that is the
port, so it reads `port`, not `inline`, as its four IntermediaryFormat
neighbours already do.
🤖 Agent: queued at 901cec2b31. All five repaired citations point at fns in mobs/render.rs that exist on main (enderman_render_offset, species_scale, base_transform_at). TSV only.
Propagate settings persistence errors to CLI and MCP, show a shared GUI notice with retry, keep old settings intact on failure and serialize same-process saves. Skip pruning after a failed selection save and preserve completed instance deletion state.
Fixes#7274
Co-Authored-By: Codex <[email protected]>
🤖 Agent: queued at 2dabb5f1f7. Each loot row names the missing loot.rs arm (set_writable_book_pages, entity_scores) and notes no vanilla table uses it, which sets the priority. TSV only.
🤖 Agent: queued at 8da05d30cc. The POI rows explain the structural reason (claims are per-mob fields, no world POI store), which is more useful than 'unsure'. TSV only, 1 citation checked.
🤖 Agent: queued at b56d376315. The projectile rows now say what is missing instead of citing fns that aren't there; applyImplicitComponent reaching only bucket mobs matches entity_nbt::bucket_components. TSV only.
lan_note_joined described every entity of the level again whenever the id
counter moved; lan_entities now takes an id floor, the counter the states
were taken at, and skips every entity at or below it. Its time is a span of
its own (lan_note_joined) in HORIZON_SPIKES and horizon-report.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
"the /item modify arm toggle_tooltips" read as if toggle_tooltips were a
function; it is the modifier id the match arm in cmd_item_modify is keyed by,
and that function is what the row's next line now points at.
The class row left the class's only method unanswered, so the ledger still
counted ToggleTooltips.run as missing. The arm that answers it is
`minecraft:toggle_tooltips` in cmd_item_modify (game/commands/items.rs:583),
which adds every false toggle to the tooltip_display component's
hidden_components and carries hide_tooltip through; the loot run has no arm,
so a loot table that names it is ignored, and no 26.3 loot table does.
🤖 Agent: queued at 68044bf45a. from_biomes writes into a growing buffer, so 'inline' for calculateChunkSize is accurate: there is no size pass to port. TSV only.
🤖 Agent: queued at 1dd0bf6b6e. creatures.rs::Game::dye_mix exists on main and picks the recipe's mix or one of the two at random, as getMixedColor does. The onDestroyed rows name a real player-visible gap: a burnt shulker box item spills nothing. TSV only, all 5 citations checked.
`TagPredicate.matches` pointed at `id_matches`, which matches an id or a tag
for items and entities and has no `expected` flag; the method's only caller
is `DamageSourcePredicate.matches`, and `damage_source` is where the flag is
honoured: each damage type's tag compared against it.
`SingleComponentItemPredicate.matches` said other components are ignored.
It now names one the shipped data asks for: `minecraft:jukebox_playable` in
adventure/play_jukebox_in_meadows, where the item predicate has nothing else
to test, so any tool passes it.
`LightningBoltPredicate.matches` said `blocks_set_on_fire` was checked
against 0 and the trigger never fired. Both hold no longer: the arm compares
the bolt's own count, which `game/lightning.rs` fills from the fires it set,
and `game/advancements.rs::trigger_lightning_strike` fires the trigger.
`CubeMobPredicate`, `RaiderPredicate` and `SheepPredicate` were filed as
missing with the note that entity() reads no such key. The advancement
reader indeed has no arm (`_ => false`, so a predicate asking for one never
matches), but the loot reader shares the vanilla class and does:
`loot::Context::entity_matches` carries `minecraft:type_specific/cube_mob`
(size bounds against the mob's size), `.../raider` (captain, no raid here)
and `.../sheep` (the sheared flag).
🤖 Agent: queued at 068a20e36f. Without this, every entity_properties condition in a carried loot table was silently false, so burning-mob and sheep-colour drops never happened. The rename covers both entity_properties and damage_source_properties. Merged with main: port-pack clippy is clean and 106 tests pass.
The eleven `Ease` rows said only that named easings come through the
`EasingType` registry. They now name what reads a timeline's ease
(`environment.rs::Environment::load_dimension`, which knows constant and
cubic_bezier) and what the shipped data asks for: `day.json`,
`early_game.json`, `moon.json` and `villager_schedule.json` use constant,
cubic_bezier or no ease at all, so no vanilla timeline names one.
`FormattedCharSequence.backward` was skipped as having no caller; its
caller is `SubStringSource.substring`, which is skip:ui here already.
Vanilla's `CubicBezierControls.validate` and the two `Crypt` PEM writers
name where their behaviour would sit: the loader that takes any control
point, and the key-pair reader that never writes one back.
`FileUtil.isPathPortable` was filed as skip:other for having no caller in
26.3, but Horizon carries the check: `structure_blocks::portable_path`
(refused path under `structures/`) tests the same empty, `.` and `..`
segments, and says so in its doc.
`FormattedCharSequence.decorateOutput` was filed as missing for a
right-to-left layout Horizon was said not to have. `bidi.rs` is that layout:
`reorder` shapes, reorders and mirrors the characters of a
right-to-left run, which is the mirror `decorateOutput` wraps a sink in.
🤖 Agent: queued at 1884ed60db. The fallback order is the right one: a parsed mods.toml naming a real id wins over an unfilled neoforge.mods.toml template, and mods_toml_lines only runs when nothing parses, so jars that load today read the same. Merged with main: port-pack clippy is clean and 106 tests pass.
`a_horse_or_llama_spawn_egg_picks_its_variant_component` runs an egg with a
`minecraft:horse/variant` over a black horse drawn with a white field: the
coat becomes DARK_BROWN and the markings stay 2, the mob reports the
component back, and its save keeps the packed `Variant` int (518) with no
`variant` name. A `minecraft:llama/variant` of gray sets a llama's 3 and a
trader llama's too.
`a_horse_and_a_llama_report_their_variant_component` walks every coat of
both enums through `species_variant_component`, `variant_index` and
`variant_name` with no vanilla data, and pins which species save the name
under `variant`.
`Horse.applyImplicitComponent` and `Llama.applyImplicitComponent` (which a
trader llama inherits) `setVariant` from `minecraft:horse/variant` and
`minecraft:llama/variant`. `entity_nbt::apply_item_components` knew the
variant components of chickens, cows, pigs, zombie nautiluses, mooshrooms,
sheep, axolotls, salmon, tropical fish, foxes and frogs, but not those two,
so an egg or item carrying one dropped it and the mob kept the coat its
spawn picked.
The component holds a plain enum name, not a holder: `Horse.Variant` is
white 0 to dark_brown 6 (`dark_brown` in the component, `darkbrown` in the
texture name `COLORS` carries) and `Llama.Variant` is creamy 0 to gray 3.
`finalize::variant_index` and `variant_name` had no entry for either, so
the name fell through the frog list and read as temperate, i.e. red.
`Horse.setVariant` writes `variant.getId() & 0xFF | this.getTypeVariant() &
-256`: the markings the spawn drew are the high byte and stay. Horizon keeps
them in `extra.variant2` and the arm touches only `extra.variant`.
`Horse.get` and `Llama.get` report the component too, from
`species_variant_component`. That function also gates the save's `variant`
key, which is `VariantUtils.writeVariant`'s and only the seven classes that
call it write — a wolf, a cat, a frog, a pig, a cow, a chicken and a zombie
nautilus. So both the write and the read of that key now go through
`finalize::saves_variant_name`: a horse and a llama keep their packed
`Variant` int and gain no `variant` name, and a mooshroom stops being read
from a key its save does not carry either.
Corrects the five ledger rows these gaps sat on (game-011.tsv).
🤖 Agent: queued at 6cd307abf8. 'home' for ExperimentalRedstoneWireEvaluator points at the file holding Evaluator, which is accurate, and Level::read_summary does read enabled_features through read_root. TSV only.
🤖 Agent: queued at 26438b9128. Kinetic::damage_use_duration exists on main and is what SpearUseGoal reads, so the port row is right. The smithing display rows now say why they are missing (Book::build builds no smithing entry) instead of 'unsure'. TSV only; every cited fn checked against main.
Eleven rows now name what the tree has instead: the server reads
`ops.json`, `whitelist.json` and `banned-players.json` from
`game/dedicated.rs` and nothing reads the pre-1.7 text lists, and the only
feature list anything reads is the world's own `enabled_features` (the game
rules, the datapack function filter and the command dispatcher), never a
pack's `feature_flags`.
Nine where cells held a bare file, a + pair, a const name or a path the fn
does not live under, so map-check reported no fn for them. They now name
Opt::set_slider, sampler_anisotropy, VideoMode::write,
OptionsFile::model_customisation, is_quit_shortcut_down,
PreeditEvent::from_sdl_text_editing, PreeditEvent::to_formatted_text and
Game::remote_ride. Row 16's stale note (a monitor index in options.txt) is
corrected with its citation: the file holds VideoMode::write's mode JSON.
Verdicts are unchanged.
map-check tools/port_map/rest-004.tsv: 148 rows, no problems.
Four where cells pointed at game.rs for fns that live in game/ime.rs, or
held prose or a line -> fn arrow, so map-check reported no fn for them.
They now name Game::ime_preedit, preedit::set_preedit, Game::sync_ime and
Title::drop_file. Verdicts and notes are unchanged. The file's remaining
line 76 (Minecraft.run -> game.rs::run) is outside this repair and stays.
map-check tools/port_map/rest-003.tsv: the four rows above are gone from
the output; line 76 still reports no fn.
Eleven where cells held a bare file, a file:line form, a + pair, a slash
pair or a const name, so map-check reported no fn for them. They now name
Oit::render_clouds, map_arm_poses, PostEffects::apply (load, run and the
frame's depth), Game::tick_world_icon, Renderer::draw_frame, Oit::run,
Oit::execute_depth_bounds_cull, Oit::execute_water_mask and
Game::transient_block_geometry. Verdicts and notes are unchanged.
map-check tools/port_map/client2-001.tsv: 150 rows, no problems.
A filled bucket that the clicked block refuses (a sneak-click on a
slab) pours into the block beside the clicked face, which
`native_fill_bucket` and `native_bucket_empty` never saw, so a claim
mod could be bypassed. Both now hear that block too when it isn't the
target, as vanilla's `mayUseItemAt(blockPos2)` checks it next to
`mayInteract(blockPos)`. Powder snow places like a block and never
pours beside, so it isn't asked twice.
Follow-up to #6783, which merged before this commit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`advancements.rs::item` reads the item ids, the count, the `potion_contents`
and `damage` components and the `enchantments` and `damage` predicates. Every
other predicate key a criterion can carry is ignored, so those criteria match
too broadly; the rows now name the function and say which keys it does read
instead of asking.
Five where cells held a bare file, a parenthetical branch note or a
slash-separated pair, so map-check reported no fn for them. Each row now
names the single fn that implements the method: enderman_render_offset,
base_transform_at, species_scale, pufferfish_bob and
Projectile::arrow_effect_color. Verdicts and notes are unchanged.
map-check tools/port_map/client-004.tsv: 149 rows, no problems.
🤖 Agent: queued at acf6b8b8e9. All six port rows cite fns that exist on main (piglin.rs update_activity, retreat_from_nearest, set_avoid_target, piglins_outnumber_hoglins, zoglin_step, witch_magic_particles). Strider.canAddPassenger missing the eye-in-lava refusal is a concrete gap. TSV only.
🤖 Agent: queued at 3e2aed287a. riders.rs::start_riding exists on main. The warden anger rows (AngerManagement.codec, add/readAdditionalSaveData) correctly flag that persistent_anger leaves the warden out: a reloaded warden is calm, a player-visible gap worth an issue. TSV only.
🤖 Agent: queued at 3c9563383f. multiplayer.rs Screen::confirm handles Enter for both the saved and the LAN entries, and Screen::arrow does the Shift+Up/Down swap; both rows check out on main. TSV only.
🤖 Agent: queued at b597fa981e. keep_chat_draft and open_chat_method are on main (ChatDraft and shouldDiscardDraft landed earlier), so both ChatComponent rows are ported. TSV only.
Each of these classes is a condition, modifier or number provider the loot
run in `crates/horizon-world/src/loot.rs` does not have an arm for. The notes
used to say only that no vanilla data uses the type; they now name the
dispatch and, for the number providers, the kinds `level_value`, `int` and
`float` do handle, so the next reader knows the shape a datapack would need.
🤖 Agent: queued at f0bdb96bb1. lan_sign_update re-checks playerIsTooFarAwayToEdit on each write, so 'inline' fits SignBlockEntity.tick. The note that a guest who walks away keeps sign_editing until its next write is a real gap worth an issue. TSV only.
🤖 Agent: queued at 9a400530fe. select_create_tab, tab_bar_key and create_world::next_tab_index all exist on main, so the three TabNavigationBar rows move from gap/missing to port correctly. TSV only.
Thirteen rows now say what the tree holds instead of asking: the attach face
a shulker keeps on boarding, the `SHULKER_BOX` move with no counterpart, the
absence of a defense target goal beside `HurtByTarget`'s alert, the ravager
whose immobility reaches no target goal, and the dropped item that is not an
addressable slot.
🤖 Agent: queued at b118744829. The four living-hook fragments that landed after this branch (#6299's drown/fall/heal/jump) are now stamped too, so main ends at API 359 with no pending fragment. xtask mod-api --check, test_stamp_native_hooks and the 612 Python tests pass; client clippy and the native tests pass on playhorizon (b1187448). It races every hook PR, so it goes in now.
Ten notes now point at the code that decides them, and two named a
`load_bucket_data` that is not in the tree: the implicit components come from
`entity_nbt::bucket_components` only. Villager claims are per-mob fields with
no point-of-interest store, the riptide spin keeps its damage and ticks but
not its stack, and nothing in `motion.rs` asks the food level before adding
exhaustion.
`MotionWorld::can_fit_pose` is the test: the pose's dimensions at the
player's position, deflated by 1e-7, against the player's collision, and
`update_player_pose` asks it for swimming, the desired pose and crouching in
the order `updatePlayerPose` does.
Five notes named code that no longer exists: the implicit-component path is
`entity_nbt::bucket_components` (a mob coming out of a bucket), not a
`load_bucket_data`. The rest now point at the writer that leaves a kind out
(`saved_with_chunk` and the shulker bullet), the wind charge's own target
list, and the pearl teleport that asks the owner nothing.
Style.shadow_color holds the colour as packed ARGB, component::shadow_color
reads it from the JSON either as a packed int or as a normalized RGBA array,
Style::inherit carries it down and the style is written back out with it, so a
component's shadow_color is no longer dropped.
ChunkBiomeData::from_biomes writes the column's biome containers in section
order into the packet's buffer and documents itself as extractChunkData, and
the buffer grows as it writes, so no size is computed the way
calculateChunkSize computes it. Cite the writer for both rows.
The eight rows left as gaps now name the missing side: nothing implements
`onDestroyed` for a block item, a bundle or the shared container spill, the
`mob_visibility` component is parsed but never read by targeting, no item
converts on break, and a stack's `damage_type` is only used for the
enchantment damage tags.
The breeding dye mix, the adventure-mode `can_break` and `can_place_on` test,
the map used on a banner and the song a jukebox loads with all exist:
`creatures.rs::dye_mix` and `dye_color_mix_in_recipes` colour offspring,
`game_mode.rs::adventure_predicate` reads the predicates, `maps.rs::
use_map_on_banner` toggles the marker, and `save.rs::decode_block_entity`
restores a jukebox's record and `ticks_since_song_started`.
Level::read_summary is LevelStorageSource.readLevelSummary, and read_root
unpacks WorldDataConfiguration's enabled_features from the level.dat it reads,
so the flags are parsed from the summary as vanilla parses them (omitted when
they are vanilla's default). The world list still draws no experimental marker
from them, which the row now carries as its gap.
A class row takes skip:<reason>, missing or home, so the port verdict on it was
rejected by the ledger ("a class row takes skip:<reason>, missing or home") and
the row never reached the map. Say where the class lives instead: Evaluator in
level/redstone/experimental.rs is its two queues and the wires it found with
their orientation, driven by Sim::experimental_wire_update.
The eight rows still missing now point at what stands in their place: the
portal test with no heart check, the pathfinder that never reads the home,
`mobs/breeze.rs` with no particle call, the boarding test with no eye-in-lava
rule and the cube size no spawn or data update applies.
`mobs/piglin.rs` carries the piglin and hoglin AI these rows were called
missing for: `update_activity` (the activity voice, `hoglin_sound` for the
angry and retreat grunts), `retreat_from_nearest`, `set_avoid_target`,
`piglins_outnumber_hoglins` and `zoglin_step`. `witch_magic_particles` is
`Witch.handleEntityEvent(15)`, wired to every witch entity event.
🤖 Agent: eyes layers take the GPU path only with improved transparency off (EntityBatch routes OIT quads by CPU vertices); FLAT jobs zero the normal and carry the OIT + EYES_TIER bits in the overlay word, byte-equal in the every-mob test for spider, cave_spider, enderman, phantom, copper_golem.
The fourteen gaps left in this batch now name the code that decides them:
the rule reaching the mob world while nothing in `mobs/piglin.rs` reads it,
`persistent_anger` leaving the warden out so its anger is never written, the
add-entity payload carrying no warden data, and the Johnny goal testing the
custom name rather than a saved flag.
Screen::confirm is ServerSelectionList.OnlineServerEntry.keyPressed and
NetworkServerEntry.keyPressed: Enter joins the selected entry, a saved server
or, past the saved servers, the LAN game entry_address picks out of the
scanner, exactly as a click on the row does. The Shift+Up/Down swap beside it
is Screen::arrow, which moves the saved server and writes the list back.
The two-mob boarding in `mobs/riders.rs` carries the piglin case: a baby
piglin that boards a hoglin goes on top of the piglins already stacked there
(`top_passenger`, three at most), after the same tests vanilla makes.
open_chat_method is ChatComponent.createScreen: it restores the kept draft
when it can (a command draft only for /), opens the box with the tab's prefix
otherwise and records whether the box started on a draft, so the row names it
rather than open_chat and no longer claims no draft is restored.
keep_chat_draft is ChatComponent.saveAsDraft: the text left in the box is
kept as the draft, with its command flag, unless the screen was closed on
purpose with Save Chat Drafts off or the message was sent, and blank text
discards it. The Save Chat Drafts option is read from options.txt
(saveChatDrafts, default false) and shown as Opt::SaveChatDrafts, so the row
no longer says the option does nothing.
ThrownTrident::selector_target returned none, so @e, /kill, /data get and
the other entity commands could not find a thrown trident. Add a
Target::Trident, hold its UUID and saved data by entity id like a rocket,
and read, keep and write NoGravity (AbstractArrow.applyGravity skips the pull
for a NoGravity entity); before, a NoGravity trident still fell.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Fourteen rows lose their "unsure" and name the code that decides the gap
instead: the chunk fix-up absent from `decode_block_entity`, the copper
golems' opener recheck standing in for a chest's, the shulker lid tick
counting neighbour updates both tick paths drop, and the sign text kept as
literal strings so nothing resolves a selector.
The per-tick clearing of a sign's edit permission is the write path here:
`lan_sign_update` re-checks `playerIsTooFarAwayToEdit` on every update and
drops the guest's `sign_editing` when it is out of reach, so `tick` has no
counterpart of its own and the row is an inline.
🤖 Agent: queued at 5dedbb4ccb. Your own main merge (5dedbb4) matches the conflict resolution I tested (gamerule ids 200-208 after #7228's max-players ids). With #7249's heartbeat on top, horizon-server clippy is clean and its 120 tests pass. run-parity is on because this changes the management harness.
select_create_tab sets the open tab by index and clears the row's focus, and
both the click path and the Ctrl+number path go through it, so the row names
it rather than the click handler and no longer claims a tab can only be
picked with the mouse.
The bar's key handling and its index math are in the tree now:
Title::tab_bar_key gates on Ctrl, on the rules and experiments screens not
holding the keys and on no world preparing, then clamps the index from
create_world::next_tab_index, where a digit is floorMod(key - 1, 10) and Tab
steps one tab forward or back, wrapping. Cite them instead of calling the
rows missing.
Spiders, cave spiders, endermen, phantoms, the copper golem and a
creaking with lit eyes went to the CPU path whole, body included: their
eyes layer (the eyes render type) is translucent, and the GPU path took
only plain opaque and cutout draws. With improved transparency off (the
default), those layers are ordinary entity quads, so they now join the
mob's skinned draw:
- a skin job's mode (was `item`) gains FLAT: the eyes layer's normal is
all zero, as the CPU path writes it;
- the OIT tag and the eyes' HDR tier ride in the job's overlay word,
the same bits the CPU path ORs into the vertex.
With improved transparency on, EntityBatch sorts those quads into its
stages by the CPU vertices' tag, so the mobs stay on the CPU path then
(`Renderer::skins_eyes`).
The every-mob test runs twice, eyes off and on: with eyes on, the five
mobs above are posed, byte-equal to the CPU path. The shader test
covers a FLAT job. Adds stress_eyes (1000 spiders, cave spiders and
endermen).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 428e9d94d1. I fixed the pack_post test for #7203's new `minimums` argument to `Buffers::new` after merging main (428e9d94). Programs whose image.* is undeclared now read a zero image instead of being skipped, which matches what packs expect; render clippy and tests pass.
🤖 Agent: queued at fa7c5b0a7b. `read_properties` gives real_packs and the game one path to the properties and features, so the test now compiles packs with the macros players get (it used to miss the renderer's and IRIS_FEATURE_* defines). Main merged in (fa7c5b0a); tests pass.
🤖 Agent: queued at bf7fbcb7eb. The emission table is built once per pack load (one pass over the block states), and the sprite-layer table grows from 2 to 3 ints a layer, so terrain pays one extra storage read per vertex only when a pack is on. Main merged in (bf7fbcb7); clippy and the shader_packs tests pass.
The seven rows left as gaps for the recipe book now say what the code holds
instead of "unsure": the book's entries come from shaped, shapeless, furnace
and special recipes only, so a smithing recipe gets a display count and a
station and no slots; a synced `SlotDisplay::Composite` is parsed but never
resolved, the book's displays hold item ids rather than stacks, remainders
stay in `Recipes::remainders` for play, and no recipe display reads the
level's enabled_features.
The spear's use duration (the delay plus the damage window) is
`Kinetic::damage_use_duration`, which `SpearUseGoal` and `SpearAttack` read
through `mobs/spear_use.rs`. `Ingredient.getSingleItem` is only asked for by
`BrewingRecipeBuilder.defaultId`, so it goes to the datagen skip with the
recipes Horizon reads from the JSON tables.
🤖 Agent: queued at 73f3e0e888. cloudTime reuses the clouds' own drift (now one shared `clouds::drift`), so the uniform and the drawn clouds cannot disagree; one rem_euclid a frame. Main merged in (73f3e0e8), fmt, clippy and the pack_uniforms tests pass on playhorizon.
WorldDrawn::spans key entity runs by type, so a mod following entities from
frame to frame (a motion blur's motion vectors) could only match them by draw
order, and paired one pig's vertices with another's when one spawned, died or
was culled. WorldDrawn::motion passes on Frame::entity_motion, which the game
already records every frame for frame generation: each mob's, player's and
dropped item's vertices tagged with its entity id. WorldDrawn is
#[non_exhaustive], so no mod breaks; an API fragment numbers it.
Closes#7280
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`--template native [--horizon <commit>]` writes a trait mod's bundle: its
horizon-mixin.toml, pinned to that Horizon commit or left unpinned, and an
[api] crate whose hooks() greets each player who joins. The files are
what docs/api/mod-files.md lets a bundle carry, so the launcher builds the
folder as it is and packs it into an .hnmod; a test packs it that way and
runs the registry's check on it.
Holder::Entity refused experience orbs, projectiles, tridents, falling
blocks, fireworks, primed TNT, End crystals, the dragon and ominous item
spawners: they were saved through the memory found again by type and
position, with a new UUID once they moved, so attachments could not be
found under it. Every entity now keeps the UUID it was loaded with (or
was given) by entity id, reads its attachments back on load, and the
holder check walks entity_lists!, so a new kind holds attachments too.
The entity memory's pruning walks the same lists: it no longer drops the
UUIDs of rockets, falling blocks and tridents at each autosave.
🤖 Agent: queued at 171085bea3. Living heal/fall/jump/drown hooks: they fire on events (a heal, a landing, air at 0, a jump), not per entity per tick, through one indirect call over an empty mod list. Mob::heal now clamps to attrs.max_health and skips a dead mob, as LivingEntity.heal/setHealth do (a few call sites used kind.max_health before). Re-merged after #7005; clippy and heal tests pass.
🤖 Agent: queued at 507f93d5d3. Attachment sync: nothing is queued or sent unless a mod is loaded (hub.sync_attachments) and the client is a Horizon client, and attachment_map records a holder only when a mod borrows it, so the per-tick flush over empty queues costs nothing without mods. Merged main; clippy, the attachment tests and mod-api pass, and current main adds no hook drift.
Calculate neighbour-dependent state before comparing or placing a setblock input, then restore explicitly supplied properties. Preserve strict mode and cover the command cases in the vanilla corpus.
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
🤖 Agent: queued at 35bdb3d076. Activity and heartbeat notifications: a login records one cheap event and the server tells notification/server/activity at most once per 30 s (activity_due is tested at the 29/30/59/60 s edges); the heartbeat thread builds a status only while a management socket is open, and status-heartbeat-interval 0 starts no thread. horizon-server clippy and 120 tests pass merged with main.
🤖 Agent: queued at aa73ebe5e1. Boat current e2e: runs in a mkdtemp folder as the game's cwd (where saves go) with lab.game_env's scratch homes and removes it unless --keep, so no player data is touched; tools-only, not wired into CI. Small nit for a follow-up: game_env(home=work) only sets an unused env var called home, since game_env makes its own scratch HORIZON_HOME.
🤖 Agent: queued at fed0ff53ae. Server docs: the hosting guide lists kick/ban/pardon/ban-ip/pardon-ip as their own horizon-server commands and drops them from 'Coming', and server.md says init writes [panel], [api] and [discord] and that an update always backs up first, matching what ships.
🤖 Agent: queued at 118bdbf9a5. textureQueryLod: an hz_textureQueryLod helper per sampler kind and dimension is emitted only when a stage calls it (the plain-program test checks it's absent otherwise), and the test validates the result through naga down to SPIR-V.
🤖 Agent: queued at 56f749e836. Interface blocks: a redeclared gl_PerVertex is blanked, and an instance name that a local or parameter shadows (or that follows a .) keeps its own uses, so only the block's references are flattened; load-time token work, crate tests and clippy pass merged with main.
🤖 Agent: queued at 6c52fb99bc. Work group sizes as constant expressions: each local_size_* expression up to its , or ) at depth 0 is evaluated, rewritten to the number with its newlines kept, and gl_WorkGroupSize uses the same values; a size it can't work out is still an error with the axis named.
🤖 Agent: queued at 5e2c8f5641. Anonymous structs: a struct keyword followed by { gets hz_structN before naga sees it, for draw and compute stages, and returns None when nothing changed so the code isn't copied; the shared-struct compute test compiles through translate_compute.
🤖 Agent: queued at ceb77a5167. #version from an included header: the first #version read wins (the depth==0 condition is gone), so a pack whose stages include a common prelude gets GLSL 4.60 keywords such as shared; tested through translate_compute, and the crate passes merged with main.
🤖 Agent: queued at 8cf2eaf48d. Unbalanced conditionals: a stray #endif is ignored and an open #if reads to the end of the file, both as warnings instead of failing the program; preprocessing runs at pack load, nothing per frame. horizon-shaders clippy and tests pass merged with main.
🤖 Agent: queued at f90ca6390d. Panic attribution: with no mods the dispatch walks an empty slice, and with mods it adds one thread-local swap per mod call; the guard puts the outer call back when a loop exits early or a panic unwinds, and nothing is caught. I regenerated the dispatch after #7005 landed, so its 27 new hooks name the mod too (f90ca6390); clippy and the mod_api tests pass.
entity_properties and damage_source_properties predicates get 26.3's
sub-predicate keys (minecraft:entity_type, minecraft:flags,
minecraft:equipment, minecraft:vehicle..., type_specific as
minecraft:type_specific/<kind>, variants and sheep colour as
components): the game's loot code reads only those, so every such
condition of a carried mod table said no. A predicate already in 26.3's
shape is left as it is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A Forge jar may carry the other loader's metadata as an unfilled template
(Drip Sounds: [[dependencies.${id}]]): the first file that parses and names
a real mod id is the jar's; when none parses, the first [[mods]] is read
line by line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: past 4096 changed cells, edited columns go to worker light jobs; the stale-job risk is covered by the sequence check in the Lit handler, and light hashes after fill/clear match main.
Normalize entity type identifiers and place every configured passenger before loading a spawner entity stack. Cover namespace resolution and actual passenger selection and vehicle relations.
Co-Authored-By: Codex <[email protected]>
Run ageable updates outside server AI while preserving growth hooks and age locking. Cover age signs, AI modes, and locks in the mob regression and parity harness.
Co-Authored-By: Codex <[email protected]>
🤖 Agent: queued at f622f09367. Fishing hook: levzzz5154's blockers are fixed at this head. The swept hit test inflates each target by computeMargin ((tickCount-2)/20 clamped to 0..0.3, counted after the tick's increment as vanilla's super.tick does), a path starting inside a box no longer hits (AABB.clip), and tests cover a hit inside the margin, a miss outside it and the inside start; the author re-ran lan_e2e fishing and fishing-item. Merged main; clippy and the fishing tests pass.
🤖 Agent: queued at 55d24da573. Live max players: merged with main's allowlist settings (#7191), keeping both handlers; the parity test's max-players block moved to ids 100-108 and its restart read to 109, after the allowlist ones, since 75-94 are taken now. horizon-server (118 tests, clippy) and dedicated client tests pass; run-parity added for the management harness.
The merged hook PRs left 202 fragments without numbers, so nothing in them is
API yet. This is the stamp pass the fragment README asks for after they merge:
one number per fragment in first-parent order, its catalog entry, its doc
paragraph and its comment marker, with the fragments consumed and the host
regenerated.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
mod_content::link mirrors the base resource root into the content root: a
symlink per entry on Unix, but a copy of every file on Windows, where symlinks
need a privilege. That is ~28,000 files and 0.6 GiB written again on every
rebuild, and the root is rebuilt after every update because its stamp holds
the executable.
A hard link needs no privilege on NTFS and costs a name, not the bytes. It
shares the base file's data, which is safe here because the root is only ever
replaced name by name (Overlay::write removes the name before it writes) and
nothing else writes into it. Where a link cannot be made (another volume, a
filesystem without links, the NTFS limit on links to one file) the file is
copied as before, and a symlink in the base is still copied through. The
resource-pack overlay's link has done the same since 3c7e4ba57.
Replaying the file operations of a 76-mod pack's rebuild (NVMe, Defender on),
the base step took about 24 s as copies and about 9 s as links. The same
build() on a prepared root with one small mod took 19-20 s with copies and
11-12 s with links.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
server_e2e: dialog_close now uses main's frame proxy (renamed PacketProxy,
with close_container through its send_packet) instead of a second proxy
class; the server runs with the proxy for book_menu or dialog_close.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: armour-stand items go into the stand's already-borrowed Skin (new skin_item) instead of submit_item_skinned, which would re-borrow skin_frame and panic; frames get their own skinned draw. Skulls/maps/glint stay on CPU.
🤖 Agent: queued at a7c3be8ebd. Mod image: the 1024² slot is packed only when a native mod is loaded (mods().is_empty() read before the staging scope), so a game without mods keeps today's HUD atlas, with a test for both cases; the fragment no longer lists native_hooks.rs and the docs in comments, which the stamp tool refuses. This head's tree is the one I checked (clippy render+client, mod_image tests, mod-api, stamp).
🤖 Agent: queued at d0248722b6. 27 hooks: with no mod loaded the hub queues no Input/Sneak events (mods_loaded set before each poll), and the Input, Sneak, Kicked, jump, projectile and release-use handlers return before any name clone or as_guest swap; the hopper's per-slot lookup sits behind the same check. Dyna's six findings are in (orb level-ups, one fire per /experience add, composter through inventory_move_item, real cart coordinates, kicks after Left, stampable markers).
Use the collect contract so mods see and change the calculated velocity
(replace generated code that did not compile), fire it for the ender
dragon and for creative players flying, and restore the lost comments.
🤖 Agent: queued at 5bc55bc5b5. Rarity and lore: the JSON-in-a-string branches are gone, so a renamed [1] stays the literal name as ComponentSerialization decodes a string tag, and guest.rs sends a string lore as one literal line like the tooltip draws it; rarity round-trips as Int(3) ↔ "epic". Only leniency left: a single compound or string lore (not a list) shows as one line where vanilla's list codec would refuse it, which only affects malformed data.
Main's allowlist settings (#7191) took management test ids 75-94; the max
players block moves to 100-108 and its restart read to 109, after the
allowlist ones. Both sides of the setting handlers are kept.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ManagementServer.scheduleHeartbeat runs NotificationService.statusHeartbeat with scheduleAtFixedRate
(interval, interval) in seconds, and nothing when status-heartbeat-interval is 0; the notification
carries ServerStateService.status. The supervisor, which serves the protocol, runs the same timer from
the property and sends the status server/status answers (up or not) to whoever is connected, and
computes nothing when nobody is.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
MinecraftServer builds a ServerActivityMonitor with SERVER_ACTIVITY_MONITOR_SECONDS_BETWEEN_NOTIFICATIONS
= 30. Its processWithRateLimit tells the notification service when an activity was reported and thirty
seconds have gone by since the last one told, and clears its flag whatever it did, so a login inside the
window is lost and not told later. The supervisor applies the same rule to the game's login_activity
events.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ServerActivityMonitor.reportLoginActivity is called when a login starts, however the login ends. Every
login attempt of a dedicated server goes through the game's login gate, which now records a typed
login_activity event for the supervisor to rate limit into the management protocol's server/activity.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
ServerChunkCache.tickChunks' natural spawning, custom spawners and thunder,
and the raids, run before ChunkMap.tick too: their entities are first
updated in the pass that pairs them, as ranges of ids rather than one
cutoff. The client paired in that pass gets the tick-0 changes after its
pairing, as vanilla's updatePlayers precedes sendChanges.
Joined states are used only in the tick after the one that noted them, are
noted only for a level a guest is in, keep the minecart steps and needs_sync
of the tick's own state, and are looked up by id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The real_packs test preprocessed every stage and translated the
composite-style programs with `Macros::standard()` alone: no renderer
macros (`MC_GL_VENDOR_OTHER`, `MC_MIPMAP_LEVEL`...) and no `IRIS_FEATURE_*`
for the features a pack turns on, so a pack taking a path behind them
counted differently from the game.
`read_properties` reads `shaders.properties` with the pack's options over
the given macros and defines the features it turns on; the game and
real_packs both use it, and real_packs adds the renderer's macros at Mipmap
Levels 4 for all of its passes.
Fixes#7206
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at c1649ac88e. Docs for the typed API from mixin code: checked against main, AttachmentHost is implemented for dyn Host (mod_api.rs:421) and not for Game, so the coercion step it stresses is the one that matters, and LevelRandomHost is the trait the GameLevel bindings name.
It sends gamerules and gamerules/update (a rule set twice to the same value, an integer rule, a value out of
range, of the wrong type, an unknown rule and a missing key) to vanilla's server and Horizon's and compares
the answers. The game rules come in the order of a map keyed by identity in vanilla, which changes from run
to run, so the list is compared sorted by key. It also compares the notifications both servers send for
the same messages, name by name and count by count, for the ones Horizon sends from the lists and the rules
(the others depend on timing).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The parity script, run against the pinned vanilla server, showed two differences from the first version of
gamerules/update:
- vanilla's MinecraftGameRuleServiceImpl.updateGameRule sets, logs and tells the notification service on
every update, also when the rule already has the value (three updates, three notifications), where this
set the rule only when its value changed;
- vanilla's codecs answer a value or a key that doesn't fit with their own texts: "Value -1 outside of
range [0:2147483647]", "Not a boolean: \"yes\"", "Unknown registry key in ResourceKey[minecraft:root /
minecraft:game_rule]: minecraft:nope" and "No key key in MapLike[{...}]". Those are now Horizon's
(a value that is not a number answers "Not a number: ..." as DFU's number codec does, which the script
doesn't cover).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A stand moving 1.5 east from x = 24 toward a border wall at x = 25:
past it without a border, its box ending on the wall with one.
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.collectColliders adds WorldBorder.getCollisionShape for any
entity inside and close to the border, but ArmorStand::tick resolved
its move against the blocks alone, so a pushed or falling stand went
through the border. Its tick now takes the level's border and resolves
through SceneCollisions::resolve_in_border.
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 670867a1fb. Management game rules: gamerules/update sets the rule only when the value changes and checks int bounds (rule.min..=max) before it, and /gamerule goes through the same set_game_rule, so both notify; the event keeps the type as valueType (the event's own type field) and the server maps it back to TypedGameRule's type.
🤖 Agent: queued at c2e8cc0fe2. Crafted recipes: award_crafted now passes the whole id (crafting::qualified adds minecraft: only to a bare name), so a recipe unlocked by crafting reaches the book and a recipe_crafted criterion matches; modded namespaces are left as they are. One String per craft, nothing per tick.
- section 4: init writes [panel], [api] and [discord] today (config.rs, the
default template), so "PR 4 adds [panel]" is no longer true; there is no
backups.before_update setting (Backups has only every and keep), because an
update always backs the world up first (update.rs, switch_stopped).
- section 7: the backup before an update is made ("later also before an
update").
- Docker: the panel runs inside the container (`horizon-server run` starts it
on [panel] bind, supervisor.rs), not "later".
- section 9: Horizon sends the locator bar since #692, merged the day of the
equal-work run.
Planned items the code has not reached stay as they are (prepare,
import-resources, the notifications #7220 is changing).
Refs #7232
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A headless single-player game builds a source-fed trench, summons an oak boat
on the flowing water and reads its Pos every ten ticks: vanilla's
Entity.baseTick pushes a boat by the water current (0.014) before floatBoat
scales the velocity, so the boat must drift downstream. Without that step the
boat sits still (#6435).
The Coming paragraph said console, ban, pardon and mcp were still to come.
console and mcp are in main.rs (the commands list already shows them), and
players, kick, ban, pardon, ban-ip and pardon-ip are dispatched there too
(cli::players, cli::moderate). Drop the paragraph and list the moderation
commands with the others.
Refs #7232
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at c029313d6c. Crash reports: the native mod list comes from native-mods.json already read at start, set once as a crash detail; nothing per frame, and a release or Nightly (no file) adds nothing.
🤖 Agent: queued at b814de1118. Spyglass arm pose: head xRot − 1.9198622 (− 0.2617994 crouching) clamped to [−2.4, 3.3], yaw ±0.2617994 per side, as HumanoidModel's SPYGLASS case; only while the spyglass is in use, from either hand.
🤖 Agent: queued at 52453208cd. Scoreboard %=: adds the divisor only when the remainder's sign differs, which is Math.floorMod and cannot overflow; the boundary table covers MIN % -1 = 0 and 1 % MIN = MIN+1, and the corpus row puts it under the vanilla oracle too.
🤖 Agent: queued at d9ae51fe46. Summon with a mod cancelling the join: spawn_mob_spawned now returns None when the join callback removed the mob, before equipment and jockeys, and a cart's join is flushed before success is reported, so /summon fails with commands.summon.failed.uuid like vanilla's addFreshEntity false.
🤖 Agent: queued at 57a0816bab. CI: same --lib --bins --tests selection as the build before it, so the ignored mod_content run reuses its artifacts instead of rebuilding 69 crates (~105 s per verify).
🤖 Agent: queued at f60c7e53d4. Clouds: measured 124.7 → 84.5 µs per cell crossing at radius 32; the ring list is cached per radius and CellIter skips the x values whose z is past the radius, keeping the old flat_map order, and faces are still emitted in faces()'s order (outer [1,0,2,3,4,5], then inner), so translucent clouds blend as before.
An item frame's item and an armour stand's hand and head items were
posed and lit vertex by vertex on the CPU every frame, while the
stand's body already took the GPU skin path. Items whose baked model
can take that path (opaque or cutout, no glint, emission or
translucency) now do too: a frame's item as its own skinned draw,
culled by its bounds; a stand's items inside the stand's open skin
draw, culled and truncated with its body. Skulls, maps and other
special models keep the CPU path.
The byte-for-byte test already poses every item in the fixed, hand and
head contexts against the CPU vertices.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A single-line comment ending in the line-continuation character includes
the next line in the comment (GLSL 4.60, 3.4). Iris 1.11.7 does so (a probe
pack at `#version 330 compatibility`: a `#define` on the line after such a
comment, after code or alone, stays undefined). Horizon ended the comment at
the line break and read the next line as code or a directive. The comment
now runs on; its line breaks stay, so later lines keep their numbers.
Fixes#7233
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A program that declares a storage image no `image.<name>` entry gives was
skipped ("image `x` is not provided yet"). Iris 1.11.7 runs such a program,
and its loads read zero, also after it stored into the image (observed with a
probe pack of our own). Such an image now binds a one-texel stand-in of its
declared format and dimension, cleared before every pass that binds it, and
the log says it reads zero.
With Shrimple v0.12 this runs composite13, composite17 and gbuffers_water,
which main skipped.
Part of #6812
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 72ce1a6186. Anvil craft hooks: a cancelled Pre restores the anvil, cost, inventory and cursor from the snapshot taken inside the click, so cancelling no longer dupes the result; without mods the take stays in the click as AnvilMenu.onTake runs it. I pushed 72ce1a618: the fragments' markers did not match their file names, so stamping would have left @API:anvil_craft_*@ in the docs.
Each is built in the air in front of the camera and recorded while it
runs, typed in chat like a player does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A /fill of 64x64x16 arrives as ~32k single block changes in one frame.
Relighting each in place cost the frame 40-60 ms (sky light under the
cleared volume). Past 4096 changed cells, the columns the edits touch get
full light jobs on the workers, as World::edit_runs already does; edits in
columns that can't be lit whole yet (the edge of the loaded area) stay in
place, so one stray edit no longer pulls the whole batch back in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
These four entity ids had no loader when a summon or a chunk creates them
from NBT, so they were kept as inert records that nothing ticked: 20 ticks
from y 110.0 they stayed at 110.0, where vanilla gives 101.0465 (spectral
arrow), 101.6 (shulker bullet) and a landed, removed spit and bottle.
Load them as projectiles: a spectral arrow as an arrow whose pickup item is
the spectral arrow (and save it under its own id), a llama spit and a
target-less shulker bullet from Projectile's keys, an experience bottle as
the bottle item with gravity 0.07. The shulker bullet's tick now uses
getGravity (0.04, none under NoGravity) instead of a fixed 0.04, and its
target is optional.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Return None when a spawn's join callback removes the mob, propagate it through summon, and flush minecart joins before reporting success while watching the new cart's ID.
Fixes#7184
Co-Authored-By: Codex <[email protected]>
`iris.features.*` may name BLOCK_EMISSION_ATTRIBUTE (ShaderDoc, "Feature
Flags": block emission in `at_midBlock.w`). Horizon left `.w` at 0 and
logged the feature as not supported (RenderPearl v2.7.2 and Visual Vibrance
v0.3.5a require it). With a probe pack, Iris 1.11.7 passes the block's light
level there, 0 to 15 (glowstone 15, torch 14, lit furnace 13, soul torch 10,
redstone torch 7, stone 0).
Terrain vertices carry their sprite layer, not their block, so the light
travels as `mc_Entity` does: in the per-layer table of flat terrain, now
three values a layer, a layer taking the light of the block named like its
sprite, else what most states drawing it give (ties: the lowest). A sprite
that blocks of different light share (a lit furnace's side, a jack
o'lantern's pumpkin side) reads the majority's.
Fixes#7205
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client mod drawing blocks in its own pass (flying debris as voxels, say) can now use the player's own block
textures, resource packs included, as the terrain samples them.
- `block_sprites::Lookup` (render crate) answers, for a block state (`oak_log[axis=x]`), what its model draws on
each of its six sides as the mesher bakes it: each face's sprite (array and layer, as the renderer cuts the
arrays), where it lies on the side and how deep, the affine map from the side to the sprite's UVs (rotations and
mirrors included), its tint source (`MeshTables::tint_source`) and whether it is cut out, translucent or
animated; its particle sprite; whether it is a plain cube. Fluids give their still texture on every side.
- Helpers `native_block_sprites`, `native_texture_sprites` and `native_block_sprites_version` (a helper-only
fragment; the Host regenerated). The version is `RenderAssets::generation`, which counts resource loads.
- `WorldDrawn::block_sprites` (`BlockSpriteArrays`): the terrain's eight array views (with the Mipmap Levels
option's levels), the device's split (`Renderer::sprite_split`), the layer size, a nearest repeating sampler and
the terrain's own filtered one.
Tests: the UV map; a GPU test that a pass samples a layer of the second array at the texels it names through the
views and the nearest sampler (repeating past the edge); an ignored test against prepared resources (planks, a
grass block's overlaid side, a log on its side, leaves, water, a slab, a flower, unknown names).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A fragment's marker is @API:<file name>@; the summaries said
@API:anvil_craft_*@, which stamping would have left in the docs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`textureQueryLod` (GLSL 4.60, 8.9.1) has no counterpart in naga, so a program
calling it failed ("Unknown function 'textureQueryLod'"): with RenderPearl
v2.7.2 every program including lib/material/normal.glsl. A program that
calls it now gets ours: `y` is the level of detail relative to the base
level, worked out as texturing does (OpenGL 4.6, 8.14.1: the log2 of the larger
texel-space change of the coordinates between neighbouring pixels), and `x`
that level clamped to the texture's levels; for 2D, 2D array, 3D and cube
samplers of float, int and uint, and through a pack function taking the
sampler as `textureGatherOffsets` is.
Fixes#7170
Co-Authored-By: Claude Opus 5.5 <[email protected]>
GLSL takes an integer constant expression for `local_size_*` (GLSL 4.60,
4.4: a layout qualifier's value is an integer-constant-expression; 4.4.1,
compute shader inputs). RenderPearl v2.7.2's `deferred` keeps its size within
the implementation's with `local_size_x = min(gl_MaxComputeWorkGroupSize.x,
N)`. Horizon took a number only ("`local_size_x` is not a number
gl_WorkGroupSize can take"), and naga reads none of it. The expression is
now worked out (numbers, + - * / %, brackets, min, max, and
gl_MaxComputeWorkGroupSize as the minimum GLSL requires, 1024, 1024, 64;
7.3) and written in its place, and gl_WorkGroupSize is its value as before.
Fixes#7171
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two declarations of RenderPearl v2.7.2 left its interface blocks unflattened
or its position unwritten:
- A block whose instance name is also used in its stage other than as
`instance.member` counted as "used whole" and stayed as written, so naga
refused its members' `layout(location, component)` ("Expected Void,
Struct or a type, found Layout"). For RenderPearl that use was not read; a
probe with a function parameter named like the instance reproduces the
error. A local or parameter of that name hides the instance in its scope
(GLSL 4.60, 4.2), and `x.v` is another struct's member: neither is the
instance now.
- A redeclared `out gl_PerVertex { vec4 gl_Position; };` (GLSL 4.60, 7.5)
became a plain output, so the vertex stage wrote no position. The
redeclaration goes; its members stay the built-ins.
Part of #7171
Co-Authored-By: Claude Opus 5.5 <[email protected]>
RenderPearl v2.7.2 starts every stage with `#include "/prelude/core.glsl"`,
whose first line is `#version 460 core`. Horizon read `#version` from the
stage file only, so these stages counted as GLSL 1.10: words that became
keywords later were renamed (`shared` -> `hz_kw_shared`) and its compute
programs failed ("Expected end of file, found Identifier(\"hz_kw_shared\")").
The first `#version` read is now the stage's, from an included file too.
Part of #7171
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at fb3d0b8f6f. Title screen hooks: the render-post call on the title only builds a NativeScreenView (cursor, clock fraction) and calls an empty hook when no mod is loaded, so the title pays nothing measurable; typing stays with the title's own boxes (title_owns_text) and Options over the title keeps its own. I pushed fb3d0b8f6: the fragment listed mod_api.rs in comments, which the stamp tool rejects.
🤖 Agent: verify green on 8bba5bd3. Only DX12 is affected: the cache file is named only when the instance includes the DX12 backend, and Vulkan, Metal and GL never touch it. Windows run 41447442462 rebuilt every pipeline from cached bytecode on WARP, and later starts fell from about 3.3 s (FXC) to 0.22 s. The binary size does not change.
The stamp tool rejects native_hooks.rs and docs/native-mods.md in
`comments` (it stamps both itself), so the stamp test failed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at ca7459f68d. Join check by version and hash: costs nothing on a server without content mods (no extra round trip). I pushed one fix: content_root runs again on every pack reload, and it re-read and SHA-256'd every .hmod there although set_local keeps the first list; it now skips that once the list is set.
An entity added after the tick's tracking pass (the item a falling block
drops when it breaks on a torch) was first seen by the next pass, after its
first tick had moved it: its AddEntity carried that position and motion.
The end of the tick now notes such entities as they are then, and the next
pass pairs them from it, as ChunkMap.addEntity does when they join.
ServerLevel.tick runs ChunkMap.tick after the block ticks and before the
entities, so an entity that joined by then (a falling block from its
scheduled tick, the last tick's late joins) has its first update, tick 0 of
its interval, in the pass that pairs it, before it moves.
Fixes#7194
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HumanoidModel's SPYGLASS arm pose was missing, so the arm stayed down
while the spyglass was drawn at the eye. The ledger's two rows on the
held-to-eye drawing are out of date too: held_to_eye_matrix does it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stamp tool rejects mod_api.rs in `comments` (it stamps that file
itself), so the stamp test failed; the summaries now read after its
"API N adds".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- player_level_change: report orb, advancement reward and station level changes; stop the double
fire on /experience add.
- inventory_move_item: composter bone meal goes through it; chest boats and minecarts are reported
at the block they are in.
- player_kicked: fires even when the guest left in the same poll.
- Doc comment markers use the fragment names the stamper numbers.
- Drop player_item_break: native_player_destroy_item (#7025) covers it.
- No mod loaded: no Input/Sneak events, clones, as_guest swaps or per-slot lookups for the hooks.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: held items join the mob's skin draw only for opaque/cutout models without glint; outlined/glowing mobs and first-person hands stay on CPU; byte-for-byte test covers all 5 contexts (15514 states).
The supervisor passes gamerules and gamerules/update to the game's management request and answers with
the typed rules (an "invalid" answer becomes an invalid-params error), and turns the game's gamerule
event into notification/gamerules/updated, whose parameter is the typed rule, whoever changed the rule:
the protocol or /gamerule.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
GameRulesService.get lists every rule the server has as a typed rule ({key, type, value}) and
GameRulesService.update sets one: the value is checked against the rule's type and bounds, and the rule
is set and logged as JsonRpcLogger logs it only when the value changes (the log names the rule by its
short name). A rule or a value that doesn't fit answers "invalid", which the supervisor turns into an
invalid-params error in the next commit.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Vanilla's GameRules.set ends in MinecraftServer.onGameRuleChanged, which tells the notification service
of the management protocol. The /gamerule command set the rule and synced the world itself; Game
::set_game_rule is now what does both for the command and for the protocol that follows, and on a
dedicated server it records a typed gamerule event (the rule's id, the type of its value, the value)
that the supervisor will turn into the notification.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A cancelled Pre left the result taken while the inputs stayed and no levels
were charged, a dupe. The menu now snapshots before the click and a cancel
restores the result slot, inputs, cost, inventory and cursor (the station
resyncs), and the click's drops wait for the hook's answer. The deferred take
is on only when a mod is loaded, so a server without mods runs
AnvilMenu.onTake in the click as before.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Number the instance_dir helper and the title-screen hooks through a
native-hook fragment instead of a hand bump; keep Options (and any screen
opened over the title) typing into its own boxes; document that
native_screen_char_typed_pre does not fire from the keyboard on the title's
own screens.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
set_local keeps the first list, so a pack reload read and hashed every .hmod
for nothing; it now skips that once the list is set.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 80f5c34206. List notifications: clear() tells one removal per entry before emptying, as ServerOpList.clear and UserWhiteList.clear do in 26.3 (checked); the watcher lives on the lists, so console, in-game commands and protocol methods all notify.
A mob's main- and off-hand items were posed and lit vertex by vertex on
the CPU every frame, even when the mob's own body went through the GPU
skin path. Items whose baked model can take that path (opaque or cutout,
no glint, emission or translucency) now join the mob's skinned draw:
one job per item with the hand pose, culled and truncated together with
the body. Outlined or glowing mobs and first-person hands keep the CPU
path, which outlines need.
The byte-for-byte test now poses every item in the ground, both
third-person hands, fixed and head contexts: 15514 states take the GPU
path and match the CPU vertices exactly; 1066 stay on the CPU.
Adds stress_held (1000 mobs holding two items each) to the stress
scenes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 35972b0eab. Storage buffers: buffer_minimums walks the group-4 storage globals of every program once at pack load (not per frame) and sizes each buffer to the larger of the declared bytes and the std430 block, so wgpu's binding-size validation no longer kills the submit thread on RenderPearl or Visual Vibrance.
🤖 Agent: queued at 7090fdd5cc. Own shoulder parrots: the owner gets SetEntityData for slots 19/20 only when they change (an array compare per guest tick, no allocation when equal), and the respawn reset resends them, matching ServerEntity.sendDirtyEntityData to the owner.
🤖 Agent: queued at 0f44ec6a95. SEPARATE_HARDWARE_SAMPLERS: honest to declare, since pack_post already binds shadowtex0HW/shadowtex1HW to the shadow depth with a comparison sampler chosen by the declared type; packs that require it (RenderPearl, Visual Vibrance) now load.
🤖 Agent: queued at 29a273d96e. Scoreboard /=: wrapping_rem in the floor correction stops the i32::MIN / -1 panic, and the quotient stays i32::MIN like Java's Math.floorDiv; the command-level test checks the divisor is left alone too.
The server compares only side = "both" entries, and any server can ask a
client for its list, so client-only and server-only mods stay private.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at d550c86157. Save round trip with riders: the scene now saves a mounted pair and the parity run on this head passes, so the Passengers nesting round-trips like vanilla's; tools-only change.
🤖 Agent: queued at da4e5bb604. Allowlist settings over the management protocol: merged with main's autosave methods, keeping both; the parity test renumbers autosave 65–74 and allowlist 75–94 so the restarted reads stay after the autosave ones. horizon-server tests (117) and dedicated client tests pass.
🤖 Agent: verify green on 6691b009. levzzz5154's review checked all 1,511 structure templates byte-for-byte through horizon-data.bin, so worldgen reads exactly the bytes it read before. The VERSION 2 bump makes every player rewrite the prepared file once on their first start (+4.4 MB).
A server with content mods on sends each mod's id, version, side and the
SHA-256 of its .hmod on horizon:mod_list; the client answers with its own
list, and a client that differs is refused with the mods named. Only
side = "both" mods count, so client-only mods never block a join.
Refs #7192
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at bcc3d47b55. Item expiry hooks: the merge of main keeps the on_ground/horizontal/every-4th-tick gate in front of SceneCollisions, so a resting item still skips the scene query 3 ticks in 4; item tests, clippy and mod-api check pass on this tree.
ComponentSerialization decodes a string tag as Component.literal, as the
tooltip (lore_texts) now does; this keeps the wire and the tooltip in step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The integrated server synced DATA_SHOULDER_PARROT_LEFT/RIGHT only to the
players around, so the owner's own model (third person) drew no parrot in
singleplayer and on servers hosted by this game. sendDirtyEntityData sends
the owner its own entity data too.
🤖 Agent: verify green on ad88c258. Recording runs only for players who opted into performance telemetry (~5 µs/frame for 100 spans). The new slow_frames field is skipped when empty, so older ingest servers keep accepting reports. The server adds the column in migrate() and hides aggregate rows below min_install_days installs.
server_player_spawn (initial_spawn.rs) still passes over candidates in unloaded
chunks for a server's new player, so main's gap note stays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 8e6896180a. BatModel draws with entityCutoutCull, so the facing-away quad of its zero-depth ears, wings and feet is culled. The new w = -126 (SHADED_CULL_W) shades like -127 and also sets cull. The only w values in use are 0, ±127 and 1, so the new (-0.996, 0) range catches only -126. The GPU skin job's word 14 writes the same byte as expand. The cost is one compare per vertex, plus a branch in the skin shader for culled models. On the head merged with main: clippy -D warnings clean, skin tests 8/8 including only_the_bat_asks_for_culled_back_faces. The GPU shader test is left to CI, since it segfaults on this Mac's GPU on main too.
Iris 1.11.7 gives packs `cloudTime`, which ShaderDoc doesn't list: observed
with a probe pack of our own, it is how far the game's clouds have drifted,
in blocks (vanilla's CloudRenderer.prepare: the game time within one trip
across the cloud texture, plus the partial tick, times 0.030000001). Horizon
read it as zero ("zero for now: cloudTime"), in eleven of Shrimple v0.12's
programs.
Part of #6812
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The crafting copy names a minecraft: recipe bare (Recipes::sources
drops the namespace, folder and world pack alike), and award_crafted
handed that bare name on: the book, the network copy, the advancements'
JSON and the saved file all know the recipe as minecraft:<name>, so a
recipe unlocked only by crafting it had no display — never in the book,
not after the craft, not after rejoining. Vanilla recipes are usually
unlocked first by their advancements, under the whole id, which hid it;
a recipe_crafted criterion never matched the bare name either, for the
same reason.
crafting::qualified now fills the default namespace in, shared with the
lan copy's own. The book learns and forgets recipes under their whole
id whatever the caller passes (Settings::add_recipes, remove_recipes),
and award_crafted qualifies before the recipe_crafted trigger and the
book. Reading a saved book already qualified bare names; a test keeps
that, so books saved before the fix show their recipes too.
Fixes#7186.
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: particle collide shortcut uses the scan's own empty_shape and may_reach_out flags, so it only skips what the scan skips; 4000 random sweeps match the full resolution bit for bit, 5 particle harnesses pass; client tick −24 %, 1 % low +14 % on stress_particles.
FXC compiles Horizon's pipelines in about 2.6 s on every start (DXC
about 1 s). wgpu-hal's shader cache only lasts one device, so the
vendored dx12 backend now also appends what it compiled to
<game home>/cache/dx12-shaders.bin and reads it back as precompiled
bytecode next time. Keys hash the compiler, shader model, stage, entry
point and naga's HLSL; records carry a checksum and a torn tail is
written over.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
management_methods.rs keeps both helpers (allowlist_setting, main's autosave). rest-017.tsv keeps both rows. The management harness keeps main's autosave requests at ids 65-74 and moves this PR's allowlist requests to 75-92 and the restarted reads to 93-94.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at e8e813638a. This matches SpawnEggItem.useOn in the 26.3 client bytecode: FAIL when the type can't spawn, SUCCESS on a client level, and neither lets Minecraft.startUseItem send UseItem, so the underwater fluid-ray second spawn is gone. Adventure mode keeps can_place_on as ItemStack.useOn does. I inlined spawn_egg_use_on_swings, which returned its own argument, and dropped its test, which only checked that identity. clippy -D warnings clean and remote tests 59/59 at this head. run-parity covers spawn_egg_fluid.
Every moving particle with physics ran the full entity movement resolution
per tick (Entity.collideBoundingBox: a BlockCollisionScan iterator, the step
machinery, its shape vectors), even in open air. SceneCollisions now tells
first, from the same cells, blocks and section flags the scan reads, that no
shape can be found: no block that can collide in the inner cells, no block
reaching out of its cell in the outer cells' sections. The resolution then
returns the movement unchanged, so the particle gets it directly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 5f656499f5. Review addressed: the localized tooltip lines are built once per language and cached; in English nothing is rebuilt. clippy -D warnings clean, creative tests 35/35 on the head merged with main.
spawn_egg_use_on_swings returned its argument, so its test only checked that identity. The branch now reads spawn_egg_allowed directly.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The generated dispatch loops over mod_api::entered, which marks the thread as
inside each mod's hook while it runs; the crash handler adds "in native mod
<id>, hook <hook>" to the panic's message, and the panic goes on unwinding as
before. The launcher's generated executable passes the bundles' ids to
mod_api::name_mods when the revision has it. Dispatch still takes
&[Box<dyn Hooks>].
🤖 Agent: queued at 2e1fae8f9f. Pufferfish.touch for mobs: a mobAttack hit for puffState + 1 on each alive, non-#not_scary_for_pufferfish mob in the fish's box inflated by 0.3, when teams allow. I added the POISON for 60 * puffState that the bytecode gives once the hit lands, before the sting sound (Mob::add_effect carries it, so the PR's 'no path' note didn't hold), and updated the ledger row. Merged with main: clippy -D warnings clean, pufferfish tests pass, and they now assert the poison duration.
🤖 Agent: queued at 8cf71732d0. Since #5446, menu.rs and breathing.rs compile into horizon-client-lib, so horizon-mixins rejected the old horizon_client:: targets. The new check that each target's first module is declared in its crate's lib root catches the next file move, even when the catalog and the expectation agree. On the head merged with main: xtask mod-api --check passes (rebuilt in this tree), Python 612 OK, clippy -D warnings clean, catalog_matches_hook_points passes.
Step 3 of #7118. In vanilla each StoredUserList holds the NotificationService, so a change tells it
whoever made the change (the console, a command in the game, the protocol's own methods):
ServerOpList.add and remove call playerOped and playerDeoped, UserWhiteList playerAddedToAllowlist and
playerRemovedFromAllowlist, UserBanList playerBanned and playerUnbanned, IpBanList ipBanned and
ipUnbanned. A clear says it once for each entry, then empties the list; removing the expired entries
that get drops says nothing.
UserList now takes a watcher (watch) that hears Added and Removed with the entry, on add, remove and
clear but not on load. The dedicated game gives each of its four lists a watcher that records a typed
event (operators_added, bans_removed, ip_bans_added...), and the supervisor, which already fetches the
game's events, turns them into the notifications, with the DTOs the lists already write.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at 0c54f51081. join/leave/saving/saved map to JsonRpcNotificationService's players/joined, players/left, server/saving and server/saved. The save events are recorded only on a dedicated server and only when the save succeeds, and the saves before the first fetch are skipped, since no client could have heard them. On the head merged with main: horizon-server 117/117 including the frame-order test, clippy -D warnings clean on horizon-server and the client, and dedicated::events passes.
🤖 Agent: queued at 2453e2d2c0. Items, orbs, primed TNT and falling blocks now collide with the border through EntityCollisions, as Entity.collectColliders adds WorldBorder.getCollisionShape. The per-move cost is is_inside_close_to_border, and the shape is only built near the border. Dropping the entity_boxes.is_empty() fast path costs nothing, since an empty entity slice iterates nothing. On the head merged with main: clippy -D warnings clean, border tests 12/12 including the new item/orb and TNT ones.
🤖 Agent: queued at 4dcbdd27fc. Host methods now get a panicking default that names the method (not implemented: Host::tell), so a test double writes only what its hook calls. The game is unaffected: the generated impl Host for Game still overrides every method, and generated_output_is_current keeps both in step. I merged main; on that head merged again with today's main, xtask mod-api regenerates nothing and --check passes (rebuilt in this tree), xtask mod_api tests 17/17, Python 612 OK, clippy -D warnings clean, and both new mod_api tests pass.
The screen hooks only covered the screens over gameplay: the title state
machine (Title, Select World, Create New World, the multiplayer screens) had
no id, fired no ScreenEvent.Render.Post and heard none of the input Pre
hooks, so a mod could not add a button to Create New World or take clicks
there, which is what a pack browser (Resourcify and the like) needs.
Give every title state an id (Mode::id; the multiplayer screens have their
own), report it through NativeScreenView with the whole window as the rect
and no menu or slots, fire native_screen_render_post once the title has
drawn (before its toasts) and let the mouse, wheel, key and drag Pre hooks
fire there, cancelling as on any other screen. Typing keeps the key's text
on the key for the title's own boxes, as Options does, and the control
socket's type now types into them.
native_instance_dir() gives a mod the instance folder, where resourcepacks/,
shaderpacks/ and horizon-mods/ are.
API 154.
A game the launcher built with native mods reads their ids and versions from
the native-mods.json beside it and adds them to the crash report's details as
Native Mods. The line stays out of crash telemetry, which keeps only its listed
details.
🤖 Agent: queued at 07709a4233. Clone fires at the end of restoreFrom (was_death true) and from server_travel's End exit with keepAllPlayerData (false). LoadFromFile fires after the .dat is applied and before placement; SaveToFile once per save, owner included. The extra create_dir_all on players/data runs only on saves and joins, and vanilla's PlayerDataStorage makes that folder anyway. I merged main (docs conflict resolved: main's pearl/chorus hooks, then these three). mod-api --check passes on this head and merged with today's main after #6912; Python 612 OK, clippy -D warnings clean, player tests 116/116.
GLSL lets a declaration define its struct type in place without naming it
(GLSL 4.60, 4.1.8): `shared struct { uint sum; uint tile[8][8]; } lds;`.
naga's frontend wants a name ("Expected identifier, found LeftBrace"), which
RenderPearl v2.7.2's compute programs (deferred, deferred1, composite,
composite3) hit. Each such type now gets one (`hz_struct<n>`) before the
stages are read.
Part of #7171
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`iris.features.*` may name SEPARATE_HARDWARE_SAMPLERS (ShaderDoc, "Separate
Hardware Shadow Samplers"): with shadow hardware filtering on, shadowtex0 and
shadowtex1 stay plain depth samplers, and shadowtex0HW and shadowtex1HW
compare. Horizon already binds the HW names to the shadow depth buffers and
picks a comparison sampler by the declared type: a probe pack of our own gets
the same comparison results as in Iris 1.11.7, and plain depths from
shadowtex0/1 alike. Only the flag was missing. RenderPearl v2.7.2 and
Visual Vibrance v0.3.5a require it, and the game logged it as not supported.
Fixes#7179
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Slice 10b of #6509: the id-string dispatch in spawn_loaded_entities moves behind a required Entity::load_nbt, one macro-generated test chain over entity_lists! (static dispatch, nothing allocated). Kinds outside the lists keep their arms in load_unlisted. Fixes#6545.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Part of #6546. native_entity_leave_level covers every kind the join hook tells, from one registry of announced ids swept where entities are removed; end crystals, the dragon, cushions, paintings and item frames join and leave; spawns between the tick's phases join in that tick.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: the language is read when the reload starts, so the apply step checks the code again and loads the newer language if the player switched while it ran; without that check, the old language would come back.
🤖 Agent: queued at 0167e02866. follow_mommy matches AbstractHorse.followMommy: bred && baby && !eating, the nearest bred horse within range(16) over its box inflated by 16, a path only when distanceToSqr > 4, and createPath(entity, 0), which leaves the speed modifier alone. Since nothing sets Bred at runtime, the per-tick cost is one early return. The Bred flag now round-trips through entity_nbt. On the head merged with main: clippy -D warnings clean, follow_mommy test passes.
🤖 Agent: queued at efe72ef985. award_stat checks mods().is_empty() before copying the acting player's name, and namespaced() uses a 96-byte stack buffer, so the per-tick counters (play_time, the cm stats) cost what they did before. Advancement progress fires only on a real grant or revoke. I merged main: the in-frame tick that #23f8bd4fe deleted takes its stat calls with it, trading.rs keeps main's offer-based merchant_traded with award_custom_stat, and hub.rs gets both fields. xtask mod-api --check passes on this head and again merged with today's main (after #6927); Python 612 OK, clippy -D warnings clean, stat tests 52/52.
🤖 Agent: queued. One concrete point: besides the new management methods, this changes `save-off` itself. The autosave countdown now keeps running while saves are off, as in vanilla (`MinecraftServer.autoSave`), instead of being held at 1, so the first save after `save-on` comes at its scheduled time rather than on the next tick. test_management_protocol_parity covers the methods. The merger waits for parity (fork).
Iris 1.11.7 draws a program with a stray `#endif` (it logs "#endif without
#if") and one whose `#if` stays open to the end of the file (seen with
probe packs written for this). Horizon reported both as errors and skipped
the program: with Pegasus v0.5 every gbuffers program (terrain and water had
none), with Bloop its composite6. Both are now warnings: the stray `#endif`
is ignored, the open `#if` reads on to the end.
Fixes#7167
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds passengers (one, two, nested) on a pig, boat, minecart and horse to the save round-trip scene, and lets the passenger copies of simulation fields differ as the entity's own do. Follows the review of #7094.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at 15fc1ce2fb. saved_with_chunk is now an exhaustive match, so a new ProjectileKind won't compile until it says whether it's saved, and the wind charges it had been dropping are now saved. Potion(stack, lingering) keeps the entity type apart from the Item, as /summon lingering_potion with a splash Item needs. attached_block_pos falls back to the summon position, as BlockAttachedEntity.setPos does. On the head merged with main: clippy -D warnings clean, nbt tests 48/48. run-parity added for save_roundtrip and world_reopen.
🤖 Agent: queued at d927a099e7. native_joins_flush returns before touching anything when no mod is loaded or on a client, so the new per-tick call is free for vanilla play. With mods it announces each new entity once, lowest id first, against one u32 mark, and a cancel goes through discard_entity before the entity ticks. I merged main (mod-api regen + --check ok); Python 612 OK, clippy -D warnings clean, join tests 17/17 on the Mac.
A pack declares a storage buffer's size (`bufferObject.<n>`), and a shader
may bind it as a larger block: std430 rounds a block to its largest
member's alignment. OpenGL binds the smaller buffer; wgpu refuses the draw or
dispatch, and the validation error stopped the submit thread, so
RenderPearl v2.7.2 (40980 bytes bound, 40992 expected) and Visual Vibrance
v0.3.5a (40 against 48) crashed the game on their first frame.
`Post::buffer_minimums` reads each storage block's size from every
translated stage that binds it, and the renderer makes each buffer at least
that large.
Fixes#7178
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 1024x1024 slot cost every player 4 MiB of atlas pixels, GPU memory and
shelf room whether a mod drew into it or not. Pass the fact in from the two
places the atlas is built: a game with no native mod has no slot, and
native_mod_image keeps answering false for the missing sprite.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
SpawnEggItem.useOn returns SUCCESS (FAIL for a mob that cannot spawn) on a
client level, so no UseItem follows and the server no longer spawns a second
mob from the fluid ray when the player is underwater.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at d5418e0137. Fixes Shrimple's shadow program, whose geometry stage used to rename float to hz_vs_float. Interface blocks are now flattened before combine reads the two stages, and the hz_in_ prefix keeps a geometry stage's same-named input and output blocks apart, as GLSL 4.60 §4.3.9 allows. It only runs at pack load, so the frame costs nothing. On the head merged with main: horizon-shaders tests 142/142 (the four new ones included) and clippy -D warnings is clean.
🤖 Agent: queued at efb76fb19c. native_hanging_break is only asked where a break would already happen: attack_frame when the frame itself breaks, the blast reach, and the survival check once it fails. With no mods it costs an empty dispatch. break_painting's new bool keeps the explosion loop's index right when a mod keeps the painting. I merged main, which regenerated generated.rs (its stale projectile-impact doc); xtask mod-api --check passes, Python tests 612 OK, clippy -D warnings clean, frame tests 19/19 on playhorizon.
🤖 Agent: the new scenarios type their commands into chat, not horizon-ctl's `command`, which blocks the frame on the server tick and made every step look like a 50 ms stall in a first try.
Pufferfish.touch gives the stung mob POISON for 60 * getPuffState() once the hit lands, before the sting sound. Mob::add_effect carries it, so the event now holds the duration and the game applies it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at cf968b78eb. Matches vanilla's classic order: executeTranslucent draws the shadows phase before translucentBlocksAndItems, and ITEM_TRANSLUCENT keeps the pipeline default of depth write on, so ice and water drawn later can't blend over a dropped slime block. The trade-off is that water right behind a glass item now hides behind it, which is also what vanilla does without Fabulous. The OIT path is untouched and the per-frame draw count is the same. I removed the gfx.rs source-text test (the repo rule is to test behaviour, not source); the before/after screenshots cover it.
`AbstractHorse.aiStep` ends with `followMommy`: a foal that is `isBred`, is a
baby and is not eating takes the nearest `AbstractHorse` that is `isBred`
within 16 blocks (`MOMMY_TARGETING`) and, when it is more than two blocks away
(`distanceToSqr(mommy) > 4.0`), installs a path to it
(`navigation.createPath(mommy, 0)`). Horizon had no `Bred` flag at all — the
save wrote a constant `0` and read nothing — so no foal ever followed.
`MobExtra.bred` now carries the flag, `Bred` is saved and read back for the
equine kinds, `Seen.bred` lets a foal see the flag on the horses around it, and
`equine::follow_mommy` runs where `AbstractHorse.aiStep` calls it. The path is
installed at the navigation's current speed modifier, as `createPath(entity, 0)`
keeps it.
Nothing sets `Bred` at runtime in 26.3 (`setBred` is called only from
`readAdditionalSaveData`), so the foal only follows with the flag in its save
or its `/summon` NBT.
Corrects the `AbstractHorse.followMommy` row of `tools/port_map/game-011.tsv`.
🤖 Agent: queued at 71b32333ef. clip_including_border matches CollisionGetter.clipIncludingBorder in the 26.3 bytecode: the isWithinBounds(from) && !isWithinBounds(hit) test with margin 0.0, getApproximateNearest of the delta, and clampVec3ToBound's maxX/maxZ - 9.999999747378752E-6. ProjWorld's border is [minX, minZ, maxX, maxZ], the same order as WorldBorder geometry's bounds(). The no-border cost is one Option match per clip. On the head merged with main: clippy -D warnings clean, border tests 10/10.
It read gfx.rs as text (the call order and a pipeline argument) rather than testing behaviour. The PR's before/after screenshots are the proof for this draw-order change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mobs, items and block_entities put 68 mob types, 50 item models and 34
block entities in view one by one (through chat, as a player types it:
horizon-ctl's `command` blocks the frame on the server tick), 0.6 s
recorded after each. screens opens the creative inventory and its 12
tabs, then the survival inventory and its recipe book, twice, printing
each step's worst frame. reload records 15 s after F3+T.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 7840fe3549. Test-only: shutil.which('bash') follows PATH, so Windows runs Git Bash instead of System32's WSL bash (#5383's cause), and the skip only covers a WSL-only machine. tests.test_pr_ready passes on macOS at this head, where the test still runs.
A resource reload's apply step (the frame that swaps the new resources
in) parsed every sounds.json (about 15 ms), walked the GUI sprites'
.mcmeta files (3 ms) and loaded the language (2 ms) on the render
thread. stage_pack now reads them on the reload worker, beside the
atlas; the apply step only puts them in use. A language picked while
the reload ran still wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 61a8cb83cc. Save refactor: each entity_lists! kind now saves through Entity::save_nbt with one save_lists! loop, and the filters (root-only mobs, alive/!removed, saved_with_chunk, the chunk keys) are unchanged. The only behaviour change is entity order within a chunk, which shifts the reload's entity ids and nothing a player sees. Dyna's game test covered riders, leads, the minecart chest and the armor stand on the server thread and the dedicated server, and verify passed at f3dd3a3. I added one doc-only commit putting strip_finalize's comment back on the function. run-parity added for world_reopen and save_roundtrip.
docs/native-mods.md: main's teleport sentence and its pearl and chorus hooks, then this PR's three player hooks.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
game.rs: main removed the in-frame player tick (#23f8bd4fe), so its stat calls go with it. trading.rs: main's offer-based merchant_traded, with award_custom_stat. hub.rs: both new fields.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 3cbd09e448. Checked against FallingBlockEntity.tick's bytecode: callOnBrokenAfterFall and spawnAtLocation both sit under dropItem && ENTITY_DROPS, so moving 1029 out of falling_block_landed into finish_falling_block's drop branch is right. A DropItem:0b anvil is now silent, as in vanilla. drop_falling_block now reads new_item_motion's own random and no longer advances the level random. On the head merged with main: clippy -D warnings clean, client falling tests 17/17, horizon-world fall tests 8/8. run-parity added for the new anvil_turtle_egg harness.
`Pufferfish.aiStep` stings every mob whose box meets the fish's own box
inflated by 0.3, filtered by `TARGETING_CONDITIONS` (alive, and not in
`#not_scary_for_pufferfish`), through `Pufferfish.touch`: a `mobAttack` hit for
`getPuffState() + 1`, then the poison and `PUFFER_FISH_STING` when that hit
lands. Horizon had only the player path (`playerTouch`), so a pufferfish that
puffed up for a zombie never hurt it.
`ai_step` now stings the touching mobs through a new `PufferfishSting` event,
which applies the hit in the game layer and plays `entity.puffer_fish.sting`
only when the hit landed, at the fish's position and not when it is silent, as
`Entity.playSound` does. The tag moves into `not_scary_for_pufferfish`, read by
the puff goal and the sting alike.
The poison the mob gets is not modelled: an effect on another mob has no
channel in the simulation, the same gap a cave spider's poison on a mob has.
Corrects the `Pufferfish.touch` row of `tools/port_map/game-011.tsv`.
Dropped slime blocks and glass blocks looked see-through: the ground shadow
was drawn over them and ice or water drawn afterwards blended over them
(#7026, #7031, #7033). Vanilla's PreparedFrame.executeTranslucent runs the
shadows before the translucent blocks and items, both ahead of the
translucent terrain (LevelRenderer.executeClassicTransparency).
On Windows, subprocess.run(["bash", ...]) goes through CreateProcess, which
looks in System32 before PATH: with WSL installed it starts WSL's bash,
which fails (execvpe(/bin/bash)), and the test errored three times on any
Windows checkout. It now runs the bash on PATH (Git Bash), and skips only
when there is none, or only WSL's (#5383).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued. This is a bench tool only (skip-release): PERF_BENCH_TRACE writes each load's timeline for Horizon and for vanilla. The one point to watch is that the trace stays off unless the variable is set, so normal bench runs aren't slowed: that is how it reads.
🤖 Agent: queued at 6e6deabda9. Harness-only: jukebox_case puts a Creative jukebox with block_entity_data.RecordItem through use_item_on, pinning JukeboxBlock.setPlacedBy's has_record. The body shows it fails (36 differing) with the jukebox arms removed, so the case has teeth. Note: the new rng.random() draw reshuffles later cases, which is harmless since oracle and probe read the same stream. run-parity added so CI runs it.
🤖 Agent: queued at 1755ea5c93. Both rest-003 rows resolve: Status::should_narrate_system_or_chat matches NarratorStatus's ALL/SYSTEM/CHAT test, and GameNarrator's home now names horizon-narrator plus game/narrator.rs instead of the stale 'no text-to-speech' note (ledger check: 2 rows, 0 unresolved).
🤖 Agent: queued at 621b9a1b3f. FixedBiomeSource.findBiomeHorizontal row → stronghold.rs RingSearch::find_fixed; the cited symbol is on main and the row parses (ledger check: 1 row, 0 unresolved). findClosestBiome3d rightly left on Pipeline::find_closest_biome.
🤖 Agent: queued at e4e5eeab7c. All 17 changed rows check out against origin/main (each cited file and symbol exists), for example ContainerEventHandler.preeditUpdated port → crates/horizon-client/src/game/ime.rs::ime_preedit.
🤖 Agent: queued at 44ace2db1c. All 1 changed rows check out against origin/main (each cited file and symbol exists), for example MultiLineEditBox.setValue port → crates/horizon-client-render/src/multiline_edit.rs::MultiLineEdit::set_value.
🤖 Agent: queued at 45717920ab. All 5 changed rows check out against origin/main (each cited file and symbol exists), for example MessageArgument.getMessage port → crates/horizon-client/src/game/commands.rs::Game::expand_selectors.
🤖 Agent: queued at adb8f08038. All 3 changed rows check out against origin/main (each cited file and symbol exists), for example SubShape.clampToShape inline → crates/horizon-core/src/geometry/shape/face.rs::VoxelShape::calculate_face.
🤖 Agent: queued at 6ce919736a. MovingPistonBlock.destroy port: removing a moving_piston now takes an extended base behind it (update.rs destroy_moving_piston_base), and an unextended base stays. On this head: horizon-world level:: tests 97/97 pass, including both moving-piston tests side by side with #7122's, and clippy -D warnings is clean.
Step 2 of #7118. Vanilla's JsonRpcNotificationService sends serverSaveStarted and serverSaveCompleted
around DedicatedServer.saveAllChunks (the save-all command, the autosave, the pause) and playerJoined and
playerLeft with the player as a PlayerDto.
The game records a typed event for each (a new "saving" and "saved", beside the "join" and "leave" it already
records for the event stream), and the supervisor, which fetches those events, turns them into the
notifications. A save is told only when it ends without failing, as vanilla's is. What the game saved while
it started is left out: no client could hear of it.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: dropped items on the GPU skin path, stacked on #7089; every vanilla item state compared byte for byte with the CPU path (3104 taken, 212 left on the CPU); stress_items 209 → 498 fps on the M4 Pro.
Game::fall_step resolved a falling block's move with its own collision
context but without the world border (Entity.collectColliders); it now
goes through EntityCollisions with the border, on the server and in a
client's copy.
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PrimedTnt::tick resolved its move against the blocks alone; it now
passes the world border too (Entity.collectColliders), on the server
and in a client's copy between updates.
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.collectColliders adds WorldBorder.getCollisionShape for any
entity inside and close to the border, but ItemEntity::tick built its
EntityCollisions with `border: None` and ExperienceOrb::tick resolved
against the blocks alone, so both went through the border. Their ticks
now take the level's border (the server's and a client's copy), and
EntityCollisions gains resolve_with_context for callers that carry
their own collision context.
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at f193527cdc. The notifications go out from their own management-notify thread through an mpsc outbox, so the supervisor never blocks on a slow socket, and stopping_told makes server/stopping fire once per run, as vanilla's ServerStateNotifications do. Registered's Drop unregisters a socket on every exit path. The parity driver checks both method names against the vanilla server.
A player who allows performance statistics now also says what froze: each
frame of 250 ms or more while playing counts under the step of the render
thread it spent most of its time in (the slowest outer span, then the span
inside it that took at least half of it: renderer/r_uploads, tick/tick_mobs).
The session's perf item carries at most 16 such steps (slow_frames), with
their frame count, longest frame and longest step.
spans: a thread can record its own spans with spans off (record_here), wall
time only, and each span keeps its depth so culprit() can follow the
nesting. The render thread records only with performance statistics on:
about 5 us a frame of 100 spans on an M4 Pro, nothing without them.
Server: perf.slow_frames (JSON, migrated onto old databases), and
/v1/aggregate's slow_frames per release and step, shown from as many
installs as the groups need.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 161e81b0bf. Matches ArmorStand.hasPhysics (!isMarker && !isNoGravity): travel returns early, so a NoGravity stand neither falls, presses plates nor takes block fire, and the velocity stays put. NoGravity is written only when set, as Entity.saveWithoutId does, and the save round-trip test pins it.
🤖 Agent: queued at ea292adbe1. Only runs while a horizon-ctl goto is active, so normal play pays nothing. Keeping the failed (from, to) edges and resetting recovery only on measurable waypoint progress breaks the replan loop on the same blocked step. Small style point: the #[path = "agent_navigation.rs"] mod could be a plain game/agent/navigation.rs module.
🤖 Agent: queued at f3b26725d6. Follows TabNavigationBar.keyPressed: nothing happens without hasControlDownWithQuirk (Cmd on macOS), a digit maps to floorMod(n-1, 10) clamped to the last tab, Tab/Shift+Tab cycle with floorMod, and the click sounds only when the tab really changed (TabManager.setCurrentTab). Open rule, experiment and preparing overlays keep the keys, as vanilla's own screens on top would.
🤖 Agent: queued at efff801398. Resolving the physical key through KeyBinds::container_hotbar_key before the swap honours rebound, keypad and offhand bindings, as vanilla's hotbar key check does, and an unbound digit returns false so it still types into the search box.
🤖 Agent: queued at 5c314bd4ab. Matches WingsLayer's armorCutoutNoCull: only the elytra's own vertex range skips the preview's n.z >= 0 back-face cull, and its back faces are re-wound so they still draw, while the rest of the preview keeps culling. It runs only while the inventory preview draws.
A native mod can draw only what the HUD atlas holds, so a world map or any
other view built from its own pixels has nowhere to put them. Reserve one
1024x1024 atlas slot for it and hand the mods native_mod_image, which patches
that slot from an RGBA buffer; native_sprite then draws it cropped and
stretched like any other sprite.
Co-Authored-By: Hermes Agent (Nous Research) <[email protected]>
🤖 Agent: queued at a5cdf9fd53. Same as #7077 for boats: carts pass the border to Entity.move's collision, as vanilla's collectColliders adds WorldBorder's shape for any entity near it, and the test stops a cart-sized box exactly at the wall (0.11 of its 0.4 move).
A shadow program with a geometry stage is folded into one vertex stage
(translate/geometry.rs), which reads both stages' declarations one variable
at a time. An `out` interface block in the vertex stage was read as a single
declaration: each member after the first had its type taken for a variable
name, and that type (`float`...) was then renamed throughout the vertex
stage, so the program failed with "`far` is `float` in one stage and
`hz_vs_float` in the other" and the shadow map stayed clear.
- Both stages' blocks become plain varyings first (interface_blocks), and
`flatten` now also lowers a block with an instance array, the form of a
geometry stage's input (`vIn[i].m` reads `hz_<block>_m[i]`).
- A geometry stage's input and output blocks may share a name (GLSL 4.60,
4.3.9): its inputs read as `hz_in_<block>_<member>`, so they no longer
collide with its outputs.
- Statement reading takes every qualifier the translator knows (`sample`,
`precise`): one it missed made a type a renamed name the same way.
Part of #6812
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 2e9421e2c4. Matches Dolphin.canAttack, which returns !isBaby() && super.canAttack: a baby dolphin spares whatever hurt it and an adult still takes its attacker, and the test covers both sides.
JukeboxBlock.setPlacedBy reads the placed item's block_entity_data and sets
HAS_RECORD when it carries a RecordItem, so a Creative pick of a jukebox with a
disc keeps the disc when it is placed. The behaviour is on main; no harness case
pinned it.
🤖 Agent: queued at e38e711286. The walk is kept: one for_each_entity! filtered by Entity::is_vehicle, run as a vehicle pass before tick_boats/tick_carts and an others pass after, with each pass's retain keeping the other group's states. Mob riders cross with their vehicle through calculatePassengerTransition's offset and turn (move_passengers), and the window's own rider still travels by its own trip. Per-tick cost: the second pass walks the entity lists once more, filtered before any work. Main merges cleanly; clippy -D warnings and the portal tests pass on playhorizon, mod-api --check passes; run-parity is the after-check.
🤖 Agent: queued at c928a17802. Matches MovingPistonBlock.useWithoutItem: an orphaned moving piston (no block entity) is removed on the server, and one still in flight passes. I pushed one fix: the removal returns Consume, not Success, because vanilla returns CONSUME, which uses the action without swinging the arm (InteractionResult::should_swing). The two moving-piston tests pass.
Since #5446, breathing.rs and menu.rs compile into horizon-client-lib,
but native-hooks.json still targeted horizon_client::breathing::… and
horizon_client::menu::…, so horizon-mixins refused a bundle naming
either hook — no mod could use them. The targets name horizon_client_lib
now, and catalog_matches_hook_points expects that.
The test also checks every target's crate really compiles its module:
the first path segment after horizon_client or horizon_client_lib must
be a module of that crate's lib root. A file moved between the two
packages then fails the test even if the catalog string and the
expectation agree — which is how these two went unnoticed.
Proven with horizon-mixins apply on this branch: minimal bundles on
living_breathe and menu_slot_rules by name both reach status ok, their
helpers compiled into the staged breathing.rs and menu.rs. The module
check was proven the same way: with the old target and the expectation
made to agree, the test still fails on horizon_client::menu.
Fixes#6662.
Co-Authored-By: ZCode AI agent (GLM-5.3)
🤖 Agent: queued. This fixes every PR's parity, which has failed in its first step since #6959. Checked on the 8 preparations of the two Mac runners: each gives OUTPUT_VERSION 3's digest again with the new rules. The one risk: the files left out could hide a real change to the output. They are all copied or written by something other than the export, and en_us.json / deprecated.json, which come from the JAR, still count (tested).
MovingPistonBlock.useWithoutItem returns InteractionResult.CONSUME when it
removes an orphaned moving piston, so the player's arm does not swing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 095f156d94. All 16 changed rows check out against origin/main, for example CreateWorldScreen.openFresh → crates/horizon-client/src/title.rs::Title::new_world.
Every PR's parity job failed since #6959 with "the prepared output changed
(23198 files) but OUTPUT_VERSION is still 3": CI now clones the 142 other
languages and pack.mcmeta into a preparation, and the digest counted them.
It also counted asset-index.json (the sounds step) and the game's run-time
caches (horizon-sprites.cache, horizon-data.bin), so it depended on whether
a harness had run the game before the check.
With these left out, the 8 preparations on the two Mac runners (old and new,
with and without languages) all give OUTPUT_VERSION 3's digest again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 8eaecfcd5b. All 8 changed rows check out against origin/main, for example BoggedModel.setupAnim → crates/horizon-client/src/mobs/anim.rs::setup.
🤖 Agent: queued at 1ff67bc1a9. All 5 changed rows check out against origin/main, for example ClientChunkCache.replaceBiomes → crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunks_biomes.
🤖 Agent: queued at a7f1e9eac5. All 11 changed rows check out against origin/main, for example ClientPacketListener.handleMinecartAlongTrack → crates/horizon-client/src/game/remote.rs::Game::remote_move_minecart.
🤖 Agent: queued at 412583dfb5. All 4 changed rows check out against origin/main, for example Level.loadedAndEntityCanStandOnFace → crates/horizon-client/src/mobs/shulker.rs::can_stay_at.
🤖 Agent: queued at 37b48246bb. All 10 changed rows check out against origin/main, for example NoxiousGasCloudParticle.tick → crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle.
🤖 Agent: queued at fe27ba6a99. All 3 changed rows check out against origin/main, for example ServerPackManager.pushLocalPack → crates/horizon-client/src/server_packs.rs::ServerPacks::push_local.
tools/port_map/game2-011.tsv: FixedBiomeSource.findBiomeHorizontal is the
fixed-source path of the ring search, stronghold.rs::RingSearch::find_fixed,
not an override that needs its own port.
🤖 Agent: queued at 82c4c758c5. Matches loadBlockStateDefinitionStack: a stacked blockstate file keeps every pack's definition bottom first under horizon:layers and the catalog binds them in turn, so CraftEngine's powered-only fence gate keeps vanilla's unpowered states. A single definition stays the file as written, so the common case adds no work. I merged main (the blockstate read now goes through prepared_files::read_to_string); clippy and the render crate's packs/models tests (47) pass on playhorizon.
Potent sulfur's noxious gas particles, the spyglass particle hiding,
the spore blossom cap and the nine-slice metadata check are all in the
tree now, and one citation that named no function is repointed.
`Dolphin.canAttack` is `!isBaby() && super.canAttack(target)`, so a hit on
a baby dolphin is never answered. Horizon's target goals asked
`spares_target`, which knew about a golem sparing creepers and nothing
about dolphins, so a hurt baby dolphin fought back.
Corrects the Dolphin.canAttack row in game-010.tsv.
The data file now holds worldgen/structure (1,511 templates, 4 MB): world
generation loaded each template from its own file, and /place's suggestions
listed the tree. The file's version goes to 2, so a version 1 file (without
them) is refused and written again.
prepared_files::files_under lists a held folder from the file's names, with
no disk listing. The texture scanners that each walked the texture folders
(the atlas's texture list, the HUD atlas, Fusion's .mcmeta scan, the GUI
sprites' nine-slice metadata, OptiFine's random, blink and emissive scans)
use it for the vanilla root; packs and mirrors are still walked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#6965 removed the in-frame mentions from hook doc comments in native_hooks.rs
without regenerating mod_api/generated.rs, so generated_output_is_current fails
on main and on every branch merged with it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FallingBlockEntity.tick spawns the item of a block it cannot place with
spawnAtLocation, at the entity's own position. Horizon popped it like a
mined block, somewhere inside the eggs' cell, where it was pushed out
sideways and ended up as far as 2.5 blocks away. The anvil's break sound
(Fallable.onBrokenAfterFall) now plays only when the item drops, and the
item's motion comes from its own random, not the level's.
Part of #5858.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pearl thrown at the border from inside ends its tick on it, 1.0E-5F inside
maxX (the inset WorldBorder.clampVec3ToBound's class file holds), facing east,
with its oldPosition inside for ThrownEnderpearl's teleport. Each axis clamps
on its own, maxX itself counts as outside (isWithinBounds), and a block hit
inside, a ray staying inside or a ray from outside are left alone.
Refs #7078
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 83060f64a7. All 6 changed rows check out against origin/main, for example ScreenEffectRenderer.renderItemActivationAnimation → crates/horizon-client/src/game/totem.rs::Game::item_activation_geometry.
ProjWorld::clip now follows CollisionGetter.clipIncludingBorder: a ray that
starts inside the border and would end (or hit a block) outside it stops on
the border, at WorldBorder.clampVec3ToBound of that point, facing
Direction.getApproximateNearest of the travel. Thrown projectiles, fireballs,
shulker bullets and rockets all clip through it in vanilla
(ProjectileUtil.getHitResultOnMoveVector), so a pearl now lands on the border
and ThrownEnderpearl.onHit teleports its owner to oldPosition, inside.
The border goes in ProjWorld rather than MobWorld; the client's own
projectile ticks use it too, as a vanilla client level does.
Refs #7078
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 3ccde697e4. All 6 changed rows check out against origin/main, for example AbstractNautilus.isAggravated → crates/horizon-client/src/mobs/nautilus.rs::aggravated.
🤖 Agent: queued at 6e39c3a1e7. All 4 changed rows check out against origin/main, for example EquineSaddleModel.setupAnim → crates/horizon-client/src/mobs/anim.rs::setup.
🤖 Agent: queued at 59a29e1ca4. All 10 changed rows check out against origin/main, for example PathNavigation.shouldRecomputePath → crates/horizon-client/src/mobs/nav.rs::Mob::should_recompute_path.
tools/port_map/game2-010.tsv: four rows left missing with an unsure note
now cite where the behaviour is - Level.loadedAndEntityCanStandOnFace,
BlockGetter.clipWithInteractionOverride, ChunkPos.distanceSquared and
LevelReader.findBlocksInBoxByManhattanDistance.
🤖 Agent: queued at df7f4fbd7c. Matches vanilla: randomTeleport clamps the target through WorldBorder.clampVec3ToBound (Mth.clamp to max - 1.0E-5F) before the column search, and mob moves collect the border's collision shape like any entity's. The enderman test checks the landing x against an independently stated border edge.
🤖 Agent: queued at d5f4c80643. Re-merged main four more times (after #6923, #7003, #7022, #7037 and #7071). Each time the in-frame pieces main touched stay removed: host_entity params, the host's Tab entry, Keys in boats, the in-frame branch of open_world (#7071's log line kept). clippy -D warnings and the 239 lan tests pass on playhorizon at the fourth merge, clippy again at this head; run-parity is the after-check.
Eleven rows of the client-002 batch said missing while main has them: the minecart's rail steps,
chunk biomes, the transient block, the totem the death protection pops up, the level loading
screen, look-at and the spectator camera, enforcesSecureChat, the simulation distance, the
crosshair entity the suggestions offer and the level-chunks-load-start event.
Three rows still said a behaviour was missing while the tree has it:
server biome changes reach the client, UpdateTags is read in
configuration, and sounds over ten blocks are distance-delayed.
Two rows recorded gaps that have since closed: the code of conduct is
shown and answered before the connection continues, and a precipitation
column that is not loaded answers none.
Eight missing rows of the client-007 batch are in the code: the bogged's sheared mushrooms, the
arrow's wobble while it shakes, the shulker bullet's model and its tumble toward the flight, the
player's cape and ears layers, and the flower bed's 0.25 shift on a copper golem's antenna.
Six WorldOpenFlows rows and four create-world rows still recorded gaps
the tree has closed. The world's own resources.zip is pushed as a local
pack before the connect, the create screen waits on createWorld.preparing,
Re-Create carries the old world's packs over, the temp pack folder is
removed once installed, the picked data packs reach the new world, an
unreadable level.dat opens the recovery screen, and opening an old world
runs under the file-fixer progress screen.
🤖 Agent: queued at 82b12d654f. All 444 uk_ua keys exist in en_us and keep the same %s/%n$s placeholders (checked by script against main), so no translated string can drop or reorder an argument at format time.
The screen's key path never reached its tab bar: only a mouse press on the
tab strip switched tabs, while Ctrl+1..9 did nothing and Ctrl+Tab moved the
keyboard focus on instead. TabNavigationBar.keyPressed takes both while Ctrl
is held, before the open tab's own widgets see the key.
Horizon's status every 50 ms after each teleport, and the vanilla driver's
loaded chunk count and the chunks it holds within the render distance minus
one, in one JSON-lines file: when the columns arrive against when the load
ends.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verify green on this exact head (retargeted to main after #7055 merged). The one ordering-sensitive reader is the client biome table: it keeps vanilla's file-name order and its flat folder, so biome indices don't move.
net.rs moved to horizon-client-lib in the client library split (42a5981e94).
`cargo test -p horizon-client --lib` no longer has net::tests, so the
harness ran zero tests and compared vanilla's cases against an empty list:
parity-main failed on lan_auth and bisected it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The dolphin's item play (pickup, the swim-to-item goal and its drop) and
its GotFish/Moistness save are in the tree, but those eight rows still
said missing. Two other rows listed two functions in `where`, which
`map-check` cannot read, so it reported them as dangling.
A cart is 0.98 wide: rolling east at its top speed of 0.4 a tick, 0.11 blocks
short of the wall, it must be stopped by the border's collision shape. The
expected 0.11 is worked out by hand from the cart's box and the wall, not read
from the code under test.
Refs #7067
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at ae0705eedd. All 3 changed rows check out against origin/main (each cited file and symbol exists), for example BackUpIfTooClose.create inline → crates/horizon-client/src/mobs/piglin.rs::too_close.
🤖 Agent: queued at 02696a0e62. All 5 changed rows check out against origin/main (each cited file and symbol exists), for example StringTagVisitor.handleKeyEscape inline → crates/horizon-client-core/src/snbt.rs::Pretty::compound.
🤖 Agent: queued at 54a006d4ef. All 23 changed rows check out against origin/main (each cited file and symbol exists), for example ConfirmScreen.getNarrationMessage port → crates/horizon-client/src/game/screen_info.rs::confirm.
🤖 Agent: queued at 160c052dbe. All 1 changed rows check out against origin/main (each cited file and symbol exists), for example CommonComponents.joinForNarration port → crates/horizon-client-render/src/widgets.rs::join_for_narration.
🤖 Agent: queued at 63e748b461. All 25 changed rows check out against origin/main (each cited file and symbol exists), for example RemotePlayer.aiStep inline → crates/horizon-client/src/game/remote.rs::Game::tick_remote_entities.
🤖 Agent: queued at 68eede4deb. All 4 changed rows check out against origin/main (each cited file and symbol exists), for example ClientboundAddTransientBlockPacket.write inline → crates/horizon-client/src/game/transient_blocks.rs::Game::add_transient_block.
🤖 Agent: queued at c3ecf70704. All 17 changed rows check out against origin/main (each cited file and symbol exists), for example VillagerRenderer.getShadowRadius port → crates/horizon-client/src/shadows.rs::mob_shadow_radius.
🤖 Agent: queued at bee28cddb6. All 1 changed rows check out against origin/main (each cited file and symbol exists), for example RepeatedNarrator.narrate skip:service → .
🤖 Agent: queued at ea0c475c02. Writing through IShellLinkW + IPersistFile::Save gives the shell its target ID list, which the hand-laid lnk lacked. The windows crate is MIT and already in the lockfile through gpui. CI builds Windows only at release, so this relies on your Windows 11 run (437 launcher tests), and the new test reads the target back with GetPath, the exact failure. Small regression: the old temp-and-rename write was atomic and Save is not; harmless for a shortcut.
🤖 Agent: queued at 9fe8a7d1ce. All 13 changed rows check out against origin/main (each cited file and symbol exists), for example Checkbox.updateWidgetNarration inline → crates/horizon-client/src/screen_narrator.rs::narrate.
The four missing rows of the client-006 batch are in the code: the equine saddle lines that show
only while the horse is ridden, the copper golem's antenna block transform, and the spear's STAB
swing in third and first person.
🤖 Agent: queued at b921e99809. All 3 changed rows check out against origin/main (each cited file and symbol exists), for example Gui.update inline → crates/horizon-client/src/game/narrator.rs::Game::narrate_screen.
🤖 Agent: queued at 3a1098e7bd. All 2 changed rows check out against origin/main (each cited file and symbol exists), for example StatsScreen.GeneralStatisticsList.Entry.getNarration inline → crates/horizon-client/src/game/stats_screen.rs::Game::draw_stats.
🤖 Agent: queued at 622bd29d4f. All 2 changed rows check out against origin/main (each cited file and symbol exists), for example WorldSelectionList.updateWidgetNarration inline → crates/horizon-client/src/screen_narrator.rs::narrate.
🤖 Agent: queued at ba4d706f00. All 1 changed rows check out against origin/main (each cited file and symbol exists), for example AccessibilityOnboardingScreen.init port → crates/horizon-client/src/title/onboarding.rs::Title::onboarding_buttons.
🤖 Agent: queued at 36744cd76c. All 14 changed rows check out against origin/main (each cited file and symbol exists), for example ServerLevel.EntityCallbacks.onDestroyed inline → crates/horizon-client/src/lan/waypoints.rs::Waypoints::untrack.
Vanilla's JsonRpcNotificationService broadcasts to every connection of the ManagementServer. Horizon
served the protocol but never sent one. The supervisor now keeps the upgraded connections and a queue
with a thread that writes to them, so the game's output pump and the supervisor never wait on a slow
client. A notification is a JSON-RPC request without an id, its one parameter in an array, as
JsonRPCUtils.createRequest writes it.
server/started goes out at the game's Done line (vanilla: DedicatedServer.initServer, after Done).
server/stopping goes out when the supervisor starts a stop or a restart, or when the game's own stop
logs "Stopping server" (vanilla: DedicatedServer.stopServer, first of all), once per run of the game.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
tools/port_map/game-013.tsv: six rows that said the behaviour was missing
now cite the fn that does it - AbstractNautilus.isAggravated, the
ZombieNautilus and Sheep implicit component, and the three
Rabbit.RaidGardenGoal methods.
🤖 Agent: queued at 828ddff14f. Dyna's clone finding is fixed: release_bow borrows the caller's stack, so a release with no mod loaded costs one empty dispatch. bow_release_power keeps getPowerForTime's (f*f + 2f)/3 capped at 1 after the projectile lookup, in vanilla's order. Keeping has_ammo as the real release-time answer is fine. Merged with main; xtask mod-api --check passes.
🤖 Agent: Metal's fast math fused the skinning shader's transform_point3 (1 ulp off the CPU on the Mac mini, exact on lavapipe); xor with an opaque zero forces glam's rounding order; the shader test now passes on Metal.
🤖 Agent: queued at e589de1c48. Runs only on the F3+S press, writing into the player's own screenshots/debug as TextureUtil.getDebugTexturePath does. The size check before encoding turns a freed-pixels atlas into an error instead of a corrupt PNG. The test decodes the written PNG and checks the dumpSpriteNames list against independent texel rects.
🤖 Agent: queued at c2ae983c93. Two lines per join or world open, no per-frame cost. The connector counter starts at #1 as ConnectScreen.UNIQUE_THREAD_ID does, the port defaults to 25565 from parse_address before SRV as ServerAddress logs it, and friend: invite codes never reach the log. The test pins the strings launchers parse, a machine-read contract worth pinning.
The post_effect chain system is ported, so the ShaderManager chain rows
are no longer missing, and the totem's item activation citation is
repointed to the function that builds it.
🤖 Agent: queued at 716ff3eaa1. MSedgeMC merged main; the death_message_reaches row resolves on origin/main (PlayerList.broadcastSystemToAllExceptTeam).
🤖 Agent: queued at c59fe9230f. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 5 rows resolve on origin/main, for example Allay.hasNonMatchingPotion → crates/horizon-client/src/mobs/allay.rs::considers_item_equal.
🤖 Agent: queued at 56d9b22e43. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 3 rows resolve on origin/main, for example ToggleTooltips → crates/horizon-client/src/game/commands/items.rs.
A vanilla world loaded in Horizon and saved lost its in-flight wind charges and
breeze wind charges (saved_with_chunk left them out although fireball_to_nbt
writes them), turned a lingering potion entity into a splash one when its Item
was a splash potion (the entity type was derived from the item), and could not
summon a painting or item frame (block_pos was required; vanilla defaults it to
the block holding the position). saved_with_chunk now has no wildcard arm so a
new projectile kind must be decided there.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at a42361a56b. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 4 rows resolve on origin/main, for example StructureTemplate.fillFromWorld → crates/horizon-client/src/game/structure_blocks.rs::Game::template_from_world.
🤖 Agent: queued at e57a64bc78. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 2 rows resolve on origin/main, for example Mirror.mirror → crates/horizon-world/src/worldgen/structure/processor.rs::turn_facing.
🤖 Agent: queued at c18178c420. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 7 rows resolve on origin/main, for example FireworkRocketEntity.addAdditionalSaveData → crates/horizon-client/src/entity_nbt.rs::firework_rocket_to_nbt.
🤖 Agent: queued at 24df4186a9. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 18 rows resolve on origin/main, for example StopAdmiringIfItemTooFarAway.create → crates/horizon-client/src/mobs/piglin.rs::Memories::admire_seen.
🤖 Agent: queued at cc097bf58b. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 19 rows resolve on origin/main, for example SectionUpdateTracker.hasAllNeighbors → crates/horizon-client-render/src/streaming.rs::Streaming::update.
🤖 Agent: queued at 89dbf44981. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 1 rows resolve on origin/main, for example FlatLayerInfo.heightLimited → crates/horizon-client/src/title/flat.rs::FlatSettings::from_tag.
🤖 Agent: queued at fe2fc60800. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 7 rows resolve on origin/main, for example SectionRenderDispatcher.RenderSection.getVisibility → crates/horizon-client-render/src/gfx.rs::chunk_visibility.
🤖 Agent: queued at e4d76873f8. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 4 rows resolve on origin/main, for example ServerPlayerGameMode.isInRangeOfGround → crates/horizon-world/src/world_movement.rs::in_range_of_ground.
🤖 Agent: queued at a88bd5c392. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 6 rows resolve on origin/main, for example SpawnerBlockEntity.setEntityData → crates/horizon-client/src/mobs/spawner.rs::Spawner::set_entity_data.
🤖 Agent: queued at d9eb111588. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 2 rows resolve on origin/main, for example Gizmos.rect → crates/horizon-client/src/game/debug_renderers.rs::Game::solid_face_shapes.
🤖 Agent: queued at 3d2e0c7a3f. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 18 rows resolve on origin/main, for example WorldOptionsScreen.updatePersonalGameModeButton → crates/horizon-client/src/game/lan_screen.rs::WorldOptions::buttons.
🤖 Agent: queued at 29792b2c0c. Requeued after a main merge (its 09:50 verify failure was main's stale mod-API output, fixed by #6982). All 16 rows resolve on origin/main, for example AbstractCommandBlockEditScreen.mouseScrolled → crates/horizon-client/src/game/command_blocks.rs::Game::command_block_scroll.
The row's gap blamed a missing narrator. It exists, so the note now says
what is actually different: the Narrator button is hardwired disabled and
labelled Not Available where vanilla uses the narrator option's own button,
and handleInitialNarrationDelay's line is not said.
The two skip:ui rows said the narration had no narrator to feed.
WorldSelectionList.updateWidgetNarration is narrate's list arm and
WorldListEntry.getNarration is title.rs::world_narration, with the
experimental-settings join noted as the one part Horizon cannot read.
Both getNarration rows were skipped as "no narrator output". The stats
screen records both texts in Game::draw_stats: narrator.select(<label>
<value>) for the general list and narrator.select(joinForNarration(kills,
killed by)) for the mobs list.
Gui.update, AbstractSelectionList.getRowBottom and narrationPriority were
skipped as "no narrator". Gui.update's narration half is Game::narrate_screen
over ScreenNarrator::frame; getRowBottom is computed in the LAN-found
announcement that is its only caller; narrationPriority is Ctx::priority.
Thirteen rows said Horizon has no narrator. The narrator exists, so each
row now names the fn or arm that answers it: narrate's checkbox, cycle,
edit-box, text, container-list and list arms, narrate_row, the lock
button's narration in lan_screen.rs, the stats screen's item row, and the
command suggestions' three narration fns in chat.rs.
Two rest-003 rows still said narration does not happen. NarratorStatus's
system-or-chat gate is Status::should_narrate_system_or_chat, and GameNarrator
is a struct in horizon-narrator with the game-side glue in game/narrator.rs.
The class row now names those files instead of claiming the class is missing.
The row said Horizon had no narrator. The narrator is in the tree, but this
class was never missing because of that: RepeatedNarrator is the Realms
long-running task screen's rate limiter around GameNarrator.saySystemNow, and
Horizon offers no Realms. The row now says that, with skip:service.
All seventeen missing rows of the client-005 batch said a layer or a renderer is not drawn while
main draws it: the shadow radius a baby villager halves, the cape (its gate, its submit and the
cape state), the crossed-arms, fox-held, dolphin-held, iron golem flower, copper golem antenna,
worn player head and shoulder parrot layers, the deadmau5 ears, the spyglass held to the eye, a
happy ghast's ropes and the arrows and stingers stuck in a body.
Three of the four had a prose where-column (a path with a code excerpt, or a
file and line number) that map-check cannot resolve, and the block entity data
row pointed at two call sites rather than the one that sends the packet for a
tick's block updates. They now name Game::add_transient_block,
Hub::broadcast_block_changes and View::changes, and map-check is clean.
The client tutorial is ported in game/tutorial.rs, so the nineteen rows that
said Horizon has no tutorial steps now cite the step functions; the Entity
Distance option does reach a LAN host's tracking, and four citations the
earlier pass left without a function name are repaired.
The row still said Horizon had no narrator output, so joining a screen's
title and message for narration went nowhere. The narrator is in the tree and
widgets::join_for_narration is CommonComponents.joinForNarration: the parts
joined with ". ", which is vanilla's NARRATION_SEPARATOR. Every narrated
screen with a title and a message builds its narration string with it.
Horizon has friends (horizon-friends, the Play with Friends screen): an
invite code for the open world. The seven FriendToast rows said Horizon has
no friends system at all. They keep skip:service, since vanilla's friend
toasts belong to the friend-request service and Horizon shares a world by
code with no requests to toast.
Ten rows of client2-012.tsv were stale. map-check reported three of them:
ime_preedit has left the game.rs module root for game/ime.rs, and two rows
cited two functions joined by "+", which the ledger reads as one name and
reports as no fn; they now cite the function that does the job (act, and
tab_step for the tab step act calls).
The other seven described a gap or a reason the tree no longer has. The
screen narrator landed, so the three "Horizon has no narrator" tab rows are
ports of screen_narrator.rs::narrate's tabs branch and Ctx::priority. The
Stats tabs are disabled while empty (stats_tab_active). The tutorial toasts
are drawn (Game::draw_tutorial_toasts), so the two TutorialToast rows are no
longer missing. onFileDropFailure keeps its verdict but not its reason:
dropped packs are copied now and only logged when they are not a pack.
Twenty-three rows were stale. Fifteen said a screen's narration needed no
port because the narrator was missing: crates/horizon-narrator and
screen_narrator.rs exist now, so they cite the function that does each job
(screen_info.rs::confirm and ::level_loading, Title::narration,
Screen::narration, the five Screen narration functions in
horizon-narrator/src/screen.rs and screen_narrator.rs), and the two that
belong to screens Horizon does not have are missing rather than skipped.
The other eight: the pause menu's report bugs, feedback and player reporting
icons are live and its now-playing song is drawn by draw_music_toast; the
backup's waiting screen does hold input; Horizon installs data packs, so the
datapack failure screen is missing because it does not exist, not because
packs are never loaded; and the upgrade progress screen and its Cancel do
exist, with no aborted screen after them.
LivingEntity.randomTeleport clamps its target with
WorldBorder.clampVec3ToBound (Mth.clamp to [min, max - 1.0E-5F] on x
and z) before looking for ground; Enderman.teleport skipped it, so an
enderman could land outside the border.
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Entity.collectColliders adds WorldBorder.getCollisionShape for any
entity inside and close to the border, but Mob::do_move resolved its
moves against the blocks alone, so mobs walked through the border.
MobWorld now carries the level's border and do_move resolves through
it (SceneCollisions::resolve_in_border), keeping the mob's collision
context (lava walkers).
Refs #7080
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: the registries.json and data-folder caches assume the prepared resources don't change while the game runs, the same assumption the recipe and loot id cache already makes; a missing registries.json is not cached, so a repaired install can still join.
Each dropped item pushed ~168 vertices per frame from the CPU (2000 items:
336 000 vertices, 14.8 MB, then sorted by material and uploaded). Its item
model now goes to the entity skin store once, keyed by its baked model's Arc,
and a frame sends one job and one part per run of quads sharing a sprite and
tint: entity_skin.wgsl writes the same vertices in the same order.
An item job (SkinJob::item) takes its colour by face axis from the part's
normal words, shaded on the CPU exactly as WorldSink does, and writes the
CULL_BACK normal: the GPU only copies and does transform_point3 and the UV
lerp in glam's order, as for mobs. States with a special model, a glint, a
glowing or translucent quad, and glowing items stay on the CPU path.
The new test draws every vanilla item on the ground both ways and compares
the vertices byte for byte: 3104 states on the GPU path, 212 on the CPU.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The display entities' synced transforms are read and written: the entity data
codec's VECTOR3 and QUATERNION arms carry translation, scale and the two
rotations, so the four FriendlyByteBuf vector rows are inlined there.
DismountOrSkipMounting.create and isVehicleValid were mapped missing when
piglins never rode anything. Piglins now ride baby hoglins: the ride
activity mounts (piglin.rs::mount) and dismounts through
dismount_or_skip_mounting, which makes the isVehicleValid test itself, so
that row is inline.
BackUpIfTooClose.create cited the goal struct where map-check wants a fn;
it now cites too_close, the fn that holds the create(5, 0.75) check.
The other missing rows in the file (SteppedInterpolationTracker.addStep,
updateTracking, getPositionPath, AttributeMap.assignAllValues,
assignBaseValues, assignPermanentModifiers, AnimalPanic.lookForWater) were
checked again and are still absent, so they are left as they are.
move_cart passed no border to EntityCollisions, so a cart rolled past the
world border. Entity.move applies CollisionGetter.borderCollision to every
entity, as the boat and the player already do.
Fixes#7067
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Two SubShape rows pointed at face.rs lines that no longer hold the clamp and
the occupancy copy (calculate_face was restructured), and
LevelChunkTicks.scheduleUnchecked said its port was a push with no duplicate
check while ChunkTicks::schedule refuses a repeat through positions.insert;
the unchecked push vanilla unpacks with is ChunkTicks::unpack.
Verdicts and citations are unchanged; only the notes move.
wgpu builds Metal libraries with the default compile options: fast math on.
Metal then fused entity_skin.wgsl's transform_point3 into fused multiply-adds
and its sums out of glam's order, so posed vertices came out an ulp off the
CPU's (the_shader_writes_what_the_cpu_reference_computes failed on the Mac
mini; lavapipe and CI were exact). Each product and sum now goes through an
xor with a zero read from the job, which the compiler cannot fold: every
operation rounds on its own, in the CPU's order.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The text filter exists (chat_filter_command) and a source without a player
gets a disguised chat of type say_command, so three rows that read missing or
carried "no text filter" gaps now name where each is done.
Slice 10 of #6509 (save half of #6545). entity_chunks now walks every
list in entity_lists! with one Entity::save_nbt per kind (static dispatch,
no per-walk allocation). A kind added to the lists cannot be left out of
saving. The load side stays as is. Entities in a chunk are written in
list order instead of the old loop order.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Splitscreen setup, its controller prompts, the repro recording shortcuts and
messages, the block entity caches, BlueMap and the HDR nits that were added
in English only. Block names follow vanilla's uk_ua (Шалкерова коробка,
Оздоблений горщик, Стяг, Статуя мідного ґолема); every %s and %.2f stays.
uk_ua.json now has every key en_us.json has.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 38b701feb0. Matches Goat.setYHeadRot: the head is clamped within getMaxHeadYRot (15) of yBodyRot after each look update, and rotate_if_necessary(head, body, 15) equals body + clamp(degreesDifference(body, head)) except at exactly 180. I pushed a test fix (horizon_core Vec3; the fixture kind's head limit set to 15 for the goat and 75 for the cow, so the clamp is what the test measures). Verify passes.
The row still said no multi-line box setter existed. MultiLineEdit is the
MultiLineEditBox port and it has one: set_value, which truncates at the
character limit and keeps the old text when the value would overflow the
line limit (vanilla's setValue(value, false)). The dialog text input's
multiline option calls it.
🤖 Agent: queued at 0f3dae18dc. Load-time only. The resource-location check (lowercase [a-z0-9_.-], '/' only in the path, no '.' or '..' segments) keeps a pack from reading outside assets/, and dynamic/ and atlas locations stay for the renderer. One mismatch to fix in a follow-up: the comments say 'resource packs over the game's own', but the read closure only joins options.resources, so a resource pack's clouds.png is not picked up yet.
🤖 Agent: queued at 9d7ea67f81. The mod_api_hooks integration test proves a mod crate can name KeyEvent and InputKeyEvent's winit::event::KeyEvent through mod_api alone. Note that re-exporting all of winit ties the mod API to our winit version, so a winit bump becomes a mod-API change. xtask mod-api --check passes on the main merge.
🤖 Agent: queued at cda58134c6. get stays get_existing-or-default, so every existing caller keeps its behaviour, and get_existing returns a decoded entry without re-encoding it (only a saved Raw entry is decoded and stored). has() is one map probe and never decodes. horizon-world's attachment tests (5) pass on the main merge; xtask mod-api --check passes.
Rows in tools/port_map/animal-1.tsv went stale after they were mapped:
three say the behaviour is missing while the Rust code has it, and two
cite a fn that was renamed when the bucket loader became the shared
item-to-mob hook.
- Allay.hasNonMatchingPotion: mobs/allay.rs::considers_item_equal compares
the potion contents of the held stack and the candidate, so the row is
inline, the id-only gap kept.
- Chicken.applyImplicitComponents and applyImplicitComponent:
entity_nbt.rs::apply_item_components reads chicken/variant and
chicken/sound_variant off the stack that spawns or buckets the mob.
- Axolotl.applyImplicitComponent and Axolotl.loadFromBucketTag cited
load_bucket_data, which the spawn-egg commit renamed apply_item_components.
🤖 Agent: queued at 2e2d9f25b7. Boats now pass the border to Entity.move's collision, as vanilla's Entity.collide adds the world border shape for every entity when isInsideCloseToBorder holds. The cost is one geometry() copy per boat per tick.
🤖 Agent: queued at b07a0ccaa4. No behaviour change intended, and the per-tick cost is equal or lower: get_gravity() is a statically dispatched trait default (NoGravity ? 0 : default) replacing eight inline checks, and TNT's 140-line TntFluidWorld copy goes through update_entity_fluids with the same 0.014 water and 0.007/0.0023 lava currents. One difference: a missing fluid state no longer aborts TNT's tick with an io error. Verify passed; run-parity (tnt, entity physics) is the after-check.
🤖 Agent: queued at 0d5f7f228d. All 16 changed rows name a file and symbol that exist on origin/main, for example ConfirmExperimentalFeaturesScreen.init → crates/horizon-client/src/title/confirm_experiments.rs::ConfirmExperimentsScreen::new.
🤖 Agent: queued at cee65d7ce2. On the main merge every @API:<name>@ marker in crates/ and docs/ now names an existing tools/native-hook-fragments/<name>.json (checked by script, no orphans), and tests.test_stamp_native_hooks passes (6). Sorting a batch's catalog entries by file name is what makes a stamped tree pass mod-api --check, which is the cause of the #6720 and #6982 regenerations. xtask mod-api --check passes.
🤖 Agent: verify green on this exact head. The point to watch is the HUD atlas: a pack's textures still come from its mirror (the mirror skips horizon-data.bin), so only the vanilla root reads from the data file.
🤖 Agent: queued at 292df3c604. Host methods that forward to the existing native_play_sound/native_send_particles/native_on/mod_key/setting helpers, with no new game logic. One API-surface note: mod_api now re-exports crate::particles::{ParticleOptions, ParticleType}, so renaming a particle variant becomes a breaking mod-API change from here on. Merged with main; xtask mod-api --check passes.
🤖 Agent: queued at ea3611dc04. attack_effects now takes melee_damage's crit instead of calling critical_attack a second time, so a mod's forced or denied crit also controls the sweep, the sound and the particles, and the vanilla path does one check instead of two. The hook fires only for a living target with amount > 0 or a magic bonus, as Player.attack's critical branch does. Merged with main; xtask mod-api --check passes.
🤖 Agent: queued at 138cbc1254. Matches DedicatedServer.setForceGameMode: the property is updated, then enforceGameTypeForPlayers runs (enforce_guest_game_mode returns at once when force is off, the same as getForcedGameType returning null). The JSON-RPC set rejects a non-boolean before it touches server.properties or server.toml, and the parity driver covers that 'yes' case.
Review fixes from #7036: the damage gate is read before the mace's smash bonus is added, damage_multiplier starts at 1.0 when vanilla's hit is not critical, and the docs say a forced crit prevents the sweep.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Review fixes from #7030: release_bow no longer clones the held stack on every release, and the fragment says side server, where the release runs.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at 01325a2566. Matches MushroomCow.shear: one EXPLOSION at getY(0.5) with zero spread and speed, sent inside convertTo before dropFromShearingLootTable. Both the hand and the dispenser paths fire it ahead of the mushroom drops, and lan_level_particles carries it to server players (an Explosion particle needs no item encoding).
🤖 Agent: queued at 1776384ac0. Experimental minecarts: max_speed_new now reads the max_minecart_speed rule as in NewMinecartBehavior.getMaxSpeed, and lerp_steps is drained by the tracker (or cleared with no viewers), so it never grows. Minecart_steps stays None outside the experiment, which keeps the old path's tracker work unchanged. I pushed a main merge (fixing the stale mod-api verify) plus one fix: the four new yaw/pitch atan2 sites now use jmath::atan2 * 180.0 / PI in Java's order instead of to_degrees(). fmt, clippy -D warnings and the minecart tests pass; run-parity is the after-check.
🤖 Agent: queued at aee99b2ad1. All 4 changed rows name a file and symbol that exist on origin/main, for example CopperGolemStatueBlock.getCloneItemStack → crates/horizon-client/src/game/block_items.rs::Game::picked_stack.
🤖 Agent: queued at 40d90d5b80. All 8 changed rows name a file and symbol that exist on origin/main, for example LivingEntity.getFallDamageSound → crates/horizon-client/src/mobs/landing.rs::MobLanding::living_fall_sound.
🤖 Agent: queued at 925c284b3e. All 11 changed rows name a file and symbol that exist on origin/main, for example EnchantmentScreen.extractBook → crates/horizon-client/src/item_render/gui.rs::draw_enchanting_book.
🤖 Agent: queued at ab5b1640d2. All 6 changed rows name a file and symbol that exist on origin/main, for example ChunkGeneratorStructureState.createForFlat → crates/horizon-world/src/worldgen/generator.rs::OverworldGenerator::flat.
🤖 Agent: queued at 989d52bcc4. All 1 changed rows name a file and symbol that exist on origin/main, for example BellBlock.onExplosionHit → crates/horizon-client/src/game/wind.rs::Game::wind_trigger_block.
🤖 Agent: queued at 02c2eb7e58. All 3 changed rows name a file and symbol that exist on origin/main, for example GhostSlots.extractTooltip → crates/horizon-client/src/game/recipe_book.rs::Game::draw_ghost.
🤖 Agent: queued at 2775ed1cdb. All 1 changed rows name a file and symbol that exist on origin/main, for example StructureStart.loadStaticStart → crates/horizon-world/src/worldgen/generator_structures.rs::StructureState::saved_starts_at.
An armor stand summoned or loaded with NoGravity:1b fell in Horizon and
stayed put in vanilla; resting in a pressure plate it powered the plate
where vanilla's does not. ArmorStand had no NoGravity flag, so the reader
dropped it. The flag is now read and written back (only when set), the
stand does not travel with it (ArmorStand.hasPhysics / travel), is not
swept for the blocks it stands in, and is not lit by a fire block. Measured
beside the real 26.3 server; three tests added.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at 5e04ef39c1. All 2 changed rows name a file and symbol that exist on origin/main, for example GuiGraphicsExtractor.outline → crates/horizon-client/src/game/key_focus.rs::draw_outline.
🤖 Agent: queued at 39b5126ed0. All 9 changed rows name a file and symbol that exist on origin/main, for example OitPipelineSet.getPipeline → crates/horizon-client-render/src/gfx/oit.rs::Oit::terrain.
🤖 Agent: queued at 08fb1e7a80. All 4 changed rows name a file and symbol that exist on origin/main, for example Fox.FoxMeleeAttackGoal.checkAndPerformAttack → crates/horizon-client/src/mobs/goals.rs::MeleeAttack::tick.
join_server parsed registries.json (about 3 ms) and build_registries read
entity-types.json, five data folders and every timeline file (about 2 ms)
in the frame that clicks Join or opens a single-player world. Both are
now cached per resources root, like the recipe and loot ids already were,
and the title screen's registries preload thread fills them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 46399c6d43. All 7 changed rows name a file and symbol that exist on origin/main, for example GizmoFeatureRenderer.buildGroup → crates/horizon-client/src/gizmos.rs::camera_relative.
The bobber's block-hit bounce is in fishing.rs, the sulfur cube carries
Vec2.rotate, and toggle_tooltips lives in the item-modify command rather
than the loot run.
Entity.move reaches CollisionGetter.borderCollision for every entity, so the
world border blocks a boat as it does the player. tick_boat passed no border
to EntityCollisions and a ridden boat sailed through.
Refs #7065
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at 41883f70f0. Off the solo path: the pad_menu Setup routing is gated on Screen::Options plus Page::SplitSetup, and the frame-time readout's four Instant::now() calls run only inside Split's own loop (print_stats returns at once without HORIZON_SPLIT_STATS). P1Pad's released slot makes sure a cancelled or expired Give Up Pad offer returns P1's own controller, not another player's. auth() returning None for split_guest also stops guests from signing chat with P1's account, which is the right fix.
A custom texture may name a texture of the game by its resource location
(`customTexture.clouds = minecraft:textures/environment/clouds.png`, ShaderDoc
"Enhanced Custom Textures": from the shader pack, the resource pack, or a
dynamic texture). Horizon resolved only the lightmap and the block atlas and
left every other location blank ("not provided yet"). The game now reads them
from its active resources (resource packs over its own), with their .mcmeta;
the generated lightmap and atlases stay with the renderer. A location outside
a resource location's characters, or with `..`, is refused.
Part of #6812
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NewMinecartBehavior computes (float) (Math.atan2(z, x) * 180.0 / Math.PI);
Rust's atan2().to_degrees() multiplies by a rounded 180/PI and uses the
platform atan2, which can land on the neighbouring float.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
mod_api re-exported winit::event::KeyEvent, which shadowed the generated
KeyEvent<'_> that Hooks::key takes, so a mod crate could not name key's event.
mod_api now re-exports winit itself, as it does serde_json, and a test builds a
mod-side Hooks with key and input_key.
Vanilla's `KeyboardHandler` branch for `key.debug.dumpDynamicTextures` takes
`TextureUtil.getDebugTexturePath(gameDirectory)` (`screenshots/debug`), calls
`TextureManager.dumpAllSheets` and shows `debug.dump_dynamic_textures` with the
folder as a clickable link. Horizon bound the key and its action was a no-op.
`hud::dump_atlas` writes the atlas as `name.png` beside `name.txt`, as
`TextureAtlas.dumpContents` does: the PNG of the pixels and one line per
sprite, sorted by name, `%s\tx=%d\ty=%d\tw=%d\th=%d%n` in texels
(`dumpSpriteNames`). The file is `minecraft_textures_atlas_gui`, vanilla's
debug name for `minecraft:textures/atlas/gui.png` (`Identifier.toDebugFileName`
replaces `/` and `:` with `_`); Horizon packs the GUI, font, particle, map and
skin sprites into that one atlas, so it stands in for the atlases vanilla dumps
apart.
Fixes#7042.
Co-Authored-By: Hermes Agent (deepseek-v4.1-flash) <[email protected]>
"Connecting to HOST, PORT" from a "Server Connector #N" thread when a join
starts (ConnectScreen.connect: the address as given, 25565 when none, before
SRV), and "Starting integrated minecraft server version 26.3" on the
"Server thread" when a single-player world opens (IntegratedServer.initServer),
with or without a server thread. Launchers read the last of them to offer
"continue playing". A friend's invite code is never logged. Closes#7032.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Six rows said missing for gizmo geometry and outlined text that the tree
has: fills and billboard texts of the debug renderers, and
block_entity_render's draw_text. Two gap notes had closed with them.
🤖 Agent: queued at b5ffacd08a. Every one of the 6 rows names a file and symbol that exist on main (checked against origin/main), for example GeyserBaseParticle.Provider.createParticle → crates/horizon-client-render/src/particles.rs::ParticleEngine::make.
The picked statue carries its pose in block_state, a waterlogged dried ghast plays
its place-in-water sound, and farmland turning to its base block emits
pushEntitiesUp - so three player-visible gap notes on those rows are stale.
🤖 Agent: queued at 452c3268e5. Only runs at an item break, so the lan_acting_name clone is off the hot path. equipment_slot_name gives Bukkit-style slot names, and the creative skip matches hurtAndBreak's own early return. Merged with main, xtask mod-api --check passes. Note #7005's player_item_break fires at the main-hand break site too; one of the two should go before both land (Dyna raised the same overlap).
🤖 Agent: queued at eacdc5b519. Cheap when unused: sees() and the state.hidden lookup are one empty-FxHashMap probe per guest, and sync_hidden returns at once while nobody is hidden. broadcast_to_player rechecking pairs on a permission change is the same path a spectator uses. Merged with main, xtask mod-api --check passes.
🤖 Agent: queued at fb024406b9. The empty-stack early return matches GuiGraphicsExtractor.item, which draws nothing for an isEmpty stack, so a mod passing air or count 0 now gets false instead of a missing-model quad. Merged with main, xtask mod-api --check passes (no regen needed).
🤖 Agent: verify passed on b0f9532; RD 32 flat frames: translucent flat calls 125-565 → 1, main thread −9..−20 % on M4 Pro; RD 16 no regression; same picture (ocean flips = run-to-run noise). Overlap with #6804 is the same functions, not the same change.
Vanilla binds every pack's definition of a blockstate file on its own,
bottom first, and a higher one replaces only the states it binds. The
overlay took the top pack's file whole, so a partial definition (as
CraftEngine ships for powered fence gates) left every other state with
no model, and the unbound gallery state failed the whole pack.
The overlay now keeps a stacked file's definitions as layers, the
catalog binds each and gives a state its topmost one, and an unbound
gallery state is skipped as the mesher skips it. The overlay cache
version goes to 7 so built overlays are rebuilt.
Fixes#7013.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Four rows of game-012.tsv described behaviour that is in the tree now: the fox's bite
sound after a strike (`MeleeAttack::bite`), the tadpole's brain package (panic 2.0,
look at players, the food tempt, the stroll actions at 0.5) and the frog's food tempt
and mate search. `Tadpole.customServerAiStep` and `TadpoleAi.initIdleActivity` had
their verdicts raised from `inline` to `port`, since the brain lives in
`animal_brain.rs::tadpole_package` rather than in the goals `aquatic.rs::register`
adds beside it.
The file's `missing` rows were re-read and are unchanged.
Keeps native_entity_pose_change firing for server players: lan_update_models
(its only caller, which this PR removes) is replaced by a per-guest pose map on
LanHost, filled only while a mod is loaded. Drops main's posed_rider reset and
the gen_light_bench --loading flag with the in-frame code they belonged to,
and the HubConfig.dedicated field that #6993 removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Eight rows said missing for the OIT pipeline set, its builder and the
three render passes; gfx/oit.rs and gfx/oit_features.rs now build and run
them. The breeze wind row's gap note had closed too.
🤖 Agent: queued at 34ea5ed3ee. skeleton_horse_swim_sound follows SkeletonHorse.getSwimSound branch for branch (checked on the 26.3 bytecode), with gallopSoundCounter shared with the hoof steps. swim_sound now runs before the silent check, the same way playSound's arguments are evaluated before isSilent, so a silent ridden horse still advances the counter. swim_volume's 0.3 and min(0.1, v*25) match playSwimSound.
MushroomCow.shear sends one EXPLOSION particle at the mooshroom's x, half its
height up and z, before the mushrooms drop. Both the player's shearing and the
dispenser's now send it, drawn here and to the LAN guests.
Fixes#7027
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The fixture kind turns heads 75 degrees; set getMaxHeadYRot's own values
(15 for a goat, 75 otherwise) so the clamp is what the test measures, and
use the Vec3 that Mob::new takes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 8bd75ba4dd. The countdown matches MinecraftServer: ticksUntilAutosave-- then autoSave at <=0, recomputed by computeNextAutosaveInterval with (float)1e9/(avg+1) while sprinting and max(100,(int)(rate*300F)), and /tick rate only shortens a running wait. One behaviour change: set_saving(false) now follows the save in the same tick, so a reader that polls the flag a tick later never sees it true. That matches isCurrentlySaving's scope; please check the client's saving indicator still shows.
🤖 Agent: queued at 630e4dc8bc. Matches IronGolem.doPush: the instanceof checks come before nextInt(20), so a creeper, a player or an animal leaves the golem's random untouched, and it runs ahead of the same-vehicle skip as vanilla's push does. golem_prey's 'monster' category stands in for Enemy, which covers slimes, ghasts, phantoms and hoglins too. The test returns early without prepared data, so CI never asserts it.
🤖 Agent: tooling only: server_bench skips horizon-bots' spawn line (every run failed with 'Extra data' since #6619); rerun on playhorizon after the fix: 2 bots joined, 20 TPS.
🤖 Agent: queued at 018b389232. Every one of the 3 rows names a file and symbol that exist on main (checked against origin/main), for example ServerGamePacketListenerImpl.handleSetTestBlock → crates/horizon-client/src/game/test_blocks.rs::Game::lan_set_test_block.
Two client2-009 rows still described behaviour the tree has: the one-pixel
outline is key_focus::draw_outline (the keyboard focus ring) and the open
book over the enchanting table is item_render::gui::draw_enchanting_book.
client2-013's ScreenRectangle rows described keyboard focus navigation as
missing; it landed with #4638. The overlap, bound and centre tests are in
gamepad::focus_step_strict/focus_step_vague and the border strip is
key_focus::edge, while the three direction enums are gamepad::Button's DPad
values. The onboarding note no longer says the screen is missing either.
A wind charge burst on a bell rings it now: `wind_trigger_block` gives the bell its
own arm, `bell_attempt_to_ring` (`BellBlock.attemptToRing` with no direction), so the
row's "an Up-face use that bell_hit rejects, so it stays silent" no longer holds. The
verdict and the citation were already right; only the note is stale.
The file's other two `gap:` notes were re-read and still hold, so nothing else
changes.
Five rows said missing while the tree ports them (flat structure state,
post-processing offsets, block entity save tag, the old-chunk scan and
its per-record test), plus a citation whose function was renamed.
horizon-bots prints the first bot's spawn position as a JSON line of its
own (for perf_bench --server-bots, #6619). server_bench joined it to the
summary report and failed every run with "Extra data". It now skips that
line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
client2-016's missing rows describe container screens the tree has: the
enchanting book (draw_enchanting_book, tick_enchanting_book), the merchant
and stonecutter scroller drags, the smithing template icons and the armour
stand preview, the command block minecart's editor, the menu tooltip's
placement and the server list's online player names. Each row now names the
function that answers it; InventoryScreen.containerTick stays missing, since
only the creative screen's switch back is ported.
🤖 Agent: queued at 34523c59de. Every one of the 11 rows names a file and symbol that exist on main (checked against origin/main), for example ChatComponent.DrawingFocusedGraphicsAccess.isMouseOver → crates/horizon-client-render/src/chat.rs::Chat::focused_tags.
🤖 Agent: queued at 499788ffcf. Every one of the 1 rows names a file and symbol that exist on main (checked against origin/main), for example PlayerList.broadcastSystemToAllExceptTeam → crates/horizon-client-core/src/scoreboard.rs::death_message_reaches.
🤖 Agent: queued at a8a0b95dbd. Every one of the 12 rows names a file and symbol that exist on main (checked against origin/main), for example HashOps.createNumeric → crates/horizon-protocol/src/hash_ops.rs::HashOps::from_nbt.
🤖 Agent: queued at b46c36f11a. Every one of the 7 rows names a file and symbol that exist on main (checked against origin/main), for example PackFormat.lastPreMinorVersion → crates/horizon-client-render/src/packs.rs::last_pre_minor.
🤖 Agent: queued at 802d8ff743. Every one of the 26 rows names a file and symbol that exist on main (checked against origin/main), for example ClientBundleTooltip.extractBundleWithItemsTooltip → crates/horizon-items/src/bundle.rs::tooltip_layout.
🤖 Agent: queued at 8af6df80ba. Every one of the 1 rows names a file and symbol that exist on main (checked against origin/main), for example Crypt.byteToPrivateKey → crates/horizon-protocol/src/rsa.rs::RsaPrivateKey::from_der.
Villager trades, trade sets and their tags, painting variants, enchantments
and the tags the enchantment table reads, particle sprite sets, the
dimension types' and biomes' ambient sounds and the client's biome table
(and its colormaps) listed and read their folders file by file: about 1,200
opens on a world start. They read through json_folder or prepared_files
(from horizon-data.bin when current) now. The biome table and enchantments
keep their order and their flat folders.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at d979df6aa5. Every one of the 2 rows names a file and symbol that exist on main (checked against origin/main), for example Util.copyBetweenDirs → crates/horizon-client-render/src/datapack.rs::install.
🤖 Agent: queued at 3684b62938. Every one of the 2 rows names a file and symbol that exist on main (checked against origin/main), for example WeightedList.contains → crates/horizon-client/src/mobs/spawn_selection.rs::valid_position_for_type.
🤖 Agent: queued at 177805fb9d. Every one of the 1 rows names a file and symbol that exist on main (checked against origin/main), for example Frog.spawnChildFromBreeding → crates/horizon-client/src/mobs/goals.rs::Breed::tick.
The pick-the-item path was renamed to `game/block_items.rs::Game::picked_stack`, so
the row's `Game::clone_item_stack` citation dangled and `map-check` reported the row
as naming no function. The verdict and the note were already right: `picked_stack`'s
FlowerPot arm picks a potted plant as that plant and an empty pot as the pot itself.
No other row of the file is touched.
client2-019's missing rows describe screens and flows the title tree already
has: confirm_experiments.rs and experiments.rs port the two experiment
screens, Title::begin_optimize/poll_optimize/draw_optimize port the optimize
screen, and play/after_prompt/start_backup port WorldOpenFlows' questions
before a world loads. Each row now names the function that answers it.
A player's damaging melee hit on a living target asks mods whether it is critical and by what multiplier. The crit is decided once in melee_damage and handed to the sweep, sound and particles.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at 3f79ea0d00. layers_shown is two string compares per avatar per frame, which is negligible. Gating render_visible's armour 'extra' and the creatures/lan/perspective layer calls matches AvatarRenderer.shouldRenderLayers for spectators, and glowing and outlines stay outside the gate as vanilla has them.
🤖 Agent: queued at 14189dc867. The 20 scutes at y+1 with spread 0.2/0.1/0.2 and speed 0.1 match Wolf.actuallyHurt's sendParticles. One gap: hub_particle still returns None for T::Item, so server-thread and LAN players won't see them until item particles are encoded (same limit as the panda crumbs). That's a follow-up, not a blocker.
The catalog takes a batch's new hooks and helpers in file name order, the order
xtask mod-api reads pending fragments in; API numbers still follow merge order.
Stamping used to append them in merge order, which reordered generated.rs.
🤖 Agent: queued at 3a8fe38077. explosion_eye_height now reads Mob::eye_height() and the box override is gone, so a baby or a size-4 slime is aimed at and pushed from its own scaled getEyePosition/getBoundingBox, which wind (#6922) also lost. The test compares against an independent height and checks that the case actually scales.
🤖 Agent: queued at 29f7d45ac8. FireworkRocketItem.useOn returns PASS while isFallFlying (checked on the 26.3 bytecode), so a gliding guest's UseItemOn no longer launches a second rocket beside use()'s boost. The helper's test only pins a bool identity; a test through launch_firework with a gliding player would carry more signal.
Four rows of game2-022.tsv said `missing` because templates were read-only in
Horizon. The structure block's save now writes them: `game/structure_blocks.rs` has
`Game::template_from_world` (`StructureTemplate.fillFromWorld`), `template_entities`
(`fillEntityList`) and `write_template` (`StructureTemplateManager.save`), and the
palette ids of `save_nbt` map states the way `StructureTemplate.SimplePalette.idFor`
does.
The fifth `missing` row, `OceanRuinPieces.OceanRuinPiece.create`, was re-checked and
stands: ocean ruin piece NBT is written (`simple.rs::TemplatePiece::saved`) but
nothing reads it back.
🤖 Agent: verify green on this exact head (rebased on main after #6893 merged). One risk worth knowing: tags, synchronized registries and advancements now come from horizon-data.bin when it is current; a stale or foreign file is refused at open, so they fall back to the folders.
`Frog.spawnChildFromBreeding` still said `missing` in game-012.tsv, but
`mobs/goals.rs::Breed::tick` makes a breeding frog the pregnant mother
(`frog::set_pregnant`, the `IS_PREGNANT` memory) instead of spawning a baby, and the
`LaySpawn` goal walks her to water and lays the frogspawn. The row's note - that no
frog ever becomes pregnant - no longer holds, and it contradicted the row above it
(`Frog.getBreedOffspring`, already `port` to the same function).
The file's seven other `missing` rows were re-checked against `main` and are still
absent, so they are unchanged.
Markers read @API:<fragment stem>@, but #6801's hooks used @API:native_<hook>@
for fragments named <hook>.json, and ten fragments named native_<hook>.json used
@API:<hook>@. server_event listed native_server.rs as a comment file without a
marker there, and state_block listed native_hooks.rs, which is always stamped.
The stamper now also stamps markers in the docs' hook lists, and a test stamps
a copy of the checkout and fails on any marker left behind.
Six rows said missing for the sulfur geyser's particles, which
particles.rs now makes and ticks: the base puffs, the eruption, the
plumes and their provider.
HudAtlas::pack decoded every walked texture, its .mcmeta and its PNG header
straight from the folders: about 10,000 opens on a world start. They go
through prepared_files now, with read_head for the 24-byte PNG header.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 3a42c67b1a: verify green, pr_ready block in the body. One point worth a look: the languages are only copied into a preparation without assets/pack.mcmeta, so the fresh key f08f9a3e (en_us alone) gets them on its next job, and the 14 preparations fixed by hand are left alone.
🤖 Agent: queued at 3eef82f7eb. Requeued at your main merge (3eef82f7e): the only new commit merges main, and the landing-dust code I checked against `checkFallDamage` is unchanged.
🤖 Agent: queued at 805ce265cd. Requeued after merging main. The verify failure was `generated_output_is_current`, caused by main's stale mod-API output from before #6982, not by this branch. The 9 `native_mods::inject` tests passed on the merge earlier.
Frames drawn flat (Metal from render distance 32, Intel Gen11/Gen12 on Vulkan) drew translucent
sections back to front from the section arena, where consecutive sections sit in different pages:
nearly a call each (180-670 a frame at RD 32 on an M4 Pro), each with its own vertex buffer and
bind group to encode. Translucent Batching's arena keeps those quads in one or two pages, so they
now draw in a few calls; it was left out of devices that draw flat, as flat draws read translucent
quads in the section arena.
- Translucent Batching is offered wherever indirect multi-draws are (still not OpenGL).
- Flat's block tables are keyed by page key: a section arena page, or a translucent arena page
plus TRANSLUCENT_KEYS, with tables the size of the translucent arena's large pages. A section's
id names both of its ranges; moves (compaction, and now the translucent arena's shrink) keep it.
- Flat draws bind a translucent page up to its last range in use, as the per-section draws do.
- With flat draws the sorted indices keep pages flat draws can bind as storage, split or not.
- Frames whose shader pack draws terrain or a shadow pass keep the section arena (translucent_split).
The back-to-front order is the same; only the vertex buffer the indices point into changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 946745362c. The 40-pack measurement is what justifies changing the defaults packs branch on: GL/GLSL 330 and the `OTHER` vendor/renderer change no preprocessed file, and `IRIS_VERSION` and `IRIS_HAS_TRANSLUCENCY_SORTING` change exactly the Complementary-family files that test for them. Defining `OTHER` rather than the real GPU vendor is the right call, since a pack's `MC_GL_VENDOR_NVIDIA` branch asks for GL extensions wgpu can't give (RenderPearl's `GL_NV_gpu_shader5`). The Iris behaviour was observed with probe packs, so it stays clean-room. I merged main: its new normal/specular-map test now uses your `translates` helper. Clippy is clean and the 138 horizon-shaders tests pass.
SET_TEST_BLOCK, SET_JIGSAW_BLOCK and JIGSAW_GENERATE all have handlers on the
LAN host (test_blocks.rs::lan_set_test_block, jigsaw_blocks.rs::
lan_set_jigsaw_block and lan_jigsaw_generate), each refusing anything but a
creative player with game master commands. The rows that stay missing are the
ones with no handler at all: the recipe book's seen-recipe (the new-recipe
highlight is never cleared), the seen-advancement tab, and the support blocks
under a player standing on air.
🤖 Agent: queued at 715dd9811b. Matches `EnderDragon.knockBack`: the push is `d / max(d², 0.1) * 4` plus `0.2F` up from the body's centre, wing damage needs `getLastHurtByMobTimestamp() < tickCount - 2` and a dragon that isn't sitting, and the head's 10 hits sitting or not. The new `dragon_contacts` oracle drives the real private methods, which is the right proof for seven scenarios. Cost: the per-mob `allowed` vector is built only while a dragon exists. Nit: `dragon_target`'s eye is `p.y + 6.8`, where vanilla's is `8.0F * 0.85F` (6.8000001907), as #6941 fixed for the explosion push. I merged main (clean); clippy with examples is clean, and the dragon and contact tests pass. `run-parity` is on.
`Goat.setYHeadRot` clamps every head rotation to `getMaxHeadYRot` (15
degrees) around `yBodyRot`, so a goat that stands still cannot swivel its
head further than one that walks. Horizon clamped the head only while the
navigation was running, so a goat looking around in place turned its head
as far as any other mob.
Corrects the Goat.setYHeadRot row in game-012.tsv.
Entity for Mob gave the shared explosion code the adult kind's box and
eye height. Vanilla's ServerExplosion.hurtEntities reads the mob's
current bounding box and eye position, which for a baby or a slime are
the scaled ones. Beside the real server, 96 values per scene differed
for scaled mobs in a TNT blast and in a wind burst; none do now.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
WeightedList.contains is the mobs-list membership test
spawn_selection.rs::valid_position_for_type makes for canSpawnMobAt, and
LongJumpUtil.isClearTransition is breeze.rs::clear_transition, which the
breeze's long jump calls.
Util.copyBetweenDirs is folded into datapack.rs::install, which copies
Create World's picked packs into <world>/datapacks, and
AbstractSampleLogger.logPartialSample is the four slot tick sample that
debug_chart.rs::log_tick fills at once and log_full pushes.
`shortcut::lnk` lays a Shell Link out from the format's specification, which is
what lets the tests check it on every system, but it writes no target ID list,
and the shell reads the target from that list. The shortcuts #6416 installs
showed an empty target in their properties and a double click started nothing;
the instance shortcuts from Home > Shortcut are laid out the same way.
`shortcut::write_windows_link` creates the link with `IShellLinkW` and saves it
with `IPersistFile`, as every installer does. `make_in` uses it on Windows and
keeps the byte layout for the other systems and for the tests.
Two tests hold it: one reads the target back with the shell's own `GetPath`
(the bug was that it read empty), and one checks that an instance's arguments
survive the shell's write, so `plays` still finds the shortcut at uninstall.
Co-Authored-By: OpenCode <[email protected]>
Crypt.byteToPrivateKey read missing; the account chat key pair's private
key is read as PKCS8EncodedKeySpec by
crates/horizon-protocol/src/rsa.rs::RsaPrivateKey::from_der, which
auth.rs::parse_certificates calls on the certificates endpoint's PEM key.
Twelve rows still said a behaviour was missing while the tree has it:
the tooltip positioner, the code of conduct and reconfiguration screens,
the Online and Skin Customisation pages, the video page's restart line,
anisotropy greying and mode list, and pack narration.
Fourteen rows recorded a gap that has since closed: the bundle's
selected-item name box, the server list's incompatible state and player
list tooltip, the LAN narration, the sound and video rows, the 26.3
control rows, and the data pack folder watch.
🤖 Agent: queued at 82c47a7f3b. Checked the guide against Mojang's rules. Step 0 makes every step owner-only (a signed-in Microsoft account that owns Java Edition, the same gate as `horizon-launcher`), the server jar is fetched from the version JSON only for that owner, and the session goes to standard input, never the command line. There's no offline path anywhere in it. Docs only, so `skip-release` is right.
🤖 Agent: queued at 0be681d696. The cause is right: during `as_guest` the hub is out of `self.lan`, so `lan_level_particles` sent nothing, and threading `host` through `hit_shulker_bullet` restores `ServerLevel.sendParticles`' 15 crits within 32 blocks. Pulling the send loop out into `send_hub_level_particles` keeps one copy of the brand and distance rules. A follow-up worth an issue: every other particle sent from inside a guest's turn is still dropped the same way. `players.rs` already puts the hub back with `enter_guest` + `self.lan = Some(host)`, so that route could cover them all instead of a `host` parameter per call.
SkeletonHorse.getSwimSound is not the generic splash: in the water it swims,
on the ground it splashes and, ridden, it counts the water steps on the same
gallopSoundCounter its hoof steps use, galloping through the water every third
step after the first five. SkeletonHorse.playSwimSound maps the volume
Entity.waterSwimSound worked out: 0.3 on the ground, min(0.1, f * 25.0) in the
water.
Horizon had no skeleton horse entry in swim_sound and the horse is not a
monster, so every one of them swam with entity.generic.swim at the full
speed-based volume.
MinecraftServer.computeNextAutosaveInterval asks for Math.max(100,
(int)(rate * 300.0F)) ticks, where the rate is the tick rate or, while /tick
sprint runs, the measured NANOSECONDS_PER_SECOND / (averageTickTime + 1);
tickServer counts ticksUntilAutosave down and saves when it reaches zero, and
onTickRateChanged shortens a wait already running. Horizon saved on every
six-thousandth tick whatever the rate, so /tick rate and /tick sprint changed
how long a world ran between saves and a slow rate kept the same interval.
The countdown is a field on Game, the interval is next_autosave_interval, and
/tick rate now calls on_tick_rate_changed. Twenty ticks a second is still the
six thousand ticks a world autosaved on, so nothing changes at the default.
Port map: the two rest-013 rows now name those functions.
prepared_files::read reads a file from its prepared root's data file when
that holds the file's folder, where a file the data file doesn't have is
missing without asking the disk (most textures have no .mcmeta). The
texture decoder and its .mcmeta, the block and item models, the blockstate
definitions and the client item definitions read through it; resource-pack
mirrors have no data file and read their folders as before.
Part of #1961.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The item and block tags (horizon-items), the LAN sync's registries and tags
(lan/data.rs walk) and the advancement tree read their folders through
json_folder, so from horizon-data.bin when the root has one, and once for
the tables a load builds from the same folder.
Part of #1961.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 6cc25834e4. One snapshot per removal site, diffed after it, is the right shape, and `vanished_by_kind` skipping mobs spawned meanwhile is tested. The cost was the problem: `mob_ids()` ran every tick before `tick_mobs`, with a `String` per mob plus a `HashSet` afterwards, even with no mod loaded. I pushed a commit so `dropped_ids`, `orb_ids` and `mob_ids` take nothing when `mod_api::mods()` is empty, and the three `*_left_since` return at once on an empty snapshot. That also fixes the item and orb versions, which had the same per-tick cost. I merged main: `mod-api --check` is clean, clippy is clean, and 119 native tests pass.
🤖 Agent: verify and launcher green on this exact head. The risk I checked hardest: a mirror reading vanilla's bytes. The data file is used only for the canonical root it was written for, with the same horizon-resources.json, never through a symlink, and the pack and content mirrors skip it (unit-tested with a copy and a link). pr_ready content-root tests 22/22 with real resources.
`IronGolem.doPush` picks an `Enemy` that touches it, never a creeper, as
its target one time in 20, drawing the random only for those, and then
pushes it. `push_entities` only ran the Warden's touch hook, so a monster
that walked into a golem never became its target.
Corrects the IronGolem.doPush row in game-012.tsv.
dropped_ids, orb_ids and mob_ids ran every tick (and around explosions,
lightning, unloads) for nothing but native_entity_leave_level; with no mod
they now take nothing, and the *_left_since diffs return at once. Saves a
Vec + HashSet per list per tick, and a String per mob.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at acfd992b34. `ItemFrame.getSlot` via `slots_named` → `Slot::Frame` and `slot_get` covers `container.0` and `contents`, as vanilla's `SlotAccess` does. All 14 rows resolve on main.
🤖 Agent: queued at c4b8a27657. `skip_blending` jumping the factor straight to 1 or 0 is `BlendState.setImmediate`, used on login and dimension change as the note says. All 3 rows resolve on main.
Keeps main's behaviour (#6491, #6665, #6797) and this slice's single NoGravity
and fluid-push decisions: minecart bubble column goes through the trait and
keeps main's fall-distance reset, throwables keep the widened-float drag.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The HashOps create* family is ported in horizon-protocol (the component hashes the
client sends in HashedStack), the clamped map and inverse lerp formulas live at their
call sites, and the PNG signature/IHDR header is read by png_size. Twelve rows still
called them missing.
The other five candidate rows are real gaps and keep their verdict.
PlayerList.broadcastSystemToAllExceptTeam is ported: death_message_reaches returns false
for the dying player's own team under HideForOwnTeam and true for everyone else, and the
LAN hub and the local chat line both go through it.
The other two candidate rows are real gaps and keep their verdict.
Nineteen rest-004 rows still said missing or unsure for behaviour the tree has: the
streaming scheduler's 3x3 neighbour gate, the keyframe unit conversion (done when the
vanilla definitions build their keyframes), the cloak trail, the selection and
confirmation keys, the player-list entry and its skin, the shoulder parrot variant, the
map in the first-person hand, the riding jump, the closest-space nudge and its
suffocation test, the sprint slowdown while using an item, the elytra sound, boat ride
input and the rope hold point.
The other seven candidate rows are real gaps and keep their verdict.
PackFormat.IntermediaryFormat.validatePackFormatForRange and
PackRepository.isAbleToClearAnyPack are both in
crates/horizon-client-render/src/packs.rs on main; the map still read them
missing. PackFormat.lastPreMinorVersion cited the file alone, so map-check
could not resolve its fn; it now names last_pre_minor.
🤖 Agent: exact refactor of the per-quad normal: same inverse-transpose calls per pose, checksum of 336k item vertices identical to main; −18 % on the submit bench. Mac frame A/B waits for disk.
🤖 Agent: queued at 4abd6ddad3. This answers the licence question cleanly. Bobby was only run, in a fork CI job against three vanilla servers, then described from its files on disk, and a fresh agent with no access to the history wrote the reader from that description. Bobby's region files are opened with `RegionFile::open` (read-only), only Horizon's own `server-terrain/` copy is opened writable, and the test checks that Bobby's file stays byte-for-byte unchanged. A chunk is read from Bobby once and then kept, so a visit doesn't keep re-reading `.minecraft`. I merged #6327's main merge (no conflicts); clippy is clean, and 84 server_terrain/lod/bluemap plus 61 save tests pass.
The IME composition and its overlay, the multiline box's drag and character
count, the wheel over a cycle button, the chat tag icons and a dialog's item
body are all in the tree now; cite them instead of calling them missing.
A Host helper for a vanish: the hub drops the player from the Tab lists of
players below the level, the entity tracker stops pairing them with those
players (as for a spectator), and their join and leave messages are skipped
for them. Kept by UUID for a player who is off. Helper-only fragment.
Closes#6996
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The portal pass is main's for_each_entity walk; the vehicles flag now
filters it through Entity::is_vehicle, so the boats and carts still handle
their portal at the start of the tick.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at 115443b7a7. Everything here sits behind far terrain. The upload `order` queue is the LOD tile uploader's, `waited` and `keeps_observed` default to false for every other `LodSource`, and the `sent_radius` clamp only feeds `Lod::update`, so a client with LOD Distance 0 runs none of it. Throwing away a sample with a hole, instead of drawing it, is what removes the empty ring while tiles download, and the 10 s `retry` bounds how often a missing tile is re-asked. Main merged in without conflicts; clippy is clean and 138 bluemap/lod/server_terrain/streaming tests pass.
GuiGraphicsExtractor.item and itemDecorations skip an empty stack (air,
or a count of 0), so native_item_stack now returns false for one instead
of drawing a ghost icon. Regenerating the mod API also refreshes the
native_projectile_impact doc that main had left stale.
Fixes#6968.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The living_fall and living_jump fragments name their targets through
horizon_client_lib, which xtask mod-api resolves (it rejected the
horizon_client::landing and ::motion paths), and list the files that
carry their API markers, as living_drown now does.
A canceled LivingFallEvent still lets a mod block's fallOn run, and a
mob's block landing keeps the distance before the mods' changes.
The player's and mobs' drowning step lives once in breathing::drown.
🤖 Agent: the game.rs part only adds spans and a frame_end Instant; spans::time is one relaxed load when HORIZON_SPIKES is off, so the frame path is unchanged for players.
🤖 Agent: queued again. Its only verify failure was main's two sign tests from #6909 (fixed by #6957); main is now merged in. The PR only drops rows from classes-02.tsv whose classes main already implements, so the ledger check is the part to watch.
🤖 Agent: queued at c2f28b5ddb. `handle_entity_portals` runs every tick, so cost was the question. The walk keeps the old rule that `can_use_portal` is asked only of entities already in `portals.entities`, and a kind with `portal_size` None (stands, falling blocks) costs one inlined match. 294.9 → 284.9 µs at 2000 entities shows no regression, and the four portal scenes are byte-identical before and after. `portal_from` using `previous` for projectiles, rockets and tridents is the same split the old code made by hand. Queued at your own main merge; `run-parity` is on.
WorldListEntry.keyPressed and WorldListEntry.canInteract are both in
the tree now: Enter or Space on the list joins the selected world
through Title::confirm_key_size, and the join is gated on
LevelSummary.primaryActionActive (WorldEntry::can_play).
horizon-data.bin (#4267's name) holds the prepared folder's data/ and
assets/ files (sounds aside) in one file, read through one open handle with
positional reads: no file opened per recipe, tag or table, which costs a
Defender scan each on Windows (#1961) and a seek on a hard disk.
- horizon-core::prepared_files: the format, its writer (temporary file
renamed into place) and reader. A file is used only for the root it was
written for (its canonical path) and the preparation it was written from
(its copy of horizon-resources.json), and never through a link; anything
else, a damaged file or HORIZON_NO_DATA_FILE, reads the folders.
- The game writes it for the vanilla root on an idle thread once the start
is done, when there is none it can use: this start read the files already.
- json_folder (the recipes, the loot tables, predicates and modifiers) reads
from it first. Other folders move to it in the next PRs.
- The resource pack and content mod mirrors don't link it, and
check_public_tree rejects the name.
Part of #1961.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Wolf.actuallyHurt, when the armor's Crackiness level changes, plays the crack
sound and then sends twenty armadillo scute item particles a block above the
wolf (spread 0.2, 0.1, 0.2, speed 0.1). hurt_wolf_armor pushed only the sound.
It now reports a MobEvent::ItemParticles, which the game draws in the host's
window (the LAN packet has no item for a particle yet).
Refs #6978
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
#6949 reworded native_projectile_impact's doc in native_hooks.rs without
running xtask mod-api, so generated.rs kept the old text and
`mod-api --check` fails on main (and in every branch's pr_ready).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 5a9e71d756. Find BlueMap is off by default (`blue_map_settings_are_advanced_rendering_options` pins that), and with it off nothing is probed. When it's on, the candidates stay on the server's own host or its `map.` subdomain. The parent domain is tried only for `play.`/`mc.` hosts and never under shared dynamic-DNS domains, so a client doesn't knock on a stranger's site. Logging map addresses only at debug level keeps them out of crash reports. Main's new `every_graphics_text_is_in_the_language_table` caught the four BlueMap texts, so I added them to en_us and fr_fr in the merge. Clippy is clean and the bluemap/graphics/lod tests pass.
client_lib.rs: main's list plus bluemap and bluemap_detect. Main's
every_graphics_text_is_in_the_language_table now covers Find BlueMap and
BlueMap Radius: en_us and fr_fr entries for their captions and help.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`Mirror.mirror` and `Mirror.getRotation` said missing with an unsure note. A
hanging entity's facing is mirrored and turned by
`worldgen/structure/processor.rs`'s `turn_facing`: the same axis flips
(`LEFT_RIGHT` swaps north and south, `FRONT_BACK` west and east) before the
rotation, which is what `HangingEntity.mirror`'s 180 degree turn amounts to
for a horizontal facing. A block state's mirrored result is not code at all:
`StateTransforms::mirror` reads the exported per-state table.
map-check over the server jar: 144 rows, no problems.
Flying rockets, projectiles and chest minecarts are saved, a dangerous
wither skull's blast caps resistance and a mob's blast leaves boats and
carts alone; the verdict map still called these missing.
Horizon filters every death-message recipient through death_message_reaches,
which answers for all three team visibilities; HIDE_FOR_OWN_TEAM is exactly
this broadcast (everyone outside the dying player's team), and the guest death
and the host's death both use it. The other two rows the file still marks
missing were re-checked and are still absent.
🤖 Agent: queued at b3461b4515. Network and cost check. Nothing is fetched until the player names a map in `horizon-bluemap.txt`, which the launcher now refuses to import from another launcher's instance, so a modpack can't make a client report its position to a map server. With no map the tick's only cost is a `None` check on `lod_blue_map`. With one, tiles come one at a time on their own thread (10 s timeout, body capped at 8 MiB, at most ten a second, longer waits while the map is down), the disk cache holds 256 MiB per server, and the worker exits once `BlueMap` drops. I merged #6324's main merge: `serialize` keeps main's block-entity cache keys and still appends `blueMapRadius`. Clippy is clean and 100 bluemap/graphics/lod/server_terrain tests pass.
The row said flat settings only cap the total at the build height and no
layer is clamped per layer. title/flat.rs clamps every layer's height as it
is read: FlatSettings::from_tag holds each height to 0..Y_SIZE, and
FlatSettings::parse caps a layer at the height left below Y_SIZE, so the
heightLimited(Y_SIZE) clamp is inline in the decoder.
graphics.rs: main's block-entity cache keys in serialize's format, kept
as `let mut text` so blueMapRadius is appended.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Launch API's guide, from picking a build to playing: an owner only, the
archives and their signed checksums, asking both programs --launch-api, the
game files and Java a preparation reads, preparing with --complete and
--progress json, --check before every start, starting with the session on
stdin, the exit codes, and updating. launch.md links it and drops it from
"Not there yet".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at b133ad22cd. I read `bluemap.rs` as a parser of untrusted server data, and it holds. `Lowres::parse` range-checks `tileSize` (1–1024), `lodFactor` (2–64) and `lodCount` (1–8), and it caps the coarsest span at 2^26 blocks, so `locate`/`span` can't overflow. `decode_tile` caps gunzip at 8 MiB and rejects a PNG whose header size isn't the layout's before it allocates the frame. Height is clamped to ±2048. Nothing is fetched yet in this slice, and the module stays inert until the player names a map in `horizon-bluemap.txt`. The BlueMap notice is MIT and credited. I merged main (notices and module list), clippy is clean on render and client-lib, and the 6 bluemap tests pass.
Single player only runs on its server thread now. --in-frame and
--no-server-thread print a notice, and HORIZON_SERVER_THREAD is ignored.
This deletes the code that only the in-frame path reached: the window's
world loader and local player tick, its portal travel, the loading screen
and ring-by-ring streaming, and the in-frame LAN host (the host player in
the hub, its models of the guests, the reserved uuid). The world-upgrade
progress screen with its Cancel button moves to the server-thread start.
Tools, the harnesses and the docs no longer use the in-frame path.
Closes#6473
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mobs that die, despawn, enter a hive or unload with their chunk are told
to native_entity_leave_level with their type as kind, from an id diff
around tick_mobs and the chunk-unload retain. Portals, command purges and
level close are not told yet.
Part of #5162.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The F3 debug renderers (collision boxes, heightmaps, block and sky light,
solid faces, water levels), the section fade-in visibility and the transient
block queue are in the tree now; cite them instead of calling them missing.
THIRD_PARTY_NOTICES.md: BlueMap's notice after main's FusionExamples;
client_lib.rs: main's list plus bluemap.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 22c2240767. The side-by-side against a real 26.3 server is what makes the unsorted order change acceptable: matches went from 112 to 120 of 148, and the order probes from 5 to 9 of 12. Vanilla's unsorted order is summon order, which neither list order reproduces exactly. Making `selector_target` required, with no default, is the right call: a new kind has to decide whether `@e` finds it. I merged main: #6925's `thunder_hit` sits beside `selector_target` on the same kinds, so I kept both. Clippy is clean and the 28 command/selector/lightning tests pass. `run-parity` is on.
A boat or minecart with a mob on it never left the portal; Entity.teleportCrossDimension
takes the passengers along and mounts them again. An empty minecart landed off-centre
(AbstractMinecart/AbstractBoat.getRelativePortalPosition reset the forward offset), a tick
late (handlePortal opens the cart's own tick; an arrival joins its level before that level's
entities tick) and at full speed (getKnownMovement clamps to 0.4), so it fell onto the ground
and halved its speed each tick.
Adds tools/cart_portal_e2e.py, which replays the vanilla numbers on a dedicated server.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
entity.rs: #6925's thunder_hit and this PR's selector_target are both new
methods on the same kinds; kept both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LivingEntity.checkFallDamage sends 150 * min(0.2 + power / 15, 2.5) block
particles from the block it lands on, once the fall is past the safe distance.
Horizon did it for the player only. A mob's check_fall_damage now reports a
LandingDust event, which the game draws and sends to the LAN guests through
Game::landing_dust.
Fixes#6950
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at ff5551084a. Checked on a main merge: `mod-api --check` is clean, xtask's 19 tests pass, and `unittest discover -s tests` passes all 611. Taking `helpers` from fragments lets a helper wait for its API number with the rest of its fragment, and the stamper rejects duplicate names. `native_item_stack` passes `Some(String::new())` as the count, so the decorations draw no count, as the doc says. The helper runs only when a mod screen calls it.
🤖 Agent: queued at 89efa41590. Good that `launch_api_options_are_in_the_help` ties `OPTIONS` to `--help`, so the promised list can't drift from what the parser documents. `--launch-api` reads the same `research-lock.json` from `--root` as `--prepare` does, so a launcher gets the pins the preparer will actually use, and a missing lock is exit 1, not a guess. Startup only: nothing touches the game.
🤖 Agent: queued at a10ada9b34. `home` is the right status for `AlertScreen` and `BackupConfirmScreen`: both live as `Alert`/`Mode` states inside `title.rs`, not as screens of their own. The rows name the drawing and button functions, so the next reader can find them. All 5 resolve on main.
Gizmos.rect and RectGizmo.fromCuboidFace are inlined in the solid face debug
renderer, which fills one cuboid face per sturdy side with the corner order
cuboid_fill_quads writes.
🤖 Agent: queued at 5131f3f960. Load-time only: `customTexture.*` is parsed once in `load_textures`, and `find` runs once per sampler when the pipelines are built, so a frame costs nothing extra. Putting a stage's own `texture.<stage>.<name>` first and falling back to the `EVERY_STAGE` entry keeps every pack that already worked unchanged, which your 40-pack translation run confirms. The Iris behaviour was observed black-box with probe packs, so this stays clean-room.
🤖 Agent: queued at a03af1c49e. With no mod loaded nothing changes: `impact` is false only through `tick_through` (a cancelled hook), `canceled_impact_inside` stays false otherwise, and `clip` gets `None`. The hook's contract does change: an arrow no longer stops where it would have hit, it flies its whole motion and ignores the block it's leaving. The doc comment now says so, which is what #6366 asked for. Missing: a test that ticks `tick_through` into a block and checks the arrow comes out unstuck.
🤖 Agent: queued at 9a9b86ad93. `full_precision_required` exists in `lan/entity.rs` and covers the item-against-a-wall case `ServerEntity` checks, so `port` is right. It resolves on main.
🤖 Agent: queued at aa497fa3d1. Good that the `addSlotWithItem` row keeps its gap (the /item container write bypasses `try_insert`) instead of claiming a full port. All 4 rows resolve on main.
🤖 Agent: queued at 7800bf7190. `PassengerPredicate.matches` as `inline` in `advancements.rs::entity` is right, since vanilla's is an any-passenger match over a nested predicate and that's how the `passenger` key is read there. All 6 rows resolve on main.
Retain the experimental per-step block effects and final TNT velocity sampling while incorporating the shared fluid update and removing duplicate outer pushes.
Co-Authored-By: GPT-6 (ZCode) <[email protected]>
🤖 Agent: queued at 6031f1a85b. Splitting `buildInitialScreens` (the queue popped by `next_ban_notice`) from `addInitialScreens` (`initial_notices` filling it) matches vanilla's two steps, and the onboarding note points to `start_onboarding`. All 8 rows resolve on main.
🤖 Agent: queued at 2f4c6ab64d. The `ConnectScreen.init` row gives the layout it checks (status at height/2 - 50, Cancel only while the connection can still be cancelled), so it can be verified against the code, not just the name. All 11 rows resolve on main.
Eighteen rows: the whole F3+F6 debug entry editor, the dialog list buttons,
dialog text clicks and input controls, the statistics tab state and item
tooltip, and the live world-options game mode buttons.
#6909's two new content-root tests (signs_take_their_form_wood_and_wall_block_from_their_declaration,
a_feature_gives_a_mods_sign_its_block_entity) expected prepared 26.3
resources. CI's verify job runs the ignored tests without them, so
verify failed on every PR based on main. They now return early like the
file's other content-root tests (let Some(base) = prepared() else return).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The command block screen scrolls its suggestion list, the container screens
draw the slot_highlight_back and slot_highlight_front sprites in vanilla's
order, a carried stack still lets a bundle's contents tooltip through, the
recipe book's search box takes a drag and its open book stops slot hovering,
the sign editor closes itself when the sign goes or the player walks off, IME
composition reaches a sign and the creative search box, the creative tabs
rebuild on a permission change and its container tick swaps a player out of
creative, a creative display slot refuses a creative_slot_lock stack, and the
smithing table's empty slots cycle their hint icons.
AbstractContainerScreen.tick still has no closeContainer for a dead player, so
that row stays missing.
map-check over a build of the client and server jars: 147 rows, no problems.
🤖 Agent: queued at 0c8f97e4a3. Checked that behaviour is unchanged where the order moved. Orbs and items are now removed one by one, last first, instead of with `retain`, but `remove(i)` keeps the survivors' order, and `hit_items` is only read with `contains`. In the mob stage, a pig or villager conversion only sets `removed` and queues a spawn event, so the indices stay valid mid-walk. The views are still captured before each mob's own damage, which is all the old up-front `collect` guaranteed, since one mob's damage doesn't change another's view. `thunder_count` adds four list scans per strike, and only per strike, so the tick is unaffected.
🤖 Agent: queued at 51e63a4f8e. `surfaces` reports only the column's top block, as `onAboveBubbleColumn` does, and the test pins the case one block lower where nothing is reported. The `Vec`s stay empty unless an entity is at a surface, so a normal tick doesn't allocate. One ordering point: the pearl's particles are drawn after the whole projectile loop, so their four `world.random` draws land after any later projectile's draws in that tick, where vanilla draws them inside the pearl's own tick. That's harmless for particles, and it's the same deferral #6474 uses for mobs.
pack_screen's harness draws ten languages (since #6129), and horizon-prepare copies them from the
official launcher's asset store, which no runner has: every preparation held en_us alone and the
harness failed on every PR in its scope. They depend only on the Minecraft version: kept once in the
shared source (languages/<version>/: assets/pack.mcmeta and assets/minecraft/lang/), cloned into the
runner's copy like any later addition, then into each preparation that lacks them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified 4 A/B join pairs (worst frame 40-50 → 22-26 ms, 0 frames >33 ms); the preload runs after start_mods so mod templates stay in the cached ids, and a world opened before it finishes waits on the cache's lock as today.
🤖 Agent: queued at 94deb80b89. The 16×8 box and pushing the dragon entity but not its parts match `getEntities`. Your arrow analysis is right too: `AbstractArrow` knocks back only a `LivingEntity`, and a part isn't one. I pushed one commit for exact floats: the eye is `8.0F * 0.85F` (6.8000001907 widened, not 6.8), and the direction is normalized with `Vec3.normalize`'s 1.0E-5F cut before `scale(power)`, instead of folding `1/l` into the power. The 24 dragon tests pass and clippy is clean.
The row was listed as missing; lan/entity.rs::full_precision_required makes
the same decision (an item entity's box against a block after a vertical or
horizontal collision needs the full position sync, with primed TNT and a happy
ghast on the precise branch before it). The four rows still marked missing in
the file were re-checked and are still absent.
Four rows still said missing or unsure though the tree has the port: the
bundle append, the any-fuel ghost list, and the dyed and any-potion book
displays.
`8.0F * 0.85F` is 6.8000001907 widened, not 6.8; `Vec3.normalize` cuts at
1.0E-5F and divides before `scale(power)`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 77613e9250. The guard reads the drag from where a mod menu type keeps it, `play.crafting.machine` for that window, so the shift-click falls through to `lan_machine_click` → `Menu::click`, which resets the drag as `doClick` does. Vanilla menu types keep their `menu.drag` check. The added test covers `Menu`'s side; the hub routing has none, but the routing change is a single early return.
🤖 Agent: queued at 21f6f79a32. `Mob::voice` is what vanilla's `makeSound` does: `getSoundVolume` plus `getVoicePitch`, which also draws from the mob's random in vanilla's order. That fixes the trader's `no` along with the baby's. The test's ranges hold whatever the random draws (adult 0.8–1.2, baby 1.3–1.7).
🤖 Agent: queued at 5350241318. Comparing both sides with LF is the narrow fix: the generator's output on Linux and macOS is unchanged, and only the assertion forgives a CRLF checkout, the same as #6375 did for `native_loot_kinds_stay_hookable`.
🤖 Agent: queued at 060e4ac80e. `SpearApproach.ableToAttack` as `inline` in `spear_use.rs::able_to_attack`, shared with `SpearUseGoal`, matches vanilla, where both call the same check. All 22 rows resolve on main.
🤖 Agent: queued at d0bdadacba. The `TicketStorage.addTicket` row names the only case Horizon has, the PORTAL ticket restarting its 300-tick timeout, which is honest about scope. All 5 rows resolve on main.
🤖 Agent: queued at d3ffd9c8ea. `age_boundary_reached` rebuilding the brain from memories is what `Villager.ageBoundaryReached` → `refreshBrain` does. All 6 rows resolve on main.
The passenger predicate key is read, any_block_use and default_block_use both
fire, the killing blow's damage source is tested, and the picked-up trigger
fires both of its instances.
client2-009's missing rows were stale for the keyboard focus work (#4638,
27f948813): Screen.keyPressed and nextFocusPath land in game/key_focus.rs,
the ban notices in ban_notice.rs and Title::next_ban_notice, and the HUD
reset on disconnect in Game::hud_disconnected. Each corrected row names the
function that answers it, with what differs noted as a gap where something
does.
🤖 Agent: queued at 6075780005. The Brushable and Shelf arms of `lan/chunks.rs::entity_tag` match vanilla's `getUpdateTag` (hit_direction plus item; Items plus align_items_to_bottom). All three rows resolve on main.
🤖 Agent: queued at 1da7f9501c. `TryFindLandNearLiquid` is the only vanilla caller that matters, and `LaySpawn::find` does skip the frog's own column inline, so `inline` is the right status. It resolves on main.
🤖 Agent: queued at 9a868c0751. Spot-checked: the `Piglin.finishConversion` row says what is still missing (the rest of the carried inventory isn't scattered), so it stays honest as a port with a gap. All 25 paths and symbols resolve on main.
🤖 Agent: queued at c27c6b8c13. The four rows keep their 'gap: minor' notes, which is right: `system_details` writes no storage, process or JVM lines, so these are partial ports, not full ones. Every path resolves on main.
🤖 Agent: queued at 3ba6ada729. `Direction::approximate_nearest` is the float overload, as the row says: vanilla seeds `best` with `Float.MIN_VALUE`, and NORTH as the fallback is what keeps a zero vector from returning DOWN. The file and symbol resolve on main.
The Recover World rows are title.rs's recover flow, and the connect, waiting,
now-playing, onboarding-fade, name-field and multiplayer-reason rows all have
code behind them; each now cites the fn.
The Launch API version (the same line as horizon-client's, a test reads it),
the preparer's version, the pinned Minecraft and Java major, the resources'
schema and output version that --check compares, the options a launcher may
pass and the exit codes. A launcher asks it before it runs a preparer it
didn't build. Part of #3545.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AlertScreen, BackupConfirmScreen and BanNoticeScreens have homes in title.rs and
ban_notice.rs; ChatScreen.removed and ChatScreen.shouldDiscardDraft are
Game::keep_chat_draft and should_discard_draft. ChatScreen.formatChat stays
missing with a corrected note.
🤖 Agent: queued at 18d82b7fa5. Requeued at your own main merge. You kept #6919's `Blast` for the wind walk and added `pushed_by_wind`, so armour stands move for a mace's burst while `blast_hit` still leaves them to `explosion_breaks_decorations`. One nit for a follow-up: `blast_direction` in explosion.rs zeroes below 1.0E-4, but `Vec3.normalize` cuts at 1.0E-5F, which your wind closure already matches. Clippy is clean and the 51 wind/explosion/entity tests pass.
The piglin fight activity's spear behaviours, the piglin's Mount.create and
the frog's TryFindLandNearLiquid are in the tree; the verdict map still
called them missing. Cite them, and leave the two rows that are really
absent alone.
The ticket rows were marked missing because portal tickets were said not to
be modelled. ticket_levels.rs has them: ChunkTickets::add_portal adds the
PORTAL ticket placePortalTicket places (same type and level restarts the
300-tick timeout, else the ticket is added) and is the one addTicketWithRadius
call, at the fixed radius 3. levels_to_tick keeps a dimension ticking while a
portal ticket is in it, which is shouldKeepDimensionActive.
EntityBasedExplosionDamageCalculator.getBlockExplosionResistance is the
source-side override exploded_positions applies: a passed-in resistance for one
block (the respawn anchor's water-like block), the wither skull's 0.8 cap and
the TNT minecart's rail exemption.
AttachedStemBlock.getCloneItemStack picks the stem's seed, which
block_items::clone_item does.
Six rows still said missing or unsure though the tree has the port: the
villager age-up brain rebuild, the wandering trader's two saved fields, the
100-tick sleep gate, the player display-name decoration and the shoulder
parrot's saved variant.
Three rows said missing while the tree ports them: the brushable and
shelf update tags (lan/chunks.rs entity_tag) and the sign command source
(sign_edit.rs sign_command_source).
putGraphics and putProcessDetails both read missing while the tree sets the
Graphics Adapter crash detail and reports the process's resident memory; the
two gap notes that claimed no GPU line now say what is still absent.
🤖 Agent: queued at 1b9c56b67a. Thanks for answering the Bobby question by removing it; the stack is clean on licences now. Cost check: `lod.observe` runs only when `self.lod` exists, and LOD Distance defaults to 0, so a default client keeps nothing and pays nothing. When LOD is on, the summary and the region write happen on the far-terrain worker. I merged main: its new `forget_remote_columns` now goes through your `forget_remote_chunk`, so batch forgets are observed too. Clippy is clean and the 57 lod/server_terrain tests pass. One follow-up: 'nothing is pruned' means a player who visits many servers grows `server-terrain/` without bound, so a size cap or age-out would be worth an issue.
Keep the experimental speed and rider rotation paths while adopting upstream cached water state and float-derived dimensions and drag.
Co-Authored-By: GPT-6 (ZCode) <[email protected]>
remote.rs: main's forget_remote_columns now calls forget_remote_chunk, so
a dropped column is still observed for far terrain; client_lib.rs gets
server_terrain in main's module list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 1bdee6af8d. Merged main: #6665 had added the same `in_water` field. I kept your `update_entity_fluids` call after the move, which both pushes the cart with the stream and sets the water state, plus main's doc comment, and replaced both helper functions with `c.in_water`. some-du6e's review is fully resolved; clippy is clean and the 28 minecart/boat tests pass.
Direction.getNearest is ported: Direction::approximate_nearest picks the axis
with the largest dot product over the six normals, seeding the best with
Float.MIN_VALUE and north as the fallback, the same as the float overload.
minecarts.rs: both branches added in_water; keeps this PR's
update_entity_fluids (stream push + water state) after the move, main's
doc comment, and drops both helper functions for c.in_water.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemRenderState::submit inverted the pose's 3x3 for every quad of a layer
(transform_normal); a dropped generated item has dozens of quads under one
pose. FaceNormals inverts it once and keeps each facing's normal: the same
arithmetic, so the same bits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlayerHeadItem.getName was marked missing with a note saying Horizon shows
the plain item name. item_tooltip.rs::player_head_name reads the head's
minecraft:profile name and name_line titles it through
block.minecraft.player_head.named, so the row is a port.
ShearsItem.useOn was marked missing and unsure about SHEARS_TRIM.
game/growth.rs::Game::shears_crop is its port: a shears that hits a growing
plant head below age 25 sets GrowingPlantHeadBlock.getMaxAgeState, plays
block.growing_plant.crop, sends the block_change, wears the shears by one and
reports the use. Pumpkin carving stays PumpkinBlock's own path in
game/pumpkin.rs.
🤖 Agent: queued at c789d7bebf. `for_each_entity!` expands to plain per-slice loops, and `count_triggers` makes `LIVING` a const generic, so the per-tick plate check has no `dyn` and no allocation, and the 19-case side-by-side shows no change. I added `run-parity` so the plate/tripwire harnesses run on the merge.
🤖 Agent: queued at ebb131c40c. Both hooks run only for a server's player (`lan_acting_uuid`), so mobs' and the in-frame player's teleports are untouched. A non-finite target keeps vanilla's spot, and chorus fruit clamps a mod's y into the level so the 16-try walk-down can't run away.
🤖 Agent: queued at 6ed4e7ef68. `vertical_collision_velocity` now follows `restituteMovementAfterCollisions`: it scales by the portion of the move made, and the drag uses the widened 0.98F rather than a flat 0.8 bounce. `slime_step_on` costs one block lookup per grounded item per tick, the same check vanilla's `stepOn` makes.
🤖 Agent: queued at c89c95255d. Defaulting `wheel_cycles_settings` to true keeps vanilla's `CycleButton.mouseScrolled`, and both wheel paths (Horizon's graphics list and the options screen) check it, each with a test that the list scrolls instead when it's off.
🤖 Agent: queued at 1098eb754d. The pearl test now expects `f64::from(0.99f32)` per tick, matching the widened throwable drag that landed with #6797; the old literal `0.99` only passed while the drag was a double.
🤖 Agent: queued at 247d5d4317. Checked against 26.3: only HappyGhast, FallingBlockEntity and PrimedTnt ever call `setRequiresPrecisePosition`, so for an item it is always false, and #6882's `full_precision_required` is now the only route to the exact-position packet.
Observed with probe packs of our own in Iris 1.11.7 on 26.3 (each stripe of
the final pass shows whether a macro is defined, or its value in the
colour's bytes):
- MC_GL_VERSION and MC_GLSL_VERSION are 330 (the GL context's 3.3, whatever
the pack's #version); Horizon said 460.
- IRIS_VERSION 11107 and IRIS_HAS_TRANSLUCENCY_SORTING (Horizon sorts
translucent terrain as vanilla does).
- MC_MIPMAP_LEVEL follows the Mipmap Levels option: the renderer defines it
with its mipmap levels.
- One GL vendor and renderer (OptiFine's shaders.txt, E and F): OTHER, since
Horizon draws through wgpu, not a GL driver. Iris defines the driver's
(MC_GL_VENDOR_NVIDIA here), but packs take driver extensions behind it
(RenderPearl's GL_NV_gpu_shader5 int16 types), which Horizon has not.
Left as they are: IRIS_TAG_SUPPORT (block tags are not read yet) and
MAX_COLOR_BUFFERS (Iris says 32; Horizon has 16 colortex buffers).
Fixes#6881
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The generator writes native_hooks.rs with LF, so on a Windows checkout
(core.autocrlf) the refilled file never equalled the CRLF original and
the test failed. Compare both with LF.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: merged main. client2-001.tsv keeps main's post-effect rows (gfx.rs ports them now) with your world-icon rows, and `hasRenderedAllSections` now targets plain `game.rs::tick_world_icon` with the `drawn_sections > 10` detail in the notes. All 15 rows resolve on main.
Asked where a guest's hit (attack_frame, Player.attack's painting
branch), a blast, or a lost wall would break a frame or painting,
before anything drops or sounds. A kept painting stays at its index, so
the blast and survival loops step past it.
Fixes#6697.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at b4330bef5c. Hot path: `Mesher::face` only asks `fusion.pick` when the face has a rule (`face.fusion` is None without a Fusion pack), so CTM-only and vanilla meshing pay one Option check. Building from the public wiki, since Fusion is All Rights Reserved, plus the MIT FusionExamples notice, keeps it clean. I merged main: mesher.rs now passes main's new `CtmAxes` orientation argument along with your shared `push`. horizon-optifine (134) and the fusion/connected/mesher tests (74) pass.
Entity.handleOnAboveBubbleColumn ends in sendBubbleColumnParticles, and
ThrownEnderpearl, ArmorStand and FallingBlockEntity all reach it. Each now
reports the column blocks it is at the top of, and the game draws them with
Game::bubble_column_particles, as it already does for mobs, items, arrows and
primed TNT.
Fixes#6908
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: merged main (#6612 had renamed the paths around the sign classifier and the renderer load). Kept your `sign()` constructor and `add_mod_signs`; clippy is clean and the 17 sign tests pass.
🤖 Agent: queued at c66d16827c. The experimental wire path only runs with `minecraft:redstone_experiments` in the level's enabled features, and the default path gains just an `Option<Orientation>` that stays None. Since this changes `neighbor_changed` and the wire updates everyone goes through, I added `run-parity` so the redstone harnesses check the default behaviour before it lands. A harness for the experimental mode itself would be a good follow-up.
🤖 Agent: queued at 60191c6b70. `Event::Traded` now carries the offer as it stood at that trade, so a shift-click buying three in a row gives each event its own `uses` (your test shows 1, 2, then 1 on the other offer). The hook fires only on the server, inside `lan_acting`.
🤖 Agent: queued at 04ada3a4c3. Good perf work; the chest-wall numbers line up with dropping the per-frame vertex rebuild. One change: the PR had moved block entities off Entity Culling's occlusion pass for every setting, so with culling on and every model cache off (the default), a chest behind a wall was drawn again. That case now keeps the old per-block `cull_entity_vertices` path (`CacheOptions::any_model_cache` is pub for it). I also merged main; clippy is clean and the 39 block-entity tests pass.
On a mod's own menu type the drag lives in the connection's machine menu,
not in the hub menu's drag, so lan_mod_quick_move passed the click to the
mods. Leave it to lan_machine_click, which resets the quick craft as
AbstractContainerMenu.doClick does.
Fixes#6907.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 76eee70652. Checked the uninstaller: it deletes only `horizon-launcher.exe` and its update leftovers from the install folder, and the opt-in 'Also delete the launcher's data' runs only when the data folder is the launcher's own `horizon-launcher` (a custom `HORIZON_LAUNCHER_HOME` is listed, not deleted). The game's saves (`game_home`) are never touched. I merged main's lang files; Mac clippy is clean, and CI's Windows job builds the `cfg(windows)` parts.
🤖 Agent: queued at 44ee33c861. Requeued after the LAND-STOP: #6820's new `checkFallDamage` code still called the removed `cart_in_water`. It now reads `c.in_water`, which is right because vanilla's `isInWater` there is `wasTouchingWater` from the tick before. The 12 minecart tests pass.
ServerEntity.getRequiresPrecisePosition is false for an ItemEntity, so needing its data sent
again must not force an exact-position packet; Horizon set precise from needs_sync, which sent
every re-synced drop as EntityPositionSync.
🤖 Agent: queued at 42165d4d45. Merged main: server_e2e.py now registers both your `dialog_text` check and main's `book_menu` in one `CHECKS` dict (the merge had left two assignments, so the second would have dropped yours). The 2 dialog lib tests pass.
🤖 Agent: queued at 83e7424576. Merged main: `compile()` now runs main's `image_parameters` rewrite first, then `integer_bounds`. The rewrite only casts when every declaration of that name in the stage is `int` and both bounds are unsigned literals, so an ambiguous name is left alone.
🤖 Agent: queued at cfbd424754. Merged main, keeping both new test modules in post.rs; the 133 horizon-shaders tests pass. `setup_pending` reruns `setup` only at load and after a resize, and the shadow pass now waits for `begin`, which is the lifecycle order the format describes.
🤖 Agent: queued at b1feecff54. Merged main: main now reads the recipe folder once for the whole sync, so I gave that read its own `sync.recipe_folder` span ahead of `sync.registries` (otherwise the registries step would have absorbed it). `test_startup_trace` and the 8 startup tests pass.
NeoForge's EntityTeleportEvent.EnderPearl and .ChorusFruit for server
players: native_ender_pearl_teleport may move the landing, change the
5 fall damage or cancel (no endermite roll, teleport, damage or sound);
native_chorus_fruit_teleport runs before each of the up to 16 attempts,
may move the spot or end the effect. TeleportConsumeWorld gains a
default teleport_allowed so other users (foxes, the parity probe) keep
vanilla's draws.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Event::Traded now carries the offer as notifyTrade left it, so a
shift-click whose trades level the villager up reports each trade's own
prices. xtask's mod API check gets a MerchantOffer stand-in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PrimedTnt reads explosion_power (4 by default, kept to 0..128) and explodes with it;
Horizon always used 4.0, so a summoned TNT with explosion_power:10 hit like ordinary TNT.
🤖 Agent: queued at bd366fac94. Merged main: main's `tnt_prime_allowed` check now wraps your owner-credited `spawn_primed_tnt`. `tnt_blast_owner` returns None once the mob is gone, like `PrimedTnt.getOwner` after the owner leaves the level, so a dead skeleton's TNT kill does not name it.
Keep the existing squid animation assertions while allowing the extracted shared velocity handler to access its minecart list.
Co-Authored-By: GPT-6 (ZCode) <[email protected]>
🤖 Agent: armor stands posed on the GPU path (bit-exact test over the stand models), item sprite rect cached per quad run; stress_armor_stands 148 → 216 fps; verify passed.
🤖 Agent: queued at 03379ce30f. Merged main (#6637 landed underneath) and kept main's lang entries plus the nine new log-panel keys in each file. Launcher clippy is clean and the tests pass; `prepared_versions_get_what_the_asset_store_adds_without_preparing_again` failed once in the full run and passes alone, and this PR does not touch backend.rs.
The per-section caches skip the per-vertex occlusion pass, but with every
cache off a chest behind a wall was drawn again with culling enabled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first world opened or server joined built build_registries' caches in its frame:
resource_ids (thousands of recipe and loot table files, 7.7 ms on an M4 Pro), the block
properties (6.1 ms) and command-registries.json (4.9 ms). Joining a server froze one frame
for 40-50 ms. A helper thread fills them once the game has started (after the mods, whose
templates it lists); whoever needs one first waits for it, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 34427987a3. `armor_stand_turtle_eggs` follows `TurtleEggBlock.stepOn`/`fallOn`'s order (egg, then mob_griefing, then `nextInt(chance)`), so the server's random is drawn only on an egg, and it returns early for a stand in the air or on a client. I merged main for the stale mod_api output; the 10 armor_stand tests pass.
Main's checkFallDamage called the removed cart_in_water; isInWater is
wasTouchingWater, which this PR keeps as in_water.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 18d256d935. The registry sort orders by the earliest or latest `published` day with downloads as the tie-break, and 'Sort by: {order}' is translated in all ten lang files. I merged main and kept main's entries plus your one new key per file.
🤖 Agent: queued at 41f880696f. Merged main: #6871 had added its own `mod tests` to world_cmds.rs, so both tests now share one module. `playsound_placement` plus `look_at`'s tests pass (3 total).
Register the feature-gated max_minecart_speed rule through commands, network trees, persistence and world creation. Correct experimental track iteration and emit weighted motion steps with pitch. Connect the rotateWithMinecart accessibility option to vanilla's smoothed rider yaw behavior.
Co-Authored-By: GPT-6 (ZCode) <[email protected]>
world_cmds.rs: main's look_at (#6871) with this PR's playsound_placement,
both tests in one tests module.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 6315a38687. `full_precision_required` matches 26.3's `isFullPrecisionEncodingRequired`, including the swapped axes for `horizontalCollision`. I made the flag use `Mth.equal`'s 1e-5 tolerance on x and z, as `Entity.move` does (it was an exact compare), so float noise on a free-moving item no longer forces full-precision syncs.
🤖 Agent: queued at a8618bd7ed. Merged main for #6894's fixture stub (the red verify was main's `test_native_split_mouse_turns_only_its_owner`, not this PR). The `!a.marker` check is what `ArmorStand.isPickable` does.
🤖 Agent: queued at 4a8e739a6a. Merged main, keeping both new translate tests (#6784's `functions_reaching_their_end_return_zero` and yours); the 132 horizon-shaders tests pass. The point from my first review still applies as a follow-up: a block with an instance in one stage and none in the other will not link.
🤖 Agent: queued at dd98a7bfa0. `lan_mod_quick_move` returns before `as_guest` unless the block is a `BlockEntity::Mod`, so vanilla containers keep their path untouched. If no mod takes the click, `settle` restores `entity.items` from the snapshot, so nothing is moved twice.
🤖 Agent: queued at 1c223ab049. Hot path: with dynamic lights on, `brighter` cloned a `String` for every held item (a zombie's sword too) twice per frame, and the coloured-light pass ran without any native pack. Now only lit sources name their item, and `native_frame_visuals` checks `has_light_colors()` first, so packs without `[[lights]]` pay nothing.
🤖 Agent: queued at 6c8f7a1ede. Checked against the 26.3 bytecode: `getOffset` passes `distance / widgetHeight` to `Mth.lerp` as an int division, so the offset is `height - 3` until the pointer is a whole height away, then 5. I made `widget_offset` divide as ints and set the test to vanilla's positions (120 and 170, not 118 and 180). I also added the whole-height case.
🤖 Agent: queued at 0c646c7f8e. The hook fires once per night skip, on the server's own day time, and the `asked >= t` check after every mod keeps `setTimeAddition`'s refusal of a time in the past, so a mod cannot rewind the clock. With no mods it is an empty loop.
Only a lit source names its item, and the frame's coloured-light pass runs
only when the native pack has [[lights]] entries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
MenuTooltipPositioner.getOffset passes `distance / widgetHeight` (two ints)
to Mth.lerp, so the offset is height - 3 until the pointer is a whole
height away, then 5.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 4b4db8c36d. Defining `MC_NORMAL_MAP`/`MC_SPECULAR_MAP` is safe without a resource pack's maps: pack_post binds `dummy_normal` and `dummy_black` to the `normals`/`specular` samplers, so those branches read a flat normal and no specular instead of garbage.
NeoForge's TradeWithVillagerEvent: once per trade a server player makes
with a villager or wandering trader, after the statistic and the
villager_trade trigger, with the trader's id, its type and the offer as
it was right after that trade (a shift-click's trades each see their own
use count). Listen only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 4cbcd2e4fc. `look_at` now matches 26.3's `Entity.lookAt` bytecode: the yaw is `wrapDegrees((float)(atan2 * 57.2957763671875) - 90f)`. I swapped `to_degrees()` for that float constant and wrapped the pitch the same way. `tp … facing` and `/rotate … facing` now share it.
🤖 Agent: queued at 32b3543820. `carries_record` reads the item's `block_entity_data` key rather than the block entity just filled, the same order as vanilla's `setPlacedBy` after `updateCustomBlockEntityTag`. Creating the empty `Jukebox` in `for_block` also means a placed jukebox saves its (empty) block entity, as vanilla does.
Armor stands drew their body, its emissive overlay, four armour layers and
their trims through mobs::render::draw on the CPU every frame. They now go
through mobs::render::put, so the GPU path (#6757) poses them when it runs;
held items and wings stay on the CPU. The models become shared (Arc) like
the mobs' so the store keeps one copy, the frame's skin setup moves to the
start of entity_geometry (mob_geometry no longer owns it), and culling of a
GPU-posed entity is shared (cull_skinned).
Two lookups ran per quad or per layer: the item renderer looked up each
quad's sprite rect by name (SipHash) though an item's quads share a sprite,
and each armour layer built a new pose map. The rect is cached like the
translucency already was; one pose map serves every layer.
A test poses the stand models (normal and small, main and four armour
layers, turned head and arm, hidden parts, the glint) both ways and
compares the vertices to the bit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: verified A/B on the Mac (RD 32 teleport render-thread CPU max 14-17 → 7-10 ms); the per-key removal path is unchanged for small drops and whenever far terrain fades sections, so walking and LOD behaviour stay as on main.
worldborder, spreadplayers, debug stop and tp printed numbers with Rust
{:.N} (exact value, half-even); vanilla uses String.format, which rounds
the shortest decimal text half-up. Route them through java_text::format_fixed.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
🤖 Agent: queued at 6638d28c2d. Merged main: the landing path keeps your `PotionOwner` plus main's `native_potion_splash`. Since summoned potions now land through `ProjectileKind::Potion` in creatures.rs, I call the hook there too (except for experience bottles), so mods see both paths. `a_summoned_potion_flies_as_the_item_its_compound_names` and the 17 projectile tests pass.
A summoned splash or lingering potion now lands through the projectile
path, so mods hear it the same as a thrown one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AbstractMinecart.tick runs pushAndPickupEntities before updateFluidInteraction,
so the cart push now runs inside tick_cart ahead of the stream push.
updateFluidInteraction and onInsideBubbleColumn both reset the fall distance.
refresh_boat_status sets status_known, as vanilla's oldStatus = status runs on
every boat.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
🤖 Agent: queued at 9ed22e8d8a. Requeued after the LAND-STOP: main's `loot_function`/`loot_condition` were missing from `mod_sizes_are_sanitised`'s `NativeLoadHooks`, so I added them as no-ops. The 1..=i32::MAX clamp applies only to a mod's value; data packs keep vanilla's 1..99.
🤖 Agent: queued at d78433b072. Warnings go only to the native-mods build log, and conflict grouping keys on (site, kind, selector, ordinal), so HEAD and RETURN on one function are reported once, separately. I merged main for the stale mod_api output. Launcher clippy is clean and the 77 native_mods tests pass.
Game::device_event now records each grabbed motion with note_input
(#6712), so the fixture that compiles the method on its own failed to
build on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 22c453af45. `own_background` only skips the plain panel and slot frames; `native_screen_draw` still runs, and the default (false) leaves existing screens unchanged. I merged main (stale mod_api output) and dropped the defaults-only test.
"Scroll wheel changes settings" (wheelCyclesSettings, on by default as
vanilla's CycleButton.mouseScrolled). Off, the wheel only scrolls the list
on every settings page, Video's (GraphicsUi) included, and a keyboard-
focused option doesn't take it either. Closes#5455.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Gate experimental propagation on the saved world feature flag and preserve the default redstone evaluator. Port vanilla 26.3 orientation transforms, queued power changes and oriented neighbor notifications.
Co-Authored-By: GPT-6 / ZCode <[email protected]>
NeoForge's SleepFinishedTimeEvent at vanilla's ServerLevel.tick point, where
enough players have slept and advance_time moves the clock to the morning:
a mod may set another wake-up time; one before the current time is refused,
as setTimeAddition refuses it. native_time_skip then hears the jump to the
chosen time. Part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 Agent: queued at 5aceab866a. This follows `SimpleEnergyHandler`: a capacity under 1 means no buffer, the limits are clamped to 0..=capacity, and an emptied item drops its `horizon_energy` key so it stacks with new ones. The helpers act on one item and hand back the changed copy, so nothing runs per tick. I merged main and made `ItemEnergyBuffer` public, re-exported from `mod_api`, since a mod builds it and the generated code since #6253 names it.
Since #6253 the hook's types appear in the generated mod_api code, which
only sees what mod_api re-exports: the buffer is now public (a mod builds
it) and re-exported there, and xtask's test stubs it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Iris's enhanced custom textures (`customTexture.<name> = <path> [<type>
<internalFormat> <dimensions> <pixelFormat> <pixelType>]`) give a sampler
of its own that every program may read, with the same sources as
OptiFine's `texture.<stage>.<name>` (ShaderDoc, "Enhanced Custom
Textures"). Horizon read only `texture.*`, so a pack's `sampler3D
texClouds` (Shrimple) was "not provided yet" in every program.
`load_textures` reads `customTexture.*` for every stage; a sampler finds
it by its name and dimension when its own stage has no texture of that
name.
Part of #6812
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PlaySoundCommand compares the squared distance with
Mth.square(getRange(volume)), which multiplies in float and only then widens.
Horizon widened first and squared in double, so a target between the two
squares was in range where vanilla has it out of range. The comparison and
the minVolume placement move into playsound_placement, which takes the float
range.
An unhandled call puts the block's items back, so nothing a mod moved without
taking the click is kept twice; a removed block closes the menu and resends the
inventory; a dead player's click and a shift-click during a drag go to the
game; the docs say the clicked slot gets the remainder. Tests for the
write-back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A server hook before the game's own quick-move routing, as a Java menu
overrides AbstractContainerMenu.quickMoveStack: a mod container on a vanilla
menu type or on its own menu type takes what it moves out of the clicked
stack and sets handled; the slot keeps the rest and the client gets the menu
again. Lets a storage terminal whose screen hides the container's slots take
a shift-click from the inventory, and a machine route fuel and inputs.
Fixes#6562, part of #4953.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ItemEntity now keeps Entity.verticalCollision and Entity.horizontalCollision
from the move its resolver returned, EntityState carries them to the tracker,
and ServerEntity.send_changes applies vanilla's
isFullPrecisionEncodingRequired: an item that a block stopped and whose x/z is
not on the 1/4096 delta grid goes out as a full position sync, not a
quantised delta.
With dynamic lights on, a held or dropped item whose block the pack colours by [[lights]] joins the
frame's coloured lights (MaterialInput.colored_light) at its dynamic light's place and reach. The
sources come from one place for both, so the plain and coloured lights agree.
Fixes#6858
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EBqm1v5zTsDGwuZF6XKFjL
Their blending covered the sky's value, so in HDR the sky behind them rose and they didn't,
and looked darker against it. A second pass over them writes the sky's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
JukeboxBlock.setPlacedBy turns has_record on when the placed item's block
entity data contains a RecordItem, and a placed jukebox gets its own block
entity so that data has somewhere to land.
A mod block only became a sign when its name ended in _sign, and then its
text never reached a client: the chunk encoder gave a mod block its own block
entity type and dropped any entity that wasn't the mod's. Its item also placed
vanilla's wall sign, and its editor looked for a texture named after the block.
Go by the template's class, as vanilla's instanceof SignBlock does, for the
block entity, the renderer, the editor, chaining and respawning, and for the
block entities world generation gives the blocks a feature places. A content
block declares wood_type, its WoodType name, and wall_block, the wall form its
item places, one of the same mod's blocks: a pair that doesn't fit leaves out
that mod alone. Mod signs go out with vanilla's sign and hanging_sign block
entity types, so text, glow, dye, wax and saving work as they do on an oak
sign. A stone block named road_sign is no longer a sign (#5532).
Written with Claude Opus 5.5 in OpenCode.
Their colour is premultiplied in the shader, as the celestial blend did, so the alpha can
carry the mark: the bright tier, lifted as far as they show.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vanilla places the tooltip of a keyboard-focused widget with
MenuTooltipPositioner, which keeps it 3 to 5 pixels from the widget's edge and
5 pixels inside the screen instead of pinning it to the pointer.
Entity.lookAt wraps the yaw with Mth.wrapDegrees, so facing a target to the
north-west leaves 135 and one due east -90. /tp ... facing stored the raw
atan2 - 90 (-225 there) and /rotate ... facing reduced it modulo 360 (270), so
anything reading the rotation saw a different number for the same direction.
Both commands now share one helper that wraps the yaw as vanilla does, with a
unit test on the angles.
GLSL lets the members of an in/out interface block carry their own
interpolation and layout qualifiers (GLSL 4.60 4.3.9: `out VertexData { flat
int id; } vOut;`). naga's frontend refuses them ("Expected Void, Struct or a
type, found Interpolation(Flat)"), which skipped every gbuffers program of
Shrimple. Such a block's members now become plain varyings
(`hz_<block>_<member>`, matched between stages by block and member name, as
GLSL matches them), `instance.member` reads them, and lines stay where they
were. Blocks with an instance array or an instance used whole are left as
written.
Part of #6812 (the translation failures; the black picture has other causes)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
OptiFine defines both when its normal and specular map options are on (its
default), and Iris 1.11.7 defines both too (observed with a probe pack of our
own: its final pass paints one stripe per macro). Horizon defined neither, so
packs took their no-normal-map branches; Bliss declares FlatNormals only in
the other branch and its terrain, block and entity programs failed with
"Unknown variable: FlatNormals".
Fixes#6811
Co-Authored-By: Claude Opus 5.5 <[email protected]>
getAirDrag is 0.99F in vanilla, widened to double, so a throwable's horizontal step is
0.9900000095367432 rather than 0.99; two tests still asserted the old value.
A far teleport moves the server's chunk centre: the client drops most of its view (3700
columns at 32 chunks) in one SetChunkCacheCenter, then gets thousands of ForgetLevelChunk.
Renderer::remove_columns looked up every section key of every column in five stores and
removed resident sections one by one (each a slot move and a column lookup): 10 ms of one
frame on an M4 Pro, and each ForgetLevelChunk did it again for its own column, with a pass
over every block entity.
remove_columns now filters each store once when the columns hold at least as many section
keys as there are resident sections (Resident::remove_columns rebuilds the dense list), and
the released sections drop on a thread of their own. poll_remote forgets a run of
ForgetLevelChunk at once, before the next packet whatever it is, as the centre change does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The new travel scenarios: an elytra-speed straight line at 33 blocks/s, two trips through a
Nether portal (checked to arrive in the Nether), and joining a dedicated server of the same
build in a game of its own. The Nether scenario travels through chat as a player does: the
control socket's `command` waits on the server thread inside the frame, a 40-70 ms stall
that no player sees.
HORIZON_SPIKES now also reports a frame whose wait before it (idle: the control socket,
world entry, pads) passed the threshold, with those steps as spans, and times poll_remote.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds EntityRules (NoGravity, default gravity, isPushedByFluid) and
Entity::push_by_fluid / push_by_bubble_column defaults; TNT now uses
update_entity_fluids instead of its own copy of the fluid world.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
`item_defaults::read` clamped `minecraft:max_stack_size` to 1 to 99 after
the `modify_default_components` hook, so a mod's bigger size was silently
taken away; and a value that was not a number skipped the clamp, which each
reader then read as its own default (the item table 64, the item models 1).
A data pack's own size is now held to vanilla's 1 to 99 before the hook, a
mod's to 1 to `i32::MAX` (the count the wire's VarInt and the save's int
carry), and a value that is not a number leaves the data pack's own. A mod
that removes the component still removes it.
A container slot now caps at the item's own size instead of vanilla's fixed
99, so a modded stack above 99 survives a click, a drag and a shift-click.
For every data pack item that is the same answer as before (their sizes are
at most 99); the creative slot packet's 1 to 99 check and `/give`'s
`max_stack * 100` limit follow the same bound.
Fixes#6713
ThrowableItemProjectile.getDefaultItem/readAdditionalSaveData and
AbstractThrownPotion.getDefaultGravity: /summon minecraft:splash_potion makes a
ThrownSplashPotion that falls with gravity 0.05, carries Motion, flies as its
Item (the default splash potion when the summon names none) and splashes with
that item's potion_contents; a lingering one leaves its cloud.
Horizon loaded both potion types as an inert "other" entity kept as its
compound - nothing ticked it, its data had no Motion, no blast could push it and
nothing splashed - and ProjectileKind::Potion carried one baked effect (the
witch's) instead of the stack, so the kind now holds the item it flies as and a
landed potion goes through the same splash() a thrown one does; the witch builds
the stack vanilla's Witch.performRangedAttack makes. ThrowableProjectile's air
drag is vanilla's 0.99F float widened to double, not 0.99, so a thrown item's
position matches the server's (the splash potion's Pos and Motion after a summon
now match to the last digit).
Fixes#6642
The world icon, the spectator post effects, the two first-person hand cases, the sound device
watch and preview, the particle cap and the pack format validators are all in Horizon; the rows
still said missing.
HORIZON_STARTUP_TRACE showed ~100 ms of the server thread and the ~700 ms
from world.first_frame to world.view_full without a step. Now:
- a `world.fill` counter track each frame until the view is full: chunks
queued and at each streaming stage, resident columns, meshes waiting to
upload and packets waiting from the server (Perfetto draws them; the
tracer's timeline ignores them);
- server.sync_adopt and server.start_mods on the server thread;
- sync.* inside SyncData::load (registries, tags, commands, recipes,
advancements);
- world.prepare and world.make_streaming on "World initialization".
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
wind_knockback walks every entity with for_each_entity_mut; each kind
says what it ignores and which box and eye height it is pushed through.
Behaviour unchanged; rockets and tridents (#6647) go through the same loop.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
TntBlock.onProjectileHit primes the TNT with the projectile's owner,
PrimedTnt.getOwner then names the blast's causing entity. A creeper
killed by TNT a skeleton lit now drops a music disc.
Add for_each_entity! over EntityLists, one loop per list with no
allocation or dyn, and move the plate and tripwire counts and the
entityInside sweep onto it. Bat and marker armor stand become
is_ignoring_block_triggers overrides. No behaviour change intended.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
Entity.restituteMovementAfterCollisions adds back the gravity and air drag of
the part of the tick after the hit, and SlimeBlock.stepOn drags the horizontal
speed (also on idle ticks). Primed TNT now bounces too, and a collided step
shorter than sqrt(1e-7) is not applied, as in Entity.move.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
- The Horizon registry hid the filter bar, so it had no sort. It now shows the
dropdown below "Add from GitHub", and sorts by Downloads, Newest (first
published) or Updated (latest version).
- A sort or filter change keeps the listed rows until the new ones arrive, so the
page doesn't shrink and the scroll doesn't jump to the top.
- The dropdown is 40px tall with its text centred on the button's face.
Refs #6714
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The Mods page's sort was a row of five segments beside the Filters button. It is now a
"Sort by: <order>" dropdown (the Language row's pattern) listing Best match, Relevance,
Downloads, Follows, Newest and Updated. The list closes on a pick and when the page changes.
Refs #6714
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Keep block entity frustum checks and section cache rendering independent of the ordinary entity occlusion culler. Show mode-specific culling help in the graphics UI.
Co-Authored-By: OpenAI Codex <[email protected]>
Include living mobs in the dragon's wing and head/neck contacts. Preserve
vanilla wing impulse, sitting behavior, team damage rules, hurt cooldowns,
and the dragon as the causing entity. Resolve the separately simulated
dragon in MobWorld so existing revenge and melee goals can target it.
Add a real-Java contact oracle, Rust transcript and repeatable parity
harness. Seven scenarios compare health, velocity and revenge attribution;
all JSON values matched exactly in the local Java 26.3 run. The normal
integrated-server check also passed (enderman health 30, dragon target).
Add an optional
integrated-server check for contact damage and enderman revenge.
The upstream damage-event/hurt-flash fix is already present and is retained.
This does not claim parity of the entire fight, pathfinding, or post-attack
enchantment effects. Full pr_ready currently cannot pass because Clippy is
not installed; individual verification: 1,934 Rust tests, 598 Python tests, rustfmt,
cargo check and the seven Java/Rust scenarios passed.
Co-Authored-By: Codex <[email protected]>
Far terrain on a server without horizon:lod draws only the chunks
Horizon kept of it. A player who also plays with Bobby has a cache of
the same server's chunks, often of areas Horizon never saw.
Far terrain now finds Bobby's folder of the level in the game folder
(HORIZON_MINECRAFT_DIR, else the official launcher's): .bobby/<address
as typed, ':' as '_'>/<hashed seed>/<namespace>/<path>, names compared
without case and with or without the default port. A chunk Horizon's
store doesn't have (or holds as an old record) is read from Bobby's
region file, decoded as a vanilla chunk with its stored light
(save::decode_lit, handed in from game.rs), summarized as a loaded chunk
is (lod::summarize) and kept in server-terrain/, so Bobby's folder is
read once per chunk and never written. Bobby's region headers count for
the wide cells too. A chunk of another DataVersion, one that doesn't
decode or has no light gives nothing. A Bobby chunk is not kept over one
the server sent while it was decoded. Friend worlds and splitscreen
guests still keep no server terrain.
Tests: folders by address (port typed or not, ':' as '_', case), another
seed, another dimension, another port; a Bobby-shaped region counted for
wide cells, read, summarized and kept, its file left unchanged, another
version's chunk ignored; with prepared resources, a chunk with both
palette shapes (names, and compounds with {"": name} wrappers) decoded
with its light and kept; and with HORIZON_TEST_BOBBY, every Overworld
chunk of a .bobby folder Bobby really wrote, found by address and seed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Carts use entities::update_entity_fluids (no second copy of the push) and keep
bubble columns. Drop game/body_fluid.rs and the boat push, now in #6453. A
boat's oldStatus stays unknown on tick 1, so one spawned in water no longer jumps.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The join strip, the waiting player's prompt and the controller notices
read horizon.splitscreen.prompt.* keys (en_us, fr_fr) instead of English
literals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
pub own_background: bool on NativeScreen leaves out Horizon's default opaque
rectangular background panel and slot frames in draw_native_screen when true,
allowing mod screens with non-rectangular geometries to draw their own
background without transparent areas exposing the grey frame.
Co-Authored-By: Antigravity <[email protected]>
Add a top-right log viewer with complete clipboard copy and open/reveal file
actions. Keep snapshots fresh across attempts and queued reads, bound the log
viewport, and wrap navigation and actions in narrow windows. Localize all
new labels and document the workflow.
Co-Authored-By: GLM 5.3 <[email protected]>
Co-Authored-By: Codex gpt-6.1-sol <[email protected]>
native_item_energy_buffer fills an out parameter, so its fragment carries
a replace contract on buffer, as #6253 gives item_fluid_tank and
block_entity_energy. Review by arismoko on #6596.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Upstream's pad rule wins: in a world a pressed controller goes to waiting
players, then joins as the next player; P1 takes it only in a menu or while
waiting for one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main's far terrain samples only the tiles its plan draws, nearest first,
with no coarser tile under the near ring to keep drawn: while BlueMap's
finest level (one pixel a block) was still downloading, the ring past
the near chunks stayed empty.
A cell up to a chunk wide whose finest map tile is on its way is drawn
from the finest coarser level that has it (BlueMap::coarser_column, the
first to arrive); the tile is sampled again when the finest arrives
(Lod::refresh). LodSampler::waited now means a hole: a cell on its way
with nothing coarser to stand in (a void there is a void, not a hole).
Only a sample with a hole is thrown away (what it would replace stays
drawn); one stood in for is drawn. Columns whose finest tile is out of
the download reach take the coarser levels too, which reach farther
(BlueMap::chunk), and chunk detail with them. A coarser level arriving
names chunk-wide cells among those it changes, and every arrival drops
the cached chunks over it (and a cell around, for a coarser level), so
they are built and sampled again with it.
Tests: coarser levels stand in for a waiting finest one (the finest of
them), and past level 1's reach; a hole without any is waited; a waited
sample is thrown away and a stood-in one drawn; in the scheduler
scenario (prepared resources, ignored), a source standing in coarse
columns keeps every chunk past the near terrain drawn while the finest
loads, and the finest replaces them once in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With BlueMap on, a tile arriving re-sampled every far-terrain tile over it
up to the roots, and a coarse one also cleared the detail of thousands of
chunks, each re-requested and re-meshed.
- Arrivals name the cell widths their level is drawn in (Lowres::widths):
Lod::refresh samples only those tiles again. Changes are handed over at
most every 250 ms, so a burst is sampled once.
- A sample, or chunk detail, equal to what is drawn meshes nothing. A tile
refreshed while its sample was in flight is sampled again.
- Tiles looked up out of reach are remembered and wanted when the player
comes near or the server is tried again.
- A level-1 arrival drops only the chunks it covers from the cache. The
cache size and pruning walk the disk outside the lock the frame takes.
- Columns end 8 blocks under their lowest neighbour's top, not at the
bottom of the world: the edge of the map's data drew a stone wall along
the horizon.
- The map's settings, once a visit: a network error backs off and keeps
the cached map; only an answer without this level's map drops it. When
the lowres settings changed, the map's cached tiles are deleted before
the new settings are kept (if they can't be, no map this visit), and
lookups or chunks built under the old settings meanwhile aren't kept.
- A map dropped or replaced redraws everywhere, not only in the download
radius (announce_all). A failed cache write no longer hides the tile
just downloaded.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a server the player names in horizon-bluemap.txt (blueMap: lines, off
otherwise), far terrain fills what Horizon didn't keep from the server's
BlueMap web map: its lowres tiles (a colour, height and block light a
column) are downloaded around the player, one at a time and at most ten
a second on their own thread, kept on disk, and drawn as the natural
block nearest each colour. A tile that arrives refreshes the far-terrain
tiles over it (Lod::refresh).
The maps have a file of their own beside horizon-graphics.txt, which
shared instances and modpacks carry: the map is asked for tiles around
where the player stands, so only the player names one. BlueMap Radius
stays a graphics setting. Tiles are cached up to 256 MiB a server
(oldest first), fetched again after three days, written whole (temp file
and rename), and an error page never replaces a cached tile; a server
that stops answering is left alone longer each time, up to ten minutes.
The settings: Graphics holds them, horizon-graphics.txt keeps
blueMapRadius:, and the blueMap: lines are read from
horizon-bluemap.txt.
Chunk detail read before a refresh over its chunk is dropped and asked
again, never kept over the newer data (Lod::refreshed_since).
The launcher's import from other launchers leaves horizon-bluemap.txt
and server-terrain/ out, so an instance another launcher made can't name
a map for the player.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The first half of far terrain from a server's BlueMap web map, nothing
wired yet: the player's settings and their formats (blueMapRadius:, and
the blueMap: lines of their own file, horizon-bluemap.txt; the next
commit reads and writes them), the web app's settings.json and a map's
lowres layout (tileSize, lodFactor, lodCount, validated so a hostile map
can't overflow or index past a tile), where a block's pixel is at every
level (LowresLayer.saveTile), the tile paths (pathFromCoords), decoding
a lowres tile's PNG (colour, height clamped to the world's reach, block
light; gzipped too), and the palette that turns a colour into the
natural block nearest it, tinted by its biome as the game draws it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With a BlueMap, a far-terrain cell up to a chunk wide whose map tile was
still downloading was sampled empty, and that tile, ready, replaced the
coarser one drawn there. A band of nothing stood past the near chunks
until the map's tile came and the tile was sampled again, ring by ring
outward.
- A sample with data still on its way (LodSampler::waited) is thrown
away: what it would replace stays drawn. The tile waits in the retry
map for 10 s (WAIT_RETRY: a shorter wait let near retries take every
sample slot), or is sampled again as soon as the data comes
(Lod::refresh), at once if it came while the sample was taken. A
refresh also marks first samples still in flight.
- A finished mesh job's tiles go up finest first, then nearest: a tile
re-meshed clipped around a finer one no longer shows its hole frames
before the finer one is drawn.
- On a server, far terrain's finest tiles stop where the server stops
sending chunks (its view distance and a ring), not at the render
distance: thousands fewer chunk-sized tiles to fill in.
Tests without resources drive the scheduler by hand: a waited sample is
retried when its data comes, a tile refreshed mid-sample is sampled once
more, the same samples mesh nothing, and kept chunks re-sample the wide
tiles over them. With nothing coarser under the near ring yet, the ring
stays empty while the finest level downloads (the ignored scenario test
added here fails here; the next commit replaces its source with one that
stands in): the next commit's coarser levels fill it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With Find BlueMap on (Video Settings > Advanced > Rendering, off by
default), joining a server no blueMap: line names asks a few likely
addresses for BlueMap's settings.json on the BlueMap worker, one at a
time with short timeouts, and keeps the answer per server (a map found
is checked again after a week, none after a day). BlueMap Radius sits
next to it. A blueMap: line always wins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A tile sampled before the server sent a chunk of it kept that sample
while it stayed in the plan. Cells up to a chunk wide take the chunk's
detail when it is observed, but wider ones (past 4 km, and the coarse
fallback drawn while finer tiles load) never did, and a sample in flight
while the chunk was being stored overwrote it with what it read before.
When a source keeps the chunks it observes (LodSource::keeps_observed:
server terrain), the tiles over a newly kept chunk whose wide cell there
is empty, or whose sample is still being taken, are made stale and
sampled again, their old samples drawn meanwhile; an unchanged sample
meshes nothing. Local worlds are unchanged.
Tests: resampled_over's choice, without resources; with prepared
resources (ignored), a player flying past 1800 chunks with a view distance
of 10 keeps every dropped one drawn, and 32 x 32 chunks kept after their
far tiles were sampled fill in (all 1024 stayed empty before).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With no BlueMap, a far-terrain cell wider than a chunk took the column
of the kept chunk nearest its middle, however few the cell held. On a
fresh cache the coarse fallback tiles, sampled first, drew a few chunks
around the player as flat slabs hundreds of blocks wide (floating ones,
from a sky build), which finer tiles then replaced with the nothing they
know of, nearest first: terrain that appeared, then vanished outward.
A wide cell now takes a kept chunk only where at least half of its
chunks are kept, as Column::reduced keeps what fills half a cell.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
native_item_energy_buffer gives a mod item a buffer (capacity, the most
one insert and one extract move), checked as a block's is. The amount is
kept in the stack's custom_data as the int horizon_energy; an empty item
carries nothing, so it stacks with a new one. native_item_energy reads one
item of a stack; native_item_energy_insert and native_item_energy_extract
answer the amount moved and the single item it becomes, without changing
the stack given. Fixes#6553.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a server that doesn't offer horizon:lod (any vanilla, Paper or Spigot
server), every chunk it sends is kept as its far-terrain summary, on
disk per server, world and dimension (server-terrain/<server>/<hashed
seed>/<namespace>/<level>/, Anvil region files), and drawn beyond the
server's view distance on this visit and later ones: a server never
sends its seed, so the client draws what it was sent. A chunk the client
forgets (ForgetLevelChunk, or a moved or resized storage) is observed
one last time first. A server's offer (horizon:lod) still comes first,
and a friend's world keeps nothing (its code changes at every share),
nor a splitscreen guest's (the player's own world, its other guests
writing the same folder).
The far terrain is rebuilt when the level changes; a proxy's next server
comes through reconfiguration. Cells wider than 128 chunks aren't
searched for kept chunks, and a failed write is logged once.
tools/server_lod_e2e.py's server without an offer now expects these kept
chunks (source "server-terrain").
server_terrain lives in horizon-client-render beside lod and streaming.
The launcher leaves server-terrain/ out when it duplicates an instance:
it is a cache, rebuilt as the instance plays.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Solid beveled panels made the screen read heavy. The plate and the strip
are now the page colour, see-through, with a 1 px edge; groups a lighter
tint on them; buttons, tick boxes and the value stepper flat; a slider a
thin accent track under its value.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review: getOnPos(0.2) takes mainSupportingBlockPos's column, so a stand
whose centre is past the egg's edge still stands on it. The stand now keeps
Entity.setOnGroundWithMovement's supporting block (GroundState, as the
player does), and its landing location effects run before fallOn, as
LivingEntity.checkFallDamage orders them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The launcher reads every mod's [[injection]] list before it builds, but
it said nothing when two mods inject at one place, or when a cancelling
HEAD sits on a native hook point. Both orders are documented (priority,
then mod id, then injection id), but nothing told the player or the
author. horizon-mixins refuses only competing OVERWRITEs.
The build now says three things in native/build.log, before anything
runs, so a build that fails later still shows them:
warning: mods beta, alpha both inject at hook `frame` HEAD; beta runs first (priority descending, mod id ascending).
warning: mods alpha (HEAD), beta (RETURN) inject into hook `tick`: HEAD runs on entry, RETURN on return, whatever their priorities.
warning: alpha's `stop` can cancel at hook `frame` HEAD: a Break skips the later injections and the function's body.
A place is the function, the kind, the selector and the ordinal as the
manifest writes them: two same-place injections run in horizon-mixins'
tie-break order (plan.rs sorts by file, node, call site, constant site,
then priority descending, mod id ascending, injection id ascending), so
only they get a "runs first". A mod's priority defaults to 0, as
horizon-mixins defaults it, not to the README example's 100. The
injections are read leniently: an entry without an id and a site is
left out, horizon-mixins refuses it when it applies. A raw `target` is
not matched against the catalog — whether it names a hook point's
function only the source knows — so the cancellation warning names a
`hook`, and only a `HEAD`: the interior kinds' cancellation skips
less than the body. Neither warning stops the build.
Fixes#6490.
Co-Authored-By: ZCode AI agent (GLM-5.3)
An armor stand is a LivingEntity, so TurtleEggBlock.stepOn (1 in 100 a tick,
from LivingEntity.aiStep's applyEffectsFromBlocks) and fallOn (1 in 3) crack
an egg under it while mob griefing is on. The stand tick never ran either.
The mobs' egg crack moves to Game::crack_turtle_egg so both share it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep custom text component click events, their identifiers and optional
payloads through parsing, style inheritance and serialization. Dispatch
dialog message clicks through the existing action handler, including the
dialog's after_action behavior.
Use the rendered glyph order and advances for message hit testing and
request a pointing cursor over clickable text and inline sprites. Show
text hover tooltips and handle the shared custom event in chat and books
as Screen.defaultHandleGameClickEvent does.
Extend the vanilla server e2e checks with message text and sprite clicks,
optional payloads, inert blank space and all three after_action modes.
Validation: 53 core tests and 10 dialog tests pass; dialog, dialog_text and
titles e2e checks pass on a vanilla server; focused Clippy and fast builds
pass. Full self-check limitations are recorded in the local PR draft.
Co-Authored-By: Codex <[email protected]>
LivingEntity.handleFallFlyingCollisions compares the delta before the glide move with the
one after it, which Entity.move leaves as the movement it resolved. Horizon read the after
value from the player's velocity, which move_entity never rewrites, so it equalled the
before value and the damage almost never came out positive.
Entity.updateFluidInteraction pushes every entity that does not opt out;
primed TNT had it, carts and boats did not. Carts also skipped bubble
columns, and their "in water" flag was a point test instead of the last
fluid update. A boat's previous status starts as none (oldStatus is
null), so a boat spawned in water no longer snaps to the surface.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018zuDTsnyHkP28bzn3u7S3m
The pages are a column of names on the left, under the search field, with
Undo and Done at its foot. The plate shows the open page alone, its
settings in named groups on panels of their own; a search shows every
page's matches under their page. The hovered setting's cost and
description sit in a strip under the plate, and what the page holds
while none is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Handle ClientboundContainerClose while a dialog, waiting screen or warning
is open, discarding the underlying screen and copied menu state as vanilla's
LocalPlayer.clientSideCloseContainer does. Keep clearDialog's distinct
parent-restoring behavior and its treatment of the waiting screen.
Extend the vanilla server e2e checks with packet injection covering waiting
dialogs, parents, warning, replacement, lectern, key release and death.
Validation: existing dialog/titles e2e checks pass; the new dialog_close check
passes three consecutive runs; 9 library dialog tests, 8 screen tests and
2 input-capture Python tests pass; focused Clippy and fast builds pass.
Co-Authored-By: Codex <[email protected]>
Three ways an old-behaviour minecart's motion drifted from vanilla's:
- comeOffTrack scales by getAirDrag() (0.95F) and applyNaturalSlowdown
by 0.95F in water: floats widened, not 0.95.
- EntityType.MINECART is sized(0.98F, 0.7F): the box that stops a cart at
a block is the float width's, half of 0.9800000190734863.
- isInWater is wasTouchingWater, which AbstractMinecart.tick updates in
updateFluidInteraction after behavior.tick: the move goes by the tick
before, and a new cart's first move is dry. Horizon looked at the
block it stood in, live, so a cart on waterlogged rails slowed by one
0.95F too many.
Harness: command_exec_corpus `minecart drag`, positions and motions to
the last bit.
Exercise P2’s actual controller menu path and check P1’s capture and real mouse movement before any recovery click.
Co-Authored-By: OpenAI Codex <[email protected]>
A paused client can retain its last velocity without moving; wait for actual position stability before measuring another owner’s movement.
Co-Authored-By: OpenAI Codex <[email protected]>
Add an explicit macOS desktop regression that observes player movement and camera rotation through native event routing, across shared views, menus, held-key handoff, separate windows and the last guest leaving. Requires Accessibility permission and an idle desktop; the portable capture and motion regressions run in the regular Python suite.
Co-Authored-By: OpenAI Codex <[email protected]>
- The arrows reach the nearest live row past each edge over greyed
ones, and with nothing focused in view Down takes the first setting
from the view's top on and Up the last down to its bottom: on a
display without HDR, a 720p window opened at HDR still reaches its
shortcut.
- Narration: keyboard focus on a tab, Undo or Done is focus, not hover.
- Tests: a held scroller lets go when the window gets too short; the
HDR page's shortcut reached past its greyed rows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The arrows reach the rows in view and exactly one past each edge
(counted in rows, not pixels), so Up from Done no longer skips the
row at the list's bottom edge.
- Undo restores what the screen edits (Horizon's graphics settings and
the vanilla options on its rows), not the narrator or other options
changed meanwhile.
- A held scroller lets go once the list is too short for it, before
its thumb's clamp could panic.
- Tests: Up from Done scrolls in by a row at most; with the focus
wheeled out of view, Down takes the first row in view; Undo leaves
an option the screen doesn't edit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`horizon-launcher-setup.exe` is the launcher's own binary under another name:
started with no arguments, it opens a setup window in the Night look instead of
Home. It copies itself to `%LOCALAPPDATA%\Programs\Horizon Launcher`, makes the
Start menu shortcut (and the desktop one if asked) and an entry in Apps &
features under HKCU, and starts the installed copy; no administrator is needed.
`horizon-launcher.exe uninstall` (or `... uninstall --quiet [--remove-data]`)
takes it all away: the shortcuts, both registry keys and the install folder,
which is always one called "Horizon Launcher" and is removed with `remove_dir`,
never `remove_dir_all`. The game's folders are never touched, and the launcher's
data folder only with `--remove-data`.
`setup --quiet [--dir <folder>] [--desktop-shortcut]` installs without a
window. Both commands are handled in `main` before the CLI parser, so neither
the completions nor the MCP tools list them: an agent cannot uninstall the
launcher.
A self-update keeps the Uninstall entry's version and size current when the
launcher runs from the install folder. The release workflow publishes the setup
exe beside the ZIP, so it is in the release's signed SHA256SUMS, and the Windows
smoke test installs and uninstalls it in a scratch folder.
Co-Authored-By: OpenCode <[email protected]>
Exercise signed-in P1 joining with offline guests, and adapt the remaining live Setup scenarios to the saved enable switch and current pause menu behavior.
Co-Authored-By: OpenAI Codex <[email protected]>
- The arrows reach the next row across a page's break, and Up or Down
with the focus out of view lands on a row in view.
- The search keeps the row being changed and matches each setting's
own description, not the one that follows its value.
- HDR's sliders and calibration wait for a display that offers HDR, as
the old screen's HDR button did.
- Ctrl+A/C/V/X in the search are always the field's.
- A list too short for vanilla's scroller draws none and takes no press
(its thumb's clamp would panic).
- Tests: arrows across a page break, the wheel through OptionsUi, HDR's
rows blocked without an HDR display.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Found on Windows (100% scale, a portrait second monitor): a half of the 1000 x 600 window has
no room for GUI scale 2, nor a sixteenth for P1's menus, so those two take a larger window;
two players on a portrait monitor share it top and bottom.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the full feature (#3541):
- Setup with a controller: A picks a player's tile up, left/right or LB/RB cycle where it goes
(the yellow < Name > arrows, the map showing it there), A drops, B puts it back;
- Give Up Pad: P1's controller is offered to join; the same pad comes back to P1 when the
offer is declined, runs out or the player leaves before entering the world (P1Pad);
- a player whose controller disconnected shows (no controller) on its Setup tile; other
players' pause menus say Leave;
- HORIZON_SPLIT_STATS=1: where a frame's time goes, every 5 s (frames.rs).
The live harness gains the full feature's helpers (pick, seed, args, page, setup, play_on,
close_page; press by prefix, still waiting for a drawn, enabled button) and its scenarios
three, live, profile_availability, gui_scales, fullscreen, leave, signedin, monitor, eight,
sixteen, pointer and pausemenu.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Undo from the keyboard no longer turns itself back on; Undo keeps
fullscreen (F11, the window manager); a calibration lights Undo.
- The search matches names and descriptions, not values, so a row
stays while its value changes; clearing it or pasting starts the list
over and lets go of the picked tab.
- The scroller is vanilla's thumb, the size Scroll drags by, 6 px wide
as its press column.
- The arrows reach only the rows in view and the next past an edge;
the wheel only scrolls (a controller's D-pad scrolls with it).
- Long names are cut before their control; hover is vanilla's white
edge, without an off-palette colour; the shortcut's caption goes
through the lang table; the title is Graphics.
- One graphics_events in options_ui instead of three copies;
graphics_level is graphics_page.
- screenshot-build saves its cache only when it builds its own branch.
- Docs: the design system's Blocks section names this screen and how
the game draws it; Leaf Culling's place; stale comments.
- Tests: Undo after several changes and with fullscreen changed, the
keyboard's Undo, HDR's working path, Calibrate and the shortcut row,
a 720p window, the scroll after a resize, the wheel's ends, the
search's clear button; the open tab checked against what the list
shows rather than its own rule.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
20 px rows, and no title under 300 GUI pixels (a 720p window at scale 3):
the tabs say where you are, and the list gets the room.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Horizon's Simple, Advanced and HDR graphics screens become one screen
holding vanilla's video options and Horizon's own, built from
Minecraft's own screen patterns in the design system's Blocks look:
the pages as tabs along a beveled panel, a search field that filters
every page as you type, a row per setting with a vanilla kind of
control (a value button, a tick box, a slider with its value on the
track), the hovered setting's description and cost in vanilla's
tooltip under the panel, and Undo and Done along the foot.
Adds screenshot-build.yml, a Linux build for a pull request's
screenshots.
Refs #5705
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 12:13:08 -06:00
1292 changed files with 156795 additions and 33328 deletions
| `horizon-anticheat` | The dedicated server's anticheat, off by default: `[anticheat]` settings, violation levels, the checks' rules; the hub runs them (`lan/anticheat.rs`) ([docs/anticheat.md](docs/anticheat.md)) |
| `horizon-cosmetics` | Cosmetic items Horizon clients draw on players (off by default): the item format, bones and animations ([docs/cosmetics.md](docs/cosmetics.md)) |
| `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)) |
Since [#1514](https://github.com/horizon-rust/horizon/issues/1514) single player runs on the server thread (`game/integrated.rs`) and the window joins it as a client; `--in-frame` keeps the old path. The open `[server-thread]` issues list the gaps. In review: breeding's advancement and statistic ([#3873](https://github.com/horizon-rust/horizon/issues/3873)), the client's menu when the server replaces the player ([#3841](https://github.com/horizon-rust/horizon/issues/3841)), splashes when falling in water ([#3621](https://github.com/horizon-rust/horizon/issues/3621)). [docs/server-levels.md](docs/server-levels.md).
Since [#1514](https://github.com/horizon-rust/horizon/issues/1514) single player runs on the server thread (`game/integrated.rs`) and the window joins it as a client. The open `[server-thread]` issues list the gaps. In review: breeding's advancement and statistic ([#3873](https://github.com/horizon-rust/horizon/issues/3873)), the client's menu when the server replaces the player ([#3841](https://github.com/horizon-rust/horizon/issues/3841)), splashes when falling in water ([#3621](https://github.com/horizon-rust/horizon/issues/3621)). [docs/server-levels.md](docs/server-levels.md).
</details>
@@ -234,11 +234,11 @@ Client-of-a-server fixes in `game/remote*.rs` and `horizon-protocol`: mobs' aggr
#### One single-player path: remove the in-frame world
Single player will have one way to run, the server thread, so every fix works everywhere and there are fewer bugs. The older in-frame mode goes once everything passes without it.
Single player has one way to run, the server thread, so every fix works everywhere and there are fewer bugs. The older in-frame mode is removed.
<details><summary>Technical</summary>
`--in-frame` (also `--no-server-thread` and `HORIZON_SERVER_THREAD=0`) still ticks the world inside the window's `Game`, and most harnesses test that path. Delete it once `lan_e2e --server-thread` and the parity harnesses pass on the server thread, which also halves the bug surface. See also 'Single player as a server' and [docs/server-levels.md](docs/server-levels.md).
`--in-frame` (also `--no-server-thread` and `HORIZON_SERVER_THREAD=0`) ticked the world inside the window's `Game`. [#6473](https://github.com/horizon-rust/horizon/issues/6473) removes it with the code only it reached (the window's world loader and player tick, its portal travel and loading screen, the in-frame LAN host); the harnesses run on the server thread. See also 'Single player as a server' and [docs/server-levels.md](docs/server-levels.md).
</details>
@@ -634,16 +634,6 @@ A spike with ray-traced sun shadows, off by default, is a draft ([#1180](https:/
</details>
#### HDR monitors
Output for HDR monitors: brighter highlights and richer colour where your screen supports it.
<details><summary>Technical</summary>
Issue [#1441](https://github.com/horizon-rust/horizon/issues/1441). Needs a surface format and a tone-mapping path per backend, and tests on real HDR screens on Windows, macOS and Linux.
More graphics options: multisample anti-aliasing, anisotropic filtering and an exclusive fullscreen mode. Today Horizon has FXAA and borderless fullscreen.
@@ -904,6 +894,7 @@ Armour and tools in End city chests should come enchanted, as in vanilla.
- **Discord Rich Presence** (v0.2.58): Your Discord profile can show that you are playing Horizon, with a switch to turn it off. [#1787](https://github.com/horizon-rust/horizon/issues/1787) · [#1788](https://github.com/horizon-rust/horizon/issues/1788)
- **Server dialogs and login screens** (v0.2.67): Servers that show dialogs, such as the register and login screens of AuthMe, will work in Horizon. [#1720](https://github.com/horizon-rust/horizon/issues/1720) · [#1734](https://github.com/horizon-rust/horizon/issues/1734) · [#1735](https://github.com/horizon-rust/horizon/issues/1735)
- **Microsoft sign-in** (launcher@0.2.17): Sign in with your Microsoft account in the launcher to play on online-mode servers. [docs/microsoft-account.md](docs/microsoft-account.md) · [#2879](https://github.com/horizon-rust/horizon/issues/2879)
- **HDR monitors** (v0.2.74): Output for HDR monitors: brighter highlights and richer colour where your screen supports it. [#1441](https://github.com/horizon-rust/horizon/issues/1441) · [#2021](https://github.com/horizon-rust/horizon/issues/2021) · [#2292](https://github.com/horizon-rust/horizon/issues/2292)
- **OpenGL for old computers** (v0.2.63): Horizon now starts on old PCs whose graphics card has no Vulkan or DirectX 12 driver: it switches to OpenGL by itself, and Options > Graphics can pick OpenGL by hand. [#1917](https://github.com/horizon-rust/horizon/issues/1917) · [#1920](https://github.com/horizon-rust/horizon/issues/1920) · [#1936](https://github.com/horizon-rust/horizon/issues/1936)
- **Server resource packs** (v0.2.48): Servers can send you their resource pack: Horizon asks, downloads it and applies it, like vanilla. [#1562](https://github.com/horizon-rust/horizon/issues/1562) · [#1569](https://github.com/horizon-rust/horizon/issues/1569) · [#1570](https://github.com/horizon-rust/horizon/issues/1570)
- **Single player on its own server thread** (v0.2.39): Single player runs on a server thread, as vanilla does, so the world keeps ticking smoothly while the window draws. [#1514](https://github.com/horizon-rust/horizon/issues/1514) · [docs/server-levels.md](docs/server-levels.md)
description="The client's world data, rendering and screens: chunks, meshing, light, streaming, the renderer, the HUD, menus and settings; no gameplay"
[dependencies]
horizon-cosmetics={path="../horizon-cosmetics"}
# The GUI atlas's pixels, kept compressed once uploaded (hud.rs `pack_pixels`).
"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::MusicPage=>{
"Music & Sounds gets a Music... page: play any built-in track now, or turn one off so the game stops picking it. Nothing changes until you turn a track off."
}
Item::MouseTweaks=>{
"Right-drag places one per slot, left-drag gathers the held item, Shift + left-drag moves every slot."
}
@@ -623,6 +720,19 @@ impl Item {
Item::ReproKeys=>{
"F3+R starts or stops a repro recording and F3+E writes its bundle. Off by default: a recording copies the world."
}
Item::CosmeticsMine=>{
"Hidden: every Horizon player sees you without your cosmetics, you included. The same switch as the launcher's Wardrobe."
}
Item::CosmeticsOthers=>{
"What other Horizon players wear: everything, only their name tag styles, or nothing. Hidden, the game never asks what they wear."
}
Item::CosmeticsAuras=>"Auras and trails other players wear. Off by default.",
Item::WheelCyclesSettings=>{
"The scroll wheel over a button in the settings changes its value, as in vanilla. Off, the wheel only scrolls the list."
}
Item::PackBrowser=>{
"Select Resource Packs gets an Add button that searches Modrinth and installs a pack into your packs folder. Off, the game never contacts Modrinth."
}
}
}
/// The frame-time cost the main switch of an extra shows.
/// The cost's colour in the HUD's encoding: the square beside its name.
pubfncost_color(cost: Cost)-> u32{
hex(cost.rgb(),255)
}
Loaded 100 of 1292 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.