mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
🧹
This commit is contained in:
+8
-5
@@ -18,6 +18,9 @@ else.
|
||||
PlaceholderAPI keys are not a compile surface, but they are a contract an operator depends on:
|
||||
[placeholders.md](placeholders.md).
|
||||
|
||||
Writing a **mod** rather than a plugin? The Fabric, Forge and NeoForge jars carry a different surface,
|
||||
`art.arcane.iris.modded.api`: [modded.md](modded.md).
|
||||
|
||||
Anything outside `art.arcane.iris.api` is internal. `art.arcane.iris.core.*`,
|
||||
`art.arcane.iris.engine.*`, `art.arcane.iris.util.*` and `art.arcane.iris.spi.*` change without
|
||||
notice and without a deprecation cycle. If you find yourself importing `Engine`, `IrisBiome` or
|
||||
@@ -29,12 +32,12 @@ notice and without a deprecation cycle. If you find yourself importing `Engine`,
|
||||
|
||||
`art.arcane.iris.api` ships in the **Bukkit plugin jar only**. The Fabric, Forge and NeoForge mod
|
||||
jars contain the same generator but not this package — there is no Bukkit `World`, no
|
||||
`ServicesManager` and no `Event` bus to hang it on. A mod that wants generator data uses the mod
|
||||
loader's own registries.
|
||||
`ServicesManager` and no `Event` bus to hang it on.
|
||||
|
||||
The mod jars carry a separate, unrelated surface at `art.arcane.iris.modded.api`, for supplying
|
||||
custom block data to the generator from a mod. It is not covered by these documents, is absent from
|
||||
the Bukkit plugin jar, and shares no types with `art.arcane.iris.api`.
|
||||
The mod jars carry a separate surface instead: `art.arcane.iris.modded.api`, documented in
|
||||
[modded.md](modded.md). It is where a mod detects Iris levels, drives pregeneration, reads and writes
|
||||
mantle data, and registers a provider so an Iris pack can place the mod's own blocks, items and mobs.
|
||||
It is absent from the Bukkit plugin jar and shares no types with `art.arcane.iris.api`.
|
||||
|
||||
Everything in these documents assumes Paper, Purpur, Leaf, Canvas, Folia or Spigot, Minecraft 26.2,
|
||||
Java 25.
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
# Iris on Fabric, Forge and NeoForge
|
||||
|
||||
`art.arcane.iris.modded.api` is the surface a **mod** compiles against. It answers three questions: is this
|
||||
level generated by Iris, how do I drive Iris from my mod, and how do I get my own blocks, items and mobs
|
||||
placed by an Iris pack.
|
||||
|
||||
It ships in the Fabric, Forge and NeoForge jars only. It is absent from the Bukkit plugin jar, shares no types
|
||||
with `art.arcane.iris.api`, and is not covered by [terrain.md](terrain.md), [world-events.md](world-events.md)
|
||||
or [tree-feller.md](tree-feller.md) — those describe the Bukkit surface, which does not exist on a mod loader.
|
||||
|
||||
Everything here assumes Minecraft 26.2, Java 25, and one of Fabric, Forge or NeoForge. The mod id is
|
||||
`irisworldgen` on all three.
|
||||
|
||||
| What you want | Where |
|
||||
|---|---|
|
||||
| Detect Iris, read the engine, start a pregeneration, read/write mantle data | `IrisModdedAPI` |
|
||||
| Have Iris place *your* blocks, items and mobs | `ModdedDataProvider` |
|
||||
| Alias one custom key onto a fixed vanilla state, with no provider class | `IrisModdedAPI.registerCustomBlockData` |
|
||||
|
||||
---
|
||||
|
||||
## Depending on Iris
|
||||
|
||||
**There is no published Maven artifact for the mod jars.** No module in this repository applies
|
||||
`maven-publish`, and the JitPack route documented in [README.md](README.md) resolves the Bukkit sources, not
|
||||
the modded adapter. Until that changes, building from source is the only path.
|
||||
|
||||
The three adapters are standalone Gradle builds — each `adapters/<loader>/settings.gradle` does
|
||||
`includeBuild('../..')` to substitute `art.arcane:core` and `art.arcane:spi` from the root build, which is what
|
||||
keeps Loom, ForgeGradle and ModDevGradle off one plugin classpath. The root build drives them through their own
|
||||
wrappers:
|
||||
|
||||
```bash
|
||||
./gradlew buildFabric # -> dist/Iris v<version> [Fabric] <mc>+<loader>.jar
|
||||
./gradlew buildForge # -> dist/Iris v<version> [Forge] <mc>+<loader>.jar
|
||||
./gradlew buildNeoforge # -> dist/Iris v<version> [NeoForge] <mc>+<loader>.jar
|
||||
```
|
||||
|
||||
Then compile against the jar you will actually run:
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
compileOnly(files('libs/Iris-fabric.jar'))
|
||||
}
|
||||
```
|
||||
|
||||
The adapters are **not** in the root `settings.gradle` by default. Add `-PincludeModdedAdapters=true` for IDE
|
||||
import only; it closes a composite build cycle (root -> adapter -> root) and Gradle may reject it.
|
||||
|
||||
### Soft dependency
|
||||
|
||||
Declare the optional relationship, then do not rely on load order — the ServiceLoader path below works
|
||||
regardless of it, because Iris does the discovering.
|
||||
|
||||
Fabric (`fabric.mod.json`) — `suggests`, not `depends`; a hard `depends` makes Iris mandatory:
|
||||
|
||||
```json
|
||||
{
|
||||
"suggests": { "irisworldgen": "*" }
|
||||
}
|
||||
```
|
||||
|
||||
NeoForge (`META-INF/neoforge.mods.toml`):
|
||||
|
||||
```toml
|
||||
[[dependencies.yourmod]]
|
||||
modId = "irisworldgen"
|
||||
type = "optional"
|
||||
ordering = "AFTER"
|
||||
side = "BOTH"
|
||||
```
|
||||
|
||||
Forge (`META-INF/mods.toml`) is the same with `mandatory = false` instead of `type`.
|
||||
|
||||
### Detecting Iris
|
||||
|
||||
Two checks, and they answer different questions.
|
||||
|
||||
**Is the mod present?** Ask the loader — `FabricLoader.getInstance().isModLoaded("irisworldgen")`, or
|
||||
`ModList.get().isLoaded("irisworldgen")` on Forge and NeoForge. Cheap, but tells you nothing about whether
|
||||
Iris actually generates anything.
|
||||
|
||||
**Are Iris classes on the classpath?** If your integration lives in a class that imports
|
||||
`art.arcane.iris.modded.api.*`, that class must not load unless Iris is present. Keep the imports behind a
|
||||
presence check and a separate class, or probe reflectively:
|
||||
|
||||
```java
|
||||
private static final boolean IRIS_PRESENT = irisPresent();
|
||||
|
||||
private static boolean irisPresent() {
|
||||
try {
|
||||
Class.forName("art.arcane.iris.modded.api.IrisModdedAPI");
|
||||
return true;
|
||||
} catch (Throwable absent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Do not gate on a version string. Probe for the class or method you need.
|
||||
|
||||
**Is *this* level Iris?** `IrisModdedAPI.isIrisLevel(level)`. A server can mix Iris and vanilla dimensions
|
||||
freely, so presence of the mod is not presence of an Iris world.
|
||||
|
||||
---
|
||||
|
||||
## `IrisModdedAPI`
|
||||
|
||||
All static, all null-tolerant: a null or non-Iris `ServerLevel` yields `false`, `null` or a no-op. You never
|
||||
have to pre-check.
|
||||
|
||||
| Method | What it does |
|
||||
|---|---|
|
||||
| `isIrisLevel(ServerLevel)` | Whether the level's chunk generator is `IrisModdedChunkGenerator`. The cheapest check |
|
||||
| `isStudioLevel(ServerLevel)` | Whether it is a throwaway pack-authoring world. Persist nothing against one |
|
||||
| `getEngine(ServerLevel)` | The `Engine` behind the level, or null. See the stability warning below |
|
||||
| `pregenerate(ServerLevel, int radiusBlocks)` | Starts a cached async pregeneration around the origin |
|
||||
| `pregenerate(ServerLevel, int, int centerX, int centerZ, boolean sync, boolean cached)` | Same, with a centre and write mode |
|
||||
| `getMantleData(ServerLevel, int x, int y, int z, Class<T>)` | Reads Iris's per-block sidecar storage |
|
||||
| `setMantleData(ServerLevel, int x, int y, int z, T)` | Writes it |
|
||||
| `deleteMantleData(ServerLevel, int x, int y, int z, Class<T>)` | Removes a value of that type |
|
||||
| `retainMantleDataForSlice(Class<?>)` | Declares a mantle type Iris must keep rather than discard |
|
||||
| `registerProvider(ModdedDataProvider)` | Registers a custom content provider imperatively |
|
||||
| `registerCustomBlockData(String namespace, String key, String state)` | Aliases one key onto a fixed block state |
|
||||
|
||||
### `Engine` is internal
|
||||
|
||||
`getEngine` returns `art.arcane.iris.engine.framework.Engine`. That type is **internal to Iris** and changes
|
||||
without a deprecation cycle — as do `art.arcane.iris.core.*`, `art.arcane.iris.util.*` and
|
||||
`art.arcane.iris.spi.*`. Treat the returned `Engine` as an opaque token to hand back to Iris. Every method in
|
||||
the table above that needs one resolves it for you; prefer those.
|
||||
|
||||
Never cache an `Engine`. A pack hotload or a level unload replaces it, and the old instance goes inert.
|
||||
`getEngine` already returns null while the generator is binding and during shutdown.
|
||||
|
||||
### Pregeneration
|
||||
|
||||
`pregenerate` returns as soon as the job is queued. Progress goes to Iris's own logging and boss bar, not to
|
||||
your caller. Only one job runs server-wide, so it returns `false` if one is already active — and `false` also
|
||||
means "not an Iris level", so check `isIrisLevel` first if you need to tell those apart. Call it on the server
|
||||
thread.
|
||||
|
||||
`cached = true` writes an on-disk pregeneration cache so an interrupted run resumes instead of regenerating.
|
||||
`sync = true` writes chunks synchronously: slower, but it bypasses the async write queue.
|
||||
|
||||
### Mantle data
|
||||
|
||||
The mantle is Iris's own per-block storage, independent of chunk NBT, and it is how Iris carries data between
|
||||
generation stages. Three things to know:
|
||||
|
||||
1. **Coordinates are world-space.** `y` is translated by the engine's minimum height internally. A `y` outside
|
||||
the engine's height range reads as null and writes as a no-op — no exception.
|
||||
2. **Reads never create storage; writes do.** `getMantleData` returns null when no mantle region exists for
|
||||
that column yet. `setMantleData` and `deleteMantleData` create the region, which can touch disk — do not
|
||||
call them per block in a tick loop on the server thread.
|
||||
3. **Declare your types or lose them.** Iris discards mantle slices it does not need once a region's
|
||||
generation data has served its purpose. Any type you write and expect to read back later must be declared
|
||||
once during mod setup:
|
||||
|
||||
```java
|
||||
IrisModdedAPI.retainMantleDataForSlice(MyMarker.class);
|
||||
```
|
||||
|
||||
Registration is by canonical class name, process-wide across every Iris world, and cannot be undone. All three
|
||||
mantle methods throw `IllegalStateException` if the engine's mantle has already been closed.
|
||||
|
||||
---
|
||||
|
||||
## `ModdedDataProvider`
|
||||
|
||||
The extension point. Implement it and a pack can name `yourmod:something` in a block palette, a loot table or a
|
||||
spawn entry, and Iris will ask you to resolve it.
|
||||
|
||||
```java
|
||||
public interface ModdedDataProvider {
|
||||
String modId();
|
||||
default boolean isReady(); // default true
|
||||
Collection<Identifier> getTypes(ModdedDataType type);
|
||||
boolean isValidProvider(Identifier id, ModdedDataType type);
|
||||
default ModdedBlockData getBlockData(Identifier blockId, Map<String, String> state); // default null
|
||||
default void processBlockPlacement(ModdedBlockPlacementContext context); // default no-op
|
||||
default Entity spawnMob(ServerLevel level, double x, double y, double z, Identifier entityId); // default null
|
||||
default void init(); // default no-op
|
||||
}
|
||||
```
|
||||
|
||||
`ModdedDataType` is `BLOCK`, `ITEM` or `ENTITY`. Constants may be added — write a `default` arm in any switch
|
||||
expression over it.
|
||||
|
||||
### The contract
|
||||
|
||||
`modId()` is your identity, not decoration. It de-duplicates registrations and labels every log line Iris emits
|
||||
about your provider. It must be non-null and stable; returning null aborts discovery.
|
||||
|
||||
`isValidProvider(id, type)` is the gate. Iris calls it before every resolution callback, on generation threads,
|
||||
for every key it could not resolve itself. Keep it to a namespace comparison or a set lookup.
|
||||
|
||||
`isReady()` is how a provider whose registries populate late excuses itself. Iris **skips** a provider that
|
||||
returns false rather than treating it as absent, so returning false is strictly better than returning wrong
|
||||
answers.
|
||||
|
||||
`getTypes(type)` feeds command suggestion and pack tooling. It is not the resolution path — return an empty
|
||||
collection rather than null, and do not do work here that `isValidProvider` should do.
|
||||
|
||||
`getBlockData(blockId, state)` resolves a claimed block. `state` holds the `[prop=value]` pairs from the pack's
|
||||
key, already parsed, possibly empty, never null. Return null to decline and Iris tries the next provider, then
|
||||
falls back to air. Return `ModdedBlockData.direct(blockState)` when the state is final.
|
||||
|
||||
`processBlockPlacement(context)` finishes a **deferred** placement. Return
|
||||
`ModdedBlockData.deferred(placeholder)` from `getBlockData` when the real block needs a loaded level — a block
|
||||
entity, neighbour state, or mod registries not reachable from a generation thread. Iris writes your placeholder
|
||||
during generation and calls you back later on the server thread with the chunk loaded. Pick a placeholder with
|
||||
the same shape and occlusion as the final block so terrain around it generates correctly. Only the *first*
|
||||
provider claiming the identifier is called for a given position.
|
||||
|
||||
`ModdedBlockPlacementContext` is an immutable record: `engine`, `level`, `position`, `blockId`, `state`,
|
||||
`blockState`. `blockState` is what is currently at `position` — normally your placeholder, though a later
|
||||
generation stage may have replaced it. `state` is defensively copied and unmodifiable.
|
||||
|
||||
`spawnMob(...)` spawns a claimed custom entity on the server thread. Return null to decline.
|
||||
|
||||
`init()` runs once, immediately after Iris accepts your provider.
|
||||
|
||||
### Threading
|
||||
|
||||
| Callback | Thread | Notes |
|
||||
|---|---|---|
|
||||
| `isValidProvider`, `getBlockData` | Generation threads, many at once | Must be fast. Must not touch world state |
|
||||
| `processBlockPlacement` | Server thread, chunk loaded | Safe to write blocks and attach block entities |
|
||||
| `spawnMob` | Server thread | |
|
||||
| `init` | Whichever thread registered you | Mod init for ServiceLoader, your caller otherwise |
|
||||
| `modId`, `isReady`, `getTypes` | Any | |
|
||||
|
||||
Implementations must be thread-safe. `getBlockData` in particular is called concurrently for every unresolved
|
||||
key a pack names, which during a pregeneration is a lot.
|
||||
|
||||
### Registration by ServiceLoader
|
||||
|
||||
Iris discovers providers with `java.util.ServiceLoader`. Ship a service file naming your implementation's
|
||||
binary name; the class needs a public no-argument constructor.
|
||||
|
||||
`src/main/resources/META-INF/services/art.arcane.iris.modded.api.ModdedDataProvider`:
|
||||
|
||||
```
|
||||
com.example.yourmod.iris.YourIrisProvider
|
||||
```
|
||||
|
||||
One binary name per line. Nested classes use `$`, for example
|
||||
`com.example.yourmod.YourMod$IrisProvider`.
|
||||
|
||||
Iris loads the service with **its own** class loader:
|
||||
`ServiceLoader.load(ModdedDataProvider.class, ModdedCustomContentRegistry.class.getClassLoader())`. Your service
|
||||
file therefore has to be visible from Iris's loader, which is the normal case on all three loaders but is the
|
||||
thing to suspect first if nothing happens. Confirm with the registration log line below; if it never appears,
|
||||
fall back to `registerProvider`.
|
||||
|
||||
### When discovery runs
|
||||
|
||||
`ModdedCustomContentRegistry.discover()` runs inside `ModdedEngineBootstrap.bootCommon(...)`, which is the very
|
||||
first thing each loader's entrypoint calls — `IrisFabricBootstrap.onInitialize`,
|
||||
`IrisForgeBootstrap`/`IrisNeoForgeBootstrap` construction. That is **before** the Iris chunk generator is
|
||||
registered and long before any server starts. Consequences:
|
||||
|
||||
- A ServiceLoader-declared provider is available before any world could resolve a block. This is the safe path.
|
||||
- Discovery runs **once per process**. A second `discover()` is a no-op.
|
||||
- Your `init()` must not assume a server, a level, or a fully populated game registry. Defer that work and gate
|
||||
it behind `isReady()`.
|
||||
|
||||
### Registration imperatively
|
||||
|
||||
`IrisModdedAPI.registerProvider(provider)` works at any time and is the option if you would rather not ship a
|
||||
service file, or need to build the provider from your own config.
|
||||
|
||||
Ordering is the catch: Iris only consults providers registered **before** a pack resolves the block in
|
||||
question, and blocks already resolved are not revisited. Register during mod setup. Registering after Iris's
|
||||
own ServiceLoader pass is fine; registering after a world has generated is not.
|
||||
|
||||
A second registration under a `modId()` already present is logged and ignored. `init()` runs during the call.
|
||||
|
||||
### How discovery and failures are reported
|
||||
|
||||
Everything below is logged under the `Iris` logger. One line per accepted provider confirms registration —
|
||||
this is what to grep for when checking whether your service file was seen:
|
||||
|
||||
```
|
||||
Iris registered custom content provider 'yourmod'
|
||||
```
|
||||
|
||||
A duplicate `modId()` is rejected with `already registered; ignoring duplicate`.
|
||||
|
||||
Iris catches throwables from every provider callback, logs them against your `modId()`, and continues with the
|
||||
remaining providers — one broken provider does not stop world generation:
|
||||
|
||||
```
|
||||
Iris custom content provider 'yourmod' failed resolving block yourmod:thing
|
||||
Iris custom content provider 'yourmod' failed post-placement for yourmod:thing at BlockPos{...}
|
||||
Iris custom content provider 'yourmod' failed spawning mob yourmod:critter
|
||||
Iris custom content provider 'yourmod' failed to initialize # registerProvider path only
|
||||
```
|
||||
|
||||
`init()` during ServiceLoader discovery is the one exception, and it is all-or-nothing: a throwable there
|
||||
aborts the pass, restores the registry to its pre-discovery state, and rethrows. The log line names the
|
||||
provider that failed, by mod id and class name:
|
||||
|
||||
```
|
||||
Iris custom content provider discovery failed at provider 'yourmod' (com.example.yourmod.iris.YourIrisProvider)
|
||||
```
|
||||
|
||||
If your provider's own `modId()` throws while Iris is building that message, the class name alone is logged; if
|
||||
the failure happened outside any provider, it reads `the provider service loader`. A `null` provider or a null
|
||||
`modId()` from the ServiceLoader fails the pass with a message naming which.
|
||||
|
||||
### Static aliases, no provider class
|
||||
|
||||
For the common case of "my key is really this vanilla block", skip the provider:
|
||||
|
||||
```java
|
||||
IrisModdedAPI.registerCustomBlockData("yourmod", "fancy_log", "minecraft:oak_log[axis=y]");
|
||||
```
|
||||
|
||||
The state string uses the same syntax packs use and is parsed **immediately** — a typo is logged at startup and
|
||||
the registration dropped, rather than surfacing later as missing blocks. Aliases take precedence over provider
|
||||
lookups for the same key. Null arguments are ignored.
|
||||
|
||||
---
|
||||
|
||||
## How pack resolution works on modded
|
||||
|
||||
Paths are relative to the loader's config directory (`config/` on a normal server install).
|
||||
|
||||
| Path | What it is |
|
||||
|---|---|
|
||||
| `config/irisworldgen/packs/<pack>/` | Installed packs. A pack is valid when `dimensions/<dimension>.json` exists |
|
||||
| `config/irisworldgen/generated/datapack/iris/` | The generated forced datapack. Iris owns this — do not edit it |
|
||||
| `config/irisworldgen/modded.json` | Mod-side config: default pack, auto-download, primary world routing |
|
||||
| `config/iris/` | Engine data directory: settings and per-world engine state |
|
||||
|
||||
Note the split: the engine's data folder is `config/iris`, but every modded pack path — installer, validator,
|
||||
command suggestions, forced datapack, engine creation — resolves under `config/irisworldgen/packs`. Install
|
||||
packs there.
|
||||
|
||||
At `bootCommon`, Iris kicks off an async default-pack prefetch. If `modded.json` has
|
||||
`autoDownloadDefaultPack` enabled and the configured `defaultPack` is missing, Iris downloads
|
||||
`IrisDimensions/<pack>` from the `master` branch into the packs folder. If that fails it logs a pointer to
|
||||
`/iris download <pack>`. A pack that is already installed is left alone.
|
||||
|
||||
When a level asks for its pack, `ModdedWorldEngines.packFolder(pack)` resolves
|
||||
`config/irisworldgen/packs/<pack>`. A missing pack is a hard failure with the expected absolute path printed —
|
||||
Iris does not silently generate vanilla terrain in its place.
|
||||
|
||||
### The forced datapack
|
||||
|
||||
Iris cannot register dimension types and per-pack biomes through a mod registry, because vanilla world creation
|
||||
reads them from the datapack layer. So Iris **generates a datapack** from the installed packs and injects it as
|
||||
a built-in, top-priority repository source:
|
||||
|
||||
- Fabric: a `PackRepository` mixin, via `FabricForcedDatapackSources`
|
||||
- Forge and NeoForge: `event.addRepositorySource(ModdedForcedDatapack.repositorySource())`
|
||||
|
||||
It contributes world presets, dimension types and biomes under the `irisworldgen` namespace, with ids derived
|
||||
from the pack and dimension names — `irisworldgen:packs/<pack>/dimensions/<dimension>/preset`,
|
||||
`.../dimension_type`, `.../biomes/<biome>`. This is why an Iris dimension shows up in the vanilla world
|
||||
creation screen as `IRIS:<Pack>`.
|
||||
|
||||
It is regenerated when the pack changes: a studio hotload calls `ModdedForcedDatapack.regenerate()`. If
|
||||
regeneration fails and a previously published pack is still readable, Iris keeps the last known-good one and
|
||||
logs the failure rather than starting with no dimension types.
|
||||
|
||||
**The failure you need to recognise.** If injection did not happen for your loader — a mixin that failed to
|
||||
apply, an event that never fired — Iris logs this once at startup and world creation will fail no matter how
|
||||
many times you restart:
|
||||
|
||||
```
|
||||
Iris forced datapack 'iris_worldgen' was never loaded by this server.
|
||||
N installed pack(s) at <path> contributed no dimension types or custom biomes.
|
||||
Datapack source injection failed for this loader (mixin/event not applied), so world
|
||||
creation will fail and restarting will not fix it.
|
||||
```
|
||||
|
||||
That is a loader/environment problem, not a pack problem. Nothing an integrating mod does can fix it.
|
||||
|
||||
### Commands worth knowing
|
||||
|
||||
The modded command tree is `/iris`, aliased `/ir` and `/irs`, gated at gamemaster permission level.
|
||||
|
||||
| Command | What it tells you |
|
||||
|---|---|
|
||||
| `/iris pack validate [pack]` | Validates every installed pack, or one. Runs on a worker thread, reports per pack, and counts unloadable packs |
|
||||
| `/iris pack status [pack]` | Replays the **recorded** validation results — blocking errors and warnings per pack. Says so and returns nothing if `validate` has not run this session |
|
||||
| `/iris pack cleanup <pack> [apply]` | Previews unused pack resources; `apply` deletes them |
|
||||
| `/iris pack restore <pack> [apply]` | Previews a restore of pack resources; `apply` performs it |
|
||||
| `/iris datapack status` | Per Iris dimension: active dimension type, its min/max/logical height, what the pack wants, and whether they match |
|
||||
| `/iris datapack install` | Writes the pack's dimension type into `<world>/datapacks/iris/data/irisworldgen/dimension_type/` as an override |
|
||||
| `/iris datapack list` | Datapack URLs declared by installed pack dimensions, plus the datapacks actually present in `<world>/datapacks/` |
|
||||
| `/iris download <pack> [branch] [overwrite]` | Installs a pack from `IrisDimensions/<pack>` into the packs folder. Aliased `dl` |
|
||||
| `/iris version` | Iris version and loader |
|
||||
|
||||
`/iris datapack status` is the first thing to run when an Iris dimension generates at the wrong height. A
|
||||
mismatch means the level's active dimension type disagrees with the pack, which happens when a world was
|
||||
created before a pack's height range changed. `install` writes the override; the world still needs a restart.
|
||||
|
||||
`/iris datapack ingest`, `pull` and `remove` exist but refuse on modded, with a message explaining why: the
|
||||
Modrinth ingest workflow is Bukkit tooling. Native vanilla and datapack structure placement **does** work on
|
||||
modded — install the datapack into `<world>/datapacks/` and restart, and its registered structures generate.
|
||||
|
||||
---
|
||||
|
||||
## What is not supported
|
||||
|
||||
- **No published artifact.** Build from source, as above. There is no Maven coordinate for the modded jars and
|
||||
no `maven-publish` in this build.
|
||||
- **Core types are internal.** `art.arcane.iris.engine.*`, `art.arcane.iris.core.*`,
|
||||
`art.arcane.iris.util.*` and `art.arcane.iris.spi.*` change without notice. `Engine`, reachable through
|
||||
`getEngine` and `ModdedBlockPlacementContext.engine()`, is the one internal type this surface exposes, and it
|
||||
is exposed as a token to hand back rather than an API to call.
|
||||
- **No event bus.** `IrisPlatform.callEvent` is a no-op on every mod loader adapter. There is no modded
|
||||
equivalent of `IrisWorldEngineEvent` or `IrisPregenerationEvent`; poll `isIrisLevel`/`getEngine` instead.
|
||||
- **No `art.arcane.iris.api`.** The Bukkit terrain, world-event, pregen and tree-feller interfaces are not in
|
||||
the mod jars. There is no modded terrain-query surface yet.
|
||||
- **No PlaceholderAPI.** [placeholders.md](placeholders.md) is Bukkit-only.
|
||||
- **Datapack ingest is Bukkit-only**, per the command note above.
|
||||
- **`ModdedCustomContentRegistry`'s resolution methods are Iris internals.** They are public only because the
|
||||
adapter's generation code lives in another package. Go through `IrisModdedAPI`.
|
||||
@@ -0,0 +1,168 @@
|
||||
# Minecraft Version Bump Checklist
|
||||
|
||||
`gradle.properties` `minecraftVersion` is the single source of truth for the target Minecraft
|
||||
version. Most build outputs derive from it. This document lists every edit required to move Iris
|
||||
to a new Minecraft version, in order.
|
||||
|
||||
## Source of truth
|
||||
|
||||
`gradle.properties`:
|
||||
|
||||
- `minecraftVersion` — target MC version (e.g. `26.2`). Drives the Bukkit plugin `api-version`,
|
||||
`BuildConstants.MINECRAFT_VERSION`, the `com.mojang:minecraft` coordinate, all mod-metadata
|
||||
minecraft ranges, and every dist/jar artifact name.
|
||||
- `fabricLoaderVersion` — Fabric Loader version.
|
||||
- `forgeVersion` — Forge version (`<mc>-<forge>`).
|
||||
- `neoForgeVersion` — NeoForge version.
|
||||
- `irisVersion` — bump the trailing `-<mc>` suffix to match (e.g. `4.0.0-26.2` -> `4.0.0-27.0`).
|
||||
|
||||
## Ordered steps
|
||||
|
||||
1. Edit `gradle.properties`: update `minecraftVersion`, `fabricLoaderVersion`, `forgeVersion`,
|
||||
`neoForgeVersion`, and the `irisVersion` suffix.
|
||||
|
||||
2. Edit `gradle/libs.versions.toml`:
|
||||
- `spigot` — the Spigot/Paper API pin used to compile against (`<mc>-R0.1-SNAPSHOT`).
|
||||
- `fabricApi-*` — the ten Fabric API module versions, if the new MC requires different
|
||||
Fabric API builds. Each module is versioned independently (`<version>+<build-hash>`).
|
||||
The ten are `base`, `registrySync`, `resourceLoader`, `lifecycleEvents`, `commandApi`,
|
||||
`eventsInteraction`, `networking`, `rendering`, `keyMapping`, `permission`. Every one of them
|
||||
is bundled jar-in-jar and must be declared in `fabric.mod.json` `jars` — see step 7.
|
||||
|
||||
3. Edit `core/src/main/java/art/arcane/iris/core/nms/datapack/DataVersion.java` (manual, structural):
|
||||
- Append a new enum constant `V<major>_<minor>("<mc>", <packFormat>, <DataFixer>::new)`.
|
||||
- `packFormat` comes from https://minecraft.wiki/w/Pack_format.
|
||||
- `getLatest()` returns the last enum constant, so append; do not reorder.
|
||||
- Add a matching `IDataFixer` implementation under `core/src/main/java/art/arcane/iris/core/nms/datapack/`
|
||||
if the datapack format changed.
|
||||
|
||||
4. Register the new Bukkit NMS binding module:
|
||||
- `settings.gradle` — add `include(':adapters:bukkit:nms:v<major>_<minor>_R<rev>')`.
|
||||
- `build.gradle` — add the binding to the `nmsBindings` map:
|
||||
`v<major>_<minor>_R<rev>: '<spigot-nms-build-version>'` (e.g. `'26.2.build.25-alpha'`).
|
||||
- Create the binding sources under `adapters/bukkit/nms/v<major>_<minor>_R<rev>/`.
|
||||
|
||||
5. Update loader version-range metadata (manual floors/ranges only; the `minecraft` ranges are
|
||||
templated from `minecraftVersion` and need no edit):
|
||||
- `adapters/fabric/src/main/resources/fabric.mod.json` — `minecraft` is `~${minecraftVersion}`
|
||||
(auto). Update the `fabricloader` floor (currently `>=0.19.3`) if the loader minimum changes,
|
||||
and the `jars` list if the bundled Fabric API modules change.
|
||||
- `adapters/forge/src/main/resources/META-INF/mods.toml` — `minecraft` versionRange is
|
||||
`[${minecraftVersion}]` (auto). Update `loaderVersion` (currently `[65,)`) and the `forge`
|
||||
dependency versionRange (also `[65,)`) for the new Forge line. Both are hand-maintained.
|
||||
- `adapters/neoforge/src/main/resources/META-INF/neoforge.mods.toml` — `minecraft` versionRange
|
||||
is `[${minecraftVersion}]` (auto). `loaderVersion` (currently `[3,)`) is the javafml
|
||||
specification version, not the NeoForge version, and rarely moves. The `neoforge` dependency
|
||||
`versionRange` is **hardcoded** (currently `[26.2,)`) and is *not* templated from
|
||||
`minecraftVersion` — hand-edit it on every bump or the mod will load on the wrong NeoForge
|
||||
line.
|
||||
|
||||
6. Re-verify the mapping-coupled files. Six files name Mojang-mapped classes, fields, and method
|
||||
descriptors directly. Nothing templates them, nothing fails fast at build time if a name moved,
|
||||
and a stale entry surfaces as a silent no-op or a load-time crash. Check every one against the
|
||||
new MC jar.
|
||||
|
||||
Access widener (Fabric) — `accessWidener v2 official`, so the names are Mojang-mapped:
|
||||
|
||||
- `adapters/fabric/src/main/resources/irisworldgen.accesswidener`
|
||||
- `MinecraftServer.levels` `Ljava/util/Map;`
|
||||
- `MinecraftServer.executor` `Ljava/util/concurrent/Executor;`
|
||||
- `MinecraftServer.storageSource` `Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;`
|
||||
- `PackRepository.sources` `Ljava/util/Set;` (accessible **and** mutable)
|
||||
|
||||
Verify: each field still exists with that exact descriptor. Loom fails the build on an
|
||||
unresolvable AW entry, so a rename shows up as an AW error — read it, do not delete the line.
|
||||
|
||||
Access transformers (Forge and NeoForge) — must stay in sync with each other and with the AW:
|
||||
|
||||
- `adapters/forge/src/main/resources/META-INF/accesstransformer.cfg`
|
||||
- `adapters/neoforge/src/main/resources/META-INF/accesstransformer.cfg`
|
||||
- both: `public net.minecraft.server.MinecraftServer levels` / `executor` / `storageSource`
|
||||
|
||||
Verify: the three ATs match the first three AW entries. Note the ATs have no `PackRepository`
|
||||
entry — Forge/NeoForge reach the pack sources through their own hooks, so do not add one
|
||||
without a reason. Wired via `minecraft { accessTransformer.from(...) }` (Forge) and
|
||||
`neoForge { accessTransformers.from(...) }` (NeoForge).
|
||||
|
||||
Mixin configs — three JSONs, eight mixin classes, all targeting Mojang-mapped members:
|
||||
|
||||
- `adapters/fabric/src/main/resources/irisworldgen.mixins.json`
|
||||
(package `art.arcane.iris.fabric.mixin`, `compatibilityLevel` `JAVA_25`, Fabric only)
|
||||
- `BlockItemMixin` -> `BlockItem.placeBlock`, `@At("RETURN")`
|
||||
- `BlockMixin` -> `Block.getDrops(...)` with a **full descriptor**
|
||||
(`BlockState, ServerLevel, BlockPos, BlockEntity, Entity, ItemInstance`) — the highest-churn
|
||||
entry in the repo; the parameter list changes across MC versions
|
||||
- `PackRepositoryMixin` -> `PackRepository.<init>`, `@At("RETURN")`
|
||||
- `adapters/modded-common/src/main/resources/irisworldgen.entity.mixins.json`
|
||||
(package `art.arcane.iris.modded.mixin`, `compatibilityLevel` `JAVA_21`, all three loaders)
|
||||
- `EntityPersistenceMixin` -> `Entity.shouldBeSaved`
|
||||
- `LivingEntityLootMixin` -> `LivingEntity.dropFromLootTable(ServerLevel, DamageSource, boolean)`
|
||||
— full descriptor
|
||||
- `MobAwarenessMixin` -> `Mob.serverAiStep`, injecting at a **field target**
|
||||
(`Lnet/minecraft/world/entity/Mob;noActionTime:I`) — verify the field, not just the method
|
||||
- `adapters/modded-common/src/main/resources/irisworldgen.client.mixins.json`
|
||||
(package `art.arcane.iris.client.mixin`, client-only)
|
||||
- `IrisWorldOpenFlowsMixin` -> `WorldOpenFlows.confirmWorldCreation` and
|
||||
`WorldOpenFlows.openWorldCheckWorldStemCompatibility`
|
||||
- `IrisWorldTypeEntryMixin` -> `WorldCreationUiState.WorldTypeEntry.describePreset`, plus a
|
||||
`@Shadow` member — shadows break silently if the field is renamed
|
||||
|
||||
The client mixin *config* lives in `modded-common/src/main/resources` but the classes live in
|
||||
`adapters/client-common/src/main/java/art/arcane/iris/client/mixin/`; the modded mixin classes
|
||||
live in `adapters/modded-common/src/main/java/art/arcane/iris/modded/mixin/`. All three
|
||||
adapters add both shared source dirs, so one edit hits every loader.
|
||||
|
||||
Registration differs per loader and each place must list the same configs:
|
||||
- Fabric — `fabric.mod.json` `mixins` (all three; the client one gated on
|
||||
`"environment": "client"`).
|
||||
- NeoForge — `[[mixins]]` blocks in `neoforge.mods.toml` (entity + client).
|
||||
- Forge — no toml entry. The jar manifest attribute `MixinConfigs` in
|
||||
`adapters/forge/build.gradle` plus `--mixin.config` args on the `runClient`/`runServer`
|
||||
configurations (entity + client). Adding a mixin config on Forge means editing the manifest
|
||||
attribute *and* the run args.
|
||||
|
||||
`injectors.defaultRequire` is `1` in all three configs, so a mixin that no longer applies
|
||||
fails the run instead of degrading quietly. Treat any "mixin apply failed" line as a bump
|
||||
blocker, and run both `runClient` and `runServer` per loader — client-only mixins are not
|
||||
exercised by a server run.
|
||||
|
||||
7. Reconcile the Fabric jar-in-jar list. `adapters/fabric/build.gradle` adds every Fabric API
|
||||
module to the `jij` configuration, which the `shadowJar` copies into `META-INF/jars` with the
|
||||
version stripped from the filename. The `jij` configuration is `transitive = false`, so the
|
||||
bundled set is exactly the declared set, and `fabric.mod.json` `jars` must list exactly those
|
||||
filenames. After changing the module list, confirm the jar agrees:
|
||||
|
||||
```
|
||||
unzip -l "dist/Iris v<version> [Fabric] <mc>+<loader>.jar" | grep META-INF/jars
|
||||
```
|
||||
|
||||
An entry in `jars` with no matching nested jar makes the loader refuse the mod; a nested jar
|
||||
missing from `jars` is dead weight the loader never mounts.
|
||||
|
||||
8. Build and verify:
|
||||
- `./gradlew :core:check`
|
||||
- `./gradlew buildBukkit`
|
||||
- `./gradlew buildFabric`
|
||||
- `./gradlew buildForge`
|
||||
- `./gradlew buildNeoforge`
|
||||
|
||||
## Derived automatically (do not hand-edit on a version bump)
|
||||
|
||||
- Bukkit plugin `api-version` — `adapters/bukkit/plugin/build.gradle` reads `minecraftVersion`.
|
||||
- `BuildConstants.MINECRAFT_VERSION` — stamped by the `generateTemplates` task in
|
||||
`core/build.gradle` from `minecraftVersion`; consumed by `Tasks.supportedVersions`.
|
||||
- Mod-metadata `minecraft` version ranges — templated from `minecraftVersion` at `processResources`.
|
||||
- Dist/jar artifact names and the `com.mojang:minecraft` coordinate — composed from
|
||||
`minecraftVersion` in the build scripts.
|
||||
|
||||
## Notes
|
||||
|
||||
- `build.gradle`, the adapter `build.gradle` files, and `settings.gradle` carry `.getOrElse('26.2')`
|
||||
defensive defaults for the version properties. `gradle.properties` always overrides them, so a
|
||||
bump does not require touching those fallbacks; refresh them only if the checked-in default should
|
||||
track the current release.
|
||||
- The Java literal `"26.2"` intentionally remains in `DataVersion.java` (structural enum constant),
|
||||
`core/src/test/java/art/arcane/iris/core/nms/MinecraftVersionTest.java`, and
|
||||
`core/src/test/java/art/arcane/iris/core/lifecycle/PaperLibBootstrapTest.java`. The test files use
|
||||
MC version strings as parser fixtures, not as a version source; update them only when the version
|
||||
string formats they exercise change.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Iris Release Checklist
|
||||
|
||||
Manual release procedure. There is no release automation by design: every step below is run by
|
||||
a person and verified by eye. Work top to bottom; do not skip the verify gates.
|
||||
|
||||
Before starting this publication procedure, complete the
|
||||
[all-platform release readiness checklist](release-readiness-checklist.md). It contains the engineering
|
||||
remediation, determinism, performance, CI, and full platform-acceptance gates. This checklist starts
|
||||
only after those gates produce GO or an explicitly accepted GO-WARN decision.
|
||||
|
||||
Reference values below assume the current `gradle.properties`: `irisVersion=4.0.0-26.2`,
|
||||
`minecraftVersion=26.2`, `fabricLoaderVersion=0.19.3`, `forgeVersion=26.2-65.0.4`,
|
||||
`neoForgeVersion=26.2.0.12-beta`. For a Minecraft version bump, do `docs/mc-version-bump.md` first,
|
||||
then start this checklist.
|
||||
|
||||
## a. Preflight
|
||||
|
||||
- [ ] Working tree clean on the exact commit you intend to tag (`git status` shows nothing to commit).
|
||||
- [ ] CI is green on that commit. The `verify` job (`.github/workflows/ci.yml`) runs
|
||||
core checks, Bukkit and shared modded tests, the SPI build, the deserialization probe, the modded
|
||||
artifact-verifier tests, and guarded Fabric, Forge, and NeoForge artifact builds on JDK 25.
|
||||
Do not release on a red or stale run.
|
||||
- [ ] `MasterChangelog.MD` Iris section is coherent: one consolidated entry set, deduplicated, no
|
||||
date-sliced headers, and it describes the current shipped state (not superseded intermediate work).
|
||||
- [ ] Version fields correct in `gradle.properties`: `irisVersion` is the release version and its
|
||||
trailing `-<mc>` suffix matches `minecraftVersion`. For a Minecraft bump, confirm every step in
|
||||
`docs/mc-version-bump.md` is done (loader ranges, `DataVersion`, NMS binding).
|
||||
- [ ] JDK 25 is the active toolchain locally (`java -version` reports 25).
|
||||
|
||||
## b. Build
|
||||
|
||||
- [ ] From the Iris project root: `./build-all.sh`. This disables local VolmLib substitution, uses the
|
||||
immutable coordinate from `gradle.properties`, and serializes the all-platform build.
|
||||
- [ ] `dist/` contains the four platform jars (exact names for this release):
|
||||
- [ ] `Iris v4.0.0-26.2 [CraftBukkit] 26.2.jar` (Bukkit/Paper/Purpur/Spigot/Folia plugin)
|
||||
- [ ] `Iris v4.0.0-26.2 [Fabric] 26.2+0.19.3.jar`
|
||||
- [ ] `Iris v4.0.0-26.2 [Forge] 26.2+65.0.4.jar`
|
||||
- [ ] `Iris v4.0.0-26.2 [NeoForge] 26.2+26.2.0.12-beta.jar`
|
||||
- Naming pattern: `Iris v<irisVersion> [<Platform>] <mc>[+<loaderDisplay>].jar`.
|
||||
- [ ] The developer SPI jar is built by the same run at `spi/build/libs/iris-spi-4.0.0-26.2.jar`.
|
||||
It is the platform-API artifact for downstream developers and is not copied into `dist/`; it is
|
||||
not uploaded to the mod portals (see publish).
|
||||
- [ ] Each mod jar bundles Iris core, SPI, and Iris-owned shaded libraries. LZ4, OSHI, JNA, and
|
||||
JNA Platform are supplied by the Minecraft 26.2 runtime and must not be bundled or relocated.
|
||||
|
||||
## c. Verify (release gates)
|
||||
|
||||
- [ ] `:core:check` and `:probe:deserializationProbe` passed in CI on the tag commit (a. covers this).
|
||||
- [ ] Golden-hash determinism VERIFY passes on all four platforms and matches the same hash:
|
||||
- [ ] Bukkit plugin: `/iris developer goldenhash world=<world> radius=<radius> threads=<threads>`
|
||||
(automatically verifies when the matching capture already exists)
|
||||
- [ ] Fabric mod: `/iris goldenhash verify <radius> <threads>`
|
||||
- [ ] Forge mod: `/iris goldenhash verify <radius> <threads>`
|
||||
- [ ] NeoForge mod: `/iris goldenhash verify <radius> <threads>`
|
||||
- The hash is interchangeable across platforms: all four MUST report identical output for the same
|
||||
pack and seed. Any mismatch blocks the release.
|
||||
- [ ] Live modded content-mod gate: on each loader, boot the mod jar alongside a real content mod
|
||||
(e.g. Create) and generate an Iris world. Confirm no load-time rejection, no class-loader crash,
|
||||
and that modded blocks/items/entities author and generate.
|
||||
- [ ] Fabric + content mod
|
||||
- [ ] Forge + content mod
|
||||
- [ ] NeoForge + content mod
|
||||
- [ ] Client-mod matrix: install the mod on the client (keybind `H` toggles the pregen HUD) and confirm:
|
||||
- [ ] Modded server + modded client: HUD receives pregen progress over `irisworldgen:main`.
|
||||
- [ ] Modded server + vanilla client: server generates normally; vanilla client is unaffected.
|
||||
- [ ] Paper (Bukkit) server + modded client: HUD receives pregen progress over vanilla plugin messaging.
|
||||
- [ ] Folia smoke: plugin loads and an Iris world generates on Folia.
|
||||
- [ ] Non-Iris server + modded client: client is inert, no errors.
|
||||
|
||||
## d. Publish (all manual, no automation)
|
||||
|
||||
- [ ] Modrinth: upload the three mod jars and the plugin jar. Tag loaders `fabric` / `forge` /
|
||||
`neoforge` on the mod files; mark the environment server + client; set game version 26.2.
|
||||
- [ ] CurseForge: upload the three mod jars with the matching loader tags and game version 26.2.
|
||||
- [ ] Existing plugin distribution channels: publish the plugin jar
|
||||
(`Iris v4.0.0-26.2 [CraftBukkit] 26.2.jar`) where the plugin already ships.
|
||||
- [ ] Sentry: add a release note / mark the release so incoming reports map to this version
|
||||
(the mod version string is the Sentry release tag).
|
||||
- [ ] Storepage / `listing.json` staleness review: check the listing copy for pre-4.0 content
|
||||
(Bukkit-only framing, old feature lists, screenshots). Flag anything stale for update before or
|
||||
right after launch. (Review only; this checklist does not change store copy.)
|
||||
|
||||
## e. Post
|
||||
|
||||
- [ ] Tag the release commit (`v<irisVersion>`) and push the tag. Archive the already verified `dist/`
|
||||
bundle with the release record; no tag-triggered bundle automation is configured.
|
||||
- [ ] Announce the release on the community channels once the portals show the new files live.
|
||||
@@ -0,0 +1,490 @@
|
||||
# Iris All-Platform Release Readiness Checklist
|
||||
|
||||
Engineering checklist for preparing Iris for a public release on Bukkit-family servers, Fabric,
|
||||
Forge, and NeoForge. Complete this checklist before running `docs/release-checklist.md`.
|
||||
|
||||
The goal is to correct confirmed defects without silently changing valid pack output, public behavior,
|
||||
or platform parity. A behavior change is acceptable when it fixes a documented defect, is covered by
|
||||
a regression test, and is recorded in `MasterChangelog.MD`.
|
||||
|
||||
The current runtime pass prioritizes isolated world creation, deterministic generation, pregeneration,
|
||||
and profiling. Hotload, reload, and shutdown refinement remains in the later lifecycle gates.
|
||||
Automated release builds, tagged bundles, and publishing infrastructure are deferred; public-beta work
|
||||
uses manually built artifacts and focuses on plugin/mod correctness and stability.
|
||||
|
||||
## Completion rules
|
||||
|
||||
- [ ] Work through the sections in order. A later section does not override a failed earlier gate.
|
||||
- [ ] Add a failing regression test or deterministic reproduction before each P0/P1 correctness fix.
|
||||
- [ ] Run the focused test while developing, then run the full gate for the affected platform.
|
||||
- [ ] Compare fixed-pack, fixed-seed golden hashes before and after every world-generation change.
|
||||
- [ ] Treat an unexpected deterministic output change as a release blocker until explained.
|
||||
- [ ] Keep loader-specific behavior behind the platform boundary; reusable behavior belongs in core or SPI.
|
||||
- [ ] Do not add compatibility shims, temporary adapters, or swallowed failure paths.
|
||||
- [ ] Preserve full stack traces for engine, lifecycle, persistence, and operator-critical failures.
|
||||
- [ ] Update `MasterChangelog.MD` as operator-visible fixes become final; merge superseded entries.
|
||||
- [ ] Do not publish while any required release gate is failed, pending, or waived without an explicit reason.
|
||||
|
||||
## 0. Secure and freeze the release baseline
|
||||
|
||||
- [ ] Rotate the GitHub credential that was embedded in the local origin URL.
|
||||
- [x] Replace the local origin with a credential-free SSH or HTTPS URL.
|
||||
- [ ] Regenerate the dev-server management secret before enabling the management interface.
|
||||
- [ ] Select and record the exact release commit, Minecraft version, JDK, and loader versions.
|
||||
- [x] Pin VolmLib to an immutable release/tag/commit rather than `master-SNAPSHOT`.
|
||||
- [x] Ensure `useLocalVolmLib` and `volmLibCoordinate` propagate into every nested adapter build.
|
||||
- [x] Make the manual release build disable local VolmLib substitution by default.
|
||||
- [x] Capture a baseline build and test record, running these as separate invocations:
|
||||
- [x] `./gradlew :core:check :spi:build :probe:deserializationProbe -PuseLocalVolmLib=false`
|
||||
- [x] `./gradlew :adapters:bukkit:plugin:test --rerun-tasks -PuseLocalVolmLib=false`
|
||||
- [x] `./build-all.sh`
|
||||
- [x] Confirm all four baseline jars pass archive integrity checks.
|
||||
- [x] Capture baseline golden hashes for the same pack, seed, radius, and thread counts on all platforms.
|
||||
- [ ] Preserve a copy of the baseline performance results described in section 8.
|
||||
|
||||
Gate: the source, dependencies, generated terrain baseline, and test evidence are reproducible on a
|
||||
second clean checkout.
|
||||
|
||||
## 1. P0 - Make concurrent generation deterministic
|
||||
|
||||
- [x] Fix the reproducible order/state-dependent generation defect. Cave painting relabeled shared,
|
||||
loader-cached biome objects as `CAVE`; shallow cave resolution can return the surface biome, so later
|
||||
height and biome decisions changed until `IrisComplex` was rebuilt. Carving now passes explicit cave
|
||||
context to surface and ceiling decorators without mutating the shared biome, preserving cave fluid
|
||||
behavior. Focused isolation and decorator tests plus a 2,025-chunk warm-sequence reproducer pass.
|
||||
- [x] Scope the confirmed height-bounds cache to its owning `IrisComplex`. The previous static thread-local
|
||||
cache keyed entries only by grid coordinates and interpolator index, allowing another engine or a
|
||||
hotloaded complex to reuse bounds from a different generator set. Focused coverage protects both
|
||||
cross-complex isolation and same-complex cache reuse.
|
||||
- [x] Scope the cave carver's scratch cache to each `IrisCaveCarver3D`. The warp cache was thread-local but
|
||||
shared by every cave profile and keyed only by sample coordinates, so a second profile on the same
|
||||
worker could reuse warp values from the first profile's noise generator. Focused same-thread coverage
|
||||
now proves distinct carvers retain their own warp samples while preserving per-carver scratch reuse.
|
||||
- [x] Make cross-chunk cave-wall painting independent of adjacent mantle load order. All 37 block differences
|
||||
in the two focused mantle-reset diagnoses were on local chunk edges (`x=0`, `x=15`, or `z=15`), where
|
||||
`IrisCarveModifier` paints the neighboring cave wall only when that neighbor's mantle chunk contains
|
||||
carving data. The carving component now declares a minimal one-block radius, which schedules the full
|
||||
adjacent chunk pass through the mantle radius conversion; focused coverage protects that contract.
|
||||
Packaged-runtime regeneration now retains the same fixed-seed hash on every available platform.
|
||||
- [x] Repeat the fixed-seed, reset-mantle GoldenHash sequence from clean startup and after a complete pregen
|
||||
on every available platform. Paper 26.2-56, Fabric Loader 0.19.3, Forge 65.0.3, and NeoForge
|
||||
26.2.0.8-beta all produced the exact combined hash
|
||||
`783cf831486858129a3730e93c2823b773a40af78442ba3ebe373425eb80fab4`; every strict single-thread
|
||||
2,025-chunk pregen completed with zero failures and every post-pregen verification matched. Fabric also
|
||||
matched after a controlled restart. Folia 26.2 remains unavailable from its upstream build endpoint.
|
||||
- [x] Explain and fix the separate 50-chunk Paper-versus-modded biome-hash difference for byte-identical
|
||||
packs. All 131 differing sampled columns were exactly `minecraft:forest` versus `minecraft:plains`:
|
||||
Bukkit's NMS biome source seeded the shared scatter generator from its first coordinate-derived RNG,
|
||||
while modded generation seeded it from the engine biome seed. Every runtime path now passes its owning
|
||||
engine explicitly, shared registrants cache by canonical engine biome seed in a bounded eight-entry
|
||||
cache, and engine-less tooling preserves supplied-seed behavior. Concurrent interleaved-engine coverage
|
||||
protects exact engine ownership, seed isolation, same-seed reuse, and bounded eviction.
|
||||
- [x] Sample direct Bukkit/modded biome derivatives at each world column, matching Bukkit NMS resolution.
|
||||
The actuator previously reused the chunk origin for every local column, so scatter selection could
|
||||
differ even after both platforms used the same generator seed. Focused actuator coverage verifies all
|
||||
four coordinates in a two-by-two chunk section are distinct world positions.
|
||||
- [ ] Add a two-thread barrier test that generates two chunks through the same `IrisEngine` concurrently.
|
||||
- [ ] Assert each generation observes its own chunk coordinates, `ChunkContext`, and generation session.
|
||||
- [ ] Add a repeated parallel golden-hash test that fails on any cross-run difference.
|
||||
- [x] Remove the shared mutable `chunkContext`/session state from the engine-wide `IrisContext` path.
|
||||
- [x] Give each active generation thread or lease an isolated context with explicit lifetime cleanup.
|
||||
- [ ] Verify maintenance, pregen, Bukkit multicore, and modded generation use the isolated context.
|
||||
- [ ] Run sequential and parallel generation for the same seed and assert identical hashes.
|
||||
- [ ] Run the test under high concurrency and with generation-session close/hotload activity.
|
||||
|
||||
Gate: repeated concurrent generation is deterministic, context-isolated, and hash-identical to the
|
||||
single-threaded result.
|
||||
|
||||
## 2. P0 - Make hotload and shutdown transactional
|
||||
|
||||
This section is retained for the later lifecycle refinement pass and is not part of the current public-beta
|
||||
runtime gate. Controlled restarts remain in scope only for existing-world persistence verification.
|
||||
|
||||
- [ ] Add a regression test: malformed dimension edit -> failed hotload -> old engine remains usable.
|
||||
- [ ] Extend the test: corrected edit -> next hotload succeeds without restarting the server.
|
||||
- [ ] Build candidate dimension, loader, complex, mode, mantle, and world-manager state privately.
|
||||
- [ ] Validate the complete candidate before changing the live engine.
|
||||
- [ ] Seal new generation and drain active leases before clearing or replacing live resources.
|
||||
- [ ] Publish the validated candidate atomically, then activate the next generation session.
|
||||
- [ ] Keep the previous engine state intact when candidate loading or setup fails.
|
||||
- [ ] Make `setupEngine()` fail closed and propagate fatal initialization failures.
|
||||
- [ ] Route `hotloadComplex()` through the same generation-session and transactional rules.
|
||||
- [ ] Ensure Bukkit exclusive-control permits are released after success, failure, and interruption.
|
||||
- [ ] Restructure `IrisEngine.close()` so every cleanup stage runs even when lease draining times out.
|
||||
- [ ] Add startup, failed-hotload recovery, successful-hotload, close, and restart tests.
|
||||
|
||||
Gate: no failed hotload can poison the live engine, admit generation into partial state, leak permits,
|
||||
or skip shutdown cleanup.
|
||||
|
||||
## 3. P0 - Make `.iris` packaging complete and lossless
|
||||
|
||||
- [ ] Define the complete pack resource graph in one shared traversal used by Bukkit and modded Studio.
|
||||
- [ ] Traverse dimensions, regions, biomes, generators, blocks, objects, entities, spawners, loot,
|
||||
structures, jigsaw pools/pieces, snippets, and every other referenced registrant.
|
||||
- [ ] Include objects referenced directly by regions, not only objects reached through biomes.
|
||||
- [x] Include entity resources referenced only by spawner `initialSpawns` entries, alongside normal `spawns`
|
||||
dependencies, with focused export dependency regression coverage.
|
||||
- [ ] Fail packaging when any required resource is missing or malformed; never report partial success.
|
||||
- [ ] Stop obfuscation/export from mutating loader-cached biome or placement objects.
|
||||
- [ ] Give Bukkit and modded packaging the same graph, validation, and error semantics.
|
||||
- [ ] Implement the modded import/unpack path or explicitly remove the unsupported claim from the UI/docs.
|
||||
- [ ] Add a minimal pack fixture containing at least one resource from every supported category.
|
||||
- [ ] Add Bukkit export -> import -> export round-trip tests.
|
||||
- [ ] Add modded export -> import -> export round-trip tests.
|
||||
- [ ] Compare normalized JSON, binary objects, dependency counts, and final resource hashes.
|
||||
|
||||
Gate: a complete fixture survives round-trip packaging without missing resources, mutated source state,
|
||||
or unexplained byte/content changes.
|
||||
|
||||
## 4. P0 - Make Object Studio Folia-safe and atomic
|
||||
|
||||
- [ ] Add a test cell that crosses multiple chunks and multiple Folia regions.
|
||||
- [ ] Capture each chunk/region snapshot only on its owning region thread.
|
||||
- [ ] Assemble the final `IrisObject` only after all owned snapshots complete successfully.
|
||||
- [ ] Serialize the object once and reuse the bytes for hashing and persistence.
|
||||
- [ ] Write to a temporary file, flush/close it, then atomically move it over the destination.
|
||||
- [ ] Commit the saved hash only after the atomic move succeeds.
|
||||
- [ ] Leave the prior hash and file untouched after capture, serialization, or write failure.
|
||||
- [ ] Confirm a failed write is retried on the next save rather than reported as “no changes.”
|
||||
- [ ] Test empty cells, unchanged cells, partial chunk availability, failure recovery, and concurrent saves.
|
||||
|
||||
Gate: Object Studio performs no cross-region Bukkit access, never exposes a partial file, and can always
|
||||
retry a failed save.
|
||||
|
||||
## 5. P1 - Make validation and schemas trustworthy
|
||||
|
||||
- [x] Split `PackValidator` into a read-only validator and an explicit cleanup command.
|
||||
- [x] Make unused-resource cleanup preview changes before moving files.
|
||||
- [x] Prevent restore from overwriting a newer live file without an explicit conflict decision.
|
||||
- [ ] Discover nested dimensions and resources using the same key rules as `ResourceLoader`.
|
||||
- [ ] Parse and validate every referenced dependency rather than checking only file existence.
|
||||
- [ ] Promote malformed referenced JSON to a blocking validation error.
|
||||
- [x] Validate nested spawner `spawns` and `initialSpawns` entries against same-pack entity resources,
|
||||
blocking malformed containers and entries, missing files, unsafe paths, and malformed referenced JSON.
|
||||
- [ ] Validate nested unknown properties where the schema disallows them.
|
||||
- [ ] Preserve namespaces for non-Minecraft enchantments and potion effects in generated schemas.
|
||||
- [ ] Add deliberate cross-namespace collision fixtures.
|
||||
- [ ] Make the schema executor lifecycle-owned and restartable after Bukkit reload and integrated-server stop/start.
|
||||
- [ ] Add validator tests for nested resources, malformed dependencies, cleanup preview, and restore conflicts.
|
||||
- [ ] Add schema tests for vanilla shorthand and fully namespaced modded values.
|
||||
|
||||
Gate: validation is read-only by default, rejects broken dependency graphs, accepts valid nested packs,
|
||||
and schema completion never changes registry identity.
|
||||
|
||||
## 6. P1 - Harden modded generation and lifecycle
|
||||
|
||||
- [ ] Make generation-session teardown cancel/retry the chunk stage instead of completing an empty chunk.
|
||||
- [ ] Add a test proving a sealed engine cannot persist a blank chunk.
|
||||
- [ ] Bound the modded chunk-generation queue and expose queue/backpressure metrics.
|
||||
- [ ] Complete or cancel every queued future during shutdown; leave no unresolved chunk pipeline.
|
||||
- [ ] Stop and await maintenance work before closing the engines it can access.
|
||||
- [ ] Await executor termination and report tasks that exceed the shutdown deadline.
|
||||
- [ ] Add negative-min-Y tests for sea level, base height, and base-column stone/water/air spans.
|
||||
- [ ] Verify custom biome cache invalidation after a successful Studio hotload.
|
||||
- [ ] Make engine-data persistence synchronized and atomic.
|
||||
- [ ] Make persisted statistics safe under parallel generation.
|
||||
- [ ] Test dedicated-server start/stop, integrated-server start/stop/start, and world unload/reload.
|
||||
- [ ] Decide and document parity for modded entity time/weather gates, awareness, and spawn effects.
|
||||
|
||||
Gate: modded shutdown/hotload cannot save blank chunks, strand futures, race maintenance, or retain stale
|
||||
world state across a second server lifecycle.
|
||||
|
||||
## 7. P1 - Harden pregeneration, Folia, and scheduling
|
||||
|
||||
- [ ] Remove direct world/chunk/IO fallback when a Folia region scheduling call fails.
|
||||
- [ ] Retry, defer, or fail the operation without touching region-owned state from the wrong thread.
|
||||
- [ ] Wrap pregenerator initialization and total-count calculation in the cleanup lifecycle.
|
||||
- [ ] Ensure every shutdown step runs even when `generator.close()` fails.
|
||||
- [ ] Clear `regionPending` and related bookkeeping on every load/generation callback failure.
|
||||
- [ ] Make the pregen cache executor restartable in the same JVM.
|
||||
- [x] Distinguish cancelled or aborted partial pregeneration from full completion after the generator drains.
|
||||
Cancellation now reports generated, total, failed, and remaining counts without emitting a successful
|
||||
`Pregen finished` summary; focused tests cover cancellation after the first chunk, normal async-close
|
||||
completion, and completion with a failed chunk.
|
||||
- [ ] Replace modded scheduler `CallerRunsPolicy` with explicit backpressure that cannot move async work
|
||||
onto the server thread.
|
||||
- [ ] Add a bounded per-tick main-thread drain budget.
|
||||
- [ ] Replace full delayed-task scans with a due-time queue or equivalent bounded scheduler.
|
||||
- [ ] Stress cancellation, pause/resume, failure, shutdown, and restart under Paper and Folia.
|
||||
- [ ] Verify chunk tickets, regions, files, protocol sessions, and executor threads are released afterward.
|
||||
|
||||
Gate: pregeneration remains thread-correct and bounded under saturation, cancellation, failure, and restart.
|
||||
|
||||
## 8. Performance and regression proof
|
||||
|
||||
### Current isolated smoke evidence
|
||||
|
||||
This evidence validates packaged-artifact generation and establishes a profiling candidate. It is not the
|
||||
final 5,000-10,000-chunk performance baseline required by this section.
|
||||
|
||||
- [x] Fixed inputs: Iris seed `1337`, GoldenHash radius `22`, one hash thread, and a 352-block serial/sync
|
||||
pregeneration radius covering exactly 2,025 chunks.
|
||||
- [x] Fixed host: Apple M3 Max, 128 GiB RAM, Temurin 25.0.2, 8 GiB instance heap.
|
||||
- [x] Paper 26.2-56: serial pregen completed 2,025/2,025 with zero failed chunks; cancellation,
|
||||
pause/status/resume, cache resume, restart persistence, and untouched far-chunk generation passed.
|
||||
- [x] Fabric Loader 0.19.3: sync pregen completed 2,025/2,025 with zero failed chunks and strict
|
||||
`peakInFlight=1 finalLimit=1`; controls, cache resume, restart persistence, and far generation passed.
|
||||
- [x] Forge 26.2-65.0.3: sync pregen completed 2,025/2,025 with zero failed chunks and strict
|
||||
`peakInFlight=1 finalLimit=1`; controls, cache resume, restart persistence, and untouched far-chunk
|
||||
generation passed.
|
||||
- [x] NeoForge 26.2.0.8-beta: sync pregen completed 2,025/2,025 with zero failed chunks; pause/cancel,
|
||||
checkpoint resume, fresh generation, and GoldenHash capture completed against the corrected pack.
|
||||
- [x] GoldenHash parity/determinism: Paper, Fabric, Forge, and NeoForge all captured the exact block+biome
|
||||
hash `783cf831486858129a3730e93c2823b773a40af78442ba3ebe373425eb80fab4` from the manually built
|
||||
candidate artifacts. Every platform then completed a strict single-thread 2,025-chunk pregen with zero
|
||||
failures and retained that hash; Fabric retained it across restart. The historical divergent hashes are
|
||||
superseded by fixes for cross-complex height bounds, cross-profile cave warp, cave-boundary scheduling,
|
||||
engine-owned biome generation, per-column biome sampling, and shared-biome cave relabeling.
|
||||
- [x] Paper JProfiler CPU, heap, and GC snapshots captured; explicit post-run GC reduced used heap from a
|
||||
sampled peak near 5.94 GiB to approximately 475 MiB, with no retained-heap leak indicated by this run.
|
||||
- [x] Fabric JProfiler sampled-allocation snapshot captured; profiling overhead made that run unsuitable
|
||||
for throughput comparison.
|
||||
- [x] Real content-mod fixture: Fabric, Forge, and NeoForge loaded Nerospace beta.7 with Neroland Core 1.4.0
|
||||
(plus Fabric API 0.154.2 on Fabric), resolved a custom entity/item/block through Iris, generated the
|
||||
exact named structure chest item, performed once-per-chunk initial spawning with zero players, replaced
|
||||
the entity's death loot, generated seven custom ore blocks in the forced test area, and completed strict
|
||||
synchronous 2,025/2,025 pregeneration with zero failed chunks on every loader.
|
||||
|
||||
- [ ] Choose one fixed release pack, seed, world height, radius, JVM configuration, and hardware profile.
|
||||
- [ ] Warm at least 256 chunks before measuring.
|
||||
- [ ] Run a 5,000-10,000 chunk pregeneration baseline on Paper.
|
||||
- [ ] Run the same workload on Fabric; repeat on Forge and NeoForge before final release.
|
||||
- [ ] Capture JProfiler CPU, allocation, GC, retained-object, thread, and executor-queue evidence.
|
||||
- [ ] Record chunks/second, total duration, p50/p95 chunk time, allocations/chunk, peak heap, and GC pause time.
|
||||
- [ ] Profile nested chunk prefill parallelism before changing it.
|
||||
- [ ] Profile modded block/biome buffer allocation before pooling or changing representation.
|
||||
- [ ] Profile height-bound sampling, custom biome caches, mantle tasks, and pregen region-drain complexity.
|
||||
- [ ] Benchmark each optimization against the unchanged baseline with the same inputs.
|
||||
- [ ] Reject or revise changes that regress median throughput by more than 5% or p95 latency/allocations by
|
||||
more than 10%, unless the correctness benefit and accepted tradeoff are documented.
|
||||
- [ ] Confirm optimized and baseline runs produce identical golden hashes where behavior should be unchanged.
|
||||
|
||||
Gate: representative generation and pregen have repeatable baselines, no unexplained regression, and no
|
||||
unbounded queue, allocation, or retained-memory growth.
|
||||
|
||||
## 9. CI and deterministic test infrastructure
|
||||
|
||||
Automated build and release-pipeline work in this section is deferred. The current beta pass uses manual
|
||||
artifacts; only correctness tests and deterministic reproducers that directly protect runtime behavior apply.
|
||||
|
||||
- [x] Add `:adapters:bukkit:plugin:test` to CI.
|
||||
- [x] Expand the broad classload probe across all top-level and nested core classfiles, with an exact reviewed
|
||||
class and dependency-category allowlist that rejects new classes, changed dependency namespaces,
|
||||
non-missing-class failures, and stale entries.
|
||||
- [ ] Move the core Bukkit purity ratchet below its current 182-file ceiling.
|
||||
- [ ] Give `genProbe` a repository fixture or require an explicit portable pack path.
|
||||
- [ ] Add a deterministic fixed-seed Iris-world task for Fabric, Forge, and NeoForge.
|
||||
- [x] Make worldcheck return a failing process result when its internal result is FAIL.
|
||||
- [x] Prevent `buildAllToOut` nested builds from racing root tasks over `core/build`.
|
||||
- [x] Verify nested adapter builds honor the selected VolmLib source/coordinate.
|
||||
- [x] Add packaged-jar server boots; manually assembled Bukkit, Fabric, Forge, and NeoForge artifacts all
|
||||
reached their runtime-ready state in isolated instances, including a real multi-mod classpath.
|
||||
|
||||
Gate: a clean CI run proves tests, deterministic generation, packaging, and server startup from the actual
|
||||
release artifacts.
|
||||
|
||||
## 10. Full platform acceptance matrix
|
||||
|
||||
Use the exact packaged release jars, not development classes.
|
||||
|
||||
The current isolated smoke proves fresh non-empty generation, exact fixed-seed block-and-biome parity, and
|
||||
complete serial/sync 2,025-chunk pregeneration on Paper, Fabric, Forge, and NeoForge. A second real content-mod
|
||||
fixture also passes entity, item, block, structure loot, death loot, headless initial-spawn, and 2,025-chunk
|
||||
pregeneration gates on all three mod loaders. It does not yet satisfy the minimum/latest loader, complete
|
||||
Bukkit-family, client, lifecycle, or full pregen-control matrix below.
|
||||
|
||||
- [ ] Bukkit-family server matrix:
|
||||
- [ ] Paper current target
|
||||
- [ ] Purpur current target
|
||||
- [ ] Folia current target
|
||||
- [ ] Spigot/CraftBukkit if still advertised as supported
|
||||
- [ ] Mod-loader matrix:
|
||||
- [ ] Fabric declared minimum loader
|
||||
- [ ] Fabric latest compatible loader
|
||||
- [ ] Forge declared minimum loader
|
||||
- [ ] Forge latest compatible loader
|
||||
- [ ] NeoForge declared minimum loader
|
||||
- [ ] NeoForge latest compatible loader
|
||||
- [ ] On every server target:
|
||||
- [ ] Fresh Iris world creation and non-empty chunk generation
|
||||
- [ ] Existing Iris world restart and new-chunk generation
|
||||
- [ ] Custom biome registration and client synchronization
|
||||
- [ ] Structures, objects, loot, spawners, and entities
|
||||
- [ ] Golden-hash match for the shared pack/seed
|
||||
- [ ] Pregeneration start, pause, resume, cancel, restart, and shutdown
|
||||
- [ ] Studio validation, hotload failure recovery, and successful hotload where supported
|
||||
- [ ] Clean startup and shutdown without leaked threads or incomplete futures
|
||||
- [x] Content-mod gate on Fabric, Forge, and NeoForge using Nerospace beta.7 and Neroland Core 1.4.0 with
|
||||
authored `nerospace:meadow_loper`, `nerospace:raw_nerosium`, and `nerospace:nerosium_ore` resources.
|
||||
- [ ] Client matrix:
|
||||
- [ ] Modded Iris server + Iris client mod
|
||||
- [ ] Modded Iris server + client without Iris where loader rules permit
|
||||
- [ ] Bukkit Iris server + Iris client mod over plugin messaging
|
||||
- [ ] Non-Iris server + Iris client mod remains inert
|
||||
- [ ] Integrated singleplayer create, leave, and create/join again in the same client process
|
||||
- [ ] Pregen HUD, Vision map, cursor overlay, keybinds, and Studio toasts
|
||||
|
||||
Gate: every advertised server, loader, client, and content path completes the same acceptance scenario or
|
||||
has a clearly documented intentional capability difference.
|
||||
|
||||
## 11. Documentation and repository hygiene
|
||||
|
||||
- [x] Make runtime splash/version identity match the artifact version; remove stale `4.0 RC.1.1.6` text.
|
||||
- [x] Correct README pregen syntax, including the required radius.
|
||||
- [ ] Document how to select an Iris world preset on each mod loader.
|
||||
- [ ] Distinguish automatic pack installation from automatic Iris main-world selection.
|
||||
- [ ] Publish an accurate Bukkit-versus-modded Studio capability matrix.
|
||||
- [ ] Document intentional entity-spawn and tooling differences that remain.
|
||||
- [x] Remove tracked generated SIMD benchmark `.class` files and jar outputs.
|
||||
- [x] Keep generated server worlds, caches, credentials, and build artifacts ignored.
|
||||
- [ ] Consolidate the Iris section of `MasterChangelog.MD` to the final shipped behavior.
|
||||
- [ ] Review store/listing copy, screenshots, commands, supported platforms, and Java requirements.
|
||||
- [ ] Write release notes with upgrade instructions, known limitations, and rollback guidance.
|
||||
|
||||
### Confirmed release blockers and follow-ups
|
||||
|
||||
- [ ] Freeze the default overworld pack to an immutable release input. The runtime downloader currently
|
||||
follows the mutable `master` branch, so any recorded tree checksum remains reproducible only while
|
||||
that upstream content is unchanged. Immutable branch/tag/commit URL resolution is implemented, but
|
||||
published commit `8e32852ee6ecd039fae27a36f701f57cdc02e83f` predates the five local slime-category
|
||||
and biome-tag corrections, the dormant standard entity resource restoration, and removal of the
|
||||
legacy default ambient-spawner attachments; publish those pack edits under a new commit/tag before
|
||||
pinning automatic installs.
|
||||
- [x] Make modded GoldenHash metadata use the active Iris engine seed. Fabric, Forge, and NeoForge generated
|
||||
identical output from Iris seed `1337`, but filenames and headers recorded each vanilla level seed,
|
||||
preventing one captured baseline file from being reused directly across loaders.
|
||||
- [x] Correct the default overworld pack's slime spawn category from implicit `MISC` to explicit `MONSTER`
|
||||
in `biomes/vanilla/mangrove_swamp.json`, `biomes/swamp/cambian-drift.json`,
|
||||
`biomes/swamp/cambian-drift-extended.json`, `biomes/swamp/marsh.json`, and
|
||||
`biomes/swamp/marsh-rotten.json`. NeoForge exposes the bad category at startup; all loaders generate
|
||||
the same bad datapack entry, which can affect natural slime spawning and mob-cap accounting.
|
||||
- [x] Extend `PackValidator` to reject authored custom-biome spawn categories that disagree with the live
|
||||
entity category instead of allowing the bad datapack to reach loader validation.
|
||||
- [x] Restore exactly the 36 standard entity resources required by the overworld's retained spawner library
|
||||
from their last authored revision, preserving their type/surface values without restoring deleted
|
||||
unique entities, while detaching every regional and spider-infestation ambient spawner so the library
|
||||
remains dormant unless a pack author explicitly references it.
|
||||
- [x] Delegate ongoing natural spawn tables in custom Iris biomes to each `vanillaDerivative` on Bukkit,
|
||||
Fabric, Forge, and NeoForge; explicit custom entries replace the same native entity type and extend
|
||||
the rest, while structure overrides remain authoritative and cached tables avoid hot-path allocation.
|
||||
- [x] Add validated custom-biome tag opt-ins and put all five explicit overworld slime biomes in
|
||||
`minecraft:allows_surface_slime_spawns`, allowing Minecraft's native surface-slime checks to succeed.
|
||||
- [x] Add Minecraft 26.2 default-clock metadata to generated Iris overworld and End dimension types so
|
||||
`/time set`, `/time add`, time queries, and clock controls work in Iris overworld dimensions. An
|
||||
isolated Paper 26.2 runtime loaded a dimension using `iris:overworld`, reported the
|
||||
`minecraft:overworld` clock, accepted day and night time markers, and returned the clock time.
|
||||
- [x] Make synchronous modded pregen completion diagnostics report meaningful concurrency values. The
|
||||
successful runs reported `peakInFlight=0 finalLimit=32` despite a strict `inFlightCap=1` sync mode.
|
||||
- [ ] Pin or fix the isolated test harness behavior before treating it as release evidence: setting an
|
||||
instance isolated currently leaves consumer-content symlinks in place. This pass used a fresh,
|
||||
dedicated harness root, so those links pointed only to test-local content and did not contaminate
|
||||
the test, but the isolation flag alone is insufficient.
|
||||
- [x] Resolve the fixed-seed order/state-dependent block generation and Paper-versus-modded biome-hash
|
||||
difference. The manually built candidate produced one exact full hash before and after 2,025-chunk
|
||||
pregeneration on Paper, Fabric, Forge, and NeoForge; Fabric also retained it after restart.
|
||||
- [ ] Re-run Folia when an upstream 26.2 server build becomes available. The official 26.2 build endpoint
|
||||
currently returns `version_not_found`; an incompatible 26.1.2 runtime is not acceptable beta evidence.
|
||||
- [ ] Nerospace beta.7's bundled `nerospace:guide/new_life` advancement uses the obsolete
|
||||
`minecraft:entity_sub_predicate_type`/`minecraft:type` shape and logs one datapack parse error on Fabric,
|
||||
Forge, and NeoForge 26.2. Iris's custom block, item, entity, chest loot, death loot, and pregeneration
|
||||
integration all pass despite that independent content-mod error; update Nerospace before using it as a
|
||||
clean-log beta recommendation.
|
||||
- [x] Preserve structure-level loot through placement persistence. Newly placed structure containers receive a
|
||||
versioned, delimiter-safe marker containing the piece object, deterministic placement id, and owning
|
||||
structure; `Engine.getObjectPlacement()` reconstructs authored loot in order at weight 1 for the existing
|
||||
Bukkit and modded application paths without overriding global loot. Legacy `object@id` markers remain
|
||||
readable, malformed and unknown-version markers fail safely, and marker writes are storage-container-only.
|
||||
- [x] Remove the unsupported `IrisStructurePlacement` `rotation`, `translate`, and `scale` fields from beta
|
||||
authoring and generated schemas. Read-only pack validation now blocks those keys specifically inside
|
||||
dimension, region, and biome `structures[]` entries instead of accepting settings with no runtime effect;
|
||||
ordinary object-placement transforms remain valid and are not inspected by this check.
|
||||
|
||||
Gate: documentation and distribution metadata describe the behavior users will actually receive.
|
||||
|
||||
## 12. Final GO/NO-GO gate
|
||||
|
||||
- [x] `unit-tests`: pass.
|
||||
- [ ] `qa-validation`: pass across the full matrix.
|
||||
- [ ] `edge-case-review`: pass or all remaining risks explicitly accepted.
|
||||
- [ ] `perf-regression`: pass against the recorded baseline.
|
||||
- [ ] `release-dry-run`: pass using final packaged artifacts.
|
||||
- [ ] `changelog-ready`: pass.
|
||||
- [x] `manual-smoke`: pass.
|
||||
- [ ] `docs-updated`: pass.
|
||||
- [ ] `known-issues-reviewed`: pass.
|
||||
- [ ] Working tree is clean on the exact release commit.
|
||||
- [ ] CI is green on that commit and all evidence artifacts are retained.
|
||||
- [ ] Complete every item in `docs/release-checklist.md` without rebuilding from different source.
|
||||
|
||||
Release decision:
|
||||
|
||||
- [ ] **GO** - every required check passes and no unresolved warning remains.
|
||||
- [ ] **GO-WARN** - every required check passes and each warning is documented and explicitly accepted.
|
||||
- [x] **NO-GO** - any required check fails or remains pending.
|
||||
|
||||
## Fixes already completed in the current working tree
|
||||
|
||||
- [x] Concurrent generation binds immutable engine/session/chunk context per worker scope and restores or
|
||||
removes that binding at scope close.
|
||||
- [x] Context-backed stream caches reject the wrong engine, a stale generation session, and coordinates
|
||||
outside the bound chunk.
|
||||
- [x] Registry-backed mantle and `.mat` reads bind the owning pack data explicitly, and heightmap object
|
||||
placement no longer depends on ambient generation context.
|
||||
- [x] Configured Matter placements use the initialized canonical Matter loader instead of a duplicate null field.
|
||||
- [x] Deterministic barrier, worker-reuse, nested-scope, close-order, and context-cache regression tests pass.
|
||||
- [x] Bukkit/Paper pregeneration accepts small positive radii and a strict one-in-flight `serial=true` mode
|
||||
without changing normal Paper/Folia concurrency.
|
||||
- [x] Pregeneration drains the final backend callback before reporting completion, eliminating the observed
|
||||
2,024/2,025 success summary; delayed final success and failure paths have regression coverage.
|
||||
- [x] Modded synchronous and asynchronous completion counters count only successful chunks, and final
|
||||
summaries include generated, total, failed, and duration values.
|
||||
- [x] GoldenHash null-biome fallback is explicitly `minecraft:plains` on Bukkit and modded adapters.
|
||||
- [x] GoldenHash metadata uses the active Iris seed across every platform, and GitHub pack downloads accept
|
||||
validated immutable commit and tag references in preparation for freezing the default pack.
|
||||
- [x] Runtime splash identity derives from the packaged artifact version instead of a stale release label.
|
||||
- [x] Pack validation is read-only; cleanup and restore require explicit preview/apply flows with fresh scans,
|
||||
direct-child containment, conflict refusal, per-pack serialization, truthful rollback reporting, and
|
||||
no-overwrite quarantine handling.
|
||||
- [x] Custom-biome spawn groups validate against live platform entity categories, including `AXOLOTLS`, and
|
||||
the default overworld slime records are explicitly `MONSTER` with isolated NeoForge proof.
|
||||
- [x] Spawner entity dependency validation covers both runtime spawn lists, malformed entry/container shapes,
|
||||
missing or malformed referenced entities, nested resource keys, and path containment; the default pack's
|
||||
dormant spawner library resolves to exactly 36 standard entities and no restored unique entities.
|
||||
- [x] `.iris` packaging collects entity dependencies from both normal and initial spawner lists, so an entity
|
||||
used exclusively during initial chunk spawning remains present after export.
|
||||
- [x] Newly placed structure containers persist versioned structure ownership and resolve the structure's authored
|
||||
loot through the shared Bukkit/modded placement path without replacing global loot or consuming generation RNG.
|
||||
- [x] VolmLib is pinned to commit `d9026a7c8ebc391c8109f401ce79a0ce65df3969`; local-development and
|
||||
clean remote-resolution modes propagate through every nested platform build.
|
||||
- [x] Headless classload validation scans all 1,166 compiled core classes, including all 353 nested classfiles;
|
||||
331 nested classes initialize without server APIs and the remaining 22 match exact reviewed class and
|
||||
dependency-namespace entries.
|
||||
- [x] Modded worldcheck uses a non-daemon coordinator, stops the server before exiting, and returns nonzero
|
||||
for internal failure, timeout, interruption, thrown checks, and shutdown failure; its exit contract is
|
||||
covered by the Fabric shared-source test gate.
|
||||
- [x] Fabric protocol startup tolerates the pre-player-list server phase.
|
||||
- [x] NeoForge registers the shared payload once as bidirectional.
|
||||
- [x] Fabric distributable metadata declares the bundled transitive access-widener.
|
||||
- [x] Fabric, Forge, and NeoForge relocate Iris's embedded Sentry runtime so another mod can bundle Sentry
|
||||
without a duplicate-package module-resolution failure; the corrected Forge and NeoForge artifacts boot
|
||||
alongside Neroland Core's jar-in-jar Sentry dependency.
|
||||
- [x] Fabric, Forge, and NeoForge resolve Minecraft 26.2's supplied OSHI, JNA, JNA Platform, and LZ4
|
||||
implementations without embedding or relocating them. The distribution gate scans outer classes and
|
||||
nested jars for private rewritten references or duplicate runtime libraries before accepting each artifact.
|
||||
- [x] Headless force-loaded chunks receive structure loot and initial entity spawning on Bukkit and every mod
|
||||
loader without requiring a player to enter the world. Bukkit target collection is global/region-safe,
|
||||
bounded, rotating, and deduplicated; modded initial-spawn requests retry and recover without caller-runs
|
||||
disk work on the server tick.
|
||||
- [x] Bukkit world creation preserves explicit `pack:dimensionKey` selection through pack installation and
|
||||
engine creation, matching the modded command behavior and preventing same-key cross-pack collisions.
|
||||
- [x] Bukkit/Folia world-manager snapshots keep world, player, entity, chunk, and force-load API access on the
|
||||
appropriate global, entity, or region scheduler and refresh saturation before its early-return gate.
|
||||
- [x] Multicore Perfection waits for isolated worker completion.
|
||||
- [x] Bukkit exclusive-control permits release after failures and interruptions.
|
||||
- [x] Modded sea-level/base-column calculations use absolute world Y.
|
||||
- [x] Low-risk map drawing, post-processing, base-column, and block-buffer loop costs were reduced.
|
||||
- [x] Core tests, Bukkit plugin tests, all-platform assembly, archive integrity, and fresh Iris-world checks
|
||||
on Fabric, Forge, and NeoForge passed for this fix set.
|
||||
|
||||
These completed items remain subject to the final packaged-artifact, Bukkit/Folia, concurrency, and
|
||||
performance gates above.
|
||||
Reference in New Issue
Block a user