Preserve Java parity oracles and comparison benchmark instrumentation. Move their remaining dependency locks under tools/benchmark-deps and remove legacy product packaging, fixtures, and CI checks. Co-Authored-By: OpenAI Codex gpt-6.1-sol <noreply@openai.com>
10 KiB
Java mods and Horizon: what can carry over
Players ask "will my mods work?". Short answer: Fabric and NeoForge jars will not run in Horizon as they are, and no bridge we could build would change that for more than a handful of mods. What carries over is the content (data packs, resource packs, the data and assets inside mod jars) and the ideas (native Horizon ports of the mods people use most, written with Horizon's own mod system). This page is the reasoning.
How Java mods work
Fabric
- Fabric Loader finds jars in
mods/, readsfabric.mod.json(entrypoints, dependencies, mixin configs, access wideners), and starts the game through a class loader that can rewrite classes as they load. - Mixin (SpongePowered) is how nearly every mod changes the game: a mixin
class names a Minecraft class (
net.minecraft.client.gui.hud.InGameHud) and injects code at points inside its methods (@Inject(method = "render", at = @At("TAIL")),@Redirect,@ModifyVariable,@Overwrite...). The bytecode of the vanilla class is rewritten at load time. Access wideners make private fields and methods public for the same purpose. - Fabric API is a set of modules that give mods events and registries
instead of raw mixins:
fabric-lifecycle-events(ClientTickEvents,ServerTickEvents),fabric-rendering(HudRenderCallback,WorldRenderEvents),fabric-command-api,fabric-key-binding-api,fabric-networking-api(custom payloads),fabric-registry-sync,fabric-resource-loader,fabric-screen-api,fabric-item-group,fabric-events-interactionand about forty more. Fabric API itself is implemented with mixins into the same vanilla classes. - Mods mix the two freely: a mod using Fabric API events still calls
MinecraftClient.getInstance().player.getBlockPos(),world.getBlockState(pos),DrawContext.drawText(...): direct calls into vanilla's classes, by their mapped names.
NeoForge (and Forge)
- An event bus (
NeoForge.EVENT_BUS, mod buses) with hundreds of events,DeferredRegisterfor registries, data attachments and capabilities, networking payloads, config specs. NeoForge patches vanilla's source directly (binary patches) to fire its events. - Mods also use mixins and access transformers, and call vanilla classes directly, as on Fabric.
Data and assets in a jar
Besides code, a mod jar carries the same folders as packs:
assets/<namespace>/ (models, blockstates, textures, sounds, language files,
shaders) and data/<namespace>/ (recipes, loot tables, tags, advancements,
predicates, functions, worldgen: biomes, features, structures). These are
vanilla formats, read by vanilla's own code.
Why a Java bridge cannot run them
Horizon is a reimplementation in Rust: there are no net.minecraft classes
at runtime, no InGameHud, no ClientPlayerEntity, no bytecode to rewrite.
- Mixins have nothing to bind to. A mixin is a patch against a specific method's bytecode. Without vanilla's classes there is no method, no injection point and no local variable to modify. This is not a missing feature we could add: it is the mechanism itself.
- Fabric API is mixins too. A shim Fabric API would have to be re-implemented from scratch as Java classes calling into Horizon. That part alone is possible for the event modules.
- Mods call vanilla directly. Even "pure Fabric API" mods use vanilla's classes for everything else: the player, the world, the block states, the renderer, the text drawing. Running them means providing Java classes with vanilla's names and behaviour, backed by Horizon's state: in effect a second Minecraft, in Java, on top of Horizon. Thousands of classes, in the mapped names of every mapping set (Yarn, Mojang, intermediary), version by version.
- The JVM itself would ship with every install (a trimmed JRE is still 40-60 MB, the full one 150+ MB), start in a second or more, hold a few hundred MB of heap before any mod loads, and bring garbage-collector pauses back into the frame: the costs Horizon exists to remove. JNI calls cost tens to hundreds of nanoseconds each and every one crosses with copies.
- No sandbox. Java mods are fully trusted native-speed code with file and network access. Horizon's mods are sandboxed and their permissions are shown; a Java bridge would be the one hole in that.
What a bridge could support, if we ever built one: a stable subset of Fabric API events (client and server tick, HUD render callback, client commands, key bindings, custom payloads), with a tiny "vanilla view" API (player position, held item, chat), for mods written only against that subset. Surveying the most-downloaded Fabric mods, nearly none qualify: the popular ones are popular precisely because they reach into rendering, the world or the UI. The cost (a JVM in the game, a second API surface to keep stable) is far above the benefit. We do not build it. If that changes, the bridge would sit on top of the WIT API like any other mod runtime, so the sandbox would still hold.
What we do instead
1. Data-driven content (packs and the data in mod jars)
Horizon already reads vanilla resource packs and data packs (functions, function tags, predicates today). Extending data-pack coverage to recipes, loot tables, tags, advancements and worldgen JSON brings most "content" packs over with no mod code at all, and it is vanilla behaviour, so the parity harnesses check it.
For Fabric/NeoForge jars, the launcher can offer "import the data": read
data/ and assets/ from the jar and load them as a data pack plus a
resource pack. What works: new recipes, loot changes, tags, advancements,
functions, worldgen tweaks that only use vanilla blocks, retextures, language
files. What does not: anything referring to a block, item or entity the jar
registers in Java code (the ids do not exist); the import skips those files and
lists them. With Horizon's planned registries interface, a native port can
register those ids and reuse the jar's JSON models, textures, recipes and loot
as they are.
2. Native Horizon ports of popular mods
Horizon itself covers the performance mods: Sodium, Lithium, FerriteCore, ModernFix, EntityCulling and ImmediatelyFast exist because vanilla Java is slow, and Horizon's renderer and simulation are the replacement (see benchmark). Zoom (OK Zoomer, Zoomify), FPS and keystroke overlays, a minimap and auto-reconnect are already built in as extras.
Port first
Ranked by players reached per unit of API work. Each needs the planned interfaces listed in mods.md.
| Mod (Java) | What it does | Horizon port | Needs |
|---|---|---|---|
| Mod Menu | list mods, open their settings | built in: Options > Mods (done), settings pages next | ui |
| AppleSkin | food saturation and exhaustion on the HUD and tooltips | SDK mod | inventory-read, ui |
| Jade / WTHIT | what the crosshair points at: block, entity, harvest tool, container contents | SDK mod | target, ui |
| JEI / EMI / REI | browse items, recipes and uses | SDK mod (or built in: Horizon already has the recipe book's data) | recipes, inventory-read, ui |
| Xaero's Minimap / JourneyMap | minimap, world map, waypoints | the minimap is an extra; a world map and waypoints as an SDK mod | world-read, render, storage |
| Litematica | schematic overlay to build from | SDK mod | world-read, render, storage, ui |
| Mouse Tweaks / Inventory Profiles Next | drag and sort inventory | built in or SDK mod | inventory-read + a sanctioned click API |
| Continuity / CIT | connected and custom item textures | built into the renderer (pack formats) | none: resource-pack features |
| Distant Horizons | low-detail far terrain | a Horizon renderer feature, not a mod | none |
| Iris + shader packs | shaders | a Horizon renderer feature (wgpu shader pipeline), later | none |
| Simple Voice Chat | proximity voice | server + client mod | server, net, audio (later) |
3. A porting guide (outline)
To be written as docs/porting-from-fabric.md once API 0.2 lands:
-
Separate logic from glue. Most of a mod is plain logic (maths, formatting, data structures): port it to Rust once, unit-test it on the host.
-
Map events.
Fabric Horizon ClientModInitializer.onInitializeClientMod::init, returning aRegistrationClientTickEvents.END_CLIENT_TICKRegistration::on_tick,Mod::tickHudRenderCallbackhud::set_panelwhen the content changes (retained), oron_frameClientCommandRegistrationCallbackRegistration::command,Mod::commandClientReceiveMessageEventsRegistration::on_chat,Mod::chatKeyBindingHelper.registerKeyBindingkeys(planned)Registry.register(Registries.ITEM, ...)registries(planned) with JSON indata/ServerPlayNetworkingpayloadsnet(planned)config libraries (Cloth Config, YACL) storage+ auisettings page (planned) -
Replace mixins with hooks. For each mixin, write down what it observes or changes. If a WIT hook covers it, use the hook. If not, open an issue asking for the hook ("I need X before/after Y"): hooks are added to the API for everyone instead of each mod patching the game.
-
Declare capabilities in
horizon-mod.toml, the least the mod needs. -
Reuse assets: textures, models, language files and recipes move as they are into the
.hmod'sassets/anddata/(withregistries). -
Test with
cargo horizon-mod runand hot reload; add unit tests for the logic; publish with a signed.hmod.
Sources
- Fabric documentation (
docs.fabricmc.net): mod structure, Mixin introduction, Fabric API modules and events. - SpongePowered Mixin wiki: injection points and how class transformation works.
- NeoForge documentation (
docs.neoforged.net): events, registries, networking. - Modrinth's most-downloaded Fabric mods, for the porting list.
- Horizon's Fabric benchmark instrumentation (
mods/horizon/src/bench/) remains a developer tool for Java comparisons. The former Java optimization mod is retired; the parity oracles remain inmods/horizon/src/test/.