README: drop the WebAssembly mention
Horizon native mod template
A minimal horizon-mixins mod template based on Horizon Veinminer. It declares one HEAD hook that logs a block-break attempt, then lets Horizon's original method run unchanged. Runtime logic lives in one Rust file that also builds as a standalone testable Cargo library.
This is the native source-mixin path: it builds a custom Horizon executable.
For content mods (data only, .hmod), use Horizon's cargo horizon-mod new
instead. This template does not install into a running game or the launcher's
ordinary .hmod folder.
Start a mod
Use GitHub's Use this template → Create a new repository, then clone your new repository. Requirements: Linux, Rust 1.95.0, Python 3.12+, Git and Cargo. The staging script uses Linux file locks; Windows/macOS are not validated.
cargo fetch --locked
cargo test --workspace --locked --offline
The exact compatible revisions are in compatibility.json:
- Horizon:
ba6344a59cb9e755b730dcf02285fc0d33b97d3c(Nightly 2026-10-30). - Horizon Mixins:
d9c83d7(0.6.0, plan schema 6).
Clone the tools beside your mod if you do not already have suitable checkouts:
git clone https://github.com/horizon-rust/horizon ../horizon
git clone https://github.com/horizon-rust/horizon-mixins ../horizon-mixins
git -C ../horizon-mixins checkout "$(python3 -c 'import json; print(json.load(open("compatibility.json"))["horizon_mixins_revision"])')"
cargo fetch --locked --manifest-path ../horizon-mixins/Cargo.toml
Horizon can stay on its existing branch: staging exports the committed pin with
git archive and never checks out or edits the original source. Its Git history
must contain the pinned commit. A shallow clone may need a fetch first.
The Mixins checkout must be at the exact recorded revision with clean tracked
engine sources. For an existing development checkout at another revision, use
a separate clone/worktree and set HORIZON_MIXINS=/path/to/pinned/horizon-mixins.
Layout and customization
Cargo.toml Standalone runtime library + source audit workspace
compatibility.json Exact upstream/tool revisions and source hashes
mixins/example_mod/
horizon-mixin.toml Module and HEAD declaration
break.rs Small typed hook adapter
runtime.rs Runtime implementation and unit tests (single source)
tools/horizon.py Export → plan → apply → check/build/test
tools/test_template.py Tiny compiled fixture; no Horizon checkout/assets needed
tools/test_stage.py Real pinned source audit and repeatability test
tools/audit/ Rust AST comparison against the pristine export
To rename the example:
- Change Cargo package name/description and the manifest's mod ID/version.
- Rename
mixins/example_mod/and update the Cargo[lib].path. - Change the manifest module name
example_modand its reference inbreak.rs. - Change the runtime log prefix, unit-test expectation and the native test filter
game::example_mod::testsintools/horizon.py. - Run
cargo generate-lockfile --offlineafter renaming the Cargo package and run the checks below.
Add behavior to runtime.rs, then expose it through small typed helpers and
TOML declarations. No payload generation is necessary: Cargo and the injected
module read the same file. Module payloads cannot use inner file attributes
(including //! comments), external mod files or include!; use ordinary
comments and inline modules. Hooks compile in their target's crate/module, so
native-specific imports can be placed in the helper. To keep runtime tests
standalone, use ordinary Rust types or a small host interface there.
This example passes only pos. To access the native game, declare an explicit
helper binding for &mut crate::game::Game<'_> with value &mut *self, as
Veinminer does. Existing crate dependencies/privacy/borrowing remain enforced;
Cargo dependencies from this template are not automatically injected into Horizon.
See Mixin contracts
and effects/state
for cancellation, captures, value changes, bridges and state.
Stage, compile and run
python3 tools/horizon.py stage --horizon ../horizon
This prints the generated stage path. It uses only committed pinned sources,
records original hashes, runs the real Mixins plan/apply CLI, and preserves
unchanged bytes/mtimes on repeated runs. The original checkout and player data
stay untouched. The plan is saved at target/mixins/plan.json.
If Horizon dependencies are not cached, fetch the staged lockfile once:
cargo fetch --locked --manifest-path target/mixins/modded/Cargo.toml
python3 tools/horizon.py check --horizon ../horizon
python3 tools/horizon.py test --horizon ../horizon
python3 tools/horizon.py build --horizon ../horizon
check checks the actual client binary. test compiles it and runs the embedded
runtime test module. build uses Horizon's fast profile and
--no-default-features (audio/controllers disabled), and prints its binary path.
These commands use offline, locked builds; raw logs are in target/horizon-*.log.
--target-dir PATH selects a reusable external Cargo target directory.
Launch the built client with your own prepared resources for the pinned Horizon version, and explicitly pass their path:
./target/horizon-cargo/fast/horizon-client --resources /absolute/path/to/prepared/resources
Prepare those resources using the compatible Horizon launcher or
horizon-prepare --prepare; game resources are not supplied by this repository.
Use the binary's --help for world/server arguments. Breaking a block should
print [example_mod] Breaking block at ... to the host's stderr. The HEAD log
reports attempts, including attempts the original game later rejects.
Compilation tests do not establish interactive gameplay/resource compatibility.
Pin to a Nightly, package, publish
A native mod builds on one exact Horizon commit, and every mod of a player's build must pin the same one, so
pin a Nightly: its release notes name the commit, and the tag nightly-source/<date>-<short> keeps that
commit's source downloadable after the Nightly is replaced. This template pins Nightly 2026-10-30
(ba6344a). The launcher also builds every mod of a build with one horizon-mixins revision, the one
they all name (horizon_mixins_revision), so keep it the same as the other mods your players combine yours with.
To move to a newer Nightly:
- Put its commit in
compatibility.json(horizon_revision) and in the manifest's[compatibility] horizon_revision. - Update
source_sha256for each target file (git -C ../horizon show <commit>:<path> | sha256sum). - Run the checks below. When a target moved (the game is being split into crates),
plannames the missing function: find its new path in that commit and fixtargetin the manifest.
Then python3 tools/package.py writes target/<id>-<version>.hnmod: horizon-native.json plus your
mixins/<id>/ sources, byte-for-byte reproducible. Submit it to
horizon-mods as a native-mod. A maintainer reads the
source before it is listed, and the Horizon launcher compiles it with its own Rust on the pinned commit, in
an instance of its own. The mod id must be lowercase letters, digits and _ (the registry's rule), and
the bundle folder is named after it.
Validation
cargo fmt --all --check
cargo clippy --workspace --all-targets --locked --offline -- -D warnings
cargo test --workspace --locked --offline
python3 tools/test_template.py
# With a Horizon Git clone containing the pin:
python3 tools/test_stage.py --horizon ../horizon
python3 tools/horizon.py check --horizon ../horizon
python3 tools/horizon.py test --horizon ../horizon
The tiny fixture compiles the actual bundle, checks the original body executes once with its result preserved, runs the shared runtime unit test, and verifies repeated source bytes/mtimes. Wrong revisions and missing targets fail before replacing the stage. The Rust audit checks the exact generated HEAD/helper and module, removes only those planned additions, then compares the remaining AST with pristine source. The same audit runs on a complete pinned Horizon export. CI runs the standalone checks and tiny fixture; the full native checks require the larger cached Horizon dependency set. See VALIDATION.md.
Build/staging tooling is adapted from the MIT-licensed Veinminer port. This repository includes no Veinminer gameplay, Minecraft assets, game binaries or exported Horizon sources. License: MIT.