Building & Contributing
This page is for working on Nebulon itself. Consumers only need Getting Started.
Environment
- JDK 21 and the Gradle wrapper (
./gradlew); no system Gradle needed. ./gradlew runClientstarts the dev client with Iris and Sodium pinned viamodLocalRuntime(versions ingradle.properties). They are compatibility-test dependencies only and are never published as dependencies of Nebulon.- The dev run already sets
-Dnebulon.showcase=truevialoom.runs.clientinbuild.gradle. Loom wipes hand-added IDE VM arguments on every Gradle sync, so keep dev flags inbuild.gradle.
Select the compatibility runtime explicitly per test pass:
./gradlew runClient -Pnebulon.compat=vanilla
./gradlew runClient -Pnebulon.compat=sodium
./gradlew runClient -Pnebulon.compat=iris
./gradlew compatibilityInfo -Pnebulon.compat=<mode> prints the selected mode without launching.
Manual test matrix for rendering changes
| Configuration | What must hold |
|---|---|
| Vanilla (no pack) | Effects visible, occluded by blocks, hand drawn over effects |
| Iris installed, pack off | Same as vanilla |
| Iris pack active | Effects visible after the pack's composite; block and hand occlusion intact |
To test the shader-pack path, drop a pack zip into run/shaderpacks/ and enable it in the Iris screen. MakeUp-UltraFast is the validation pack. For fog changes, also walk into the showcase volume to exercise the inside-volume (exit-face, no depth test) path.
Architecture rules
- Server safety: nothing under
api/network,api/render(excludingclient), ornetworkmay touch client-only classes. That separation lets dedicated servers sync effects they never render. - Iris isolation: all Iris access goes through the reflection-only
client/compat/IrisBridgeand degrades to vanilla behavior when anything is missing. No compile-time Iris import outside that class. - The deferred
END_MAINwindow must not move. With a pack active, Nebulon replays the stage insideGameRenderer.renderWorldimmediately afterWorldRenderer.renderreturns — after the pack's composite, before the hand depth clear. Earlier, Iris intercepts unmapped passes; later, world depth reads 1.0 and occlusion breaks. - GPU resources are created lazily on the render thread and released via
EffectBatchRenderer.close(); the same mixin tears everything down whenGameRenderercloses.
Building and releasing
./gradlew build # remapped jar + sources jar in build/libs/
CI runs on Forgejo Actions (.forgejo/workflows/build.yml): every push and PR builds and uploads jars as artifacts. Direct pushes to main and tags starting with v additionally publish de.nexusrealms:nebulon to https://maven.riftrealms.de/releases.
Version format: <base>+mc<minecraft>.<Berlin yyyyMMdd>.<global build number>, e.g. 0.1.0-beta+mc1.21.11.20260715.123. Local builds use build number 0; override with -Pnebulon.buildDate=yyyyMMdd / -Pnebulon.buildNumber=N. ./gradlew versionInfo prints the coordinate (quote dotted -P arguments in PowerShell).
Release procedure:
- Bump the base
mod_versioningradle.propertiesonly when the API/release line changes. - Commit and push to
mainfor an automatic dated Maven build; tag milestones withv*. - Maven credentials live as protected Forgejo secrets
MAVEN_USERNAMEandMAVEN_PASSWORD.
Workstation publishing:
MAVEN_USERNAME=<username> MAVEN_PASSWORD=<password> ./gradlew publish