A client-side mod for Minecraft 26.3 — for Fabric and NeoForge — that paints with blocks:
solid extrude/fill, colour/brightness gradients, and 3D noise patterns. This README is the
developer/build guide; user-facing documentation lives in
docs/curseforge-description.md.
| What | Version | Notes |
|---|---|---|
| JDK | 25 (Temurin recommended) | Minecraft 26.3 requires Java 25 |
| Minecraft | 26.3 | pinned in gradle.properties |
| Fabric Loader | 0.19.3+ | pinned in gradle.properties |
| Fabric API | 0.160.6+26.3 | compile dep; also needed in any Fabric instance running the jar |
| Fabric Loom | 1.17-SNAPSHOT | Gradle plugin; 26.x ships unobfuscated, Loom runs non-remapping |
| NeoForge | 26.3.0.3-beta | pinned in gradle.properties (neo_version) |
| ModDevGradle | 2.0.141 | NeoForge's Gradle plugin (moddev_version) |
| Gradle | — | none needed; the wrapper (gradlew) downloads itself |
Installing JDK 25:
- Windows:
winget install EclipseAdoptium.Temurin.25.JDK— Gradle auto-detects it fromC:\Program Files\Eclipse Adoptium\. (Gradle's daemon-JVM auto-download does not work on Windows, so install the JDK yourself.) - Linux (Debian/Ubuntu):
sudo apt install temurin-25-jdk(Adoptium repo) or download from adoptium.net. Any JDK 25 works; the build uses a Java 25 toolchain.
All game code is loader-free and lives in common/ — 26.x ships unobfuscated with Mojang
names, so the exact same compiled classes run on both loaders. fabric/ and neoforge/
are thin shells: an entrypoint class each (~80 lines) that registers keybinds and forwards tick /
render / input events into the common core, plus the loader metadata file. Each loader jar bundles
common's classes and assets, so the shipped jars are self-contained.
# Linux / macOS / Git Bash
./gradlew build
# Windows (cmd / PowerShell)
gradlew.bat build- Outputs:
fabric/build/libs/fw-paint-fabric-<version>.jarandneoforge/build/libs/fw-paint-neoforge-<version>.jar. buildalso runs the unit tests; a red build means failing tests, not just compile errors.- First build downloads Minecraft + dependencies for both loaders — allow a few minutes and disk
space under
~/.gradle/%USERPROFILE%\.gradle.
./gradlew runClient # Fabric (alias for :fabric:runClient)
./gradlew :neoforge:runClient # NeoForgeLaunches Minecraft 26.3 with the mod loaded (offline dev session; Realms/auth warnings in the log
are normal). Each loader keeps its own world/config under fabric/run/ and neoforge/run/.
./gradlew testUnit tests live in common/ and cover the pure, Minecraft-free maths: gradient
ordering/deviation (GradientRampTest), texture pixel analysis (TextureStatsTest), noise
(NoiseTest), flood fill (FloodFillTest), marker-segment geometry (NoisePlacerTest), palette
band maths (PaletteMathTest), and the palette model/store (PaletteTest,
PaletteStoreNamingTest). Anything touching Minecraft classes is exercised via runClient,
not tests.
Copy the jar matching the instance's loader into its mods/ folder:
- Fabric:
fw-paint-fabric-<version>.jar— the instance must also contain Fabric API for 26.3. - NeoForge:
fw-paint-neoforge-<version>.jar— no other dependency.
The mod is client-side only; servers need nothing.
- The version lives in
gradle.properties(mod_version) — bump it there; bothfabric.mod.jsonandneoforge.mods.tomlpick it up at build time. - CI (
.github/workflows/build.yml): every push builds + tests and uploads both jars as thefw-paint-jarsartifact. Releases are automatic: a push tomasterwhosemod_versionhas no matchingv<version>tag yet creates the tag and publishes a GitHub Release with both jars attached (body fromdocs/release-notes/v<version>.md) — so merging a version-bump PR is the release action. Manually pushing av*tag still works too. - Repo rulesets:
masteris PR-only (no direct pushes, no deletes, no force-pushes — no bypass), and new branches must matchfeature/*,release/*,fix/*, orchore/*.
common/src/main/java/co/fax/wang/
Gradient.java loader-free core: keybinds, tick driving, shared state
GradientScreen.java the K screen (Paint / Solid / Palette / Finder / Settings / Help tabs)
PaletteEditScreen.java the palette editor PaletteListPanel.java the Palette tab's rows
PatternEditScreen.java the pattern editor PatternThumb.java mini pattern renderer
PaletteChoice.java resolves the active palette into placeable ramps (Automatic segments etc.)
PatternChoice.java pattern press state + Oklab-neighbour variation
HelpPanel.java in-game manual UiIcons.java shared pixel-art glyphs
HudOverlay.java helper-text + palette-row HUD HudPlacementScreen.java move-the-text screen
MarkerManager.java marker modes/persistence + in-world rendering
PaintPlacer.java unified Single/Face/3D placement for all paint types
GradientCaches.java session memory NoisePlacer.java marked-region geometry
GradientRamp.java gradient-order maths BlockTextures.java / TextureStats.java texture analysis
BlockPlacement.java multiplayer-safe placing
Noise.java / NoiseType.java / FloodFill.java / CurveFunction.java / FaceOverlay.java
PlacementMode.java / PaintType.java / SolidMatch.java / GradientMode.java / GradientSource.java
palette/ the v2 model: Palette, PaletteSegment, PaletteStore (fw-paint-palettes.json),
PaletteMath, PatternMath, AutoMode, PaletteOrder, PaletteKind,
PatternTiling, SizingMode, MissingBlockPolicy
config/GradientConfig.java + config/ConfigManager.java Gson config -> config/gradient.json
common/src/test/java/co/fax/wang/ unit tests (pure maths only)
fabric/src/main/java/co/fax/wang/fabric/GradientFabric.java Fabric entrypoint
neoforge/src/main/java/co/fax/wang/neoforge/GradientNeoForge.java NeoForge entrypoint
fabric-26.2-mod-starter.md 26.2 API migration notes (worth reading before touching GUI/render code)
- 26.x is unobfuscated — no mappings/remapping; Loom runs in non-remapping mode and NeoForge
uses the same Mojang names, which is what makes the shared
common/module possible. Don't add mapping-dependent tooling. - 26.2 renamed/replaced several APIs (
GuiGraphics→GuiGraphicsExtractor,setScreen→setScreenAndShow, HUD/keybind registration) — seefabric-26.2-mod-starter.mdbefore porting snippets from older versions. - Nothing in
common/may import loader classes — its compile classpath contains only the vanilla jar, so a straynet.fabricmc/net.neoforgedimport fails the build by design. - Config is plain Gson with defaults for every field, so adding config fields is
backwards-compatible; removing/renaming needs a migration (see
ConfigManager.migrateLegacyTools).
MIT.