Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a9c411
docs: M0.P1 plan review: anchored ignore rules, GameTest names per lo…
kashgummaraju Oct 25, 2026
20dc537
chore: remove the Blockbench shape exports and toml4j; general ignore…
kashgummaraju Oct 25, 2026
85def6d
refactor: remove @OnlyIn from common and gui code and the mergetool-a…
kashgummaraju Oct 25, 2026
bc72edf
build: run the unit tests with JUnit 5 under Gradle and remove the st…
kashgummaraju Oct 25, 2026
457b7fe
build: run the unit tests from the build folder so their log stays ou…
kashgummaraju Oct 25, 2026
e4778d0
test: GameTest bodies in common with a Forge and a Fabric holder; str…
kashgummaraju Oct 25, 2026
fcec92d
chore: mod metadata, dependency declarations and pack format for 1.20.1
kashgummaraju Oct 25, 2026
b826fb0
docs: README for building, running and contributing; pull request tem…
kashgummaraju Oct 25, 2026
4bb4d68
docs: config reference and performance baseline; register D284 for th…
kashgummaraju Oct 25, 2026
c0113de
docs: handoff after M0.P1 Tasks 1 to 7
kashgummaraju Oct 25, 2026
6ffd74e
docs: performance baseline measures tick time in ms from F3; register…
kashgummaraju Oct 25, 2026
7bd69da
docs: performance baseline names the sub-millisecond gates F3 cannot …
kashgummaraju Oct 25, 2026
d22e512
ci: build both loaders and run the unit tests on every push and pull …
kashgummaraju Oct 25, 2026
5e76d69
build: print the unit test count after each run and fail when no test…
kashgummaraju Oct 25, 2026
b03a303
ci: move the deprecated actions to Node 24 major versions
kashgummaraju Oct 25, 2026
c74da93
ci: read-only token, cache saved on integration/1.20.1, fail on a mis…
kashgummaraju Oct 25, 2026
da24d35
docs: codebase analysis facts after M0.P1; README branch, EULA, GameT…
kashgummaraju Oct 25, 2026
066240b
docs: performance baseline names the peak, Load B and lab machine gat…
kashgummaraju Oct 25, 2026
7d1100c
test: GameTest holder rule in package-info, Javadoc on the template n…
kashgummaraju Oct 25, 2026
dc84706
docs: tick the M0.P1 in-scope items in the engineering backlog
kashgummaraju Oct 25, 2026
a7da2f7
docs: handoff after M0.P1 (PR #67)
kashgummaraju Oct 25, 2026
77d6419
docs: M0.P1 handoff records the headed Forge client check
kashgummaraju Oct 25, 2026
402034e
ci: build pushes only on the integration branch; PR branches build on…
kashgummaraju Oct 25, 2026
0b01ec4
docs: speed targets are Mojang's minimum and recommended PCs, checked…
kashgummaraju Oct 25, 2026
02f8f14
docs: handoff for M0.P2; README and workflow comment follow the new d…
kashgummaraju Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Summary

<!-- Write what this pull request changes, in a few sentences. -->

## Work package

`M0.Px / F1.x`: link to the task plan in `docs/superpowers/plans/`.

## Acceptance

<!-- Write one row for each Acceptance line of the work package. -->

| Line | pass/fail | Evidence |
| --- | --- | --- |
| | | |

## Runs performed

<!-- For each run, write what you did and what you saw. -->

- Forge client:
- Fabric client:
- Dedicated server:
- GameTest servers:

## Decisions for the lead

<!-- For each design gap: the question, an in-game example and your choice. -->

- Question:
- In-game example:
- Choice:

## Definition of done

From `docs/plan/engineering-backlog.md`, section "5. Definition of done".

- [ ] Every `In scope` item is implemented, or the PR names the cut item and the reason.
- [ ] Every `Acceptance` item passes. The PR lists each one with "pass" or "fail" and the evidence.
- [ ] It compiles and runs on Fabric and NeoForge. The feature was exercised in both clients.
- [ ] Unit tests for its pure logic pass in `./gradlew :common:test`.
- [ ] No debug output, no TODO without an issue number, no dead code added.
- [ ] Config keys are in `docs/config-reference.md` with defaults and the section 11 reference.
- [ ] `en_us.json` has every new key. Datagen ran again if a provider changed.
- [ ] Every Art list row in its Art line is "Done" (no Placeholder, no To check).
- [ ] Every row in its "Recipe rows" line ships with the ingredients of the recipe table.
- [ ] For a machine: the PR shows each screen and animation next to the video frame at the cited time. It lists each difference and the spec or section 11 rule that causes it.
- [ ] The PR links this plan and the work package id. It names the open decisions that it worked around.
39 changes: 39 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Build
on:
# Pull requests build through pull_request, so a push to a PR branch builds once.
push:
branches: [integration/1.20.1]
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
- uses: gradle/actions/setup-gradle@v5
with:
# Only pushes to the integration branch save the cache. The branch is named here, so a change of the default branch does not move the cache.
cache-read-only: ${{ github.ref != 'refs/heads/integration/1.20.1' }}
- run: chmod +x gradlew && ./gradlew build :common:test --no-daemon --console=plain
- uses: actions/upload-artifact@v6
with:
name: projectnublar-forge
path: |
forge/build/libs/*.jar
!forge/build/libs/*-sources.jar
!forge/build/libs/*-javadoc.jar
if-no-files-found: error
- uses: actions/upload-artifact@v6
with:
name: projectnublar-fabric
path: |
fabric/build/libs/*.jar
!fabric/build/libs/*-sources.jar
!fabric/build/libs/*-javadoc.jar
if-no-files-found: error
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ build
eclipse
run

# Not source: crash logs, data-run output, the datagen cache, agent config
hs_err_pid*.log
forge/dataRun/
common/src/generated/resources/.cache/
# Not source: crash logs, data-run output, the datagen cache, agent and tool exports
hs_err_*.log
dataRun/
.cache/
.ai/
graphify-out/
73 changes: 56 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,63 @@
# MultiLoader Template
# Project Nublar

This project provides a Gradle project template that can compile mods for both Forge and Fabric using a common sourceset. This project does not require any third party libraries or dependencies. If you have any questions or want to discuss the project join our [Discord](https://discord.myceliummod.network).
Project Nublar is a Minecraft mod. The player clones dinosaurs from fossil DNA and keeps them in powered enclosures. This code is for Minecraft 1.20.1 on Forge and Fabric. Work on the Beta is in progress.

## Getting Started
## Beta scope

## IntelliJ IDEA
This guide will show how to import the MultiLoader Template into IntelliJ IDEA. The setup process is roughly equivalent to setting up Forge and Fabric independently and should be very familiar to anyone who has worked with their MDKs.
In the Beta, the player finds fossils and amber, extracts and edits DNA, and hatches six dinosaur species. The player keeps the dinosaurs in a powered, fenced and fed park. The Beta release is for Minecraft 26.2 on Fabric and NeoForge. The design decisions are in [section 11 of the codebase analysis](docs/codebase-analysis.md#11-design-decisions-for-beta). The milestones are in [the project plan](docs/project-plan.md).

1. Clone or download this repository to your computer.
2. Configure the project by editing the `group`, `mod_name`, `mod_author`, and `mod_id` properties in the `gradle.properties` file. You will also need to change the `rootProject.name` property in `settings.gradle`, this should match the folder name of your project, or else IDEA may complain.
3. Open the template's root folder as a new project in IDEA. This is the folder that contains this README file and the gradlew executable.
4. If your default JVM/JDK is not Java 17 you will encounter an error when opening the project. This error is fixed by going to `File > Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JVM`and changing the value to a valid Java 17 JVM. You will also need to set the Project SDK to Java 17. This can be done by going to `File > Project Structure > Project SDK`. Once both have been set open the Gradle tab in IDEA and click the refresh button to reload the project.
5. Open the Gradle tab in IDEA if it has not already been opened. Navigate to `Your Project > Common > Tasks > vanilla gradle > decompile`. Run this task to decompile Minecraft.
6. Open your Run/Debug Configurations. Under the Application category there should now be options to run Forge and Fabric projects. Select one of the client options and try to run it.
7. Assuming you were able to run the game in step 7 your workspace should now be set up.
## Build

### Eclipse
While it is possible to use this template in Eclipse it is not recommended. During the development of this template multiple critical bugs and quirks related to Eclipse were found at nearly every level of the required build tools. While we continue to work with these tools to report and resolve issues support for projects like these are not there yet. For now Eclipse is considered unsupported by this project. The development cycle for build tools is notoriously slow so there are no ETAs available.
You need Java 17. On Windows, use `gradlew.bat` in place of `./gradlew`.

## Development Guide
When using this template the majority of your mod is developed in the Common project. The Common project is compiled against the vanilla game and is used to hold code that is shared between the different loader-specific versions of your mod. The Common project has no knowledge or access to ModLoader specific code, apis, or concepts. Code that requires something from a specific loader must be done through the project that is specific to that loader, such as the Forge or Fabric project.
```sh
./gradlew build
```

Loader specific projects such as the Forge and Fabric project are used to load the Common project into the game. These projects also define code that is specific to that loader. Loader specific projects can access all of the code in the Common project. It is important to remember that the Common project can not access code from loader specific projects.
This command builds the four modules. `common` holds the shared code and resources. `nublar-gui` holds the screen widgets. `forge` and `fabric` hold the code for each loader. The Forge jar goes to `forge/build/libs/`. The Fabric jar goes to `fabric/build/libs/`.

## Run

| Command | Starts |
| --- | --- |
| `./gradlew :forge:runClient` | The Forge client |
| `./gradlew :fabric:runClient` | The Fabric client |
| `./gradlew :forge:runServer` | The Forge dedicated server |
| `./gradlew :forge:runGameTestServer` | A Forge server that runs the mod's GameTests, then stops |
| `./gradlew :fabric:runGametest` | A Fabric server that runs the mod's GameTests, then stops |

The Fabric client has no client entry point until M0.P2 (the port to Minecraft 26.2). It crashes when it enters a world.

The first `./gradlew :forge:runServer` writes `forge/run/eula.txt` and stops. To accept the [Minecraft EULA](https://aka.ms/MinecraftEULA), set `eula=true` in that file and run the command again.

In one measured run of each, the Forge GameTest server took about 11 minutes and the Fabric GameTest server took less than 1 minute. Both GameTest servers report failures at this time and end with `BUILD FAILED`. On Forge, some herd tests fail, and the number changes from run to run (8 or 9 of 13 in recent runs). On Fabric, all 13 herd tests fail, because Fabric does not register the mod's data reload listeners until M0.P2. The machine tests pass on both loaders. These failures are known. Do not try to fix them in your work package.

## Test

To run the unit tests:

```sh
./gradlew :common:test
```

To run the GameTests in the Forge client:

1. Start the client with `./gradlew :forge:runClient`.
2. Create a creative world.
3. Type `/test runall` to run all of the mod's GameTests.
4. Type `/test run <name>` to run one GameTest. The name is the method name in lower case, for example `/test run fivegallimimusformoneherdledbythestrongest`.

The Fabric client cannot run GameTests until M0.P2. On Fabric, use `./gradlew :fabric:runGametest`. The GameTest bodies are in `common/src/main/java/net/dumbcode/projectnublar/gametest/`.

## Contribute

1. Read [How to use this plan](docs/plan/engineering-backlog.md#how-to-use-this-plan) in the engineering backlog.
2. Write the task plan for your work package in [`docs/superpowers/plans/`](docs/superpowers/plans/). Get a review of it before you write code.
3. A new clone starts on `integration/1.20.1`, the default branch. Make your branch from it. Name it `<wp-id>/<short-name>`, for example `f1.1/fossil-worldgen`.
4. Make one commit per task. Start each commit message with a conventional prefix, for example `feat:` or `fix:`.
5. Run `./gradlew build` before each push.
6. Open a pull request. The template lists the [Definition of done](docs/plan/engineering-backlog.md#5-definition-of-done).

## Licence

See [`LICENSE`](LICENSE).
22 changes: 19 additions & 3 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,38 @@ dependencies {
compileOnly group: 'org.spongepowered', name: 'mixin', version: '0.8.5'
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
compileOnly group: 'org.jetbrains',name:'annotations',version: '24.1.0'
compileOnlyApi 'net.minecraftforge:mergetool-api:1.0'

implementation "fuzs.forgeconfigapiport:forgeconfigapiport-common:${project.forge_config_api_port_version}"
implementation 'com.moandjiezana.toml:toml4j:0.7.2'
implementation "net.tslat.smartbrainlib:SmartBrainLib-common-${project.minecraft_version}:${project.sbl_version}"
compileOnly "software.bernie.geckolib:geckolib-forge-${minecraft_version}:${geckolib_version}"
// implementation ("com.nyfaria.nyfsguilib:nyfsguilib-common-${minecraft_version}:${ngl_version}") {transitive = false}
implementation 'mysticdrew:common-networking-common:1.0.3-1.20.1'
implementation "earth.terrarium.botarium:botarium-forge-1.20.1:2.3.4"
// modImplementation "dev.architectury:architectury:$rootProject.architectury_api_version"
compileOnly "dev.architectury:architectury-forge:$rootProject.architectury_api_version"
testCompileOnly 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2'
}

// Unit tests may use Minecraft value classes (for example Vec3); the main compile classpath holds Minecraft.
sourceSets.test.compileClasspath += sourceSets.main.compileClasspath
// The tests load Minecraft value classes and the mod libraries at run time too.
sourceSets.test.runtimeClasspath += sourceSets.main.compileClasspath

test {
useJUnitPlatform()
testLogging { events "failed"; exceptionFormat = "full" }
// Minecraft's log4j2.xml writes logs/ relative to the working directory, so run the tests in build output.
workingDir = layout.buildDirectory.dir("test-run")
doFirst { workingDir.mkdirs() }
// CI evidence: print one count line per run, and fail when the run finds no test.
afterSuite { desc, result ->
if (!desc.parent) {
println "Unit tests: ${result.testCount} run, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped"
if (result.testCount == 0) throw new GradleException("No unit test found in ${project.path}:test")
}
}
}

configurations {
commonJava {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.joml.Vector3f;

public class ElectricFenceBlock extends BlockConnectableBase implements EntityBlock {
Expand Down Expand Up @@ -95,7 +93,6 @@ public void neighborChanged(BlockState state, Level level, BlockPos pos, Block n
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return new BlockEntityElectricFence(blockPos, blockState);
}
@OnlyIn(Dist.CLIENT)
public boolean normalize(Vector3f norm) {
float f = norm.x * norm.x + norm.y * norm.y + norm.z * norm.z;
if ((double)f < 1.0E-5D) {
Expand All @@ -109,7 +106,6 @@ public boolean normalize(Vector3f norm) {
}
}

@OnlyIn(Dist.CLIENT)
public float fastInvSqrt(float p_226165_0_) {
float f = 0.5F * p_226165_0_;
int i = Float.floatToIntBits(p_226165_0_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

/**
* Block entity for a wire-only fence block. Persists its {@link Connection}s under the frozen
Expand Down Expand Up @@ -86,7 +84,6 @@ public void addConnection(Connection connection) {
}

@Override
@OnlyIn(Dist.CLIENT)
public Set<Connection.CompiledRenderData> compiledRenderData() {
return super.compiledRenderData();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -65,7 +63,6 @@ private Connection findExistingConnection(Connection reference) {
return null;
}

@OnlyIn(Dist.CLIENT)
public Set<Connection.CompiledRenderData> compiledRenderData() {
Set<Connection.CompiledRenderData> compiled = Sets.newLinkedHashSet();
if (this.level == null) {
Expand Down
Loading
Loading