Server mods get interface world: get-block(dimension, x, y, z) reads a
loaded chunk's block state as "minecraft:oak_log[axis=y]", get-biome its
biome ("minecraft:plains"), and
set-blocks(dimension, changes) sets up to 4096 blocks per call, checked at
once, made as /setblock makes them (place_state: neighbours, light, block
entities, every client told through the block log) after the call, before
that dimension's next tick.
Reads cost no copy: for a mod call the game lends Mods the levels' chunk
maps (moved out with mem::take and put back after), only while a running
mod has world. Writes for a level that isn't ticking (another dimension,
in-frame) wait for it, up to 65,536.
Test scripts get blocks (setup) and expect.blocks; cargo horizon-mod check
reports a trial run's block writes. Example beacons: /beacon builds a
glowstone pillar beside the player, /ground reads the block underfoot and the biome.
parse_state is now a free function so the mod path shares it.
Docs: docs/mods.md (the capability, script keys), docs/api/mods.md and the
regenerated reference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game that simulates a world now starts its server mods (API 0.3,
experimental): single player's server thread, single player in-frame
(beside client mods), a LAN host either way, and the dedicated server.
Windows that are clients of a server never run them.
game/server_mods.rs: tick_server_mods at the end of the tick diffs the
players (player_joined, player_left), delivers the tick's chat and block
events in order, then calls tick. Block events come from players' breaking
and placing (the acting player is the guest during as_guest); chat from the
local chat path and the hub's new HostEvent::PlayerChat; a guest's /command
reaches its mod before vanilla's dispatcher. tell and command replies go to
their player (the window's chat or a SystemChat); broadcast goes to every
player and the log.
--dev-mod now works on the server thread, and servers check a dev mod's
file once a second from their tick. Every hook returns at once when no
server mod runs; without the mods feature they are empty stubs.
Docs: docs/mods.md (where server mods run, when hooks run, not yet),
docs/api/mods.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server mod runs where the world is simulated. Its manifest says side =
"server" and api = "0.3" (only a server mod may), and its component targets
the new WIT world horizon-server-mod:
- imports log and storage as client mods do, and the new capability players:
online() (id, name, position, dimension, game mode, health), tell(id, text),
broadcast(text), 8 lines per call shared with chat;
- exports server-events: init, tick, player-joined, player-left, chat, command
(with the player who typed it; its feedback goes back to that player),
block-broken and block-placed (with the block state as text).
horizon-mods: a second bindgen world sharing log and storage with the first;
a running mod holds either kind of hooks; Mods::load_for(Runs) starts only the
mods a game runs (the window client mods, the world's server server mods,
in-frame single player both) and marks the others Elsewhere. Client and server
commands are owned separately. The game passes Runs::Client for now, so
nothing changes in game until its server code calls these hooks.
The SDK gains horizon_mod_sdk::server (ServerMod, Registration,
export_server_mod!), the example server mod welcome (greets, announces
newcomers kept in storage, /online, /mined), and cargo horizon-mod check's
trial run plays a server mod with a player joining, chatting, typing its
commands and leaving. The manifest refuses client capabilities on a server
mod and players on a client mod. The generated reference, the guide, the
portal and the stability table describe it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mod's tests/<name>.toml scripts run in the sandbox the game uses, with no game
and no window. The runner plays the game's part (the player and clock the mod
reads, ticks, frames, chat lines, commands, a reload) and each step checks what
the mod did: its commands, chat lines, command feedback and errors, HUD panel
and corner, or that it was turned off. A failing step names itself and prints
expected against got. Each script runs on a scratch copy, so storage starts
empty and is removed afterwards.
horizon_mods::testing (feature runtime) holds TestGame and Script; the cargo
subcommand builds the mod (or takes --hmod), filters scripts by name, and fails
when one does. The template gains tests/hello.toml, coords-hud gains panel and
where scripts, and the sandbox tests run the example's scripts and check the
runner's failures, reload and expected stops. Scripts don't care what language
the mod is written in, so script mods (Lua) get the same runner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its own workspace, as the telemetry server's, for its own Railway service.
GitHub sign-in with no scope (the user's token reads the login once and is
dropped), signed-cookie sessions, an origin check and a CSRF token, six
submissions per account and sixty in all per hour. A submission runs the
file's own checks (horizon_mods::index::inspect, the signature, the entry's
fields through registry::project), uploads it to the registry's `staging`
release as <sha256>.<extension>, writes mods/<id>.toml on a new branch with
the submitter as co-author, and opens the pull request. Only an entry's
owners publish updates; a key change is a pull request by hand. Shader packs
are refused until their format exists.
Tested end to end against a mock GitHub: sign-in, the signed sample mod,
what lands on GitHub, and what is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `horizon_mods::index::registry`: the registry repository's
`mods/<id>.toml` and `revoked.toml`, `build` (every entry and file
checked, every problem listed, the index written) and `review` (a
published file never changes or disappears, a kind never changes;
non-owner, owner and key changes pointed out).
- `cargo horizon-mod registry build|review|check|list` for the registry's CI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
horizon_mods::index (feature index): index.json's types and rules (unique
ids, https URLs, plain file names, hex SHA-256, minisign keys and
signatures, a modpack's mods present in the index), open-ended kinds
(mod, modpack, resourcepack, shader; unknown kinds kept, not offered),
revocations, and check_file: size, hash, revocation, signature and the
file's own manifest against its entry. horizon_mods::modpack reads
.hpack files: horizon-modpack.toml and an allow-list of overrides.
docs/api/mod-index.md documents both, with a sample index the tests read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/api/mods.md gets "Ways in": one API (the WIT world) and one sandbox
(WebAssembly) for every language. Rust is first-class; Lua scripts are
planned, run by an interpreter that is itself a WebAssembly mod linked with
the script's capabilities only, with hot reload of the script; C, C++, Zig,
TinyGo, AssemblyScript and JavaScript/TypeScript are to be evaluated, each
listed with its cost measured against Rust.
Server mods are their own line (api 0.3); block entities are no longer
tied to a draft. Promotion to stable: the orchestrator proposes with the
evidence, Ludovic approves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/api/ gets one page per surface other programs build on (mods,
shaders, agent control, servers, the launcher, data formats), each with an
overview measured from the source, the guide, the generated reference, examples,
its stability, what is not there yet and a changelog, plus the policy every
API follows (docs/api/stability.md): a version per API, semver lines,
experimental / preview / stable, deprecation windows, compatibility tests and
the level of every part today.
The website gets a "Build on Horizon" page (website/docs/apis.md) that
summarises them and links the repository's pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/api/server-reference.md lists horizon-server's commands (its help
text), every server.toml key (config::TEMPLATE: default, the
server.properties key it writes, what it does) and the control API's
endpoints. Tests fail when the page is out of date (HORIZON_WRITE_DOCS=1
rewrites it), when the template and Config disagree on the keys, when a key
has no comment, or when api::ROUTES and the endpoints route() answers
differ.
api::ROUTES replaces the endpoint list in api.rs's module comment and the
hand-kept path list of the 405 arm. The template's uncommented keys get a
comment (what init writes for new servers; no setting changes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/api/mods-reference.md lists every capability, world, interface, type,
function and hook of horizon:mods, generated from
crates/horizon-mod-sdk/wit/horizon.wit and the manifest's capabilities by a
test in horizon-mods (feature pack, as CI runs it). The test fails when the
page is out of date (HORIZON_WRITE_DOCS=1 rewrites it), when a function has
no doc comment, or when a capability grants an interface the contract lacks.
The WIT's undocumented functions and fields get doc comments (no change to
the contract itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>