How the repository is laid out, how to build and run it, and how releases are made. For using MachineEngine, see the documentation.
- docs/ARCHITECTURE.md is the specification. Its decision log (D1, D2…) is binding: a change that goes against a decision changes the decision first, with its reason.
docs/*_PLAN.mdare the plans of larger pieces of work, with their stages and what was found on the way. They stay as history once done.
| Module | Purpose |
|---|---|
api |
Public API and SPI, including the Paper-facing services addons use (api.paper) and version access (api.platform). |
common |
Implementation: the engine (core packages), its Paper integration and its CraftEngine integration. |
hooks/zmenu |
Optional zMenu integration (machine menus). One hooks/<plugin> module per optional plugin. |
nms/common |
Reflective fallback for Minecraft versions without a module of their own. |
nms/v1_20_6, nms/v26_3 |
Version-specific code, built with paperweight userdev against each version. |
plugin |
The plugin jar (everything above), and the dev servers. |
test |
Headless test harness for MachineEngine and addons, built on the assembled plugin. |
api <- nms:common <- common <- hooks:* <- plugin <- test
nms:v1_20_6, nms:v26_3 <- plugin
Every module compiles against the newest Paper API (26.3) and CraftEngine, and emits Java 21
bytecode, except nms/v26_3 (Java 25, loaded only on 26.x). check fails if the bytecode uses
anything missing from Paper 1.20.6, or is newer than Java 21 (verifyPaperBaseline), and if a
bundled library is not relocated (verifyRelocation). Why: ARCHITECTURE.md §5, D24–D28.
Mekanized, the content addon, is its own repository (D42). With both checked out side by side, Mekanized builds against this checkout.
JDK 25 builds everything (the Gradle toolchain downloads it if needed).
./gradlew buildThe plugin jar is plugin/build/libs/MachineEngine-<version>.jar.
./gradlew :plugin:runPaper-26.3Also runPaper-1.20.6, runPaper-1.21.4, runPaper-1.21.11, runFolia-1.20.6 and
runFolia-26.2. Servers run in
run/<paper|folia>-<version>/; on the first start, accept the Minecraft EULA in its eula.txt.
Do not rebuild while a dev server runs: it loads the jars from build/libs.
| Property | Effect |
|---|---|
-Pcraftengine_runtime=dev (default) |
CraftEngine built from its craftengine_dev_ref branch into .craftengine/, the snapshot MachineEngine compiles against |
-PrefreshCraftEngine |
Rebuild that CraftEngine jar |
-Pcraftengine_runtime=modrinth |
The published release (craftengine_modrinth_version) instead |
-Pcraftengine_runtime=none |
No CraftEngine: MachineEngine must refuse to start |
-PwithMachineEngine=false |
CraftEngine alone (checks that it keeps machine data) |
-PwithZMenu=false |
No zMenu |
-Pcraftengine_build_heap=4G |
Heap of the CraftEngine build |
-Pcraftengine_version=<v> |
Compile against another CraftEngine snapshot (CI's daily run uses the newest) |
The dev servers get two test blocks (plugin/src/dev, never shipped). From the console, after
forceload add 0 0 63 63:
| Command | Expected |
|---|---|
machineengine check drop <x y z> world |
a bin with 100 iron ingots broken without a player drops all 100 |
machineengine check hopper <x y z> world |
hopper → bin → hopper → chest moves all 10 ingots into the chest |
machineengine check comparator <x y z> world |
a comparator reads 8 from a half-full bin, 0 from an empty one |
machineengine check fill <size> <x y z> world |
a grid of idle crushers, to measure the idle cost |
Each logs its result with the expected value. /machineengine debug shows machine states, ports and
networks in the world.
./gradlew test runs the headless tests: a real engine with fake worlds, regions and clock
(test module). New engine behavior comes with a test there; what needs vanilla blocks or a
client is checked on a dev server.
- Compile and baseline. Raise the Paper API in the version catalog if the new version needs
it;
./gradlew checkstill enforces the 1.20.6 baseline (verifyPaperBaseline). - Version access. A version without a module of its own uses
nms/common(reflection). For world fluids, check whatWorldFluidNmsdoes there: the key biomes injected into the registry data, the biome section format (BiomeSections: the array length prefix went away in 1.21.5), the biome update packet and the attribute names. A version that changed them gets a branch inReflectiveWorldFluidsor its ownnms/v<version>module. - Biome lookup.
BiomeZoomcopies the client'sBiomeManager.getBiome; compare it with the new client's (unchanged from 1.20.6 to 26.3). - Shaders. The pack builder patches the new client's water shader at runtime, and a shader that
fails to compile disables the whole resource pack. Add the version to
tools/world-fluids/fetch_vanilla.py(andfetch_sodium.pyonce a Sodium for it reads pack shaders), then follow tools/world-fluids/README.md: patch every version, compile every define set with glslang, 0 failures. AShaderPatch.MismatchExceptionmeans the shader lost a line the patch relies on: extendShaderPatchfor it. - Dev server. Add
registerDevServer("<version>", …)inplugin/build.gradle.kts, run it, accept the EULA, and check: the server checks above, a fluid poured from a bucket, its look in game (with Sodium too on 26.2+),/machineengine fluid info. - Docs. The supported versions in the README and on the documentation site.
Work happens on develop (the default branch); main holds releases only (D48).
- Pull requests target
develop, from a branch of your own (feature/…, a fork). - Only the owner opens pull requests into
main, fromdevelop(a release) or ahotfix/*branch. TheRelease sourcecheck fails any other, and themainruleset requires it andBuild (CraftEngine pinned), takes merge commits only, and blocks force pushes and deletion.developblocks force pushes and deletion. - Dev builds: every push to
develop(not docs-only) runsdev-build.yml: version<next>-dev.<run>(0.2.1-dev.3), built and tested, then the rolling GitHub pre-releasedev(replaced each time) and the Hangar Snapshot channel. A newer push cancels a dev build still building or testing, never one already publishing. Put[skip dev]in the commit message (the last one of a push) of a change that does not need a dev jar: the Build workflow still runs.[skip ci]skips every workflow.
- On
develop: in CHANGELOG.md, turn "Unreleased" into the version and date (with every API break listed, in a minor version only). - Open the pull request
develop→maintitledRelease <version>, wait for the checks, merge it (merge commit). - Tag the merge commit on
mainwith the version (0.2.1,0.2.1-beta.1) and push the tag:git fetch origin && git tag 0.2.1 origin/main && git push origin 0.2.1. - On
develop: raiseversioningradle.propertiesto the next-SNAPSHOTandapiBaselineinapi/build.gradle.ktsto the version just released.
:api:japicmp fails a patch version that breaks the API of apiBaseline; its report is in
api/build/reports/japicmp.
A hotfix: branch hotfix/<version> from main, fix and update the changelog there, pull request
into main, tag as above, then merge main into develop.
The tag runs two workflows. Both stop, publishing nothing, if the tagged commit is not on main:
release.ymlbuilds and tests with that version, checks the API with japicmp, makes a GitHub release with the jar, uploads it to Hangar (a hyphenated tag goes to the Snapshot channel);maven.ymlbuilds and tests, then publishes every module to the Maven repositoryhttps://1robie.github.io/maven/(repository1robie/maven), with the API Javadoc. It can also be run by hand for an existing tag.
hangar-page.yml keeps the Hangar page equal to the README on main, so it changes with releases. Secrets: HANGAR_API_TOKEN,
MAVEN_REPO_TOKEN. Release MachineEngine before a Mekanized release that needs it.