Multi-version
How one source tree builds a jar for each supported Minecraft version, where version-specific code may live, what differs between the versions today, and the checklist for adding the next one.
On this page
Vantage ships one jar per Minecraft version it supports, and as of 2.0.1 that is 26.1.2 and 26.2. Both jars come from the same source tree at the same commit. This page is for anyone changing the code: how the build picks a version, where version-specific code is allowed to live, what actually differs between the two versions today, and what to touch when Minecraft releases the next one. If you only want a jar, Building is all you need.
One tree, two jars
Almost the whole mod is version-independent and lives in src/main/java. The handful of places where Minecraft
moved something live beside the version they belong to, and the build picks the matching set. The point is that a
bug is fixed once: there is no second branch to forget.
You choose the version with one Gradle property. Leave it out and you get the default, which is the
minecraft_version line in gradle.properties (26.2 as of 2.0.1):
./gradlew build -PnoVersionBump # the default version
./gradlew build -PnoVersionBump -Pminecraft_version=26.1.2 # the other oneThe jar is named vantage-<mod version>+<minecraft version>.jar, because build.gradle sets the project version
to mod_version + "+" + minecraft_version. Two builds in a row therefore leave two differently named jars side by
side in build/libs, which is exactly what the release job relies on.
What lives beside a version
Everything that is true of exactly one version lives in one of three places, named after the version:
| Where | What it holds | As of 2.0.1 |
|---|---|---|
gradle/versions/<v>.properties | Facts about that version only | Two keys: fabric_api_version and minecraft_dependency |
src/main/java-mc<v>/ | Source that cannot be written against both versions | Four compat classes and one mixin |
src/main/resources-mc<v>/ | Resources that differ by version | The access widener, vantage.accesswidener |
The properties file is short. This is the one for 26.2, as it stands at 2.0.1:
fabric_api_version=0.158.0+26.2
# The range the jar advertises. Deliberately declared rather than derived from the version above: a
# build is only known to work on what it was compiled and tested against, and claiming more just turns
# a compile error into a crash on somebody else's machine.
minecraft_dependency=>=26.2 <26.3The range is written by hand on purpose, and the comment says why. A jar claims only the versions it was built and tested against. Fabric Loader checks that range at start-up and refuses the jar outside it, so a player on the wrong version gets a clear message instead of a crash later.
Everything the two versions share stays in gradle.properties: the loader, Loom, the mod version, JUnit, the
Hypixel Mod API and ModMenu. There is no per-version slot for those today.
How the build picks one
build.gradle turns minecraft_version into an overlay name, mc<version>, and does four things with it:
- Adds
src/main/java-mc<version>andsrc/main/resources-mc<version>as extra source directories. - Reads
gradle/versions/<version>.propertiesfor the Fabric API version and the declared range. - Points Loom's access widener at
src/main/resources-mc<version>/vantage.accesswidener. - Expands
${minecraft_dependency}intofabric.mod.json'sdepends.minecraftwhile processing resources.
A version the tree does not know fails at configuration time, before a single class compiles. Without that check a
typo would surface as a few hundred cannot find symbol errors. The message names what is missing and lists what
exists; for a version that has no files it reads like this:
Vantage does not support Minecraft 26.3. It needs gradle/versions/26.3.properties and src/main/java-mc26.3. Supported: [26.1.2, 26.2]The check looks for the properties file and the Java overlay directory. It does not look for the resources overlay, so create all three together.
What actually differs
As of 2.0.1, these are the differences between the two versions, and the class that absorbs each one. Everything here is read from the overlay classes' own documentation.
| Area | On 26.1.2 | On 26.2 | Absorbed by |
|---|---|---|---|
| Screens and the HUD | One Gui class; get-prefixed accessors; the hide-GUI flag on Options; legacy colour data on ChatFormatting | Gui split into a screen manager and Hud; most get prefixes dropped; the hide-GUI flag moved; colour data on TextColor | Mc |
| Dyed blocks and items | One constant per colour, such as Blocks.LIME_WOOL | A colour collection, such as Blocks.WOOL.lime() | Dyed |
| Slimes and magma cubes | In entity.monster | In entity.monster.cubemob | Mobs |
| GPU pipelines | Uniforms and samplers declared one call at a time; vertex layout from named constants; draw mode part of the layout | Bind groups; vertex layout built from GpuFormat; draw mode separate | Gpu |
| GPU timer queries | One query object per measurement, kept alive to be read a frame later | One long-lived object that rotates its own slots and reports an average | GpuTimer, made by Gpu |
| The burning overlay | ScreenEffectRenderer.renderFire, drawing into a MultiBufferSource | ScreenEffectRenderer.submitFire, submitting to a SubmitNodeCollector | ScreenEffectRendererMixin |
The shaders themselves did not change between the versions, which is why the GPU difference is a seam and not a rewrite. The burning overlay is what the Hide the fire overlay option of Weather and light removes, so its mixin has to exist in both shapes.
The overlay rule
A file belongs in an overlay only when it genuinely cannot be written against both versions, and every overlay
must present the same API. Nothing in src/main/java may be able to tell which version it is being compiled for.
In practice the differences funnel through one package, core.compat:
| Class | Covers | Where it lives |
|---|---|---|
Mc | Client internals that moved: screens, the HUD, chat, the tab list, legacy text formatting | Each overlay |
Dyed | The sixteen dyed variants of blocks and items | Each overlay |
Mobs | Mob types whose package moved, which an import alias cannot paper over | Each overlay |
Gpu | Pipeline construction and the few encoder calls that changed shape | Each overlay |
GpuTimer | The one shape a GPU timer query takes; each version's Gpu.newTimer() supplies its own | src/main/java, shared |
Mc describes itself as the only file in the mod allowed to know which Minecraft version it is being compiled
against. Every method on it is a thin forward with no state of its own. A feature never touches the moved API; it
asks the seam:
Screen open = Mc.screen(); // the screen currently open, or null in the world
boolean hidden = Mc.hudHidden(); // F1, wherever this version keeps the flag
Block wool = Dyed.wool(DyeColor.LIME);
if (Mobs.isSlime(entity)) { … } // true for magma cubes too, as Minecraft's hierarchy saysWhen the next version moves something, prefer widening one of these classes over adding a new overlay file. When a whole file has to be duplicated, say why in its Javadoc, because the next person has to keep both copies in step.
Access wideners
Each version has its own access widener, in the accessWidener v2 official format, at
src/main/resources-mc<v>/vantage.accesswidener. A member that common code reaches has to be widened in both
files. The two files differ only where one version already makes a member public:
| Widened in | Member | Why |
|---|---|---|
| Both | The tab list's header and footer, a display entity's scale, a container screen's bounds and hovered slot | Read by the tab list reader, a foraging feature and the container overlays |
| 26.1.2 only | Minecraft.overlay | The UI smoke test waits for the loading overlay; 26.2 has a public accessor for it |
| 26.2 only | RenderPipelines.register, two pipeline snippets, RenderType.create | 26.2 made them non-public, and the world renderer builds its render types the way vanilla does |
Mixins that may vanish
Every mixin config is declared not required, so a target that moves turns into a missing feature rather than a
crash. That degradation is silent, which is the price. The tool that makes it loud again is
tools/mixin_targets.py:
python tools/mixin_targets.py # every version in gradle/versions/
python tools/mixin_targets.py 26.1.2 # just oneFor each version it collects the common mixins plus that version's overlay mixins, finds each @Mixin target
class and each injected method name in the merged Minecraft jar Loom has already downloaded, and exits non-zero if
any is missing. It reads that jar from Loom's cache under ~/.gradle/caches/fabric-loom/<version>/, so build each
version once before you run it. A healthy run ends with the line All mixin targets resolve.
A mixin whose target differs between versions lives in the overlays, as ScreenEffectRendererMixin does. Its
config, vantage.qol.mixins.json, names the class once, and each overlay supplies a class by that name.
Adding the next version
No document lists these steps in one place; they follow from the build's error message, the access widener pair, the release tool and the pipeline. Work through them in order.
- Create
gradle/versions/<v>.propertieswithfabric_api_versionandminecraft_dependency. Declare only the range you will test. - Create
src/main/java-mc<v>/by copying the nearest version's overlay, then change what Minecraft moved. Keep every public signature inMc,Dyed,MobsandGpuidentical to the other overlays. - Create
src/main/resources-mc<v>/vantage.accesswidener, starting from the nearest version's file. Drop what the new version made public; add what it hid. - Compile and test the new target and the old ones:
./gradlew compileJava test -Pminecraft_version=<v>, then the same for every other version. - Check the mixins:
python tools/mixin_targets.py. A mixin whose target moved goes into the overlays. - Look at it running:
./gradlew runUiSmokeTest -Pminecraft_version=<v>, then read the report. Testing says what a healthy report looks like. - Teach the release tool: add the version to
TARGETSintools/release.py. It refuses to publish a release that lacks a jar for any target, and it writes the release notes' "Which jar" table from that tuple. The sentence under the table begins "Both require…", so change it once there are three. - Teach the pipeline: add a
./gradlew build -Pminecraft_version=<v> -PnoVersionBumpline to thereleasejob in.gitlab-ci.yml, beside the existing two. - Decide the default:
minecraft_versioningradle.propertiesis what thecompileandbuild-jarjobs build, and what a bare./gradlew buildbuilds. - Regenerate the website's catalogue:
python tools/sitedocs.py. It lists every version that has both a properties file and an overlay, so the version list on this site follows from step 1 and step 2. - Update the prose that names versions by hand: the README and Install.
Dropping a version is the same list in reverse: delete its three files, its entry in TARGETS and its line in the
release job.
Related
- Building: the JDK, the Gradle tasks and the version-bump flag.
- Architecture: where
core.compatsits among the other packages. - Testing: the unit tests and the smoke test you run on each target.
- Releasing: how both jars become one release.