Members declared 1.85 to 1.95, or nothing. horizon-mods needs 1.95 and sits
under horizon-client-lib, cargo-horizon-mod and the launcher, so 1.95 is the
version the game really needs; the lower numbers promised builds that could
not happen. Set it once in [workspace.package] and inherit it everywhere.
The separately built workspaces (launcher, telemetry server, mod registry,
third_party) cannot inherit it and keep their own.
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.
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.
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]>
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
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 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 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.
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]>
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]>
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]>
Generated mod API code is also compiled by xtask's test, which cannot see
horizon_command; mod_api now re-exports Suggestion and the test stubs it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new `Game` hook can start with the placeholder body
`let _ = (every, parameter);`. Given its mod_api entry,
`cargo run -p xtask -- mod-api` replaces the placeholder with the
`mod_api::dispatch_<hook>` call, laid out as `cargo fmt` would; `--check`
reports it instead. A body that is neither the placeholder nor already
calls the dispatch is an error, named and never changed. Free-function
hooks stay wired by hand. Generated code is formatted for the
workspace's edition.
`menu_slot_rules`, `living_breathe` and `ingredient` are free functions in
horizon-client-lib, which can't see the mod list. One `NativeClientHooks`
struct of callbacks, installed once from the executable's entry point as
horizon-world's `NativeLoadHooks` is, carries the three to their dispatch.
The generator resolves `horizon_client_lib::` targets (and the older
`horizon_client::menu` and `breathing` names) to the client source.
With these, every hook has a mod_api entry, and the check now fails
naming any hook without one.
Every hook in native-hooks.json and the pending fragments gets a mod_api
contract, so trait mods can answer each through `Hooks`. The generator
reads the fragments too, rejects duplicate names once, and the game's
hook bodies call their dispatch in one short form. Event types the
generated trait names are re-exported from `mod_api`, and horizon-world's
`NativeLoadHooks` gains loot functions and conditions.
Each trait hook now takes its host, where it has one, and one
generated #[non_exhaustive] <Hook>Event holding the hook point's
arguments, as NeoForge's events do: a later version can add a field
without breaking any mod. A number or bool a mod may change is a plain
field, written back once every mod has run, even after a cancel; other
mutable arguments stay &mut fields. One event goes through every mod,
so each sees the changes before it. Cancel and replace outputs stay the
return value. Dispatch signatures and the native_* hook points are
unchanged. Trait injections get the same pattern, their bindings
becoming the event's fields.
46 of 78 named native_hooks while the method lives in another game
module; the generator found methods by name anywhere and checked the
target only against two module names. It now records each method's
module and requires the target to match it.
old_world_e2e asked horizon-client-lib for the horizon-client binary, which
only horizon-client has; three e2e docstrings, the agent-control guide and
xtask's comment still built horizon-ctl from horizon-client.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
world_drawn and render_needs dispatch through an ordered RenderHooks
trait, which a mod returns from Hooks::render. The renderer list is
installed once beside the mod list and called on the render thread
while the frame is built. block_entity_submit and the existing
block-entity drawing helpers come through the generated trait and
Host, so a trait mod can draw its block entities without a mixin.
Trait mods had no way to call the game back. Host is generated from an
explicit `helpers` allowlist in native-hooks.json: each listed public
native_* helper becomes an object-safe method with the helper's
receiver and docs, implemented by forwarding to Game. Helpers whose
signatures use gameplay-private types or generics stay out, and the
docs list them with the reason.
Native mods could only reach the game through mixins, so every build
patched the game's source. A mod can now implement
horizon_client::mod_api::Hooks instead.
The trait is generated from native-hooks.json. Each catalog hook gets a
mod_api contract (listen, cancel, replace or collect), and
`cargo run -p xtask -- mod-api` writes the trait methods and their
dispatch from the hook functions' own signatures and docs. Hooks keep
their exact receivers. Level hooks get read-only or mutable level hosts,
and library hooks (loot tables, default components) dispatch through a
set-once table that entry::main_with installs. Types that mods need move
out of the game into mod_api, which re-exports library types rather
than copying them.
The launcher links a bundle's [api] crate into a generated executable
that calls entry::main_with with every trait mod, in bundle order. A
build with only trait mods skips horizon-mixins. pr_ready runs
`xtask mod-api --check` when the catalog, the hooks or xtask change.
Keep native hook targets and source paths stable. Run gameplay tests in the gameplay library, and build ctl/probes from horizon-client-lib without gameplay. Update build/test selection and the fixed mixin-tool pin.
Co-Authored-By: Codex <[email protected]>
playhorizon.dev now deploys from the private ludovic111/horizon-site, which holds website/'s history. The files
its build still reads from here move out of website/:
- the design tokens to crates/horizon-theme/tokens.css (xtask tokens writes them, tokens_css_is_up_to_date checks them);
- the roadmap's JSON to roadmap/roadmap.json (tools/roadmap.py build);
- the benchmark fixtures to crates/horizon-telemetry-server/tests/fixtures/benchmarks/ (the site keeps a copy);
- the web icons to docs/media/brand/web/ (xtask icons).
pr_ready loses its website area and the tokens special case; rust_changes, the roadmap's links and every doc
that pointed into website/ follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Compile and validate original exporter bundles during release builds, cache development compilation, and reuse initialized JVMs and collision data during preparation. Keep legacy preparers compatible while current releases need only a Java runtime.
Co-Authored-By: Codex <[email protected]>
horizon-client's cpal output and its controllers (gilrs, the DualSense's HID output) move behind the
audio and controllers features, on by default, so the game is unchanged. Built with
--no-default-features it links neither libasound.so.2 nor libudev.so.1. release.yml packages that
build as horizon-engine in horizon-server-linux-x86_64.tar.gz (xtask package-server, checked by
check_imports.py --server); install.sh installs it on Linux (and --archive installs a local one);
horizon-server already prefers horizon-engine. The Dockerfile uses it too, and CI lints it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- packaging/server/install.sh (Linux, macOS) and install.ps1 (Windows) install the latest release (or
--version) from GitHub, check the SHA-256 GitHub publishes for it, and keep releases side by side:
<dir>/current points at the one in use, so an update or a rollback is one link, and the game files
prepared from the owner's Minecraft stay in <dir>/game-files across updates.
- packaging/server/Dockerfile builds the server and the engine; the prepared game files are mounted
read-only, and the first start writes server.toml from HORIZON_EULA, HORIZON_WORLD, HORIZON_SEED,
HORIZON_PORT, HORIZON_WHITELIST, HORIZON_PLAYERS and HORIZON_OP (`init` reads them too).
- `horizon-server service install|uninstall|print` writes and loads a systemd unit (user, or --system),
a launchd agent, or a Windows task at logon. It runs the installer's launcher, so updates apply on
the next restart.
- Releases build horizon-server and ship it beside horizon-ctl and horizon-prepare; PLAY.txt says how
to run a server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Discord server banner (page + PNG), the server icon render and Kira's avatar (SVG master +
render) now live in the private kira repo (ludovic111/kira#1). xtask icons no longer renders them;
the brand README points there, and CONTRIBUTING states what belongs in this repository.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mark is an H whose crossbar is the horizon: a violet line edge to edge, the stems above it
and their reflection below at a third, on whole units of a 16-unit grid so it stays sharp at
16 px. docs/media/brand holds the SVG masters (square icon, macOS tile, avatars, the mark for
light and dark backgrounds), the two alternatives that were shown with it, the GitHub social
preview and the Discord banner, and a README of what goes where and how to upload what only
Ludovic's accounts can.
`cargo run -p xtask -- icons` renders the masters (rsvg-convert) and packs, in plain Rust:
packaging/icons/horizon.ico and horizon.icns, Linux's hicolor icons 16–512 and the scalable
SVG, the game's run-time window icon, the avatars and the website's favicons.
packaging/linux has .desktop entries for the launcher and the game.
On Windows, build.rs embeds the .ico as icon resource 1 in horizon-client (and its tools) and
horizon-launcher (winresource, a Windows-only build dependency); gpui loads that resource as
the launcher's window icon. The game window sets the icon itself (X11, Windows) and names
itself `horizon` on Wayland so the desktop entry matches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/design-system.md describes the new design language (after mkdir.directory: type as the
interface, numbered index lists with hairline rules, one violet accent, space instead of
borders, quiet motion, a faint wordmark) and its tokens, components, surfaces and mark.
crates/horizon-theme now holds the whole system: the two palettes (cool neutrals, light and
dark, each text colour checked against WCAG AA on every background by a test), the type scale
with web sizes, spacing, radii, rules, control heights and motion with CSS easing curves.
Instrument Serif is gone; display type is Geist at medium weight.
`cargo run -p xtask -- tokens` writes website/src/tokens.css from the crate, and a test fails
when the file is out of date. The launcher and the game's menus read the new names through
small adapters; the restyles follow in their own changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
`cargo run -p xtask -- package BINARY OUTDIR` does what package.sh did:
OUTDIR wiped and recreated, the `git archive HEAD tools
mods/horizon/src/test/java crates/horizon-prepare research-lock.json LICENSE`
tree extracted (the tar read in Rust, executables kept executable), the client,
horizon-ctl and horizon-prepare copied (.exe beside an .exe, the same error when
one is missing) and PLAY.txt. release.yml calls it; package.sh is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>