From c59592e448916623d331350338072cfe503fa3fd Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 12 Aug 2026 14:14:48 -0400 Subject: [PATCH] rmd --- README.md | 24 +- docs/00 - Overview.md | 162 ---- docs/01 - Installation & Platforms.md | 247 ------ docs/02 - Getting Started.md | 307 -------- docs/03 - Configuration.md | 310 -------- docs/04 - Commands & Permissions.md | 426 ---------- docs/05 - Concepts & Pack Layout.md | 229 ------ docs/06 - Worlds & Lifecycle.md | 318 -------- docs/07 - Pregeneration.md | 207 ----- docs/08 - Localization.md | 151 ---- docs/09 - PlaceholderAPI.md | 185 ----- docs/10 - Studio & VSCode Schemas.md | 279 ------- docs/11 - Dimensions.md | 466 ----------- docs/12 - Regions.md | 250 ------ docs/13 - Biomes.md | 526 ------------- docs/14 - Generators & Noise.md | 355 --------- docs/15 - Caves & Carving.md | 414 ---------- docs/16 - Surfaces, Decorators & Deposits.md | 403 ---------- ...ngi, Coral, Crystals, Formations, Ruins.md | 453 ----------- docs/18 - Structures Overview.md | 174 ----- docs/19 - Objects.md | 237 ------ docs/20 - Object Placement.md | 497 ------------ docs/21 - Jigsaw Structures.md | 725 ------------------ docs/22 - Native Structures & Datapacks.md | 624 --------------- .../23 - Loot, Entities, Spawners, Markers.md | 576 -------------- docs/24 - Pack Mods & Snippets.md | 275 ------- docs/25 - Pack Management.md | 273 ------- docs/26 - Example - Minimal Dimension.md | 244 ------ docs/27 - Example - Configuring Overworld.md | 302 -------- docs/28 - Integrations.md | 166 ---- docs/29 - Client HUD & Protocol.md | 159 ---- docs/30 - Platform Differences.md | 183 ----- docs/31 - Operator Runbooks.md | 447 ----------- docs/32 - Determinism & Goldenhash.md | 189 ----- docs/33 - Performance Tuning.md | 145 ---- docs/85 - Maintainer - MC Version Bump.md | 126 --- docs/86 - Maintainer - Release Checklist.md | 69 -- docs/87 - Maintainer - Release Readiness.md | 347 --------- docs/90 - API - Getting Started.md | 174 ----- docs/91 - API - Terrain.md | 443 ----------- docs/92 - API - World Events.md | 377 --------- docs/93 - API - Tree Feller.md | 434 ----------- docs/94 - API - Modded.md | 491 ------------ 43 files changed, 12 insertions(+), 13377 deletions(-) delete mode 100644 docs/00 - Overview.md delete mode 100644 docs/01 - Installation & Platforms.md delete mode 100644 docs/02 - Getting Started.md delete mode 100644 docs/03 - Configuration.md delete mode 100644 docs/04 - Commands & Permissions.md delete mode 100644 docs/05 - Concepts & Pack Layout.md delete mode 100644 docs/06 - Worlds & Lifecycle.md delete mode 100644 docs/07 - Pregeneration.md delete mode 100644 docs/08 - Localization.md delete mode 100644 docs/09 - PlaceholderAPI.md delete mode 100644 docs/10 - Studio & VSCode Schemas.md delete mode 100644 docs/11 - Dimensions.md delete mode 100644 docs/12 - Regions.md delete mode 100644 docs/13 - Biomes.md delete mode 100644 docs/14 - Generators & Noise.md delete mode 100644 docs/15 - Caves & Carving.md delete mode 100644 docs/16 - Surfaces, Decorators & Deposits.md delete mode 100644 docs/17 - Trees, Fungi, Coral, Crystals, Formations, Ruins.md delete mode 100644 docs/18 - Structures Overview.md delete mode 100644 docs/19 - Objects.md delete mode 100644 docs/20 - Object Placement.md delete mode 100644 docs/21 - Jigsaw Structures.md delete mode 100644 docs/22 - Native Structures & Datapacks.md delete mode 100644 docs/23 - Loot, Entities, Spawners, Markers.md delete mode 100644 docs/24 - Pack Mods & Snippets.md delete mode 100644 docs/25 - Pack Management.md delete mode 100644 docs/26 - Example - Minimal Dimension.md delete mode 100644 docs/27 - Example - Configuring Overworld.md delete mode 100644 docs/28 - Integrations.md delete mode 100644 docs/29 - Client HUD & Protocol.md delete mode 100644 docs/30 - Platform Differences.md delete mode 100644 docs/31 - Operator Runbooks.md delete mode 100644 docs/32 - Determinism & Goldenhash.md delete mode 100644 docs/33 - Performance Tuning.md delete mode 100644 docs/85 - Maintainer - MC Version Bump.md delete mode 100644 docs/86 - Maintainer - Release Checklist.md delete mode 100644 docs/87 - Maintainer - Release Readiness.md delete mode 100644 docs/90 - API - Getting Started.md delete mode 100644 docs/91 - API - Terrain.md delete mode 100644 docs/92 - API - World Events.md delete mode 100644 docs/93 - API - Tree Feller.md delete mode 100644 docs/94 - API - Modded.md diff --git a/README.md b/README.md index 80556e07d..4a37cc229 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ studio authoring workflow. The same engine runs as a Bukkit-family plugin and as or NeoForge server mod. Cross-platform generation targets deterministic parity for identical artifacts, pack bytes, seeds, and test areas. The master branch targets Minecraft 26.2. -# [Support](https://discord.gg/3xxPTpT) **|** [Documentation](docs/00%20-%20Overview.md) **|** [Git](https://github.com/IrisDimensions) +# [Support](https://discord.gg/3xxPTpT) **|** [Documentation](https://github.com/VolmitSoftware/docs/blob/master/iris/00-overview.md) **|** [Git](https://github.com/IrisDimensions) -Authoritative docs live in [`docs/`](docs/00%20-%20Overview.md) (flat numbered files). Hosted external docs are not authority. +Authoritative docs live in the central [VolmitSoftware/docs](https://github.com/VolmitSoftware/docs/tree/master/iris) repository, which is the source for the hosted wiki. Consider supporting development by buying Iris on Spigot. @@ -50,8 +50,8 @@ is identical on every platform. | Mob spawning, including mod mobs | Yes | Biome spawn tables are merged with the vanilla derivative's | With `importedFeatures` off - the default - chunk output is byte-for-byte what Iris has always -produced. See [`docs/94 - API - Modded.md`](docs/94%20-%20API%20-%20Modded.md) and -[`docs/01 - Installation & Platforms.md`](docs/01%20-%20Installation%20%26%20Platforms.md) for the full control reference, including +produced. See [94 - API - Modded](https://github.com/VolmitSoftware/docs/blob/master/iris/94-api-modded.md) and +[01 - Installation & Platforms](https://github.com/VolmitSoftware/docs/blob/master/iris/01-installation-platforms.md) for the full control reference, including which `pointed_dripstone` keys the 26.2 `speleothem` rename does and does not affect. Independently of that flag, Iris custom biomes now inherit the biome tags of their vanilla @@ -270,12 +270,12 @@ composite cycle. The build and release paths do not need it. ## Documentation -Full product docs are under [`docs/`](docs/00%20-%20Overview.md). Start with: +Full product docs live in the central [VolmitSoftware/docs](https://github.com/VolmitSoftware/docs/tree/master/iris) repository. Start with: -- [Overview and index](docs/00%20-%20Overview.md) -- [Installation and platforms](docs/01%20-%20Installation%20%26%20Platforms.md) -- [Getting started](docs/02%20-%20Getting%20Started.md) -- [Structures overview](docs/18%20-%20Structures%20Overview.md) -- [API — Getting Started](docs/90%20-%20API%20-%20Getting%20Started.md) -- [Maintainer — MC version bump](docs/85%20-%20Maintainer%20-%20MC%20Version%20Bump.md) -- [Maintainer — release checklist](docs/86%20-%20Maintainer%20-%20Release%20Checklist.md) +- [Overview and index](https://github.com/VolmitSoftware/docs/blob/master/iris/00-overview.md) +- [Installation and platforms](https://github.com/VolmitSoftware/docs/blob/master/iris/01-installation-platforms.md) +- [Getting started](https://github.com/VolmitSoftware/docs/blob/master/iris/02-getting-started.md) +- [Structures overview](https://github.com/VolmitSoftware/docs/blob/master/iris/18-structures-overview.md) +- [API — Getting Started](https://github.com/VolmitSoftware/docs/blob/master/iris/90-api-getting-started.md) +- [Maintainer — MC version bump](https://github.com/VolmitSoftware/docs/blob/master/iris/85-maintainer-mc-version-bump.md) +- [Maintainer — release checklist](https://github.com/VolmitSoftware/docs/blob/master/iris/86-maintainer-release-checklist.md) diff --git a/docs/00 - Overview.md b/docs/00 - Overview.md deleted file mode 100644 index c3361c697..000000000 --- a/docs/00 - Overview.md +++ /dev/null @@ -1,162 +0,0 @@ -# 00 - Overview - -Iris is a world generation engine for Minecraft: it replaces the vanilla chunk generator with terrain, biomes, caves, structures, objects, and entities built from editable JSON packs. The same engine ships as a Bukkit-family plugin and as a Fabric, Forge, or NeoForge server mod, and generates identical chunks on all four when artifacts, pack bytes, seed, and area match. This page is the map of the documentation set: read it to find the page you actually need, then leave. This branch targets Minecraft 26.2 and requires Java 25 everywhere. - -## Who this documentation is for - -There are three audiences and the numbering reflects them. Pages `00`–`33` are for **server operators** installing Iris and **pack authors** writing dimensions, in roughly the order a newcomer needs them. Pages `85`–`87` are **maintainer** checklists for cutting a release. Pages `90`–`94` are for **Java developers** consuming the Iris API from their own plugin or mod. - -Reading the set front to back is a waste of time. Pick the outcome you want from the table below and follow only that row. - -## Choose a learning path - -| You want to | Read, in order | -|---|---| -| Get Iris running and make one world | `01 - Installation & Platforms.md` → `02 - Getting Started.md` → `31 - Operator Runbooks.md` | -| Write a pack from scratch | `05 - Concepts & Pack Layout.md` → `10 - Studio & VSCode Schemas.md` → `26 - Example - Minimal Dimension.md` | -| Shape terrain and lay out biomes | `11 - Dimensions.md` → `12 - Regions.md` → `13 - Biomes.md` → `14 - Generators & Noise.md` | -| Add caves, surface detail, and vegetation | `15 - Caves & Carving.md` → `16 - Surfaces, Decorators & Deposits.md` → `17 - Trees, Fungi, Coral, Crystals, Formations, Ruins.md` | -| Place a building or structure | `18 - Structures Overview.md` first to pick an approach, then `19 - Objects.md` + `20 - Object Placement.md` for single `.iob` objects, `21 - Jigsaw Structures.md` for multi-piece Iris structures, or `22 - Native Structures & Datapacks.md` for vanilla/datapack ones | -| Ship a pack to a production server | `25 - Pack Management.md` → `06 - Worlds & Lifecycle.md` → `07 - Pregeneration.md` → `31 - Operator Runbooks.md` | -| Make another plugin or mod work with Iris | `28 - Integrations.md` → `30 - Platform Differences.md`; if you're writing Java against Iris, start at `90 - API - Getting Started.md` | - -Each tutorial page ends with something you can observe — a world that loads, a chunk that generates, a hotload that lands. Confirm that before moving to the next page. Iris failures cascade misleadingly: a biome key you typo'd in step two shows up three systems later as a cave, decorator, or structure that "doesn't work," and you'll debug the wrong thing. - -## Platforms - -One plugin jar covers the whole Bukkit family; each mod loader gets its own jar. Pick by what your server runs, not by feature set — the generator is the same code on all of them. - -| Platform | Artifact | Minecraft | What's different | -|---|---|---|---| -| Paper / Purpur / Leaf / Canvas | plugin jar | 26.1.2 – 26.2 | Nothing; this is the reference plugin target | -| Spigot / CraftBukkit | plugin jar | 26.1.2 – 26.2 | Nothing for generation. Paper-only APIs degrade gracefully | -| Folia | plugin jar | 26.1.2 – 26.2 | Region-safe scheduling, and `/iris create` cannot build a live world at runtime — it stages the world and requires a restart. See `01 - Installation & Platforms.md` and `06 - Worlds & Lifecycle.md` | -| Fabric | mod jar | 26.2 | Server worldgen plus an optional client HUD; needs Fabric Loader 0.19.3+ and Java 25 | -| Forge | mod jar | 26.2 | Same; needs Forge 65.x (built against 26.2-65.0.4) | -| NeoForge | mod jar | 26.2 | Same; needs NeoForge 26.2.x (built against 26.2.0.12-beta) | - -The plugin registers as `Iris` with command `/iris` (aliases `/ir`, `/irs`), `folia-supported: true`, `load: STARTUP`, and `api-version: 26.1` — the low api-version is deliberate so one jar loads on both 26.1.2 and 26.2. Its descriptor declares two permissions, `iris.all` (the whole command tree) and `iris.treefeller` (survival tree felling only), both defaulting to op. Optional soft-dependencies load before Iris; Multiverse-Core is ordered *after* Iris so Multiverse sees Iris generators once they exist. Full list in `01 - Installation & Platforms.md`. - -All three mod loaders use mod id `irisworldgen`, and register `/ir` and `/irs` as command redirects the same way the plugin does. - -## Feature map - -Every feature of Iris is documented on exactly one page. Find the subject, go there. - -| Area | What it covers | Doc | -|---|---|---| -| Install and platforms | Plugin vs mod jars, data dirs, first boot, native worldgen matrix | `01 - Installation & Platforms.md` | -| First steps | Create, load, teleport, pregen, studio | `02 - Getting Started.md` | -| Configuration | `settings.json` keys, defaults, hotload | `03 - Configuration.md` | -| Commands and permissions | Full `/iris` tree, Bukkit vs modded argument style | `04 - Commands & Permissions.md` | -| Pack layout | Roots, keys, snippets, world snapshot vs studio | `05 - Concepts & Pack Layout.md` | -| Worlds | create / load / unload / remove, main world, Folia, pack copy | `06 - Worlds & Lifecycle.md` | -| Pregeneration | Jobs, cache, mantle, HUD | `07 - Pregeneration.md` | -| Localization | Locales, overrides, client lang | `08 - Localization.md` | -| PlaceholderAPI | `%iris_…%` keys and migration | `09 - PlaceholderAPI.md` | -| Studio and schemas | Studio worlds, VSCode workspace, hotload | `10 - Studio & VSCode Schemas.md` | -| Dimensions | Dimension JSON, modes, height, imports | `11 - Dimensions.md` | -| Regions | Region-level content | `12 - Regions.md` | -| Biomes | Biome JSON, layers, custom biomes, spawns | `13 - Biomes.md` | -| Generators and noise | Generators, styles, expressions, images | `14 - Generators & Noise.md` | -| Caves and carving | Cave profiles, field modules | `15 - Caves & Carving.md` | -| Surfaces | Decorators, deposits, palettes | `16 - Surfaces, Decorators & Deposits.md` | -| Procedural decoration | Trees, fungi, coral, crystals, formations, ruins | `17 - Trees, Fungi, Coral, Crystals, Formations, Ruins.md` | -| Structures overview | Objects vs jigsaw vs native | `18 - Structures Overview.md` | -| Objects | Creating and importing `.iob` | `19 - Objects.md` | -| Object placement | Placing objects in biomes and regions | `20 - Object Placement.md` | -| Jigsaw | Iris multi-piece structures | `21 - Jigsaw Structures.md` | -| Native structures | Vanilla / datapack structures on Iris | `22 - Native Structures & Datapacks.md` | -| Loot and entities | Pack entities, loot, spawners, markers | `23 - Loot, Entities, Spawners, Markers.md` | -| Pack extensions | Reusable snippets and the inactive pack-mod schema | `24 - Pack Mods & Snippets.md` | -| Pack management | Download, validate, cleanup, package, update-world | `25 - Pack Management.md` | -| Minimal pack example | Walkthrough | `26 - Example - Minimal Dimension.md` | -| Overworld example | Editing the shipping overworld | `27 - Example - Configuring Overworld.md` | -| Integrations | WorldEdit, Multiverse, Mythic, item plugins, tree feller | `28 - Integrations.md` | -| Client HUD | Client mod HUD and protocol channel | `29 - Client HUD & Protocol.md` | -| Platform matrix | Bukkit vs Fabric / Forge / NeoForge differences | `30 - Platform Differences.md` | -| Operator checks | Manual verification | `31 - Operator Runbooks.md` | -| Determinism | Goldenhash cross-platform gate | `32 - Determinism & Goldenhash.md` | -| Performance | Threads, mantle, SIMD, pregen caps | `33 - Performance Tuning.md` | -| Maintainer — MC version bump | Version bump procedure | `85 - Maintainer - MC Version Bump.md` | -| Maintainer — release | Release steps | `86 - Maintainer - Release Checklist.md` | -| Maintainer — readiness | Living readiness tracker | `87 - Maintainer - Release Readiness.md` | -| API — setup | Bukkit public API dependency | `90 - API - Getting Started.md` | -| API — terrain | Terrain query service | `91 - API - Terrain.md` | -| API — events | Engine and pregen events | `92 - API - World Events.md` | -| API — tree feller | Tree feller service | `93 - API - Tree Feller.md` | -| API — modded | Modded public API (`art.arcane.iris.modded.api`) | `94 - API - Modded.md` | - -## Content model - -Six terms carry most of the documentation. Learn them here and the rest of the set reads much faster. - -| Term | What it is | -|---|---| -| Pack | A folder of JSON and `.iob` files under `packs//`. It needs at least one `dimensions/*.json` to count as a pack at all — a folder without one is treated as absent and will be re-downloaded | -| Dimension | The root config for one world type: height range, generation modes, which regions it uses, what native content it imports. One dimension file is one world's ruleset | -| Region / biome / generator | The authoring units under a dimension. Regions divide the map, biomes fill regions, generators produce the actual heightmap noise | -| Object / structure | Placed content. An object is a single saved build (`.iob`); a structure is either an Iris jigsaw of several objects, or a vanilla/datapack/mod structure Iris allows through | -| Studio | A throwaway authoring world that reads the live pack folder and hotloads your edits into new chunks. Deleted when you close it, and any leftovers are purged at startup | -| World pack snapshot | A production world copies the pack into `/iris/pack` at creation and reads only that copy forever after. This is the single most common source of "my edits did nothing" — see `05 - Concepts & Pack Layout.md` | - -## Project layout - -Relevant if you're building Iris or filing a bug against a specific subsystem. - -| Path | Role | -|---|---| -| `core/` | The engine: pack loader, generation pipeline, pregen, studio services, localization catalogs. Pure JVM, no platform types | -| `core/agent/` | Java instrumentation agent (premain/agent-class jar) consumed by the core build | -| `spi/` | The platform contract (`IrisPlatform`, protocol types) that lets `core/` stay platform-free | -| `adapters/bukkit/plugin/` | Bukkit plugin main class, the Director command tree, the public Bukkit API, and both plugin descriptors | -| `adapters/bukkit/nms/v26_2_R1/` | NMS bindings for the current Minecraft line | -| `adapters/minecraft-common/` | Source shared by the Bukkit and mod-loader adapters | -| `adapters/modded-common/` | Source shared by Fabric, Forge, and NeoForge: worldgen hooks, Brigadier commands, services | -| `adapters/client-common/` | Client-dist source: HUD, keybinds, world-type screens | -| `adapters/fabric/`, `adapters/forge/`, `adapters/neoforge/` | The three loader builds. Each is a standalone Gradle build with its own `settings.gradle` | -| `probe/` | Offline tooling and a stub platform for running the engine without a server | -| `buildSrc/` | Gradle helpers: artifact verification, NMS bindings, API generation | -| `dist/` | Where `buildAllToOut` drops the finished consumer jars | -| `docs/` | This documentation set, which is the authority over any hosted copy | - -`minecraft-common`, `modded-common`, and `client-common` are source trees only — they have no `build.gradle` and are not Gradle projects. The loader builds pull them in as extra source directories. - -## Developer build check - -Set `JAVA_HOME` to a JDK 25, then from the Iris root: - -```text -java -version -./gradlew build -./gradlew buildAllToOut -``` - -The check passes when `build` finishes with no failed tasks and `buildAllToOut` leaves one current jar per platform in `dist/`. `build` already runs the tests, so use `./gradlew test` only when you want to rerun tests without reassembling every artifact. - -At version `4.0.0-26.2` the four jars are named like this — the CraftBukkit one carries the supported Minecraft *range*, the loader jars carry `+`: - -```text -Iris v4.0.0-26.2 [CraftBukkit] 26.1.2-26.2.jar -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 -``` - -Build one platform at a time with `./gradlew buildBukkit`, `buildFabric`, `buildForge`, or `buildNeoforge`. The SPI jar comes from `./gradlew :spi:jar` and lands in `spi/build/libs/`. - -To iterate on a loader adapter, drive it from its own project root — these are separate Gradle builds, so a root-level invocation won't reach them: - -```text -./gradlew -p adapters/fabric runServer -./gradlew -p adapters/forge runServer -./gradlew -p adapters/neoforge runServer -``` - -`-PincludeModdedAdapters=true` surfaces those builds in the root composite for IDE import. It's off by default because each adapter includes the root build back for `core`/`spi` substitution, which closes a composite cycle. - -A passing Bukkit jar proves nothing about the loaders. Loom, ForgeGradle, and ModDevGradle each fail in their own ways, so if a loader build breaks while the root build is green, rerun that adapter from its own root and fix its first error rather than re-running the root build. - -The current version lives in `gradle.properties` as `irisVersion=4.0.0-26.2`. - -Next: install Iris with `01 - Installation & Platforms.md`. diff --git a/docs/01 - Installation & Platforms.md b/docs/01 - Installation & Platforms.md deleted file mode 100644 index baabbc661..000000000 --- a/docs/01 - Installation & Platforms.md +++ /dev/null @@ -1,247 +0,0 @@ -# 01 - Installation & Platforms - -Iris ships as one Bukkit-family plugin jar and three self-contained mod jars (Fabric, Forge, NeoForge). This page gets the right artifact onto your server, tells you how to prove the install actually worked, and documents where Iris puts its files on each platform. Java 25 is required everywhere. On first boot Iris downloads the managed `overworld` and `underworld` beta packs if they aren't already present. - -Read this before `02 - Getting Started.md`. If Iris is already installed and you just want a world, skip ahead. - -## What a good install looks like - -Whichever path you take, you're done when all three of these are true: - -1. Iris reached its enabled/ready state with no exception in the startup log. -2. The data directory has a `settings.json` and loadable `packs/overworld/` and `packs/underworld/` directories. -3. `/iris` prints help from the server console. - -On a modded client the Iris keybind category showing up is a nice extra signal, but it proves the client mod loaded — not that the server can generate chunks. Always check server-side. - -Keep the old jar and the entire Iris data directory until the new build passes these checks. Swapping the binary does **not** update pack snapshots already copied into existing worlds; that's a separate operation covered in `06 - Worlds & Lifecycle.md`. - -## Requirements - -| Requirement | Value | -|---|---| -| Java | 25. The mod jars declare `java >= 25` and refuse to load on anything older | -| Minecraft (plugin) | 26.1.2 – 26.2. One jar covers both; `api-version` is pinned to 26.1 so it loads on the older line too | -| Minecraft (mod) | 26.2 only | -| Fabric Loader | 0.19.3+ | -| Forge | 65.x (built and tested against 26.2-65.0.4) | -| NeoForge | 26.2.x (built and tested against 26.2.0.12-beta) | -| Network | Outbound HTTPS to `github.com` on first boot, to fetch the IrisDimensions Overworld and Underworld beta release assets | - -Before replacing an existing installation: - -1. Run `java -version` **on the server** and confirm it reports 25. Having Java 25 installed somewhere on the box is not the same as the server process using it. -2. Match the jar label to the platform and Minecraft version you're running. -3. Stop the server cleanly. -4. Back up the Iris jar/mod, the Iris data directory, and every Iris world you intend to keep. - -Never put two Iris platform jars in the same `plugins/` or `mods/` folder. That fails in confusing ways rather than picking a winner. - -## Plugin install (Paper / Purpur / Leaf / Canvas / Folia / Spigot) - -1. Drop the CraftBukkit-labelled plugin jar into `plugins/`. -2. Start the server. Iris loads at `STARTUP`, before worlds are created, because it has to register generators first. -3. First boot writes `plugins/Iris/settings.json` with defaults if it's absent, and provisions `overworld` and `underworld` into `plugins/Iris/packs/` from their IrisDimensions `beta` release ZIPs when missing. - -Then verify from the server console: - -```text -/iris version -/iris pack validate pack=overworld -/iris pack validate pack=underworld -``` - -`/iris version` prints exactly one line — `Iris v by Volmit Software`. That's the whole output; it does not report platform or Minecraft version, so use it only as a "the command tree is alive" check. Each `pack validate` must resolve the downloaded pack and finish with no blocking errors. - -Pass an explicit pack name. `/iris pack validate` with no argument fails with a missing-argument error rather than validating everything, because the parameter is required. To validate every installed pack, pass the key with an empty value: `/iris pack validate pack=`. - -A command responding is not proof the generator can produce chunks. Finish with the disposable-world walkthrough in `02 - Getting Started.md`. - -### Startup validation gates login - -Iris blocks player login until external datapack validation and dimension-pack validation both complete. The kick message names the reason and tells you to check the console. - -The two gates behave differently. A failed or restart-pending **external datapack** state keeps login locked and blocks all Iris world creation until you fix it and restart — Iris tells you when a restart is what's required. Unchanged, already-validated datapacks and packs reuse their persisted results, so this costs nothing on a normal boot. A **dimension pack** with blocking errors does *not* lock the server: that one pack is refused for world and Studio creation, an error listing the reasons is printed at startup, and every healthy pack stays usable. - -### Permissions - -The descriptor declares two permissions, both defaulting to op: - -| Permission | Grants | -|---|---| -| `iris.all` | The entire `/iris` command tree — worlds, studio, pregen, packs, developer tools | -| `iris.treefeller` | Survival tree felling with an axe. Nothing else | - -There is exactly one permission check, at the command root, against `iris.all`. Subcommands do not derive their own permission nodes — there is no `iris.all.pregen`. If a non-op needs any Iris command, they need `iris.all`, and that is all of it. See `04 - Commands & Permissions.md`. - -Command root is `/iris`, aliases `/ir` and `/irs`. - -### Soft dependencies - -None of these are bundled or required. When present they load before Iris so Iris can see them: PlaceholderAPI, CraftEngine, Nexo, ItemsAdder, SCore, ExecutableItems, MythicLib, MMOItems, eco, EcoItems, MythicMobs, MythicCrucible, KGenerators, WorldEdit. Multiverse-Core is deliberately ordered *after* Iris so that Multiverse sees Iris generators once they're registered. Integration details in `28 - Integrations.md`. - -### Folia - -`folia-supported: true`, and engine work uses region-safe scheduling. The one behavioral difference that matters at install time: `/iris create` cannot build a live world at runtime on Folia. Instead it stages the world files, installs the pack snapshot, registers the world in `bukkit.yml`, and prints a message telling you to restart. After the restart the world generates and loads on its own from that `bukkit.yml` entry — you do not need to run `/iris load`. See `06 - Worlds & Lifecycle.md`. - -### If the first-boot download fails - -Fix network access and restart. Do not create an empty folder named `overworld` to silence the error: a folder without a `dimensions/*.json` inside it is treated as absent (and re-downloaded), and a partial pack is not a usable dimension. To install by hand later, use `/iris download overworld` and `/iris download underworld`. - -Before you create a world you care about, run the Bukkit fresh-install runbook in `31 - Operator Runbooks.md`. - -## Mod install (Fabric / Forge / NeoForge) - -1. Drop the matching mod jar into `mods/`. -2. Start the dedicated server, or a client if you want singleplayer. -3. The jar is self-contained — engine, SPI, and the required Fabric API modules are bundled. Mod id is `irisworldgen` on all three loaders. -4. On first boot, if `autoDownloadDefaultPack` is true in `config/irisworldgen/modded.json` (it is by default), a daemon thread installs the managed `overworld` and `underworld` beta packs when missing, then a configured non-managed `defaultPack` if you've set one. Only after that does it write the forced worldgen datapack. - -Verify server-side: - -```text -/iris version -/iris pack validate overworld -/iris pack validate underworld -``` - -Modded `/iris version` prints more than the Bukkit one — mod version, platform, Minecraft version, and the count of loaded Iris dimensions. The install passes when that line looks right, both managed pack directories contain their primary dimension JSON, and validation reports no blocking errors. - -### Restart once after installing a pack - -This is the modded-specific gotcha. Packs register their custom dimension types (which set the world height range) and their custom biomes through the forced datapack, and that datapack is read when the server builds its registries at start. A pack installed during *this* boot may land after registries are already built. - -So: **restart once after any pack is installed, before creating a world with it.** Worlds created before that restart run with fallback heights and will not have the pack's real height range or custom biomes. If a pack or its generated dimension-type datapack was installed during the current boot, restart before continuing. - -### Singleplayer on a modded client - -Installed Iris packs show up as selectable World Types on the Create New World screen, named `IRIS:` (or `IRIS: / ` when a pack exposes more than one dimension). The integrated server runs the same engine as a dedicated one. - -### Client HUD - -Installing the mod jar on a client adds a pregeneration HUD showing a progress bar, chunks done and total, percent, chunks per second, and ETA, turning yellow while paused. `H` toggles it. The keybind category is "Iris" and also holds `M` (Iris Vision Map) and `J` (Iris What overlay); all three are rebindable. Details in `29 - Client HUD & Protocol.md`. - -The HUD talks to both modded Iris servers and Bukkit/Paper Iris over channel `irisworldgen:main` — custom payloads on modded, plugin messaging on Bukkit. Vanilla clients are unaffected and get the server-side boss bar instead. On a non-Iris server the client mod is inert. - -## Data directories - -### Plugin - -| Path | What lives there | -|---|---| -| `plugins/Iris/settings.json` | Engine settings. Written with defaults if absent, and rewritten on every read, so keys added by a new Iris version appear automatically with their defaults and your edits survive | -| `plugins/Iris/packs//` | Installed packs. This is the live tree the Studio reads and edits | -| `plugins/Iris/bootstrap/` | First-boot provisioning marker (`provisioned.properties`) recording what was installed and against which compiler identity | -| `plugins/Iris/datapacks/` | External datapack imports pulled from Modrinth by `/iris datapack`, plus a `staging/` subfolder used mid-download | -| `plugins/Iris/languages/overrides/.json` | Optional server message overrides. See `08 - Localization.md` | -| `/datapacks/iris/` | The aggregate worldgen datapack Iris compiles from your installed packs. Iris owns this; do not hand-edit it | -| `/dimensions///` | Storage for a managed Iris world. Namespace is `iris` for worlds Iris creates | -| `/iris/pack/` | The per-world pack **snapshot**. A production engine reads only this copy, never `plugins/Iris/packs/` | - -`` is the server's level directory — the folder named by `level-name` in `server.properties`. - -That last row is worth internalizing early: editing `plugins/Iris/packs/overworld/` has no effect on a world that already exists, because that world froze a copy of the pack at creation time. See `05 - Concepts & Pack Layout.md`. - -### Mod - -Paths are relative to the game instance's `config/` directory. - -| Path | What lives there | -|---|---| -| `config/irisworldgen/packs//` | Installed packs. A pack counts as installed when `dimensions/.json` exists | -| `config/irisworldgen/generated/datapack/iris/` | The generated forced datapack (datapack id `iris_worldgen`), plus a hash sidecar used to detect staleness. Iris owns this; do not edit it | -| `config/irisworldgen/modded.json` | Mod-side config: default pack, auto-download, primary-world routing, main-world override | -| `config/iris/` | Engine data directory — `settings.json` and per-world engine state | - -Two different roots, and mixing them up is a common mistake. Packs, the generated datapack, and mod config live under `config/irisworldgen/`. The shared engine's own data lives under `config/iris/`. - -### `modded.json` - -Written with these defaults on first read. If the file is unparseable Iris logs an error and falls back to defaults without rewriting it, so a syntax error is silent apart from the log line — check the log if a setting seems ignored. - -| Key | Default | What it does | -|---|---|---| -| `defaultPack` | `overworld` | The pack `/iris create` uses when you don't name one, and the extra pack auto-download will fetch beyond the two managed betas | -| `autoDownloadDefaultPack` | `true` | Whether boot installs missing packs at all. Set false for air-gapped servers where you place pack folders by hand | -| `primaryWorld` | `""` | Dimension id players get routed into. Empty means no routing. Set by `/iris world replace-overworld` rather than by hand | -| `routePlayersToPrimaryWorld` | `true` | Whether the routing above actually happens. Set false to keep a primary world configured but stop moving players into it | -| `mainWorldPack` | `""` | Pack whose generator replaces the vanilla main world. Empty means the vanilla overworld is untouched | -| `mainWorldSeed` | `0` | Seed for that main-world override | -| `mainWorldAutoRestart` | `false` | When true, `/iris world mainworld` halts the server immediately so the override takes effect. Leave false unless you have a supervisor that restarts the process | - -Only `/iris world replace-overworld`, `/iris world disable|delete` on the primary, and `/iris world mainworld` rewrite this file at runtime. - -## Settings that affect install and first world - -`IrisSettings` is shared across every platform; only the file location differs. The three keys most likely to matter before your first world: - -| Key path | Default | When you'd change it | -|---|---|---| -| `generator.defaultWorldType` | `overworld` | Bukkit `/iris create` resolves `type=default` through this. Point it at your own pack so plain `/iris create ` produces your world instead of the stock overworld | -| `general.language` | `en_US` | Server-side message locale. See `08 - Localization.md` | -| `studio.openVSCode` | `true` | Set false on a headless box so `/iris studio vscode` writes the workspace file without trying to launch an editor | - -Full key list: `03 - Configuration.md`. - -## First-boot pack download - -| Platform | What happens | -|---|---| -| Plugin | `DefaultPackBootstrapProvisioner` installs the Overworld and Underworld beta release assets into `packs/overworld` and `packs/underworld` independently, then compiles the aggregate datapack once | -| Mod | If `autoDownloadDefaultPack` is on, a daemon thread installs both managed beta packs plus any distinct configured `defaultPack` into `config/irisworldgen/packs`, then regenerates the forced datapack | - -Manual install is `/iris download ` (alias `dl`). The two managed packs are special-cased: `overworld` and `underworld` always come from their pinned beta release assets and **ignore the `branch` argument entirely**. Any other pack resolves as `IrisDimensions//`, with `branch` defaulting to `stable`. - -The managed-pack match is case-sensitive, so `/iris download Overworld` misses the special case and tries `IrisDimensions/Overworld/stable` instead. Use lowercase. - -One more branch inconsistency to be aware of on modded: when `/iris create` auto-downloads a pack that isn't installed, and when boot fetches a non-managed `defaultPack`, both use the `master` branch — not `stable`. Only the explicit `/iris download` command defaults to `stable`. Pin the branch explicitly if it matters. See `25 - Pack Management.md`. - -## When the install goes wrong - -| Symptom | Likely cause | Fix | -|---|---|---| -| `/iris version` does nothing | Wrong directory, wrong platform jar, a duplicate Iris jar, Java below 25, or an exception during enable | Stop the server, leave exactly one matching artifact in place, confirm Java 25, then fix the **first** Iris exception in the startup log — later ones are usually fallout | -| `settings.json` exists but a managed pack is missing | The beta download failed or is still running | Restore outbound HTTPS or drop in the complete release pack, then restart. Do not create an empty pack folder to paper over it | -| Players are kicked at login with an Iris message | Startup validation hasn't passed | Read the reason in the kick text and the console. External datapack failures lock login; fix the datapack state and restart | -| Pack validates, but modded heights and biomes are wrong | The forced datapack was generated after registries had already loaded | Restart once with the pack already on disk, then create a fresh disposable world to confirm | -| A non-op can't run any Iris command | `iris.all` isn't granted | Grant `iris.all`. `iris.treefeller` only covers survival tree felling and grants no commands | -| Client HUD missing but server commands work | Client mod absent, keybind unbound, or capability not negotiated | Install the matching client mod, reconnect, check the Iris keybind category. Server-side generation never depends on the client HUD | -| An existing world ignores a newly installed pack | The world is reading its frozen snapshot | Use the explicit snapshot update or create a new world — `06 - Worlds & Lifecycle.md` and `25 - Pack Management.md` | - -## Native worldgen over Iris terrain - -Iris replaces the chunk generator outright, so vanilla and mod worldgen only runs where Iris explicitly runs it. This behaves the same on every platform. - -| Vanilla / mod worldgen | Runs over Iris terrain? | Control | -|---|---|---| -| Structures (vanilla, datapack, mod) | Yes, on by default | Deny families with `importedStructures.disabled`, or one complete key with `importedStructures.disabledExact` | -| Placed features: ores, trees, plants, springs, geodes | Yes, but **off by default** | `importedFeatures.enabled` per dimension, with per-step (`steps` / `disabledSteps`) and per-key (`disabled`) filters | -| Carvers (caves, canyons, mod carvers) | Never | There's no `NoiseGeneratorSettings` for a carver to sample against. Use the pack's `caves` and `carvings` instead | -| Surface builders and surface rules | Never | Iris builds surfaces from pack palettes | -| Mod biomes | Only as a `derivative`, `vanillaDerivative`, `biomeScatter`, or `biomeSkyScatter` target | Iris always picks the biome from the pack | -| Mob spawning, including mod mobs | Yes | Biome spawn tables are merged with the vanilla derivative's | - -With `importedFeatures` off — the default — chunk output is pure Iris. The full control reference is `94 - API - Modded.md`, which also applies conceptually on Bukkit for the imported native stages. - -Separately from that flag, Iris custom biomes inherit the biome tags of their vanilla derivative on every platform. Tag-driven content such as `#minecraft:is_overworld` and mod spawn rules therefore applies to Iris custom biomes without any extra configuration. - -## Build artifacts - -If you're building rather than downloading, from the repo root with JDK 25: - -```text -./gradlew buildAllToOut -``` - -Four jars land in `dist/`. The CraftBukkit jar's version token is the supported Minecraft *range*; the loader jars use `+`: - -| Pattern | Platform | -|---|---| -| `Iris v [CraftBukkit] 26.1.2-26.2.jar` | Plugin (whole Bukkit family, including Folia) | -| `Iris v [Fabric] 26.2+.jar` | Fabric | -| `Iris v [Forge] 26.2+.jar` | Forge | -| `Iris v [NeoForge] 26.2+.jar` | NeoForge | - -Per-platform build tasks and the developer build gate are in `00 - Overview.md`. - -Next: create a world and open the Studio in `02 - Getting Started.md`. Every settings key is in `03 - Configuration.md`. diff --git a/docs/02 - Getting Started.md b/docs/02 - Getting Started.md deleted file mode 100644 index 30e069605..000000000 --- a/docs/02 - Getting Started.md +++ /dev/null @@ -1,307 +0,0 @@ -# 02 - Getting Started - -This page takes you from a working Iris install to a world you can stand in, a small pregenerated area, and an open Studio session for editing packs. It's written for whoever is running the server, and assumes nothing about pack authoring. Command syntax differs between the plugin and the mods, so each step gives both forms. - -Full command trees and permissions: `04 - Commands & Permissions.md`. World lifecycle detail: `06 - Worlds & Lifecycle.md`. Studio detail: `10 - Studio & VSCode Schemas.md`. - -## What you'll end up with - -One disposable Iris world built from the `overworld` pack, entered and generating chunks, with roughly a 45×45-chunk area pregenerated, plus a separate Studio session pointed at the live pack. Use seed `1337` throughout. Changing seeds while you're still diagnosing something makes every comparison meaningless. - -Work through the sections in order and confirm each one before moving on. Confirm the world loaded before teleporting; confirm ordinary chunks generate around you before starting a pregen; confirm the Studio world is genuinely separate from your production world before editing files. Skipping a check doesn't save time here, because Iris failures surface late and in the wrong place. - -## Prerequisites - -- Iris installed and verified per `01 - Installation & Platforms.md` -- A Java 25 server or mod instance running -- Operator access on Bukkit (the `iris.all` permission), or permission level 2 / gamemaster on modded for anything that mutates state -- The managed `overworld` and `underworld` packs present, or your own pack installed under the platform's packs directory - -## The one syntax rule that trips everyone up - -On the plugin, Iris uses the Director command framework, and it has a hard rule: **only required parameters accept a bare positional value. Every optional parameter must be given as `key=value`.** A leftover positional token isn't ignored — it's an error, and the command fails. - -```text -/iris create myworld type=overworld seed=1337 correct -/iris create myworld overworld 1337 fails: unexpected argument -``` - -Parameters marked contextual (like `world` on pregen, which normally comes from where you're standing) also never take a positional, so name them with `key=` when you need to override them. - -Modded is Brigadier and works the way you'd expect: everything is positional, in order, and pregen options are literal flag words. - -| Platform | Required args | Optional args | Example | -|---|---|---|---| -| Plugin (Bukkit) | Positional, in declaration order | Must be `key=value` | `/iris create myworld type=overworld seed=1337` | -| Mod (Fabric / Forge / NeoForge) | Positional | Further positional tokens or literal flags | `/iris create myworld overworld 1337` | - -Director also matches command and parameter names fuzzily, so shortenings and near-misses often resolve. That's convenient but don't rely on it in scripts — write the real names. - -## 1. Create a world - -### Plugin - -```text -/iris create [type=…] [seed=…] [main=true|false] [overwrite=true|false] -``` - -| Parameter | Aliases | Default | What it does | -|---|---|---|---| -| `name` | `world-name` | required | The world name. The only parameter that takes a positional value | -| `type` | `dimension`, `pack` | `default` | Which pack/dimension to generate. The literal `default` is resolved at runtime through `generator.defaultWorldType` (stock value `overworld`), so it follows your config rather than being hardcoded | -| `seed` | — | `1337` | World seed | -| `main` | `main-world` | `false` | Promote this world to the server's main world. See below — it does not take effect until a restart | -| `overwrite` | `force` | `false` | Replace an existing world in place instead of creating a new one. This is a staged, restart-to-publish operation, not something to reach for casually | - -The command itself has alias `c`. - -`main=true` on a non-Folia server registers a JVM shutdown hook. On shutdown it copies `data/`, `datapacks/`, and `players/` from the current level root along with the Iris dimension folder into a new level directory, then rewrites `server.properties` with the new `level-name` and `level-seed`. Nothing changes while the server is up. On Folia the promotion is applied inline during staging instead, and rolls back the `bukkit.yml` entry and the staged folder if it fails. - -**Names Iris refuses.** `iris` and `benchmark` are rejected outright (case-insensitive) and Iris suggests something like `irisworld`. Before those checks, the name also has to be a safe single path segment matching `[a-z0-9_-]`, so anything containing `/`, `\`, or `..` is rejected, as is any name that would collide with a vanilla world slot — your server's `level-name`, `_nether`, or `_the_end`. Those produce a different message about only Iris-managed worlds being changeable. - -**Already exists.** Without `overwrite=true`, create aborts if the managed dimension folder is already there. That folder lives at `/dimensions/iris/`, not next to your server jar. - -**Folia.** Runtime creation is disabled. Iris stages the world files, installs the pack snapshot, registers the world in `bukkit.yml`, and tells you to restart. After the restart the world generates and loads on its own from that registration — you don't need `/iris load`. - -**Everything else.** Create builds the world immediately through `IrisToolbelt.createWorld()`, as a production world (not a studio world). - -```text -/iris create myworld type=overworld seed=1337 -``` - -Now run `/iris worlds` (alias `accesslist`). It prints two lists — Iris worlds and plain Bukkit worlds. On a non-Folia server `myworld` must appear under Iris worlds. On Folia, success is the staging-and-restart message; restart before continuing. - -### Mod - -```text -/iris create [pack] [seed] -``` - -| Parameter | Default | What it does | -|---|---|---| -| `name` | required | Dimension id. A bare name is normalized into the `irisworldgen` namespace, so `myworld` becomes `irisworldgen:myworld` | -| `pack` | `overworld` | Pack key. Use the `pack:dimension` form when the pack's dimension key differs from its name | -| `seed` | `1337` | Long seed | - -Alias `c`. You can't pass `seed` without also passing `pack`. - -The `pack:dimension` form has to be **quoted** — `"overworld:overworld"` — because Brigadier's unquoted string type doesn't accept a colon. Iris's own help text says the same thing. - -If the pack isn't installed, create prints a message, downloads `IrisDimensions/` on a background thread (from the `master` branch), then injects the dimension. Once that succeeds the dimension is live and gets re-injected on later startups. - -```text -/iris create myworld overworld 1337 -``` - -There's no separate load step on modded. The same world management also lives under `/iris world create|enable`, where `create` is simply an alias of `enable`. That form requires the pack argument and has no `overworld` default. - -Confirm with `/iris world status`, which lists each loaded Iris level with its pack and dimension key. Then run `/iris info` to check the seed. `/iris info` takes an optional greedy string that acts as a **substring filter** across dimension id, generator identity, and pack key — not a dimension selector — so `/iris info myworld` narrows the listing. The seed is only printed to gamemasters; at lower permission levels the line simply omits it. - -## 2. Load a world (plugin only) - -```text -/iris load -``` - -The real command node is `loadWorld`, with alias `import`; `/iris load` reaches it through fuzzy matching. It requires the managed dimension directory to already exist on disk, then loads through `BukkitWorldReconciler` and registers the world with the server. - -This one is **player-origin only** — the console cannot run it. On a headless server, load worlds by having them registered in `bukkit.yml` (which create already does) and restarting, or run it as a player. - -Modded worlds created with `/iris create` or `/iris world enable` are already injected. Teleport instead. - -## 3. Teleport - -### Plugin - -```text -/iris teleport [player=…] -``` - -Alias `tp`. The world is positional; the player is optional and therefore keyed — `/iris tp myworld player=Notch`. Left out, it targets whoever ran the command, so console needs to name a player explicitly or it reports that the player doesn't exist. The teleport itself is performed asynchronously where the platform allows it. - -```text -/iris tp myworld -``` - -You've passed this gate when the teleport completes and chunks generate normally around spawn. If the teleport target doesn't exist, go back to the create/load step — don't push on to pregen. - -### Mod - -```text -/iris teleport [player] -/iris tp [player] -``` - -Dimension is a loaded-level argument and tab-completes Iris dimensions; a non-Iris dimension is rejected. Console must name a player. You land at x=8.5, z=8.5 in that dimension, at the `MOTION_BLOCKING` height for that column, and Iris force-loads the chunk with a ticket if it isn't loaded yet. - -```text -/iris tp irisworldgen:myworld -``` - -Success is entry into `irisworldgen:myworld` with `/iris info myworld` still reporting the pack and seed you expect. - -## 4. Pregenerate - -Radius is in **blocks**, measured from the center outward, and one pregeneration job runs server-wide at a time. - -The block radius is converted to an inclusive chunk box, so a 352-block radius at `0,0` covers chunks -22 through 22 on both axes: **45 × 45 = 2,025 chunks**, or 720 × 720 blocks of actual generated area. The command's own feedback describes the request as 704 × 704 blocks (radius × 2); the extra chunk on each edge is the inclusive rounding. Pick your radius knowing that chunk count, not the block number, is what determines how long this takes. - -### Plugin - -```text -/iris pregen start [world=…] [center=x,z|me] [gui=true|false] [serial=true|false] -``` - -| Parameter | Aliases | Default | What it does | -|---|---|---|---| -| `radius` | `size` | required | Radius in blocks; must be greater than 0. The only positional parameter | -| `world` | — | your current world | Target world. Contextual, so it must be keyed when you override it — typically when running from console | -| `center` | `middle` | `0,0` | Center point. `me` uses the running player's position | -| `gui` | — | `true` | Open the pregen progress window. Set false on a headless server | -| `serial` | — | `false` | Generate one chunk at a time. Much slower, but the safe option when parallel generation is destabilizing the server. Requires a Paper-compatible server | - -```text -/iris pregen start 352 world=myworld center=0,0 gui=false -``` - -Immediately run `/iris pregen status`. It should report a 2,025-chunk job that advances without a growing failure count. - -Control it with `/iris pregen stop` (alias `x`), `/iris pregen pause`, and `/iris pregen status`. **`resume` is an alias of `pause`, and `pause` is a toggle** — there's no distinct resume command, so running either one on a paused job resumes it and on a running job pauses it. - -### Mod - -```text -/iris pregen start [dimension] [at ] [gui] [sync] [nocache] -``` - -| Piece | What it does | -|---|---| -| `radius` | Blocks, 1–100000 | -| `dimension` | Optional target level; defaults to the dimension you're in | -| `at x z` | Optional center; defaults to 0, 0 | -| `gui` | Ask for the progress map window on the server display. Silently ignored when no GUI can be launched | -| `sync` | Synchronous chunk writes | -| `nocache` | Disable the resumable checkpoint cache. Caching is on by default, which is what lets a stopped job pick up where it left off | - -The three flags are combinable in any order and each may appear once, but **`at ` must come before any flag.** `/iris pregen start 100 gui at 0 0` is a syntax error; `/iris pregen start 100 at 0 0 gui` is fine. - -```text -/iris pregen start 352 irisworldgen:myworld at 0 0 sync -``` - -Run `/iris pregen status` right away and confirm the target dimension, total, and generated count. Stop before retrying with different flags. As on Bukkit, `pause` and `resume` are the same toggle. Progress shows in the client mod HUD when present, otherwise a boss bar or the console. - -## 5. Open a Studio - -Studio worlds are transient. They're discarded when you close them and any leftovers are purged at startup. Crucially, a Studio world reads the **live** pack directory and hotloads JSON and object edits into newly generated chunks. Production worlds never do this — they read the frozen snapshot copied into the world at creation. That difference is the reason Studio exists, and the reason pack edits appear to do nothing on a production world. - -### Plugin - -```text -/iris studio create [name=…] [template=…] -/iris studio open [seed=…] -/iris studio vscode [dimension=…] -/iris studio close -``` - -| Command | Aliases | Notes | -|---|---|---| -| `create` | `+` | Both parameters are optional, so **neither takes a positional value** — use `name=mypack`. With no template it scaffolds a minimal starter pack (`dimensions/`, `regions/`, `biomes/`, `generators/` with one of each). With a template it copies an existing packs entry, downloading it if needed | -| `open` | `o` | Opens a temporary studio world for a pack. `dimension` is required and positional; `seed` is optional (alias `s`) and defaults to `1337` | -| `vscode` | `vsc` | Writes and opens a `.code-workspace` with live registry schemas. `dimension` is optional, so keyed only, and defaults to `default` | -| `close` | `x` | Discards the studio world | - -The studio group itself has aliases `std` and `s`. Default create name is `studio`; if a project by that name already exists, Iris picks the next free name rather than failing. - -```text -/iris studio open overworld seed=1337 -/iris studio vscode dimension=overworld -``` - -### Mod - -```text -/iris studio create [name] [template] -/iris studio open [seed] -/iris studio vscode [pack] -/iris studio update [pack] -/iris studio close -``` - -| Command | Aliases | Notes | -|---|---|---| -| `create` | `+` | Name defaults to `studio`, template defaults to **`example`**. This differs from the plugin, where omitting the template scaffolds a starter pack instead. The template pack is auto-downloaded (from `master`) if missing | -| `open` | `o` | Pack is required; seed defaults to `1337` | -| `vscode` | `vsc` | Writes the workspace and opens it | -| `update` | — | Regenerates the workspace schemas without opening anything | -| `close` | `x` | Discards the studio world | - -Group aliases are `std` and `s`. - -```text -/iris studio open overworld 1337 -/iris studio vscode overworld -``` - -A number of Bukkit studio and content tools deliberately refuse on modded and print an explanatory message rather than half-working: `importvanilla` (`importv`, `iv`), `loot`, `profile`, `spawn`/`summon`, `objects`/`find-objects`, the object `we`, `studio`, and `convert` subcommands, structure `import`/`import-all`/`reimport`, and datapack `ingest`/`pull`/`remove`. Do that work on a Bukkit server and copy the pack folder across. - -The Studio gate passes when the transient world opens, the workspace points at the live `packs/overworld/` tree, and saving a valid JSON change produces a hotload result in-game. Close it with `/iris studio close` and confirm your production `myworld` is still there and unaffected. - -## The whole first session - -1. Confirm the pack: `overworld` (or yours) exists under the platform packs directory. -2. Create the world using the form for your platform. -3. On Folia only: restart after the staging message. The world comes back on its own. -4. Teleport in and fly around a little to confirm chunks generate. -5. Optional: `/iris pregen start 352 …` for a 45×45-chunk area. -6. Optional: `/iris studio open ` and use the VSCode schemas for block, item, and entity autocomplete — mod content is included in those schemas on mod loaders. - -The session is genuinely finished when you restart the server cleanly, the production world loads again, and it generates new chunks from its copied pack snapshot. Remove a disposable world through the lifecycle command after evacuating players, never by deleting folders — see `06 - Worlds & Lifecycle.md`. - -## Common pitfalls - -| Pitfall | What actually happens | What to do | -|---|---|---| -| Bukkit optional args passed positionally | Hard parse error, command does nothing | Write `seed=1337`, not a bare second token | -| `/iris studio create mypack` | Fails — both params are optional so neither is positional | `/iris studio create name=mypack` | -| `/iris pack validate` with no argument | Missing-argument error, not "validate everything" | Name the pack, or pass an explicitly empty `pack=` to do all of them | -| World named `iris` or `benchmark` | Create rejected | Pick another name, e.g. `irisworld` | -| Editing `packs/` after creating a production world | **No effect** on that world, ever | Production engines read `/iris/pack`. Push changes with `/iris developer update-world world= pack= confirm=true` and restart, or accept that only new chunks change. Studio reads the live pack | -| Expecting pack edits to change existing chunks | Only newly generated chunks use the new config | Fly to unexplored terrain, pregen a fresh radius, or use a Studio world | -| Folia: create then teleport immediately | The world isn't live yet | Restart after the staging message, then teleport | -| Modded: new pack's heights or biomes missing | The forced datapack wasn't applied before registries loaded | Restart once with the pack already installed | -| `/iris load` from console | Player-origin only; console can't run it | Rely on the `bukkit.yml` registration plus a restart, or run it as a player | -| `/iris load` on modded | No such subcommand | Use create or `world enable`, then teleport | -| Modded `pack:dimension` unquoted | Brigadier rejects the colon | Quote it: `"overworld:overworld"` | -| Modded pregen flags before `at x z` | Syntax error | Put `at ` before any flag | -| Starting a pregen while one is running | Start fails | `/iris pregen stop` first | -| `/iris pregen resume` expected to only resume | It's an alias of `pause`, which toggles | Check `/iris pregen status` instead of assuming | -| Studio closed mid-edit | The studio world is discarded | Your edits are on disk in `packs/` and survive. Reopen the studio | -| Managed pack download blocked | Startup, create, or studio open fails with a missing pack | Allow HTTPS or run `/iris download overworld` and `/iris download underworld`. An offline install must contain each complete pack tree | -| Relying on `type=default` | Resolves through `generator.defaultWorldType`, which someone may have changed | Name the pack explicitly: `type=overworld` | - -## Quick reference - -**Plugin** - -```text -/iris create myworld type=overworld seed=1337 -/iris tp myworld -/iris pregen start 352 world=myworld center=0,0 gui=false -/iris pregen status -/iris studio open overworld seed=1337 -/iris studio close -``` - -**Mod** - -```text -/iris create myworld overworld 1337 -/iris tp irisworldgen:myworld -/iris pregen start 352 irisworldgen:myworld at 0 0 -/iris pregen status -/iris studio open overworld 1337 -/iris studio close -``` - -Next: how packs are structured in `05 - Concepts & Pack Layout.md`, and every settings key in `03 - Configuration.md`. diff --git a/docs/03 - Configuration.md b/docs/03 - Configuration.md deleted file mode 100644 index b0d9b8c73..000000000 --- a/docs/03 - Configuration.md +++ /dev/null @@ -1,310 +0,0 @@ -# 03 - Configuration - -Iris keeps its shared runtime settings in `settings.json` under the platform data folder. On first boot Iris writes a full defaults file if one is missing, and every successful load rewrites the file so new keys appear with defaults. Bukkit adds `compat.json`; mod loaders add `modded.json`. See `01 - Installation & Platforms.md` for data paths and `33 - Performance Tuning.md` for how to measure a tuning change. - -## What you actually need to change - -The shipped defaults are correct for almost every server. Most operators only ever touch a handful of keys: - -| You want to | Change | -|---|---| -| Run the server in another language | `general.language` | -| See why generation is behaving oddly | `general.debug`, or `/iris debug` | -| Stop Iris opening desktop windows on the host | `gui.useServerLaunchedGuis`, `studio.openVSCode` | -| Survive pregen on a memory-constrained box | `pregen.maxResidentTectonicPlates`, the `performance.*CacheSize` keys | -| Turn on the survival tree feller | `treeFeller.enabled` | -| Catch broken pack keys instead of silently ignoring them | `general.strictContentKeys` | - -Everything else is either already right, only meaningful while diagnosing a specific problem, or inert on your platform. Each table below marks which is which. - -## File locations - -| Platform | Shared settings | Packs root | Platform-only config | -|----------|-----------------|------------|----------------------| -| Bukkit / Paper / Folia | `plugins/Iris/settings.json` | `plugins/Iris/packs/` | `plugins/Iris/compat.json` | -| Fabric / Forge / NeoForge | `/iris/settings.json` | `/irisworldgen/packs/` | `/irisworldgen/modded.json` | - -`` is the loader config directory (game `config/` on Fabric, Forge, and NeoForge). Both surfaces use the same `IrisSettings` schema for `settings.json`. - -The modded split is real and easy to get wrong: the engine data folder is `/iris`, but installed packs, the generated datapack, and `modded.json` live under `/irisworldgen`. Iris also creates an empty `/iris/packs` directory; that is not the pack root and putting a pack there will not load it. - -## Changing a setting safely - -1. Start Iris once so it writes the current schema and defaults. -2. Copy `settings.json` outside the server directory as a rollback file. -3. Change one key. Keep its JSON type: quoted values such as `"false"` are strings, not booleans. -4. Save the file, then run `/iris reload` or wait for the hotload poll (about 3 seconds on both platforms). -5. Confirm the console logs `Hotloaded settings.json` or the reload success message, with no parse error. -6. Exercise the affected feature. If nothing changed, check the "Takes effect" column below — several keys are captured when a service, pool, or cache is constructed and need a restart. - -If parsing fails, restore the saved file and restart. Do not delete `settings.json` unless resetting every setting to defaults is what you want. - -To change only the server locale, edit the existing `general` object in place: - -```json -{ - "general": { - "language": "de_DE" - } -} -``` - -That fragment shows the field location; do not replace a populated settings file with it. After `/iris reload`, run `/iris help` and confirm the selected locale is active. Iris rewrites the complete settings file after a successful load, including defaults for fields that were absent. - -### Validation and rollback - -| Result | Meaning | Action | -|---|---|---| -| Reload succeeds and the feature changes | The file parsed and the setting is read live | Keep the backup until the next clean restart | -| Reload succeeds but behavior is unchanged | The value was captured when a service, pool, or cache was built | Restart, then retest the same workload | -| Parse error in console, file unchanged | Gson threw before the rewrite, so your broken file is still on disk and Iris is running built-in defaults | Fix the JSON, reload; restore the backup if you cannot | -| File is rewritten with defaults | Missing or unknown fields were normalized by `IrisSettings` | Reapply only intentional overrides; do not restore an obsolete full file over new defaults | -| Modded and Bukkit paths differ | The wrong data root was edited | Use the path table above and confirm the file timestamp changed before reloading | - -## Load, save, hotload - -| Action | Behavior | -|--------|----------| -| First boot | Create `settings.json` with current defaults if the file is absent | -| Load | Parse with Gson into `IrisSettings`. On failure, log `Configuration Error in settings.json!` and run on built-in defaults for that boot — the bad file is left untouched, because the rewrite never runs | -| After a successful load | Rewrite `settings.json` as pretty JSON so new keys and migrated values persist. Comments and hand formatting are lost | -| `/iris reload` | Invalidate the cached settings, re-read the file, reload the locale. On modded it also schedules a forced datapack regeneration. It does not restart services, reload packs, or rebuild engines | -| Hotload (Bukkit) | `SettingsHotloadWatch` polls every 60 ticks (about 3 s) through the VolmLib `ConfigHotloadEngine`. A `lastModified` or size change triggers a read; the reload only runs if the normalized file content actually differs. Logs `Hotloaded settings.json` | -| Hotload (modded) | `ModdedSettingsHotloadService` polls `lastModified` every 3 s. Because a load rewrites the file, a touch with no edit still produces one reload (it does not loop). Logs `Hotloaded settings.json` | -| Locale refresh | Bukkit calls `IrisLanguage.update()` on every poll; modded calls it only when the file is unchanged, and calls a full `IrisLanguage.reload()` when it did change | -| `forceSave()` | Only `/iris debug` writes settings back from memory | - -Legacy migration: if the raw JSON still contains `world.anbientEntitySpawningSystem`, the value is copied to `world.ambientEntitySpawningSystem` and logged once. - -## Root object - -Top-level Gson fields on `IrisSettings`. Every nested object is created with defaults when missing. - -| Field | Nested class | Covers | -|-------|--------------|--------| -| `general` | `IrisSettingsGeneral` | Locale, debug output, console colors, datapack ingest, strict keys, splash | -| `world` | `IrisSettingsWorld` | Entity systems, async world tick, WorldEdit CUI, pregen cache | -| `gui` | `IrisSettingsGUI` | Server-launched desktop GUIs | -| `autoConfiguration` | `IrisSettingsAutoconfiguration` | Spigot/Paper server-file fixups, custom-biome restart | -| `generator` | `IrisSettingsGenerator` | Default pack for world creation, leaf decay | -| `concurrency` | `IrisSettingsConcurrency` | Nothing configurable — see below | -| `studio` | `IrisSettingsStudio` | Studio world behavior | -| `performance` | `IrisSettingsPerformance` | Mantle residency, loader caches, SIMD, engine service pool | -| `pregen` | `IrisSettingsPregen` | Pregen scheduling, mantle backpressure, timeouts | -| `sentry` | `IrisSettingsSentry` | Error reporter | -| `treeFeller` | `IrisSettingsTreeFeller` | Survival tree feller | - -Static helper `IrisSettings.getThreadCount(int c)`: for `c` in `{-1, -2, -4}` it returns `max(availableProcessors / -c, 1)`; otherwise `max(c, 2)`, floored at 1. - -## `general` — locale, diagnostics, and console output - -This group decides what Iris says and how loudly. `language`, `debug`, and `strictContentKeys` are the ones worth touching; the colour and spin keys are cosmetic; the datapack keys change startup work on Bukkit only. - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `language` | `"en_US"` | Live | Selects the locale catalog for all Iris messages. Reloaded by `/iris reload` and by both hotload watchers | -| `commandSounds` | `true` | Live | **Bukkit only.** Plays the amethyst chime on `/iris` tab completion and success/failure sounds after a command. Turn off if the noise annoys staff | -| `debug` | `false` | Live | Enables verbose engine tracing on the console and writes per-chunk crash dumps under `debug/chunk-errors/`. Toggle with `/iris debug` rather than editing by hand; leave off in production because it is loud | -| `dumpMantleOnError` | `false` | Live | When a tectonic plate read reports an error, dump the decoded region to `dump/.bin` instead of logging a timing line. Turn on only when investigating mantle corruption | -| `disableNMS` | `false` | **Restart** | **Bukkit only.** Forces the no-op NMS binding. Iris logs a warning and world creation stops working entirely, so this is a diagnostic escape hatch, not a compatibility switch. Read in a class initializer, so a reload will not change it | -| `pluginMetrics` | `true` | **Restart** | **Bukkit only.** Registers the bStats reporter at enable | -| `splashLogoStartup` | `true` | **Restart** | Prints the ASCII logo and version block at startup. Set false for quieter console logs | -| `useConsoleCustomColors` | `true` | Live | Gradient/hex colouring for console output. Set false if your log viewer mangles it — you still get legacy colour codes. Iris also forces both colour keys off in memory if Adventure fails to bind | -| `useCustomColorsIngame` | `true` | Live | Same, for messages sent to players | -| `adjustVanillaHeight` | `false` | **Restart** | **Bukkit only.** Overwrites the vanilla `overworld`/`the_nether`/`the_end` dimension-type JSON with Iris height when compiling the datapack. It is part of the datapack fingerprint, so flipping it forces a datapack rebuild | -| `autoIngestDatapacks` | `true` | **Restart** | **Bukkit only.** Downloads and installs configured `datapackImports` during the startup admission gate. Unchanged committed content reuses its persisted result instead of revalidating; managed structures stay scoped to the declaring Iris dimensions | -| `autoImportDatapackStructures` | `false` | Live (next ingest) | **Bukkit only.** Converts every registered datapack structure into editable Iris pools, pieces, and objects — thousands of files in your pack folder. Native generation never needs those copies, so leave it off and run `/iris structure import ` when you actually want them | -| `strictContentKeys` | `false` | Live | Promotes unresolved pack content keys and bad block-state properties from warnings to blocking pack errors. Worth turning on while developing a pack. `-Diris.strictContent` overrides it in both directions, and the bare property with no value counts as true | -| `spinh` | `-20` | Live | Hue factor of the animated "aura" gradient on Iris text | -| `spins` | `7` | Live | Saturation factor of the same gradient | -| `spinb` | `8` | Live | Brightness factor of the same gradient | - -## `world` — entity systems and the async world tick - -Iris runs its own spawning and effects pass on a background loop, separate from vanilla mob spawning. These keys decide whether that loop does anything and how often. Turning the spawn systems off makes Iris worlds feel emptier but removes an entire class of tick cost; the defaults are the intended experience. - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `postLoadBlockUpdates` | `true` | Live | Runs a block-update pass over freshly generated chunks near players so placed objects settle (physics and waterlogging fixups). Turning it off is faster but leaves floating or unwatered blocks from some objects | -| `forcePersistEntities` | `true` | Live | Marks every Iris-spawned entity persistent so vanilla mob-cap and distance rules do not despawn it. Turn off if pack-spawned mobs are accumulating | -| `ambientEntitySpawningSystem` | `true` | Live | Enables the biome/region ambient spawn lists on the async tick (legacy key `anbientEntitySpawningSystem` is migrated automatically) | -| `asyncTickIntervalMS` | `700` | Live (next tick) | Milliseconds between world-manager passes that handle spawning, effects, and cleanup. Raise it to cut background cost on a busy server; lower it only if pack spawns feel too sparse | -| `targetSpawnEntitiesPerChunk` | `0.95` | Live | Entity saturation ceiling. Once entities per loaded chunk exceed this, Iris stops spawning (the Bukkit path also backs off for 5 seconds). Lower it on servers already near their entity budget | -| `markerEntitySpawningSystem` | `true` | Live | Enables spawning driven by mantle marker blocks, which is how packs place specific mobs at specific generated features | -| `effectSystem` | `true` | Live | Applies per-biome and per-region `IrisEffect`s (potion effects, particles, sounds) to players | -| `worldEditWandCUI` | `true` | Live | **Bukkit only.** Lets a WorldEdit selection act as an Iris wand and draws the particle outline for it | -| `globalPregenCache` | `false` | Live, one event late | **Bukkit only.** Maintains a persistent per-world bitmap of already-generated chunks so pregen can skip finished work across restarts. The enable/disable flip is observed on the following world-init or chunk-load event, not the current one | - -With both `markerEntitySpawningSystem` and `ambientEntitySpawningSystem` false, the world manager skips all related entity work. - -## `gui` — desktop windows launched by the server - -Iris can open AWT windows on the machine running the server: the noise explorer, the vision map, and the pregen viewer. That is useful on a local dev box and wrong on a headless host, which is the only reason to touch this group. - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `useServerLaunchedGuis` | `true` | Live | Allows server-side GUI hosts to open windows. Set false on any remote or headless server; the commands then report that GUIs are unavailable instead of trying | -| `maximumPregenGuiFPS` | `false` | Live | Repaints the pregen map window as fast as possible instead of roughly four times a second. Only affects the local window, never generation throughput | -| `colorMode` | `true` | Per window open | Colour rendering in the noise explorer instead of grayscale. It is captured when the window opens, so close and reopen the explorer to apply a change | - -## `autoConfiguration` — Bukkit server-file fixups - -Iris edits a couple of server config files at boot so long chunk generation does not look like a hang to the server's own watchdogs. Leave these on unless you manage those files yourself. They are all Bukkit-only and all read once during enable. - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `configureSpigotTimeoutTime` | `true` | **Restart** | Raises `timeout-time` in `spigot.yml` so a long generation stall does not kill the server | -| `configurePaperWatchdogDelay` | `true` | **Restart** | Raises Paper's watchdog early-warning and timeout for the same reason | -| `autoRestartOnCustomBiomeInstall` | `true` | **Restart** | When a datapack install registers new custom biomes and reports that a restart is required, Iris restarts the server itself instead of waiting for an admin | - -These keys are no-ops on mod loaders. - -## `generator` — defaults for world creation - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `defaultWorldType` | `"overworld"` | Live | **Bukkit only.** The pack key used whenever a world, studio, or command omits one — including a bare `Iris` generator string in `bukkit.yml` and `/iris create name type=default`. Mod loaders use `defaultPack` in `modded.json` instead | -| `preventLeafDecay` | `true` | Effectively **restart** | Marks generated leaves persistent so they do not decay. The flag is baked into resolved block data that is then cached, so already-resolved leaf blocks keep the old behavior after a reload. Unrelated to the per-dimension `preventLeafDecay` field in pack JSON | - -## `concurrency` — nothing to configure - -This object has no serialized fields. Gson writes `{}`, and anything you type inside it is silently discarded the next time Iris saves the file. The values are derived from CPU count at runtime: - -| Method | Result | Used by | -|--------|--------|---------| -| `getParallelism()` | `max(2, availableProcessors)` | Default `MultiBurst` pools, hybrid pregen thread count, locator searches | -| `getIoParallelism()` | `max(2, availableProcessors / 2)` | The shared IO burst pool | -| `getWorldGenThreads()` | `max(2, availableProcessors)` | Async pregen concurrency cap and the Moonrise worker-pool adjustment | - -## `performance` — caches, mantle residency, and the engine service pool - -This is the memory-versus-rework group. Larger loader caches trade heap for fewer pack reloads; mantle keys decide how long generated region data stays resident before being written out. Most keys here are captured when a pool or cache is built, so plan on a restart. Use `33 - Performance Tuning.md` for the measurement procedure — changing these blind usually makes things worse. - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `trimMantleInStudio` | `false` | Live | Lets the maintenance pass trim mantle in studio worlds. With the default `false`, studio engines skip the whole maintenance pass, which keeps edits responsive at the cost of growing memory during long authoring sessions | -| `mantleKeepAlive` | `30` | Live | Seconds a mantle plate stays resident before it is eligible for trimming. Scaled down automatically as reclaim pressure rises. Lower it when heap is tight, raise it if the same regions are reloaded repeatedly | -| `noiseCacheSize` | `1024` | Mixed | Capacity of the noise sample caches. The terrain query API picks it up live; the engine's own caches need an engine hotload or restart. Pregen temporarily raises it to at least 4096 in memory and does not lower it again or persist the change | -| `resourceLoaderCacheSize` | `1024` | **Restart / pack reload** | How many loaded pack resources stay cached per loader. Captured when a pack's `IrisData` is opened | -| `objectLoaderCacheSize` | `4096` | **Restart / pack reload** | Same, for `.iob` objects, matter objects, and images. Raise it for object-heavy packs when heap allows; lower it first when profiling shows retained pack data | -| `mantleCleanupDelay` | `200` | Live | Delay in **ticks** before a loaded chunk's mantle cleanup runs — the default is 10 seconds. Read from the raw field with no clamping, so a negative value is floored at 0 ms and a huge value really does postpone cleanup | -| `simdKernels` | `true` | **Restart** | Uses Vector API noise kernels when `jdk.incubator.vector` is on the module path, otherwise scalar fallbacks. Chosen once during class initialization, so toggling it and reloading does nothing, and it is silently inert without the JVM module flag | - -### `performance.engineSVC` - -The engine maintenance service is a small scheduled pool that trims and unloads mantle plates. Its three sizing keys are read once at enable, so a restart is required for any change to matter. - -| Key | Default | Takes effect | What it does | -|-----|---------|--------------|--------------| -| `useVirtualThreads` | `true` | **Restart** | Builds the maintenance thread factory from virtual threads instead of platform threads | -| `forceMulticoreWrite` | `false` | Live | Makes every maintenance pass unload all eligible tectonic plates instead of only unloading under heap pressure. Trades steadier memory for more write work; useful during long pregens on a small heap | -| `priority` | `5` (`Thread.NORM_PRIORITY`) | **Restart** | Thread priority, clamped to `[MIN_PRIORITY, MAX_PRIORITY]`. It is applied only when `useVirtualThreads` is false, so with the shipped defaults this key does nothing | -| `parallelism` | `-1` | **Restart** | Maintenance pool size. `>0` is capped at `processors * 2`; `<=0` uses `ceil(sqrt(processors))`, at least 1 | - -## `pregen` — scheduling, timeouts, and mantle backpressure - -These keys bound how aggressively pregeneration pushes the server. They are read when a pregen job is constructed, so a change applies to the *next* job, not a running one. The two that matter in practice are `maxResidentTectonicPlates` (the memory ceiling) and, on mod loaders, `moddedPregenInFlight` (the concurrency ceiling). The rest exist for diagnosing a specific failure mode. - -| Key | Default | Applies to | What it does and how it resolves | -|-----|---------|------------|----------------------------------| -| `runtimeSchedulerMode` | `AUTO` | Bukkit | `AUTO`, `PAPER_LIKE`, `FOLIA`. A regionized (Folia) runtime resolves to `FOLIA` before the setting is consulted, and off Folia a configured `FOLIA` is downgraded to `PAPER_LIKE`. Since `AUTO` also lands on `PAPER_LIKE` for every recognized and unrecognized fork, this key changes nothing in practice — the one exception is a non-regionized server that still identifies itself as Folia by name or version, where `AUTO` picks `FOLIA` and an explicit `PAPER_LIKE` does not | -| `paperLikeBackendMode` | `AUTO` | Bukkit, non-Folia | `AUTO`, `TICKET`, `SERVICE`. `SERVICE` uses the service executor (`paper-service`); `TICKET` and `AUTO` both use the ticket executor (`paper-ticket`). Ignored entirely on Folia. Try `SERVICE` only if ticket-based chunk loading is producing timeouts | -| `chunkLoadTimeoutSeconds` | `15` | Both | Clamped to `[5, 120]`. How long pregen waits for one chunk load before it counts as timed out. **On mod loaders the effective value is floored at 120**, so any value below that is ignored there | -| `timeoutWarnIntervalMs` | `500` | Bukkit | Minimum 250. Rate-limits the "timed out async pregen chunk load" and failed-release warnings so a bad run does not flood the log. Not read on mod loaders | -| `saveIntervalMs` | `30000` | Both | Clamped to `[5000, 900000]`. How often a running pregen flushes progress. Lower it if you expect to lose the process and want a closer resume point; the cost is more IO | -| `maxResidentTectonicPlates` | `96` | Both | Minimum 16. The mantle memory ceiling, and the first knob to lower on an out-of-memory pregen. The effective cap is also scaled by world height and by roughly 60% of the heap budget against a ~48 MB reference plate at height 384, with a floor of 16 — so on a small heap you may already be running below the configured number | -| `mantleBackpressureWaitMs` | `25` | Both | Clamped to `[5, 1000]`. Sleep granularity while pregen waits for resident plates to drop below the cap | -| `mantleBackpressureTimeoutMs` | `60000` | Both | Clamped to `[5000, 600000]`. How long that wait may last before Iris logs a backpressure warning and lowers its adaptive in-flight limit. Seeing this warning repeatedly means `maxResidentTectonicPlates` is too high for your heap, not too low | -| `moddedPregenInFlight` | `0` | Modded | Concurrent chunk budget for modded pregen. `>0` is capped at 512; `<=0` derives `max(16, min(48, cpu * 2))`. Lower it when modded pregen causes chunk-load timeouts or memory growth. Inert on Bukkit | - -## `sentry` — error reporting - -Read once during boot on both platforms, so every change here needs a restart. - -| Key | Default | What it does | -|-----|---------|--------------| -| `includeServerId` | `true` | **Bukkit only.** Attaches the server id to reports so recurring reports from one server can be grouped. Not read on mod loaders | -| `disableAutoReporting` | `false` | Skips Sentry initialization entirely. Set true if you do not want automatic error reports leaving the machine | -| `debug` | `false` | Turns on Sentry's own debug logging. Useful only when reports are not arriving | - -## `treeFeller` — survival tree felling - -Off by default because it changes survival gameplay. Both keys are read live, so `/iris reload` is enough. - -| Key | Default | What it does | -|-----|---------|--------------| -| `enabled` | `false` | Master switch. With it on, a permitted player breaking one log fells the whole Iris-managed tree. Disabling it mid-run cancels an in-flight fell on mod loaders only; the Bukkit runner does not re-read the setting once a fell has started | -| `durabilityPreservationChance` | `0` | Percent chance per block that the axe takes no durability, clamped to `[0, 100]`. An integration may override this per call | - -Requires permission `iris.treefeller` on Bukkit, or the platform tree-feller node on mod loaders. See `04 - Commands & Permissions.md` and `28 - Integrations.md`. - -## `studio` — authoring world behavior - -Only two of the four keys in this section do anything today. - -| Key | Default | What it does | -|-----|---------|--------------| -| `openVSCode` | `true` | Whether `/iris studio vscode` launches an editor after writing the workspace file. Set false on a headless box | -| `entitySpawning` | `true` | Whether mobs spawn inside studio worlds. Has no effect on normal worlds | -| `disableTimeAndWeather` | `true` | Nothing. Present in the settings model but not read by any code path today | -| `autoStartDefaultStudio` | `false` | Nothing. Present in the settings model but not read by any code path today | - -Studio workflow details: see `10 - Studio & VSCode Schemas.md`. - -## Bukkit-only: `compat.json` - -On Bukkit, Iris loads `plugins/Iris/compat.json` at startup and writes the complete built-in table to `compat.default.json` next to it for reference. This is how you keep a pack working on a server that lacks some block or item it references: Iris substitutes the replacement instead of failing. - -Built-in mappings always stay active; entries read from `compat.json` are appended to them. Both files are read once at boot — `/iris reload` does not re-read them, and mod loaders do not use them at all. - -```json -{ - "blockFilters": [ - { "when": "example:missing_block", "supplement": "minecraft:stone", "exact": false } - ], - "itemFilters": [ - { "when": "example:missing_item", "supplement": "minecraft:stick" } - ] -} -``` - -| Field | Applies to | Behavior | -|-------|------------|----------| -| `when` | block and item filters | The unsupported source key to match | -| `supplement` | block and item filters | The replacement key. If the replacement is also unsupported, Iris re-runs the lookup on it, up to 16 hops, and falls back to `STONE` with an error | -| `exact` | block filters only | When true, match the full key including namespace and block-state properties (`minecraft:some_log[axis=x]`). When false, match the bare material name. Item filters have no `exact` field | - -A block substitution logs `Compat: Using '' in place of '' since this server doesnt support ''` as a warning; item substitutions log the same at debug level. Invalid JSON logs the failure and leaves the built-in mappings active. - -One quirk to know: when `compat.json` is absent, Iris seeds it with a copy of the entire built-in table. On the next boot those entries are appended to the built-ins again, so the runtime list holds every default twice. It is harmless because the first match wins, but if you are editing the file, delete the entries you did not add. - -## Modded-only: `modded.json` - -Path: `/irisworldgen/modded.json`, written with defaults on first load if missing. Not used by the Bukkit plugin. Unlike `settings.json` this file is parsed by hand rather than Gson, is cached once, and has no hotload — a restart is required except for the keys that Iris rewrites itself. Malformed JSON logs `Iris modded config at … is invalid; using defaults` and runs on defaults **without** rewriting your file. - -| Key | Default | What it does | -|-----|---------|--------------| -| `defaultPack` | `"overworld"` | Pack used by `/iris create` when none is given. A distinct non-managed value is also prefetched when auto-download is on | -| `autoDownloadDefaultPack` | `true` | Downloads the managed Overworld and Underworld beta packs when missing, plus any distinct configured default. Set false on an air-gapped server and install packs by hand | -| `primaryWorld` | `""` | Iris dimension id used for player routing | -| `routePlayersToPrimaryWorld` | `true` | Sends players to the primary world when one is set | -| `mainWorldPack` | `""` | Pack (or `pack:dimensionKey`) for the main-world preset | -| `mainWorldSeed` | `0` | Seed for the main-world preset | -| `mainWorldAutoRestart` | `false` | Restarts the server automatically after a main-world inject instead of telling you to | - -`/iris world mainworld`, `/iris world replace-overworld`, and the primary-world clear paths write this file directly. See `06 - Worlds & Lifecycle.md` and `30 - Platform Differences.md`. - -## What is not in these files - -- Pack JSON (dimensions, biomes, objects) lives under `packs//` — see `05 - Concepts & Pack Layout.md`. -- Per-world studio and workspace files are generated under pack roots — see `10 - Studio & VSCode Schemas.md`. -- Locale files and overrides — see `08 - Localization.md`. - -## Related - -- `01 - Installation & Platforms.md` -- `04 - Commands & Permissions.md` -- `07 - Pregeneration.md` -- `25 - Pack Management.md` -- `30 - Platform Differences.md` -- `33 - Performance Tuning.md` diff --git a/docs/04 - Commands & Permissions.md b/docs/04 - Commands & Permissions.md deleted file mode 100644 index da25c3410..000000000 --- a/docs/04 - Commands & Permissions.md +++ /dev/null @@ -1,426 +0,0 @@ -# 04 - Commands & Permissions - -Iris exposes one root command, `/iris` (aliases `/ir`, `/irs`), on every platform. Bukkit uses VolmLib Director, where optional arguments are always `key=value`; Fabric, Forge, and NeoForge register a Brigadier tree with positional arguments and literal flags. This page is the complete command reference; platform gaps are marked **Bukkit-only** or **modded-only**. See `30 - Platform Differences.md` for the platform matrix and `03 - Configuration.md` for what `/iris reload` re-reads. - -## Everyday commands - -Four workflows cover most operator use. The Bukkit and modded forms are separate command trees, not translations of each other, so do not port `key=value` tokens to a mod loader. - -### Create a world and enter it - -``` -/iris create tutorial type=overworld seed=1337 # Bukkit -/iris create tutorial overworld 1337 # modded -``` - -`type` (aliases `dimension`, `pack`) takes a pack key or `pack:dimensionKey`. Left at its default `default`, it resolves to `generator.defaultWorldType`. Bukkit refuses the names `iris` and `benchmark`, and refuses any name whose dimension folder already exists. - -When it works, Bukkit prints `Successfully created your world!` and the world is immediately teleportable with `/iris tp tutorial`. On Folia the world is staged instead and the message tells you to restart before it exists. On mod loaders the dimension appears in `/iris world list`, and you enter it with `/iris tp irisworldgen:tutorial`. - -If the pack is missing you get `Could not find or download dimension …` plus a `/iris download ` hint — the world is not created. - -### Pregenerate an area - -Radius is in **blocks** and measured from the center, so `352` covers a 704x704 block square. - -``` -/iris pregen start 352 world=tutorial center=0,0 gui=false # Bukkit -/iris pregen start 352 irisworldgen:tutorial at 0 0 # modded -``` - -On Bukkit, `world` resolves from the sender's current world when omitted and is hidden from the in-game usage line, but `world=` still works — use it from console. `center=me` uses your own position. On modded, `at` must come after the dimension, never before it, and the flags `gui`, `sync`, `nocache` are literals in any order. The resumable checkpoint cache is on unless you pass `nocache`. - -Confirm with `/iris pregen status`. A running job prints the target world, generated and total chunks, percent, chunks/s, ETA, elapsed time, the generation method, and a failure count. No output line for failures means nothing has failed. `/iris pregen pause` toggles, and `/iris pregen stop` finishes in-flight work before cancelling. Only one job runs server-wide. Detail: `07 - Pregeneration.md`. - -### Open a studio for pack authoring - -``` -/iris studio open overworld seed=1337 # Bukkit -/iris studio open overworld 1337 # modded -``` - -A transient studio world opens and you are teleported into it; saving any pack file hotloads the change into that world. `/iris studio close` (alias `x`) discards the world. `/iris studio vscode` regenerates the `.code-workspace` and JSON schemas, and opens it in the desktop editor unless `studio.openVSCode` is false. Detail: `10 - Studio & VSCode Schemas.md`. - -### Check a pack before you rely on it - -``` -/iris pack validate pack=overworld # Bukkit — pack is required, see the note below -/iris pack validate # modded — empty means every pack -``` - -A clean pack reports no blocking errors; the all-packs form finishes with a broken-pack count out of the total scanned. `/iris pack status` reprints the last recorded result without revalidating. Warnings do not block world creation, blocking errors do. Detail: `25 - Pack Management.md`. - -**Bukkit quirk:** `/iris pack validate` and `/iris pack status` describe `pack` as optional ("leave empty for all"), but the parameter is declared with a blank default, which Director treats as *no* default. Both commands therefore reject a bare invocation with a missing-argument error, and the all-packs branch in the code is unreachable from Bukkit chat. Pass a pack name explicitly. - -### Other common goals - -| Goal | Bukkit-family | Fabric / Forge / NeoForge | Detailed guide | -|---|---|---|---| -| Replace the vanilla Nether slot | `/iris create world_nether type=underworld seed=1337 overwrite=true`, then restart | Not available | `06 - Worlds & Lifecycle.md` | -| Create an in-game jigsaw project | `/iris jigsaw create overworld village/demo` | Not available; author on Bukkit and copy the saved pack | `21 - Jigsaw Structures.md` | -| Inspect an Iris jigsaw graph | `/iris structure info overworld ` | `/iris structure info ` while in its Iris dimension | `21 - Jigsaw Structures.md` | -| Remove a disposable Iris world | Evacuate players, `/iris unloadWorld `, then `/iris remove ` | `/iris world delete ` | `06 - Worlds & Lifecycle.md` | - -If a command fails before doing work, check in this order: platform syntax, permission, sender type (player versus console), exact pack/world key, then lifecycle busy state. A parse error is not evidence that the underlying feature failed. - -## Syntax - -### Bukkit (Director) - -- Root: `/iris` / `/ir` / `/irs`. Subcommand and group names come from the method or class name unless `@Director(name=…)` overrides it, so several commands read differently than you would guess — see the reference tables. -- **Required parameters are positional; optional parameters are never positional.** `/iris pregen start 500 true` is a parse error; write `/iris pregen start 500 gui=true`. -- A parameter declared with a blank default counts as required even when the description says otherwise. -- Names and aliases match case-insensitively. -- Help uses the Director mini-menu: required renders as ``, optional as `[name=…]`. -- **Contextual** parameters (world, dimension, pack, location, generator, template on many nodes) resolve from the sender's current world or look target. They are hidden from the usage line and from tab completion, but they still accept `name=value`, which is how you drive them from console. -- Tab completion is not permission-gated; only execution is. - -### Modded (Brigadier) - -- Same root and aliases; `ir` and `irs` are redirect nodes onto `iris`. -- Arguments are ordered literals and typed arguments, never free-form `key=value`. -- `/iris` and `/iris help [section]` open the help browser: a paginated clickable UI for players (17 entries per page, trailing page number accepted in the section string) and a flat text list for console. Bare group nodes route into the same help. -- Flags are literals where used (pregen `gui`, `sync`, `nocache`; download `force`). - -## Permissions - -### Bukkit - -| Permission | Declared in | Default | Gate | -|------------|-------------|---------|------| -| `iris.all` | `plugin.yml` and `paper-plugin.yml` | `op` | `CommandSVC.executeRoot` rejects every `/iris` execution without it | -| `iris.treefeller` | `plugin.yml` and `paper-plugin.yml` | `op` | Survival tree feller only (`TreeFellerSVC`); also requires `treeFeller.enabled` in settings | - -`iris.all` is code-gated as `ROOT_PERMISSION` in `CommandSVC`. There are no per-subcommand permission nodes: a sender either has the whole tree or none of it. Custom-biome restart warnings also notify online players who are op **or** hold `iris.all`. - -### Modded - -| Gate | Brigadier level | Applies to | -|------|-----------------|------------| -| Gamemaster | `Commands.LEVEL_GAMEMASTERS` (2) | Everything that mutates: create/world, studio, object tools, pregen, download, debug, reload, evacuate, teleport, seed, edit, find/goto, structure, datapack, pack, developer, regen, goldenhash, accesslist | -| Read-only | `Commands.LEVEL_ALL` (0) | `help`, `version`, `info` (seed field omitted unless gamemaster), `worlds`, `height`, `metrics`, and the entire `what` subtree | - -The root `iris` literal itself carries no requirement, so any player can run it and reach the help browser; the help marks itself as restricted when the source fails the gamemaster check. - -Tree feller on mod loaders uses the platform permission API with node `irisworldgen:treefeller` (Fabric `Identifier`; Forge and NeoForge `PermissionNode`), defaulting to gamemaster level on all three. It is not a Bukkit permission string. - ---- - -## Root: `/iris` - -Bukkit names below are the names Director actually registers. Where that differs from what you would expect, the method name is what you type. - -| Command | Aliases | Platforms | Params (Bukkit-style) | Description | -|---------|---------|-----------|------------------------|-------------| -| (empty) / help | | Both | `[section]` (modded) | Open help; modded supports a section path and page number | -| `version` | | Both | — | Print Iris/platform/Minecraft version and engine count | -| `info` | | **Modded** | `[dimension]` (substring filter) | List Iris dimensions and pack details; seed only for gamemasters | -| `create` | `c` | Both | **Bukkit:** ` [type=default] [seed=1337] [main=false] [overwrite=false]` (`name` alias `world-name`; `type` aliases `dimension`,`pack`; `main` alias `main-world`; `overwrite` alias `force`). **Modded:** ` [pack=overworld] [seed=1337]` | Create an Iris world/dimension; Bukkit `overwrite=true` stages an exact slot replacement for restart | -| `teleport` | `tp` | Both | **Bukkit:** ` [player]` (defaults to the sender). **Modded:** ` [player]` | Teleport self or a named player into an Iris world/dimension | -| `evacuate` | | Both | **Bukkit:** ``, player origin. **Modded:** `[dimension]` | Move players out of an Iris world to fallback/primary | -| `height` | | Both | — | Print world height; player origin on Bukkit | -| `worlds` | `accesslist` (Bukkit) | Both | — | **Bukkit:** access list of worlds. **Modded:** two separate nodes — `worlds` is read-only and takes no argument, `accesslist` needs gamemaster; both print the `info` listing | -| `remove` | `rm` | **Bukkit** | ` [delete=true]` | Remove a managed Iris world; disk deletion defaults to true. `world` is a name, so worlds that exist only on disk are accepted | -| `loadWorld` | `import` | **Bukkit** | ``, player origin | Load a managed Iris world | -| `unloadWorld` | | **Bukkit** | ``, player origin | Unload an Iris world | -| `debug` | | Both | — | Toggle `general.debug` and save settings | -| `download` | `dl` | Both | ` [branch=stable] [overwrite=false]` (`pack` alias `project`; `overwrite` alias `force`) | Download a pack; `overworld` and `underworld` resolve to managed beta release ZIPs | -| `metrics` | `measure` | Both | — | Generation metrics; player origin on Bukkit | -| `reload` | | Both | — | Reload `settings.json` and locale; modded also schedules forced datapack regeneration | -| `seed` | | **Modded** | — | Print world and engine seeds | -| `regen` | `rg` | **Modded** root; Bukkit under `Developer` | `[radius]` — modded default `0`, range `0..64` | Delete and regenerate nearby chunks | -| `goldenhash` | `gold` | **Modded** root; Bukkit under `Developer` | `[radius=8] [threads=8] [capture\|verify]`, radius `0..256`, threads `1..64` | Deterministic buffer hashes | -| `wand` | | **Modded** root (+ `object`) | — | Give object wand | -| `dust` | `d` | **Modded** root (+ `object`) | — | Give reveal dust | -| `find` | `goto` | Both | see Find | Locate biome/region/object/structure/POI | -| `what` | | Both | see What | Inspect context | -| `edit` | | Both | see Edit | Open pack JSON in the desktop editor | -| `pregen` | `pregenerate` | Both | see Pregen | Pregeneration control | -| `object` | `o` | Both | see Object | Object tools | -| `studio` | `std`, `s` | Both | see Studio | Studio / pack authoring | -| `jigsaw` | `jig`, `jgs` | **Bukkit** | see Jigsaw | Transaction-owned planar/spatial Jigsaw Studio | -| `pack` | `pk` | Both | see Pack | Validate/cleanup/restore/status | -| `structure` | `struct`, `str` | Both | see Structure | Structure index/import/place | -| `datapack` | `datapacks`, `dp` | Both | see Datapack | Datapack helpers | -| `Developer` | `dev` | Both | see Developer | Diagnostics; the group name is registered with a capital `D`, but matching is case-insensitive | -| `world` | `w` | **Modded** | see World | Runtime dimension enable/disable | - ---- - -On Paper-family servers, `overwrite=true` is deliberately restart-only; Spigot rejects it because it has no pre-registry plugin bootstrap. The exact target dimension folder must already exist; use ordinary `/iris create` for a new world. The name may resolve to a safe `iris:*` world or exactly the configured main, `_nether`, or `_the_end` alias; arbitrary `minecraft:*` and foreign namespaces are rejected. Iris stages and validates a fresh pack snapshot, compare-and-swaps only that world's `bukkit.yml` generator and seed, and retains the existing dimension folder as a rollback backup until the restarted world proves its Iris identity, pack, dimension, environment, and seed. Multiple distinct slots may be staged before one restart. `main=true` is valid with overwrite only when the name is the configured main-world name. Exact vanilla slots preserve the authoritative seed shared by the existing level, regardless of the supplied `seed`; this keeps Overworld/Nether/End coordinate generation aligned, and Iris tells you which seed it used instead. Use ordinary new-main promotion when a new level seed is required. - ---- - -## Find: `/iris find` (`goto`) - -**Origin:** player (Bukkit). **Modded:** gamemaster gate. - -| Command | Params | Description | -|---------|--------|-------------| -| `biome` | **Bukkit:** ` [teleport=true]`. **Modded:** `` | Find an Iris biome; teleport defaults to true on Bukkit | -| `region` | **Bukkit:** ` [teleport=true]`. **Modded:** `` | Find an Iris region | -| `object` | **Bukkit:** ` [teleport=true]`. **Modded:** `` | Find an object placement (Bukkit may teleport to the object studio first) | -| `structure` | **Bukkit:** ``, runs sync. **Modded:** `` | Find a vanilla/datapack/Iris structure | -| `poi` | **Bukkit:** ` [teleport=true]`. **Modded:** `` | Find a supported point of interest | -| `unregistered` | — | Print structures excluded from goto completion, and the rejection reasons, to console | - ---- - -## What: `/iris what` - -**Bukkit origin:** player only, and there is no bare or `here` form. On modded the whole subtree is `LEVEL_ALL`, so any player can use it. - -| Command | Platforms | Params | Description | -|---------|-----------|--------|-------------| -| (empty) / `here` | **Modded** | — | Full inspect at the player position | -| `biome` | Both | — | Current Iris biome | -| `region` | Both | — | Current Iris region | -| `block` | Both | — | Target block | -| `hand` | Both | — | Held item | -| `markers` | Both | `` | Reveal nearby markers (for example `cave_floor`, `cave_ceiling`, `object`) | - ---- - -## Edit: `/iris edit` - -**Bukkit origin:** player. Opens pack JSON in the desktop editor. The same tree is also mounted at `/iris studio edit …` on Bukkit. - -| Command | Aliases | Params | Description | -|---------|---------|--------|-------------| -| `biome` | `b` | **Bukkit:** ``. **Modded:** `[key]` | Open biome JSON (modded: omit the key for the current biome) | -| `region` | `r` | **Bukkit:** ``. **Modded:** `[key]` | Open region JSON | -| `dimension` | `d` | **Bukkit:** ``. **Modded:** — | Open dimension JSON (modded: current pack) | - ---- - -## Pregen: `/iris pregen` (`pregenerate`) - -| Command | Aliases | Params | Description | -|---------|---------|--------|-------------| -| `start` | | **Bukkit:** ` [world] [center=0,0] [gui=true] [serial=false]` (`radius` alias `size`, `center` alias `middle`, `me` for the player position; `world` is contextual with no default). **Modded:** ` [dimension] [at ] [gui] [sync] [nocache]`, radius `1..100000` | Start pregen; radius in **blocks**; the resumable checkpoint cache is on by default on modded unless `nocache` | -| `stop` | `x` | — | Stop the active pregen after in-flight work closes | -| `pause` | `resume` | — | Toggle pause/resume | -| `status` | | — | Progress, chunks/s, ETA, elapsed, method, failures | - -**Bukkit:** `serial=true` requires a Paper-compatible server (strict serial chunk generation) and is rejected elsewhere. **Modded:** `sync` is the serial-like flag; `gui` opens the boss-bar/GUI path when available. - -See `07 - Pregeneration.md`. - ---- - -## Object: `/iris object` (`o`) - -**Bukkit:** group origin is player. Root `wand`/`dust` also exist on modded. - -| Command | Aliases | Platforms | Params | Description | -|---------|---------|-----------|--------|-------------| -| `wand` | | Both | — | Give the Iris object wand | -| `dust` | `d` | Both | — | Give reveal dust | -| `save` | | Both | **Bukkit:** ` [overwrite=false] [legacy=true]` (`overwrite` alias `force`; a contextual `dimension` is resolved from your world, or passed as `dimension=`). **Modded:** `[overwrite] ` | Save the wand selection as `.iob` | -| `paste` | | Both | **Bukkit:** ` [edit=false] [rotate=0] [scale=1]`. **Modded:** `[at x y z] [rotate degrees] ` | Paste an object | -| `expand` | | **Modded** | `[amount=1]`, range `1..256` | Expand the selection along your look direction | -| `contract` | `-` | Both | `[amount=1]` | Contract the selection along your look direction | -| `shift` | | Both | `[amount=1]` | Shift the selection along your look direction | -| `position1` | `p1` | Both | **Bukkit:** `[here=true]` uses the block under your feet; `here=false` uses your look target. **Modded:** feet by default, `look` switches to the look target | Set one selection corner; requires the Iris wand in hand | -| `position2` | `p2` | Both | same | Set the other selection corner | -| `x+y` | `xpy` (modded) | Both | — | Autoselect up and out | -| `x&y` | `xay` (modded) | Both | — | Autoselect up, down, and out | -| `analyze` | | Both | `` | Composition stats | -| `shrink` | | Both | `` | Shrink the object to its minimum bounds | -| `plausibilize` | | Both | **Bukkit:** ` [dryrun=false] [reach=12]`; `target` accepts a key or a `prefix/`. **Modded:** greedy `` parsed as `key [dryrun] [reach]`, same defaults | Grow branches so leaves survive vanilla decay | -| `undo` | `u` | Both | `[amount=1]` | Undo pastes | -| `we` | | **Bukkit**; modded stub | — | Wand plus import of the WorldEdit selection | -| `studio` | | **Bukkit**; modded stub | `[dimension] [seed=1337]` | Object studio grid world | -| `convert` | | **Bukkit**; modded stub | — | Convert `convert/` folder `.schem` to `.iob` | - ---- - -## Studio: `/iris studio` (`std`, `s`) - -| Command | Aliases | Platforms | Params | Description | -|---------|---------|-----------|--------|-------------| -| `open` | `o` | Both | **Bukkit:** ` [seed=1337]` (`dimension` alias `dim`, `seed` alias `s`). **Modded:** ` [seed]` | Open a temporary studio dimension; the owning player may replace an active Jigsaw Studio, and Iris waits for its autosave and active-operation barriers before closing it | -| `close` | `x` | Both | — | Close the studio and discard the world; Bukkit requires `/iris jigsaw close` for an active Jigsaw Studio | -| `tpstudio` | `stp` | Both | — | Teleport into the open studio | -| `status` | | **Modded** | — | Show the open studio and pack | -| `create` | `+` | Both | **Bukkit:** `[name=studio] [template]`. **Modded:** `[name] [template=example]` | Create a pack project | -| `pkg` | `package` | Both | **Bukkit:** `[dimension=default] [obfuscate=false] [minify=true]`. **Modded:** `[pack]` | Zip and package a pack. The Bukkit command name is `pkg`; `package` is the alias | -| `version` | | Both | **Bukkit:** `[dimension=default]`. **Modded:** `[pack]` | Pack version | -| `regions` | | Both | **Bukkit:** `[radius=500]`, player origin. **Modded:** `[radius]`, default 500 | Nearby region distribution | -| `noise` | `nmap` | Both | **Bukkit:** `[generator] [seed=12345]`. **Modded:** `[generator] [seed]` | Noise explorer GUI | -| `map` | `render` | Both | **Bukkit:** contextual `world`, required. **Modded:** — | Vision map GUI | -| `vscode` | `vsc` | Both | **Bukkit:** `[dimension=default]`. **Modded:** `[pack]` | Generate and open the code workspace | -| `update` | | Both | same pack argument as `vscode` | Regenerate the workspace only | -| `importvanilla` | `importv`, `iv` | **Bukkit**; modded stub | ` [variants=3] [structures=true]` | Import vanilla trees/objects/structures into a pack | -| `scoreboard` | `board`, `sidebar`, `sb` | **Bukkit** | — | Toggle the studio debug scoreboard | -| `loot` | | **Bukkit**; modded stub | `[fast=false] [add=true]` | Simulate chest loot in a GUI | -| `profile` | | **Bukkit**; modded stub | `[dimension=default]` | Pack performance profile | -| `spawn` | `summon` | **Bukkit**; modded stub | ` ` (`location` is contextual) | Spawn an Iris entity | -| `objects` | `find-objects` | **Bukkit**; modded stub | — | IGenData chunk report for nearby chunks | - -See `10 - Studio & VSCode Schemas.md`. - ---- - -## Jigsaw: `/iris jigsaw` (`jig`, `jgs`) - -**Bukkit-only; player origin.** This opens a transient Jigsaw Studio through the same single active Studio lifecycle. Saved Iris jigsaw resources run through the shared core on every platform, but Fabric/Forge/NeoForge do not register this authoring command tree. - -| Command | Params | Description | -|---|---|---| -| `create` | ` [mode=planar] [compatibility=iris] [width=15] [height=15] [depth=15] [seed=1337]` | Add-only atomic graph creation followed by open; `key` aliases `structure` and `name`; `mode` completes `planar`/`spatial`, compatibility completes `iris`/`vanilla`; planar X/Z `3..128`, spatial X/Z `1..128`, Y `1..192`, volume `<=2,097,152` | -| `convert` | ` [target=auto] [seed=1337]` | Add-only conversion of one live registered vanilla/datapack jigsaw into an owned Iris graph, then open it; aliases `import`, `import-vanilla` | -| `adopt inspect` | ` [target=auto] [strategy=auto]` | Asynchronously inspect an existing Iris closure and issue a hash-pinned `IN_PLACE`, `CLONE_REQUIRED`, or `BLOCKED` plan; strategy completes `auto`, `in-place`, `clone` | -| `adopt apply` | `` | Revalidate and atomically apply that player's unexpired plan, then open the target at seed `1337`; an active or opening Jigsaw Studio is rejected | -| `open` | ` [seed=1337]` | Open an existing graph in compact workcells; aliases `edit`, `reopen`; existing Iris structure keys tab-complete; owner, autosave, and operation barriers protect replacement | -| `close` | `[discard=false]` | Close Studio; refuse active autosave/load/graph work or a pending dirty capture unless deliberately discarded | -| `status` | — | Show project/workcell state and the current automatic seed-`1337` evaluation, theme, piece count, and diagnostic | -| `menu` | — | Open the six-row controls also opened by the generated chest or three sneaks within 1.5 seconds | -| `select` | — | Select the workcell containing the player | -| `goto` | `` | Select and teleport above a stable workcell ID; alias `teleport` | -| `particles` | `` | Toggle player-local workcell-bound, connector, and temporary assembly-preview particle trails | -| `save` | `[bay=selected]` | Flush the selected dirty workcell's automatic capture now; normal block and container updates already autosave | -| `connector channel` | `` | Look at a saved marker in the active owned workcell within 8 blocks and set/clear its Iris-only channel at the inverse-mapped source position | -| `bounds` | ` ` | Set the selected workcell capacity without resizing any variant object; every existing variant must fit, and the compact Studio layout regenerates in place; aliases `cell`, `resize` | -| `workcell capacity` | ` ` | Explicit nested form of `bounds` (group alias `cell`); planar capacities are per canonical archetype and spatial capacity is the shared envelope for its one-row variant cells | -| `workcell label` | `` | Set the selected planar or spatial workcell's author label; quote spaces; solver identity remains canonical | -| `workcell label-reset` | — | Reset the selected workcell to its canonical solver label; alias `reset-label` | -| `pool create` | ` [fallbackPoolKey=none]` | Atomically create an empty owned pool, optionally using an existing owned direct fallback | -| `piece create` | ` [weight=1]` | Create and load an owned variant; planar derives connectors from the contextual canonical workcell | -| `piece add` | ` [weight=1]` | Re-add and load an existing piece/object already owned by this project | -| `piece remove` | `` | Remove the active variant from a pool without deleting its owned resources | -| `piece rotatable` | `` | Persist cardinal rotation for the active variant; portable sessions reject `false` | -| `piece expand` | — | Resize the active planar or spatial owned variant exactly to workcell capacity; planar canonical sockets move to the new faces | -| `variant weight` | ` ` | Set the active variant's positive weight in an owned pool | -| `variant resize` | ` ` | Resize only the active owned variant within its workcell capacity; safe shrink rejects cropped content and the active cell reloads in place | -| `variant label` | `` | Set the active variant's author label; quote spaces | -| `variant label-reset` | — | Reset the active variant to its resource-key fallback; alias `reset-label` | -| `variant duplicate` | — | Copy the active variant's object, metadata, and exact pool memberships into one new variant in this workcell | -| `variant duplicate-family` | `[themeKey=next]` | Atomically clone every enabled workcell's active owned variant into one coherent Iris family and load the whole family; alias `family` | -| `rules limits` | ` ` | Set depth `1..30` and radius `1..32` (group alias `rule`); `VANILLA_PORTABLE` is restricted to `<=20` and `<=8` | -| `rules fallback` | ` ` | Set or clear one owned pool's direct fallback after compiling the complete graph; pass `none` to clear. Unlike `pool create`, this argument is required | -| `preview goto` | — | Teleport above the permanent seed-`1337` block preview; alias `teleport` | -| `preview assemble` | `[seed=1337]` | Compute a deterministic read-only assembly at the player, report its complete piece count, and show in-range bounds as purple particle boxes for 10 seconds within the shared particle budget; places no blocks | -| `export` | `[namespace=iris] [output=jigsaw-export] [format=zip] [replace=false]` | Start a background strict Minecraft 26.2 vanilla datapack export as one direct artifact under the Studio packs `exports/` folder | -| `delete` | `[confirm=false]` | With `confirm=true`, scan reverse references, close Studio, and hash-pinned-delete the complete owned project; alias `remove` | - -There is no Jigsaw Studio undo command, adoption rollback command, or mod-loader authoring command. **Undo Last Autosave** in Workcell Settings restores the newest of five previous saved graph iterations retained in one `.iris/jigsaw-history/key-.json` file. **Reset Connector Blocks** restores the selected workcell's saved connector blocks without replacing its other edited blocks. Planar Studio always has six independently capacitated/enabled canonical workcells. Spatial Studio places every variant in a dedicated one-row cell; a new project seeds seven 15×15×15 variants with 0 through 6 cumulative face-center connectors. All cells retain one clear block of separation and use physical white-concrete edge cages with player-local particle trails inside them; Jigsaw Studio spawns no display entities for workcell bounds. Every variant retains its own exact dimensions and optional display label. Workcell and variant rename tools are renamed in an anvil, right-clicked to apply, and sneak-right-clicked to reset. A catalog may contain at most 512 variants. Natural creature spawning is disabled in the transient Studio world. The seed-`1337` assembly is evaluated automatically and rendered as a permanent protected block preview; spatial previews form an elevated connected assembly, while `preview assemble` remains the separate temporary arbitrary-seed particle diagnostic. See `21 - Jigsaw Structures.md` for GUI/toolbox controls, whole-assembly theme chances, independent pool-entry chances, rules/caps, markers, ownership, placement, export, and recovery. - -Bukkit has one global Studio project/world and the Jigsaw session belongs to one owning player. Only that owner can control, load, or mutate it; entering a workcell makes that physical cell the owner's next menu selection. Non-owner edits are cancelled and non-owner commands use a strict informational/communication allowlist. Block and inventory changes in loaded owned workcells autosave after a 40-tick quiet period. Duplicate-one and duplicate-family actions queue once behind pending autosave, expedite it, and continue automatically against the same request and source variants. The chest and live preview are protected; schema-1 or otherwise stale toolbox sticks are rejected. A later mutation after capture starts remains dirty for another capture. Plugins that bypass covered events must call `JigsawStudioService.markDirty(...)` or `markAllDirty(...)`. - ---- - -## Pack: `/iris pack` (`pk`) - -| Command | Aliases | Params | Description | -|---------|---------|--------|-------------| -| `validate` | `v` | **Bukkit:** `` (required despite the "leave empty for all" description). **Modded:** `[pack]`; empty means all | Validate pack(s) and publish results | -| `cleanup` | `c` | **Bukkit:** ` [mode=preview]`. **Modded:** ` [apply]` | Preview or quarantine unused resources | -| `restore` | `r` | same pattern as `cleanup` | Preview or restore the latest quarantine | -| `status` | `s` | **Bukkit:** `` (required, same blank-default trap). **Modded:** `[pack]` | Startup-published validation status, including persisted unchanged results | - -See `25 - Pack Management.md`. - ---- - -## Structure: `/iris structure` (`struct`, `str`) - -Bukkit `dimension` parameters all carry the alias `dim`. - -| Command | Aliases | Platforms | Params | Description | -|---------|---------|-----------|--------|-------------| -| `list` | `ls` | Both | **Bukkit:** ``. **Modded:** current engine pack | Write `structure-index.json` | -| `info` | | Both | **Bukkit:** ` `. **Modded:** `` | Resolve jigsaw graph bounds | -| `place` | `p` | Both | **Bukkit:** ` `, player origin. **Modded:** `` | Assemble and place at the player; Bukkit reports the exact changed-block count and rejects air-only or already-identical no-op results | -| `import` | `import-all`, `reimport`, `imp`, `all` | **Bukkit**; modded stub | `` | Import all vanilla/datapack structures as editable Iris resources (overwrites) | -| `capture` | `cap` | **Bukkit**; modded stub | `` | Capture code-only structures via a scratch world | -| `verify` | `locateall` | Both | **Bukkit:** ` [radius=48]`, clamped to `1..1000`. **Modded:** `[key]` | Native/Iris structure reachability report | - -See `18 - Structures Overview.md`, `21 - Jigsaw Structures.md`, `22 - Native Structures & Datapacks.md`. - ---- - -## Datapack: `/iris datapack` (`datapacks`, `dp`) - -| Command | Aliases | Platforms | Params | Description | -|---------|---------|-----------|--------|-------------| -| `ingest` | `pull` | **Bukkit**; modded stub | `[restart=false]` | Download and install Modrinth `datapackImports` into world datapacks | -| `list` | `ls` | Both | — | **Bukkit:** configured imports plus installed. **Modded:** configured/installed world datapacks | -| `remove` | `rm` | **Bukkit**; modded stub | `` | Remove an installed datapack by id | -| `status` | | **Modded** | — | Check Iris dimension-type overrides against pack heights | -| `install` | | **Modded** | — | Install the dimension-type override datapack for loaded Iris dimensions | - -See `22 - Native Structures & Datapacks.md`. - ---- - -## World (modded-only group): `/iris world` (`w`) - -Bukkit uses root `create` / `loadWorld` / `unloadWorld` / `remove` / `evacuate` instead. `[seed]` accepts a number or the literal `random`; blank means `1337`. - -| Command | Aliases | Params | Description | -|---------|---------|--------|-------------| -| `enable` | `create` | ` [seed\|random]` | Create/inject a persistent Iris dimension (downloads the pack if missing) | -| `replace-overworld` | | ` [seed\|random]` | Inject primary world routing | -| `mainworld` | | ` [seed\|random]` | Configure the main-world preset in `modded.json` | -| `disable` | | `` | Evacuate and unload; keep disk data | -| `delete` | `remove`, `rm` | `` | Disable and wipe chunk/mantle data | -| `list` | `ls` | — | List loaded Iris dimensions | -| `status` | | — | Loaded dimensions plus primary world config | - ---- - -## Developer: `/iris Developer` (`dev`) - -| Command | Aliases | Platforms | Params | Description | -|---------|---------|-----------|--------|-------------| -| `EngineStatus` | | **Bukkit** | — | Loaded tectonic plate count | -| `Sentry` | `sentry` (modded) | Both | — | Send a test exception to the error reporter | -| `genhash` | | **Bukkit** | `[radius=4] [centerX=0] [centerZ=0]`, contextual `world` | Hash generated blocks in a fixed area. The center parameters are `centerX`/`centerZ` here, not the hyphenated `goldenhash` names | -| `update-world` | `^world` | **Bukkit** | `[confirm=false] [fresh-download=false]`, contextual `world` and `pack` (`pack` alias `dimension`; `confirm` alias `c`; `fresh-download` aliases `fresh`, `new`) | Unsafe pack swap into a world | -| `mantle` | | **Bukkit** | `[plate=false] [name=21474836474]` | Dump a mantle section or plate under the dump folder | -| `packBenchmark` | | **Bukkit** | `[dimension=overworld] [radius=2048] [gui=false]` (`dimension` alias `pack`) | Pack benchmark | -| `upgrade` | | **Bukkit** | `[version=latest]` | Data version upgrade helper | -| `mca` | | **Bukkit** | `` (a world folder path) | Scan MCA region files | -| `delete-chunk` | `dc` | **Bukkit** | `[radius=0]`, player origin | Delete nearby chunk blocks for regen testing | -| `network` | `ip` | Both | — | List network interfaces | -| `regen` | `rg` | **Bukkit** (modded root) | `[radius=5]`, player origin | Delete and regenerate nearby chunks | -| `goldenhash` | `gold` | **Bukkit** (modded root) | `[radius=8] [center-x=0] [center-z=0] [reset-mantle=true] [threads=8] [deep=false]`, contextual `world` | Buffer golden hash capture/verify | - -The modded developer group implements only `sentry` and `network`/`ip`; its help section still advertises a region file scan that has no command node. - ---- - -## Platform gap summary - -| Feature | Bukkit | Modded | -|---------|--------|--------| -| Root permission node | `iris.all` (code-gated, whole tree) | `LEVEL_GAMEMASTERS` / `LEVEL_ALL` per node | -| World lifecycle | `create`, `loadWorld`, `unloadWorld`, `remove` | `world enable/disable/delete`, `create`, `mainworld` | -| Seed print | — | `/iris seed` | -| Object expand | — | `/iris object expand` | -| Object WE / studio / convert | yes | help stubs only | -| Studio loot/profile/spawn/objects/scoreboard/importvanilla | yes | stubs or messages | -| Jigsaw Studio create/edit/autosave/export commands and GUI | yes | no; copy a Bukkit-authored Iris pack | -| Structure import/capture | yes | messages (run on Bukkit, copy the pack) | -| Datapack Modrinth ingest/remove | yes | messages | -| Datapack status/install (dimension types) | — | yes | -| `regen` / `goldenhash` | under `Developer` | root | -| Pregen flags | `serial`, `gui`, center string | `sync`, `gui`, `nocache`, `at x z` | -| `pack validate` / `status` with no pack | rejected (blank default is required) | validates all packs | -| Tree feller permission | `iris.treefeller` | `irisworldgen:treefeller` via the loader permission API | - ---- - -## Related - -- `03 - Configuration.md` -- `02 - Getting Started.md` -- `06 - Worlds & Lifecycle.md` -- `07 - Pregeneration.md` -- `10 - Studio & VSCode Schemas.md` -- `21 - Jigsaw Structures.md` -- `25 - Pack Management.md` -- `28 - Integrations.md` -- `30 - Platform Differences.md` -- `32 - Determinism & Goldenhash.md` diff --git a/docs/05 - Concepts & Pack Layout.md b/docs/05 - Concepts & Pack Layout.md deleted file mode 100644 index e3de175ac..000000000 --- a/docs/05 - Concepts & Pack Layout.md +++ /dev/null @@ -1,229 +0,0 @@ -# 05 - Concepts & Pack Layout - -A pack is a folder of JSON files, binary objects, and images that fully describes one or more worlds. Iris loads it through `IrisData`, which registers one loader per resource type, turns short string keys into files on disk, and caches what it reads. Every world you create gets its own frozen copy of the pack; only Studio worlds read the folder you are editing. - -See also: `00 - Overview.md`, `01 - Installation & Platforms.md`, `10 - Studio & VSCode Schemas.md`, `11 - Dimensions.md`, `24 - Pack Mods & Snippets.md`, `25 - Pack Management.md`. - -## What a pack actually is - -There is no manifest file, no registry, and no build step. A pack is a directory whose subfolder names tell Iris what type each file is. `biomes/plains.json` is a biome because it sits in `biomes/`. Move that same file to `regions/` and Iris will try to parse it as a region. - -The pack folder's own name is the pack key. A folder called `packs/myworld/` is the pack `myworld`. Rename the folder and you have renamed the pack. - -The only hard requirement is at least one `.json` file directly inside `dimensions/`. Everything else is optional, and a folder you never create simply has no resources of that type. - -## A pack you can read in one screen - -```text -packs/myworld/ - dimensions/ - myworld.json -> key "myworld" - regions/ - main.json -> key "main" - biomes/ - plains.json -> key "plains" - hills/ - rolling.json -> key "hills/rolling" - generators/ - plain.json -> key "plain" -``` - -Five files. `dimensions/myworld.json` lists `"main"` in its `regions` array. `regions/main.json` lists `"plains"` and `"hills/rolling"` in `landBiomes`. Each biome names `"plain"` as a generator. That chain is the whole pack. - -Note `hills/rolling`. Subfolders are yours to organize however you like — they become part of the key, and nothing else changes. - -## Keys: the one rule - -**A key is the file's path under its type folder, with the extension removed.** - -| File on disk | Type folder | Key you write in JSON | -|---|---|---| -| `biomes/plains.json` | `biomes/` | `plains` | -| `biomes/temperate/plains.json` | `biomes/` | `temperate/plains` | -| `objects/trees/oak/big.iob` | `objects/` | `trees/oak/big` | -| `snippet/style/soft.json` | (snippets, see below) | `snippet/style/soft` | - -There is no namespace and no type prefix. You never write `biomes/plains` or `iris:plains` — the field you are filling in already knows it wants a biome, so it searches `biomes/` for you. Cross-references everywhere (region biome lists, object placements, spawner entity ids, structure piece pools) use exactly these keys. - -### What happens when the exact file is missing - -Iris first tries `/.json` and returns it if it exists. That is the normal path and the only one that works for nested keys. - -If there is no exact hit, Iris scans the type folder's own files (not subfolders) for any name whose first dot-segment equals the key. This is what makes `plains.disabled.json` still load for key `plains` — useful for parking a variant, surprising if you forgot you did it. If two files match, Iris logs `Ambiguous in : ...` and takes the alphabetically first one. Keep one canonical filename per key and this never bites you. - -The literal string `"null"` is refused with a warning by direct file lookups and by warning-enabled loads. Silent loads (the cross-pack fallback search) do not refuse it and will look for `null.json`. Do not name a file `null.json`. - -## How the pieces relate - -Generation walks a graph, and the graph starts at exactly one place: the dimension you named when you created the world. - -```text -dimension -> regions -> biomes -> generators (terrain height/noise) - -> objects (.iob models) - -> decorators (surface clutter) - -> structures (jigsaw / native) - -> spawners -> entities - -> loot -``` - -- **Dimension** — the root. Sets world height, environment, seed behavior, and which regions exist. One dimension equals one world type. -- **Region** — a spatial zone. Regions decide which biomes can appear where, and can carry their own objects and structures that span biome edges. -- **Biome** — the workhorse. Block layers, surface treatment, decorations, object placements, structures, and mob spawns. -- **Generator** — noise and height math. Biomes reference generators to get terrain shape; several biomes can share one. -- **Object** — a `.iob` block model with its own placement rules. -- **Structure / jigsaw pool / jigsaw piece** — multi-piece assemblies, either Iris-native or bridged to vanilla structures. - -The practical consequence: **a file that nothing references is inert.** It parses, it validates, it never generates. When a resource you wrote is not showing up, the first question is not "is the JSON wrong" but "is it reachable from the dimension." Work forward from `dimensions/.json` and find where the chain breaks. - -## Trace one reference end to end - -Do this once on a pack you did not write. It takes two minutes and makes everything above concrete. - -Prerequisites: a loadable pack under the packs root, `iris.all` (Bukkit) or gamemaster (modded), and an editor that will not reformat your JSON. - -1. Validate first, so you know a later failure is yours: `/iris pack validate pack=overworld` on Bukkit, `/iris pack validate overworld` on a mod loader. -2. Open `dimensions/overworld.json`. Pick one key out of the `regions` array. -3. Open `regions/.json`. Pick one key out of `landBiomes`. -4. Open `biomes/.json`. Follow its first generator, object, decorator, or structure reference into the matching type folder. -5. At each hop, confirm the key is the path under the type folder with the extension removed — nothing more. -6. Open the pack in Studio, focus that biome, save one valid edit, and wait for hotload. Re-validate. - -You are done when every reference resolved without guessing at a namespace or filename, hotload succeeded, and validation reports no blocking errors. - -## Snippets - -A snippet is a JSON fragment you write once and reference from many places. Types tagged `@Snippet("")` in the engine accept either an inline object or a string pointing at a snippet file. - -```json -"style": "snippet/style/soft-hills" -``` - -resolves to `/snippet/style/soft-hills.json`. - -| Rule | Actual behavior | -|---|---| -| Trigger | Only a JSON **string** value. Inline objects parse normally and never touch the snippet path | -| Required prefix | The string must start with `snippet/`. Anything else resolves to `null` **with no log line at all** — the most common silent snippet failure | -| Re-rooting | If the string starts with `snippet/` but not `snippet//`, Iris strips `snippet/` and re-roots the remainder under this field's own type. So `snippet/decorator/foo` on a style field becomes `snippet/style/decorator/foo`, not an error | -| On-disk path | `/snippet//.json`, resolved from the pack root, not from the type folder | -| Subfolders | Allowed; `` may contain `/`. Discovery walks the tree recursively | -| Missing file | Logs `Couldn't find snippet in ` and yields `null` for that field | -| Unreadable file | Logs `Couldn't read snippet in ()` and yields `null` | -| Inline parse failure | Different path: logs `Failed to read ... faking objects a little`, then substitutes a **default-constructed instance**, not `null` | -| Schema | Studio writes `.iris/schema/snippet/-schema.json` so the editor offers completions for `snippet//…` | - -The shipping overworld uses `snippet/decorator/*` and `snippet/style/*`. - -## Two copies of every pack - -This is the concept that causes the most confusion, so it is worth being blunt about. - -**The pack you edit and the pack a world generates from are different files.** - -When you create a non-Studio world, Iris copies the entire pack tree into `/iris/pack` and the world's engine reads only that copy for the rest of its life. Editing `packs/overworld/` afterwards changes nothing about that world. This is deliberate: a world's terrain must stay reproducible even if you keep authoring. - -Studio worlds are the exception. A Studio world's engine points directly at the live pack folder and watches it for changes, which is what makes hotload possible. - -| Mode | Pack the engine reads | Hotload | Copied into the world? | -|---|---|---|---| -| Studio (`studio=true`) | Live `packs//` (or the studio project path) | Yes | No | -| Production create | `/iris/pack` | No | Yes, atomic stage then publish via `StudioSVC.installIntoWorld` | -| Benchmark | `/iris/pack` | Studio flag still governs transient cleanup | Yes | - -Hotload opens a fresh `IrisData` on the same folder, reloads the dimension by its key, builds a replacement engine runtime under the lifecycle lock, publishes it, retires the old `IrisData`, then refreshes the editor workspace and datapacks in the background. If any step fails it rolls back to the previous runtime and reports the error. - -The watcher polls every 250 ms but only checks the folder about once per second, backing off to once per 4 s during maintenance or within 2 s of chunk generation. It watches `.json` and `.iob` and ignores anything under `.iris`. It runs only while the world is a Studio world that is not closing and not in jigsaw-studio mode. - -To push pack edits into an existing production world, see `update-world` in `25 - Pack Management.md`, or just create a new world — which is the right answer for any change to height or dimension type. - -## Where packs live - -| What | Bukkit-family | Fabric / Forge / NeoForge | -|---|---|---| -| Packs you author and download into | `plugins/Iris/packs//` | `config/irisworldgen/packs//` | -| Platform data dir (`settings.json`, languages, caches) | `plugins/Iris/` | `config/iris/` | -| A world's frozen snapshot | `/iris/pack/` | same, under the modded world root | -| Prefetch key indexes | `/prefetch//.ipfch` | same | -| Studio schemas | `/.iris/schema/` | same | - -On mod loaders the pack root and the platform data dir are two different folders — packs go under `config/irisworldgen/`, everything else under `config/iris/`. If you are hand-placing a pack on a modded server, `config/irisworldgen/packs/` is the one that matters. - -Folders whose names start with `.` are skipped when Iris lists packs, which is why `.iris/` inside a pack is invisible to the pack listing. Pack listing itself follows symbolic links; the stricter check (`requireSafePackTree`, used when installing a pack into a world) refuses a symlinked root, any symlink anywhere in the tree, and any non-regular file, and skips hidden subtrees. - -## Registrant folders - -`IrisData` registers 17 loaders. Each one owns exactly one folder name and one file extension. - -| Folder | Extension | What lives here and when you touch it | -|---|---|---| -| `dimensions/` | `.json` | World roots. Height, environment, region list, imports. **Required** — a pack with none is not loadable | -| `regions/` | `.json` | Which biomes appear in which climate zone, plus region-wide objects and structures | -| `biomes/` | `.json` | Where most authoring time goes: layers, surface, decorators, objects, structures, spawns | -| `generators/` | `.json` | Reusable noise/height math that biomes point at. Edit here to change terrain shape across many biomes at once | -| `objects/` | `.iob` | Binary block models saved from the wand or imported from schematics. Referenced by placements, never edited as text | -| `structures/` | `.json` | Structure graphs, including the `minecraft_*` graphs that bridge vanilla structures | -| `jigsaw-pools/` | `.json` | Weighted sets of pieces a jigsaw connector can pick from | -| `jigsaw-pieces/` | `.json` | One placeable piece: its object, its connectors, its rules | -| `entities/` | `.json` | Entity definitions with equipment, attributes, and custom data, used by spawners and markers | -| `spawners/` | `.json` | When and where entities spawn — time, block, biome, and rate rules | -| `markers/` | `.json` | Named points Iris records during generation so spawners and other systems can find them later | -| `loot/` | `.json` | Iris loot tables applied to generated containers | -| `blocks/` | `.json` | Named custom block states you can reference instead of repeating long block data strings | -| `expressions/` | `.json` | Math expressions callable from generators and placement rules | -| `images/` | `.png` | PNG maps sampled as noise or as direct biome/height input | -| `matter/` | `.mat` | Matter binaries. The loader exists and resolves keys, but no runtime system consumes them | -| `mods/` | `.json` | Injector/replacer documents. Loaded so schemas and tooling see them; the engine has no path that applies them | - -Anything else in a pack directory is not a resource type. The shipping overworld ships empty `caves/`, `ravines/`, and `jigsaw-structures/` folders plus `README.md`, `Schema.json`, and a `.code-workspace` file — none of those names are keys, and none are loaded. - -A reduced init path used by the datapack compiler registers only `biomes` and `dimensions`; that is internal and not something a pack author configures. - -## What makes a pack loadable - -`PackValidator` fails fast on three structural problems, in order: - -1. The pack folder is missing or is not a directory. -2. There is no `dimensions/` directory. -3. There are no `*.json` files **directly inside** `dimensions/`. Nested dimension files do not count toward this check. - -Passing those three does not mean the pack is loadable. `PackValidator` then runs roughly ten content validators — dimension, cave profile, loot, object/surface, structure graph, native structure, spawn, and content-key checks — and any blocking error from those also makes the pack not loadable. Content-key problems are blocking only under strict content mode. Read the first blocking error and fix that one; the rest are usually downstream. - -Presence on disk is a weaker notion than loadability: a pack "exists" if its directory is safe and holds at least one non-symlink `dimensions/*.json`. - -### Download key rules - -Downloaded pack keys must match `[a-z0-9_-]+`, and the check applies both to a caller-supplied expected key and to the key Iris derives from the archive. - -An archive with no expected key must contain exactly one dimension; its load key becomes the install folder name. Managed-release and listing downloads carry an exact expected key, so their archive may hold extra dimensions — the expected key picks the folder name, and the whole pack is validated before publication. - -## When a resource does not resolve - -| Symptom | Likely cause | Fix | -|---|---|---| -| File exists, key does not resolve | You included the extension or the type folder in the key, the case differs, or you counted the path from the wrong root | Rebuild the key as the exact relative path under the type folder, extension removed | -| Nested dotted variant not found | The dotted-name fallback only scans the type folder's own files, never subfolders | Give nested files their exact key name, or move the variant to the type folder root | -| File validates but never generates | Nothing in the dimension → region → biome chain references it, or a chance/filter excludes it | Trace forward from the dimension root; test with Studio focus or buffet mode | -| Snippet silently becomes null | The string does not start with `snippet/` — this failure logs nothing | Write the full `snippet//` form | -| Snippet loaded the wrong file | A `snippet//…` string was re-rooted under this field's own type | Use the type that matches the field | -| Studio does not offer a new resource in completions | Workspace schema enums are stale | `/iris studio update dimension=` on Bukkit, `/iris studio update ` on modded | -| Console warns "Ambiguous \ \" | Two files share a base name before the first dot | Keep one canonical filename; Iris took the alphabetically first | -| Production world ignores your fix | It is reading `/iris/pack`, not your live pack | Validate in Studio, then run the explicit world-update workflow or create a new world | - -## The shipping overworld pack - -For orientation when reading `packs/overworld/`: - -| Path | What is in it | -|---|---| -| `dimensions/overworld.json` | The single root dimension | -| `regions/*.json` | Climate zones that partition the biome set | -| `biomes/**` | Nested biome sets — temperate, hot, frozen, ocean, and so on | -| `generators/**` | Shared terrain generators | -| `objects/**` | `.iob` trees, structures, clutter, and vanilla imports | -| `structures/*.json` | Structure graphs, including `minecraft_*` bridges | -| `jigsaw-pieces/**`, `jigsaw-pools/**` | Jigsaw assembly data | -| `entities/standard/**`, `spawners/**`, `loot/**` | Mob and loot content | -| `images/*.png` | Noise and map images | -| `snippet/decorator/**`, `snippet/style/**` | Shared fragments referenced across biomes | - -Feature-level detail: `12 - Regions.md`, `13 - Biomes.md`, `14 - Generators & Noise.md`, `18 - Structures Overview.md`, `19 - Objects.md`, `21 - Jigsaw Structures.md`, `23 - Loot, Entities, Spawners, Markers.md`. diff --git a/docs/06 - Worlds & Lifecycle.md b/docs/06 - Worlds & Lifecycle.md deleted file mode 100644 index b7829a3f3..000000000 --- a/docs/06 - Worlds & Lifecycle.md +++ /dev/null @@ -1,318 +0,0 @@ -# 06 - Worlds & Lifecycle - -Creating an Iris world copies the pack into the world folder, registers the world so the server rebuilds it on every boot, and hands generation to the Iris engine. This page covers the full lifecycle on Bukkit-family servers and on Fabric, Forge, and NeoForge: create, load, unload, remove, main-world promotion, and the exact-slot replacement path. Iris worlds are managed under the level root as `dimensions/iris//` on Bukkit; mod loaders keep theirs in `iris-dimensions.json`. - -See also: `02 - Getting Started.md`, `04 - Commands & Permissions.md`, `05 - Concepts & Pack Layout.md`, `07 - Pregeneration.md`, `10 - Studio & VSCode Schemas.md`, `30 - Platform Differences.md`. - -## Create a world you intend to keep - -The difference between a throwaway world and one you will still be running in six months is that you decide the pack, seed, and height **before** the first chunk generates. None of those are editable afterwards without regenerating terrain. - -Before you start: a pack that validates, a seed you have written down, a current backup, and no other lifecycle command running. - -### Bukkit-family - -```text -/iris pack validate pack=overworld -/iris studio open overworld seed=1337 -``` - -Fly around, look at the terrain, then close the studio: - -```text -/iris studio close -``` - -Now create the real world. This is the step that freezes the pack: - -```text -/iris create release_candidate type=overworld seed=1337 -``` - -On Folia, this stages files and prints a restart instruction — stop the server, start it again, and the world loads on boot. On every other Bukkit-family server the world is created immediately. - -```text -/iris worlds -/iris tp release_candidate -``` - -**Success looks like:** `release_candidate` appears in `/iris worlds` as a loaded Iris world, you spawn in it, and chunks generate as you fly. - -Now prove it survives a restart, because a world that only works in the session that created it is not actually created: - -```text -/iris pregen start 352 world=release_candidate center=0,0 gui=false -``` - -Wait for it to finish (see `07 - Pregeneration.md`), restart the server cleanly, teleport back in, and fly past the pregenerated boundary. New terrain must still appear. - -**The world is now committed.** It generates from `/iris/pack`, its own frozen copy. Continuing to edit `packs/overworld/` affects Studio only. Never delete or replace that snapshot while the world is loaded. To ship pack changes into it later, use the deliberate path in `25 - Pack Management.md`; for anything that changes height or dimension type, create a new world instead. - -### Fabric / Forge / NeoForge - -```text -/iris pack validate overworld -/iris world enable irisworldgen:release_candidate overworld 1337 -/iris world status -/iris tp irisworldgen:release_candidate -``` - -The seed argument is optional and defaults to `1337`. `enable` also accepts the alias `create`, and the whole group is reachable as `/iris w`. - -```text -/iris pregen start 352 irisworldgen:release_candidate at 0 0 -``` - -Restart the server when it finishes. - -**Success looks like:** `/iris world status` lists the same dimension with the same pack after restart, and `/iris info irisworldgen:release_candidate` as a gamemaster reports seed `1337` read back from `iris-dimensions.json`. - -From here, `/iris world disable ` unloads it and keeps the files; `/iris world delete ` is the destructive path. Both require the dimension argument. - -## Remove a world without losing anything else - -Removal is the operation most likely to cost you data, so the order matters. - -1. **Back up first.** Nothing below is undoable. -2. **Get everyone out.** `/iris evacuate ` moves players to another loaded world, or kicks them if there is nowhere to go. Removal does this for you, but doing it deliberately means you see who was in there. -3. **Unload it.** `/iris unload `. This marks the world for maintenance, evacuates, unloads through the lifecycle service, and closes the generator. -4. **Remove it.** `/iris remove ` deletes the files. `/iris remove delete=false` keeps them and only unregisters — use this when you want the directory back later. -5. **Read the status Iris prints.** It tells you what actually happened; see the status table below. - -**Success looks like:** `UNREGISTERED` (files kept) or `DELETED` (files gone), the world is absent from `/iris worlds`, and its directory under `/dimensions/iris/` matches what you asked for. - -`DELETE_QUEUED` means the files could not be deleted now and were quarantined for deletion at next startup. Restart and confirm the target is gone before reusing that name. - -On mod loaders the equivalent is `/iris world delete `, which disables and then wipes chunk and mantle data. - -### If unload hangs - -Unload has a hard 150-second ceiling. If the world, generator, or scheduler work has not settled by then, Iris marks a terminal timeout, requests a server restart, and fails the command. Let the restart happen. Do not delete a live world directory to force the issue. - -## Lifecycle recovery - -| Symptom | What it means | What to do | -|---|---|---| -| "busy" response | Another lifecycle operation holds the coordinator. It is one global mutex, so a pack download or publish blocks world create just as much as another create does | Wait for the running operation. Retrying concurrently will not help | -| Startup validation pending / failed / restart-required on login or create | External datapack ingestion or dimension-pack validation has not reached a safe state | Fix the first logged failure, or complete the requested restart. Do not hand-create world folders or hand-edit `bukkit.yml` | -| Folia create succeeded but teleport says no such world | Folia create only stages files and registration | Restart, then load or teleport | -| Load reports missing or inconsistent data | The dimension root, the `bukkit.yml` registration, or the `iris/pack` snapshot is incomplete | Keep the directory and restore from backup. Load never re-downloads a snapshot | -| Unload hits its terminal timeout | Work did not drain in 150 s | Allow the restart. Do not force-delete the live directory | -| Remove returns `DELETE_QUEUED` | Files were quarantined for startup deletion | Restart, confirm the target is gone, then reuse the name | -| Modded registry renamed to `.broken-` | The whole `iris-dimensions.json` failed to parse | Keep the backup. Iris logs whatever ids it could salvage from the raw text; recreate each with its original pack, dimension, and seed, then verify with `/iris world status` | - -## Identity and storage - -| Item | Rule | -|---|---| -| Managed namespace | The safe/managed API accepts `iris` only, so create, load, and remove can never touch a `minecraft:` or third-party dimension folder | -| Logical name | For `iris:foo` the logical name is `foo` — that is what you type in commands | -| Storage root | `Server#getLevelDirectory` on Paper. If that method is missing, Iris latches a permanent fallback to `/` read from `server.properties` (default `world`) | -| Dimension folder | `/dimensions/iris//` | -| Pack snapshot | `/iris/pack/` | -| Pregen cache | `/iris/pregen/` | -| Registry | `worlds.json` in the Iris data folder (a flat `worldIdentity → dimensionType` map, written atomically) plus the `worlds:` section of `bukkit.yml`, which stores `generator: "Iris:"` and the seed | -| Name normalization | The name is lowercased and spaces become `_` before validation, so `My World` becomes `my_world` rather than being rejected | -| Name constraints | After normalization the key must match `[a-z0-9_-]+`; `/`, `\`, and `..` are rejected as unsafe path segments, and symlinks on any component of the dimension root are refused | -| Reserved names | `/iris create` rejects `iris` and `benchmark` case-insensitively. This is a create-time check only; the storage layer does not enforce it | - -The vanilla main, nether, and end worlds map to `minecraft:` keys derived from `level-name`, `level-name_nether`, and `level-name_the_end`. They are not Iris-managed dimension folders and are only reachable through the exact-slot replacement path below. - -## Command surface (Bukkit) - -| Command | What it does | -|---|---| -| `/iris create [type=default] [seed=1337] [main=false] [overwrite=false]` | Create a managed world now, stage one for Folia's next boot, or stage an exact-slot replacement | -| `/iris load ` / `/iris import ` | Reconcile a world that already exists on disk back into the server. Never downloads anything | -| `/iris unload ` | Evacuate, unload, close the generator. The safe first half of removal | -| `/iris remove [delete=true]` | Unregister the world, and by default delete its files | -| `/iris evacuate ` | Move every player out of an Iris world, or kick them if no other world is loaded | -| `/iris tp [player=]` | Teleport yourself, or a named player, to the world spawn | -| `/iris worlds` | List which loaded worlds are Iris worlds and which are not | - -Aliases and permissions: `04 - Commands & Permissions.md`. - -### Create parameters - -| Param | Default | What it controls | -|---|---|---| -| `name` (`world-name`) | required | Becomes `iris:`. With `overwrite=true` it may instead name the configured main world or its `_nether` / `_the_end` alias | -| `type` (`dimension`, `pack`) | `default` | Which pack and dimension to generate from. `default` resolves to `settings.generator.defaultWorldType` (`overworld`); otherwise a pack name or `pack:dimensionKey` | -| `seed` | `1337` | The world seed. Ignored for exact vanilla-slot overwrites, which must keep the level's existing authoritative seed | -| `main` (`main-world`) | `false` | Promote this world to `level-name` in `server.properties`. Happens in a JVM shutdown hook on Paper-family, or inline during Folia staging | -| `overwrite` (`force`) | `false` | Stage a validated replacement of an existing world slot for the next restart. Never touches a loaded world in place | - -Create refuses to run on the primary thread. Before it takes a lifecycle lease it requires startup datapack validation to be ready and the chosen pack to have a loadable validation result; then the `WORLD_MUTATION` / `WORLD_CREATE` lease must be free or the command fails busy. A refusal at any of those gates leaves no dimension folder and no registration behind. - -## What create actually does (non-Folia) - -1. Resolve the managed key and dimension. No directory is created yet. -2. Require startup datapack readiness and a loadable validation result for the owning pack. -3. Install datapacks for the dimension types. If the types are not loaded yet, queue a restart. -4. Copy the pack into `/iris/pack` through `StudioSVC.installIntoWorld` — staged into a temp directory, published atomically, then validated at that exact root. A validation failure rolls the publication back. -5. Build a `WorldCreator` with the Iris generator and `studio=false`. -6. Create the world through `WorldLifecycleService` / NMS async create, with a 120-second timeout. A timeout triggers a server restart rather than leaving a half-created world. -7. Register the world in `bukkit.yml` with the Iris generator, dimension key, and seed. Update the Multiverse link if Multiverse is present — that step has its own 30-second budget and also escalates to a restart. -8. Run creation-time pregen if the caller attached a `PregenTask` through the API. - -Rollback phases carry the same 120-second budget. - -## Folia staging - -Folia cannot create worlds at runtime, so `/iris create` becomes a staging operation: - -1. Require startup datapack readiness and a loadable pack validation result. -2. Acquire the `WORLD_CREATE` lease. -3. Install datapacks if they changed. -4. Abort if the dimension folder already exists. -5. Stage the pack into the managed dimension root through `installIntoWorld`; the published snapshot must pass exact-root validation. -6. Register the world in `bukkit.yml`. -7. If `main=true`, promote the main-world files immediately under the same lease. A failure here rolls back `bukkit.yml` and deletes the staged folder. -8. Tell the operator to restart. Generation and loading happen on the next boot. - -`WorldLifecycleStaging` holds the staged generator and biome provider for the backend that picks them up at load. - -## Exact world-slot replacement - -`overwrite=true` is how you put Iris generation into a slot that already exists — including the vanilla overworld, nether, or end. It uses lifecycle kind `WORLD_REPLACE` and always stages for a full restart. There is no in-place variant. - -It requires a Paper-family early bootstrap, which plain Spigot never runs; on Spigot the command fails closed. The target dimension folder must already exist — ordinary create is still the path for a new world. - -Accepted targets are safe `iris:*` keys and exactly three vanilla slots resolved from the configured level name: `minecraft:overworld`, `minecraft:the_nether`, and `minecraft:the_end`. A vanilla slot additionally requires: - -- a pack whose environment matches the slot (`NORMAL`, `NETHER`, or `THE_END`), checked both before staging and after install; -- `allow-nether` or `allow-end` enabled in the server config for those two slots. - -Foreign namespaces, other `minecraft:*` keys, path traversal, symlinks, and special filesystem entries all fail closed. `main=true` may accompany `overwrite` only when the target is `minecraft:overworld`. - -Minecraft stores one authoritative seed for a level, so every vanilla-slot replacement keeps the loaded overworld's seed and Iris warns you when that differs from the `seed` you passed. Changing the level seed is the ordinary new-main promotion workflow, not this one. - -### How the transaction is made safe - -The stage copies and validates a fresh frozen pack on the same filesystem, fingerprints it, binds a journal to the canonical level root and logical world name, records the original target and the existing `bukkit.yml` generator and seed, then compare-and-swaps that one configuration entry. Several distinct slots can be queued before a single restart. - -At the next boot, Paper's bootstrap reconciles each authorized transaction before Iris compiles its aggregate datapack and before Minecraft builds registries: it atomically moves the old dimension directory to a retained sibling backup and publishes the stage. The filesystem must support atomic replacement for the world directories, the journal, and `bukkit.yml`; without it Iris refuses rather than falling back to a destructive move. - -Publication retains Paper's per-world `data/paper/metadata.dat`, `data/paper/level_overrides.dat`, and `data/minecraft/world_gen_settings.dat` so the slot keeps its metadata and authoritative seed. Old `region`, `entities`, `poi`, and Iris runtime data are never merged — they stay in the backup, and the replacement starts from the staged snapshot. - -The backup is only eligible for deletion after `WorldLoad` proves the exact namespaced identity, Iris generator, selected dimension, seed, vanilla-slot environment, and an unchanged pack fingerprint. A failed check journals a rollback and requests another restart, after which cold bootstrap restores the retained directory and the prior `bukkit.yml` entry. A crash between any move, config write, or journal phase is retried idempotently. Conflicting manual configuration, changed roots or names, changed staged bytes, unsafe storage, or a duplicate or corrupt journal aborts early bootstrap and preserves the artifacts rather than guessing. - -## Studio create - -Studio worlds use `IrisCreator.studio(true)` and differ from production worlds in ways that matter: - -- Startup datapack validation and the pack's own validation must both be loadable before any Studio folder, snapshot, generator, or Bukkit world is created. Missing validation fails closed. -- The pack is **not** copied into the world folder, except for benchmark runs. The engine reads the live pack directly, which is what enables hotload. -- Studio worlds are transient. Unloaded Studio worlds are cleaned up, and their `bukkit.yml` entries are removed during shutdown cleanup. -- Open and close go through the `StudioSVC` transition queue (`10 - Studio & VSCode Schemas.md`). -- Biome Buffet prepares a changed focus before opening the chunk generation session; its exclusive fair-stage admission downgrades straight to the retained chunk permit so no other transition can slip in between the focus hotload and that chunk. -- Ordinary Studio suppresses native structure starts only while the initial FULL entry chunk loads, then restores them for later preview chunks. -- A failed open never unloads or closes the generator while that asynchronous entry request is still active. Another Studio open is rejected in the meantime; cleanup starts once it settles, and if it is still active 120 seconds later the transient world is queued for deletion at the next clean startup. - -## Load - -`/iris load` (alias `/iris import`) reconciles a world that already exists on disk: - -1. Parse the managed key and require the dimension root directory to exist. -2. Run `BukkitWorldReconciler.loadWorld(bukkit.yml, worldKey)`. -3. Report success, busy, restart-required, or failure. - -Load never downloads a pack. The world must already have `iris/pack` content and registration data consistent with Iris. Reconciliation checks startup readiness and then lazily validates that world's exact snapshot root before it touches `bukkit.yml` or calls a world backend. Validation results are path-scoped, so two worlds whose snapshot folders are both named `pack` cannot authorize or reject one another. - -## Unload - -`/iris unload` runs synchronously from a player origin: - -1. Require an Iris world and acquire the `WORLD_UNLOAD` lease. -2. Mark the world for maintenance. -3. `IrisToolbelt.evacuateAsync` → `WorldLifecycleService.unloadAsync(world, true)` → `generator.closeAsync()`. -4. On a 150-second terminal timeout, mark the timeout, request a server restart, and fail the future. - -There are two timers in play: the inner `WorldLifecycleService` unload has its own 120-second budget, and the command wraps the whole sequence in the 150-second ceiling. - -`WorldUnloadEvent` stops Iris engine maintenance immediately, but Iris does not treat it as proof that Paper's chunk scheduler has drained. Generator close waits for the raw world-lifecycle backend to confirm a successful unload, and the 26.2 noise pipeline holds one generation lease through terrain generation and worldgen-heightmap priming. - -## Evacuate - -`/iris evacuate ` moves every player out of an Iris world into another loaded world, or kicks them when there is nowhere else to go. It runs as a step inside both unload and removal, and is worth running on its own first so you can see who was affected. - -## Remove - -`/iris remove [delete=true]` delegates to `IrisWorldRemovalService`, which reports one of 18 statuses. - -| Status | Meaning | -|---|---| -| `UNREGISTERED` | Unloaded and unregistered; files kept. This is success for `delete=false` | -| `DELETED` | Unregistered and files deleted. Success for the default | -| `DELETE_QUEUED` | Files could not be deleted now and were quarantined for deletion at next startup. Restart and confirm before reusing the name | -| `BUSY` | Another world or pack mutation holds the coordinator | -| `INVALID_IDENTIFIER` | The name is not a parseable managed key | -| `PROTECTED_WORLD` | The target is a world Iris refuses to remove | -| `NOT_IRIS_WORLD` | The target exists but is not Iris-managed | -| `UNSAFE_PATH` | The resolved directory failed a path-safety check (traversal, symlink, wrong namespace) | -| `NOT_FOUND` | No such managed world | -| `RESOLUTION_FAILED` | Iris could not resolve the world identity to a directory | -| `TELEPORT_FAILED` | Players could not be evacuated, so removal stopped before touching files | -| `GENERATOR_CLOSE_FAILED` | The Iris generator did not close cleanly; the world may still hold resources | -| `UNLOAD_FAILED` | The server refused or failed to unload the world | -| `CONFIGURATION_FAILED` | The `bukkit.yml` entry could not be updated | -| `REGISTRY_FAILED` | The `worlds.json` registry could not be updated | -| `QUARANTINE_FAILED` | The world directory could not be moved to the quarantine name | -| `DELETE_FAILED` | Quarantine succeeded but deletion did not | -| `INTERNAL_FAILURE` | An unexpected error; read the logged cause | - -Any status other than `UNREGISTERED`, `DELETED`, or `DELETE_QUEUED` means the registry may have changed without the files being removed, and a quarantine directory may still exist. Check the world directory before retrying. - -Only safe `iris` namespace dimension paths are mutable. Each phase has a 120-second timeout and can request a restart when it gets stuck. - -With `delete=true`, Iris records the exact quarantine name in a durable startup queue **before** moving the directory, so a crash mid-delete still gets cleaned up on the next boot. Both immediate cleanup and the startup retry snapshot every directory's direct children before deleting, reject symlinks and special filesystem entries, and keep the queue entry with the full error when a concurrent writer or filesystem failure leaves content behind. - -## Main world promotion - -`main=true` on a non-Folia server installs a JVM shutdown hook. At shutdown it rewrites `level-name` and `level-seed` in `server.properties` and publishes files: - -1. Stage a temp directory (`..promoting-`) under the world container. -2. Copy the shared `data`, `datapacks`, and `players` folders from the current level root. -3. Copy the Iris dimension tree into `/dimensions/minecraft/overworld`. -4. Re-check that the target is absent, then move the stage into place with a plain rename. -5. Write `server.properties` atomically, with an fsync and an `ATOMIC_MOVE` (falling back to a non-atomic write if the filesystem refuses). - -Only step 5 is atomic; the directory move in step 4 is an ordinary rename. Promotion requires the target level folder to be absent and aborts if it finds a symlink anywhere in the copied tree. The whole sequence runs under a `WORLD_MUTATION` / `WORLD_PROMOTE` lease. Folia performs the same publish inline during staging rather than deferring to shutdown. - -To put Iris into the currently configured main slot **without** creating a new level root, name that exact main world and use `overwrite=true`. That keeps the top-level level root, shared datapacks, player data, and non-target dimensions intact. Plain `main=true` remains the new-level-root workflow above. - -## Modded persistent-dimension registry - -Fabric, Forge, and NeoForge persist dynamic Iris worlds in `/iris/iris-dimensions.json`: - -```json -{ - "dimensions": [ - { "id": "irisworldgen:myworld", "pack": "overworld", "dimension": "overworld", "seed": 1337 } - ] -} -``` - -All four fields are required per entry: `id` is the registered dimension id, `pack` is the installed pack folder, `dimension` is its dimension load key, and `seed` is the generation seed. Writes go to a sibling `iris-dimensions.json.tmp`, get an fsync, and are moved into place with `ATOMIC_MOVE` where the filesystem supports it. - -Entries that are individually invalid are logged, kept verbatim, and re-appended on the next write — Iris never silently drops one. Duplicate ids keep the first valid entry and warn. - -If the whole file fails to parse, only the startup load path quarantines it as `iris-dimensions.json.broken-`, salvages whatever ids it can from the raw text into the log, and continues with no persistent Iris dimensions. Every other code path throws rather than discard persistent worlds. Keep the quarantined file, recreate each reported world with `/iris world create`, verify pack, dimension, and seed, then delete the backup. - -## Pack snapshot vs studio - -| Operation | Effect on the pack | -|---|---| -| Production create | Full pack tree installed under the world's `iris/pack` and frozen there | -| Studio open | Engine reads the live packs root; nothing is installed into the world | -| `/iris studio package` | Exports an archive; no world is touched | -| `/iris dev update-world` | Replaces a world's `iris/pack`. Unsafe, and restarts the server if an engine still holds that pack | -| Hotload | Studio only. A production snapshot never changes underneath a running world | - -## Concurrent lifecycle guards - -`LifecycleOperationCoordinator` is a **single global mutex** shared by the `WORLD_MUTATION` and `PACK_MUTATION` domains. It is not one lock per domain: a pack download or publish will make a world create report busy, and vice versa. A third domain, `SERVER_LIFECYCLE`, is reserved and cannot be acquired. - -Thirteen operation kinds run under it: `WORLD_CREATE`, `WORLD_LOAD`, `WORLD_UNLOAD`, `WORLD_REMOVE`, `WORLD_REPLACE`, `WORLD_PROMOTE`, `STUDIO_OPEN`, `STUDIO_CLOSE`, `PACK_CREATE`, `PACK_DOWNLOAD`, `PACK_PUBLISH`, `DATAPACK_COMPILE`, and `SERVER_RESTART`. - -Ordinary create also refuses when the dimension root already exists or the world is already loaded. Exact replacement runs as a separately journaled restart transaction and never relaxes the removal-path protections. diff --git a/docs/07 - Pregeneration.md b/docs/07 - Pregeneration.md deleted file mode 100644 index fc4e0d88e..000000000 --- a/docs/07 - Pregeneration.md +++ /dev/null @@ -1,207 +0,0 @@ -# 07 - Pregeneration - -Pregeneration forces chunks to generate ahead of time so players never wait on terrain generation when they explore. You give it a block radius and a center; Iris walks the square area region by region in a spiral and generates every chunk in it. One pregen job runs at a time per server, driven by `/iris pregen` on Bukkit-family and by the same subcommand tree on mod loaders. - -See also: `02 - Getting Started.md`, `03 - Configuration.md`, `04 - Commands & Permissions.md`, `06 - Worlds & Lifecycle.md`, `29 - Client HUD & Protocol.md`, `33 - Performance Tuning.md`. - -## Pregenerate 10,000 blocks around spawn - -This is the common production task: generate a large area once, up front, so the server never generates terrain during play. - -Before you start you need a world whose ordinary chunk generation already works, free disk space for the area, a backup or a world you can afford to lose, and no other pregen job running. - -**Radius is in blocks, not chunks and not regions.** A radius of `10000` covers 20,000 blocks across, which is 1,251 chunks per axis and **1,565,001 chunks total**. That is hours of work and tens of gigabytes. Do not type it first. - -### 1. Prove the pipeline with a small run - -```text -/iris pregen start 352 world=myworld center=0,0 gui=false -``` - -That is 2,025 chunks and finishes in a minute or two. Watch it: - -```text -/iris pregen status -``` - -You should see the world name, `2,025` total chunks, a rising generated count, a chunks-per-second rate, an ETA, and the method name. **Failed count must stay at zero.** If failures accumulate, stop now — a big run will only produce more of them. - -### 2. Run the real thing - -```text -/iris pregen start 10000 world=myworld center=0,0 gui=false -``` - -If your spawn is not at `0,0`, stand at spawn and use `center=me` instead. That token only works for a player sender; from console, pass explicit coordinates. - -Drop `gui=false` only if the server has a desktop and you want the visual renderer. - -### 3. Know when it is done - -Poll `/iris pregen status`. The job is finished when: - -- generated equals total (1,565,001 for this run), -- failed is still `0`, -- and `/iris pregen status` reports no active task after it ends. - -The last one is the real signal. While a job exists, status prints progress; once the job closes, status tells you there is no active pregeneration task. That transition is the completion condition — not the percentage, which can sit at 100% while in-flight chunks finish writing. - -Then restart the server cleanly and fly to the edge of the generated area. Chunks inside must load without generating; chunks past the boundary must generate normally. - -### Pausing and stopping - -```text -/iris pregen pause -``` - -`pause` is a **toggle**, and `resume` is just an alias for the same command. Running `/iris pregen resume` on a job that is currently running will pause it. The command echoes the resulting state, so read the reply rather than assuming. - -```text -/iris pregen stop -``` - -Stop lets in-flight chunks finish, then cancels. Wait for it to actually close before starting another job — starting a new one closes the previous instance, which is not the same as it having shut down cleanly. - -Unloading or removing a world also stops a pregen targeting that world. That path blocks for up to 15 seconds waiting for the job to close and throws if it does not. - -### Fabric / Forge / NeoForge - -```text -/iris pregen start 352 irisworldgen:myworld at 0 0 -/iris pregen start 10000 irisworldgen:myworld at 0 0 nocache -``` - -The dimension argument comes after the radius, `at ` after that, and `gui`, `sync`, and `nocache` are order-free literal flags you can combine. Radius accepts `1`–`100000`. Modded pregen shows a boss bar automatically unless the player is running the Iris client mod, which draws its own HUD instead. - -## Recovery - -| Symptom | Check | What to do | -|---|---|---| -| Start reports an active job | There is one pregen job server-wide, not one per world | Check `/iris pregen status`; finish or stop it, and wait for closure before retrying | -| Total chunk count is not what you expected | Bounds are inclusive and round outward to whole chunks, so the area is slightly larger than `radius × 2` | Recompute: chunks per axis is `ceil(radius/16) - floor(-radius/16) + 1` centered on your center chunk | -| Failed count climbing | Chunk load timeout, a generation exception, disk failure, or a lifecycle interruption | Stop, fix the first logged failure, confirm ordinary generation works, then retry the same small area | -| `serial=true` rejected | Strict serial generation needs a Paper-compatible server | Use the normal method, or run the diagnostic on Paper | -| Desktop GUI never opens | The server is headless, or `gui.useServerLaunchedGuis` is off | Use `gui=false` and watch status, console, or the client HUD | -| Progress repeatedly stalls | Heap high-water or mantle plate backpressure is engaging | Stop the job before tuning. Lower resident plates and in-flight work before raising anything heap-sensitive | -| Restart regenerates work you already did | The cache wrapper was off — Folia routing disables it, `nocache` was passed, the world has no engine access, or the files under `iris/pregen` were deleted | Treat the rerun as uncached. Regeneration alone is not evidence of corruption | -| "world may not be fully loaded" warning | A player sender started pregen in a world Iris has no engine access to | Confirm the world is loaded and its engine initialized before trusting the run | - -## Commands - -| Command | What it does | -|---|---| -| `/iris pregen start [world=] [center=0,0] [gui=true] [serial=false]` | Start a job. Closes any previous job instance first | -| `/iris pregen stop` (alias `x`) | Request stop. In-flight chunks finish, then the job cancels asynchronously | -| `/iris pregen pause` (alias `resume`) | Toggle pause. One command, two names — it flips whatever state the job is in | -| `/iris pregen status` | Print a progress snapshot for the active job, or report that none exists | - -The command root is `/iris pregen` with alias `/iris pregenerate`. - -### `start` parameters - -| Param | Default | What it controls | -|---|---|---| -| `radius` (`size`) | required | Blocks from center on both X and Z. Must be greater than zero. The chat confirmation reports the span as `radius × 2` blocks, which slightly understates the real area because bounds round outward to whole chunks | -| `world` | contextual | Which world to generate. Falls back to your current world. A non-Iris world runs the hybrid method with no engine, so no engine-backed cache wrapper | -| `center` (`middle`) | `0,0` | Block X/Z the square is centered on. Accepts `me`/`here`/`self` for your position, `look`/`cursor` for your look target, and `player:` — all player-sender only | -| `gui` | `true` | Open the desktop renderer when the host supports it. Headless servers log and carry on | -| `serial` | `false` | Generate one chunk at a time through the strict serial hybrid method. **Requires Paper**; rejected elsewhere. For diagnosing instability, not for throughput | - -## Area model - -`PregenTask` builds saturating block bounds at `center ± radius`, converts them to chunk and region ranges, then iterates regions in a spiral from the center, ordering chunks within each region toward the center too. That ordering is why the area around your center becomes playable first. - -Bounds are inclusive on both edges: the minimum block floors to a chunk, the maximum ceils. For radius 352 at `0,0` that gives chunks `-22..22` on each axis — 45 per axis, 2,025 total. - -| Limit | Value | -|---|---| -| Maximum region span per axis | 117,189 regions, which is the ±30,000,000 block Minecraft world limit | -| Oversized or non-positive request | `IllegalArgumentException` at construction, so the command fails immediately instead of hanging | -| Modded radius argument range | 1 to 100,000 | - -## Generation methods - -| Situation | Method used | -|---|---| -| Iris world, parallel (default) | `HybridPregenMethod(world, threadCount)` | -| Iris world, `serial=true` | `HybridPregenMethod.strictSerial(world)` | -| Non-Iris world | The same hybrid method with a null engine | -| Caching enabled, engine present, scheduler not Folia | `CachedPregenMethod` wrapped around whichever of the above applies | - -`HybridPregenMethod` delegates to `AsyncOrMedievalPregenMethod`, which picks `AsyncPregenMethod` on Paper and `MedievalPregenMethod` elsewhere. Region-at-a-time generation is not supported on this path; it is always chunk by chunk. - -The `threadCount` argument is vestigial — `AsyncPregenMethod` ignores it and recomputes concurrency from the server's worker pool, CPU count, and world-gen thread settings, and `MedievalPregenMethod` takes no thread count at all. Tune concurrency through the settings in `33 - Performance Tuning.md`, not by expecting that parameter to do something. - -## Cache - -The cache records which chunks are already generated so a restarted or repeated run can skip them. - -| Piece | Where | Behavior | -|---|---|---| -| Per-world skip cache | `/iris/pregen/` | Created through `GlobalCacheSVC.createDefault`. Records generated chunks and regions. Only consulted when the `CachedPregenMethod` wrapper is active | -| `world.globalPregenCache` | `settings.json`, default `false` | When true, Iris also maintains the cache during ordinary play — creating it at world init and marking chunks on every `ChunkLoadEvent`, so normal exploration counts toward it. When false, the pregen job still gets a real on-disk cache; it just is not fed by ordinary chunk loads | -| Folia | Resolved runtime scheduler is Folia | The cached wrapper is disabled entirely for pregen | -| No engine | Non-Iris world | The wrapper is skipped, since the cache is keyed to the engine's world identity | - -Cache contents are written on world unload, on service disable, when the setting is toggled off, and when the cached method closes or saves. If the Iris service itself is disabled, `createDefault` hands back an empty cache rather than touching disk. - -Modded pregen keeps its cache in the equivalent `/iris/pregen`. - -## Mantle and heap caps - -Pregen generates faster than chunks get saved, so Iris throttles itself against tectonic plate residency and heap use. These are the knobs that decide whether a large run finishes or thrashes. - -| Control | Default and rule | Why you would change it | -|---|---|---| -| `pregen.maxResidentTectonicPlates` | `96`, floored at `16` | The headline speed/memory tradeoff. Raise it to keep more mantle in RAM and cut re-reads; lower it when the run is pushing the heap | -| Effective plate cap | `max(16, min(baseCap, heightScaledCap, heapBudgetCap))` | Computed, not configured. `heightScaledCap` scales the base cap by `384 / worldHeight`, so tall worlds automatically hold fewer plates; `heapBudgetCap` allows about 60% of max heap against a 48 MB reference plate | -| `mantleBackpressureWaitMs` | `25`, clamped 5–1000 | How long the generator sleeps per backpressure check. Rarely worth changing | -| `mantleBackpressureTimeoutMs` | `60000`, clamped 5s–600s | How long backpressure waits before giving up. On timeout Iris logs and proceeds anyway — it never deadlocks the run | -| Hard cap trigger | Loaded plates greater than `effectiveCap × 2` | Forces a wait-and-evict cycle. Seeing this in logs means the cap is too high for your heap | -| Heap high water | Pause at 92% used, release at 82% | Deliberate hysteresis. Generation stalls at 92% and does not resume until it drops to 82%, so brief spikes do not cause flapping | -| Heap panic | 96% requests a panic reclaim and GC | Throttled to once per 30 seconds. Repeated panic lines mean the heap is undersized for the settings | -| `pregen.saveIntervalMs` | `30000`, clamped 5s–900s | How often the pregen loop flushes. Shorter means less lost work on a crash and more I/O | - -Full settings reference: `03 - Configuration.md`. Tuning guidance: `33 - Performance Tuning.md`. - -## Other `pregen` settings - -| Key | Default | What it controls | -|---|---|---| -| `runtimeSchedulerMode` | `AUTO` | Whether Iris treats the server as Folia or Paper-like. This is what decides if the pregen cache wrapper is available. `AUTO` probes the server; a regionized server always resolves to `FOLIA`, and configuring `FOLIA` on a non-regionized server is forced back to `PAPER_LIKE` | -| `paperLikeBackendMode` | `AUTO` | Which Paper-like lifecycle backend loads chunks. `AUTO` resolves to `TICKET` | -| `chunkLoadTimeoutSeconds` | `15`, clamped 5–120 | How long a single chunk load may take before it counts as a failure. Raise it on slow storage; a rising failed count with a low value here is usually I/O, not corruption. Modded pregen floors this at 120 seconds regardless of the setting | -| `timeoutWarnIntervalMs` | `500`, minimum 250 | How often stalled loads warn. Purely log volume | -| `moddedPregenInFlight` | `0` | In-flight chunk budget for modded pregen. `0` auto-resolves to `max(16, min(48, cpu × 2))`; an explicit value is capped at 512 and floored at 8 | - -## Pause, stop, and status - -| Action | What happens | -|---|---| -| Pause | `PregeneratorJob.pauseResume()` flips the flag. The generator loop spins while paused, and also while heap high-water is engaged | -| Stop | `shutdownInstance()` closes the pregenerator and interrupts the worker asynchronously, so the command returns before the job is fully closed | -| Status | `progressSnapshot()` returns percent, generated, total chunks, chunks remaining, chunks per second, ETA, elapsed time, method name, paused flag, failed count, world name, and world identity | - -Failed chunks are counted separately from generated ones and only appear in the status line when the count is above zero. A run can reach 100% with failures — check the failed count, not just the percentage. - -## HUD, GUI, and protocol - -| Surface | Behavior | -|---|---| -| Desktop GUI (Bukkit) | `PregenRenderer` opens when `gui=true` and a GUI host is available. It draws the progress text and a pause hint over a chunk map — there is no color legend on screen. Chunks being generated are muted green and network-sourced chunks purple. For an Iris world, finished and pre-existing chunks are painted with the engine's biome colors instead of flat status colors; the flat green and dark-green status colors only appear when there is no engine | -| Boss bar | **`/iris pregen` on Bukkit shows no boss bar.** Only creation-time pregen and studio progress claim a HUD slot. Modded pregen does show a boss bar — green while running, yellow while paused — and skips it entirely for players running the Iris client mod | -| Client HUD | `IrisProtocolServer.broadcastPregenProgress` sends progress every tick to connected Iris client sessions that hold the pregen capability, plus per-region deltas. This is the only path client HUDs are fed on any platform | - -GUI toggles live at `settings.gui.useServerLaunchedGuis` and `settings.gui.maximumPregenGuiFPS`. Client HUD detail: `29 - Client HUD & Protocol.md`. - -## Performance profile - -Starting a pregen applies `PregenPerformanceProfile` to the engine before the job is constructed. It raises the noise cache to at least 4096 entries and enables the fast cache path, then rebuilds the biome complex if anything actually changed. The studio `profile` command applies the same profile while measuring pack cost, so pregen and profiling numbers are comparable. - -## Operator notes - -- Radius is in **blocks**. Every mistake in this area is someone typing a chunk count. -- Re-running over the same area is fast only when the cache wrapper was active and the files under `iris/pregen` still exist. -- Serial mode is a diagnostic. Use it to reproduce a generation failure deterministically, not to go faster. -- Stop a job before tuning mantle or heap settings. Changing them mid-run makes the before/after meaningless. -- Change one setting at a time against the 352-block baseline before scaling back up. See `33 - Performance Tuning.md`. diff --git a/docs/08 - Localization.md b/docs/08 - Localization.md deleted file mode 100644 index f3585ad49..000000000 --- a/docs/08 - Localization.md +++ /dev/null @@ -1,151 +0,0 @@ -# 08 - Localization - -Iris ships its command, Studio, runtime, HUD, and UI text as typed Java message catalogs, with translated overlays for seventeen languages and an operator-editable override file per locale. You pick the server language with `general.language` in `settings.json`; you change individual strings by dropping a partial JSON file into `languages/overrides/`. Client keybind labels are a separate surface and live in the mod jar's Minecraft lang assets. See also `03 - Configuration.md`, `04 - Commands & Permissions.md`, and `29 - Client HUD & Protocol.md`. - -## Change one message - -Say you want German, but you dislike the wording Iris uses when someone mistypes a subcommand. You need two things: the locale setting, and an override file that redefines exactly that one key. - -Prerequisites: write access to the Iris data folder, a backup of `settings.json`, and an account that can run `/iris reload`. - -1. Set `general.language` to `de_DE` in `settings.json` (`plugins/Iris/settings.json` on Bukkit-family, `/settings.json` on a mod loader). -2. Create `/languages/overrides/de_DE.json`. Iris creates the `languages/overrides/` folder itself on the first locale load, so it should already exist. - - ```json - { - "locale": "de_DE", - "messages": { - "iris.command.unknown": "Kenn ich nicht. Probier /iris help" - } - } - ``` - -3. Run `/iris reload`. A green `Hotloaded settings and locale de_DE.` means the settings and the locale both applied. A yellow `Settings were reloaded, but locale ... was rejected` means the overlay failed validation and the previous locale is still active — read the errors in the console before going further. -4. Run `/iris` with a subcommand that does not exist, for example `/iris zzz`. - -Success looks like your override text appearing verbatim. Everything else in the same session — help output, pregen status, Studio messages — should be in German from the bundled `de_DE` overlay, and any key that neither file defines falls back to the built-in English rather than printing a raw key id. - -Edit the file again and save it. The settings hotload poll calls `IrisLanguage.update()`, which compares the override file's path, modification time, and length against what it last loaded, so an edit is picked up on the next poll with no command and no restart. Delete the test override when you are done. - -If you are authoring a whole new locale, translate one command group first and confirm it loads before you translate the rest. A single bad key rejects the entire file. - -### Recovery - -| Symptom | What actually happened | Fix | -|---|---|---| -| `Rejected locale setting '...'` in the log | The value does not match `[A-Za-z0-9_-]+`, so it never reached the loader | Correct the string in `settings.json`; the previously active locale keeps running in the meantime | -| `Rejected locale reload for ` | The overlay failed validation. The console then lists up to 12 concrete errors and a count of any it omitted | Fix the listed keys and reload. Nothing partial is applied — the previous locale stays active in full | -| `Locale overlay key is not declared by the message catalog` | You invented a key name. Overrides can only redefine keys that already exist in code | Copy the exact key id from the bundled locale file for your language | -| `Expected [x, y] but found [x]` | Your text dropped or renamed a `{name}` placeholder | Match the English template's placeholder set exactly. Order and surrounding words are free; the set of names is not | -| `Expected 5 lines but found 4` / `Expected plural forms [...]` | A lines key needs the same line count as English, and a plural key needs the same form names | Restore the missing entries | -| Override edits do nothing | Wrong data folder, or the filename does not match the active locale id | The file must be `/languages/overrides/.json`, spelled exactly as `general.language`. Run `/iris reload` to force it | -| Server text is translated but keybind labels are still English | Those labels come from the mod jar's client assets, not from the server locale | See "Client language assets" below | - -## Where English comes from - -Canonical English is owned by code in `core/.../localization`. `IrisMessages` assembles the catalog from every surface class plus the shared Director command keys from VolmLib. There is no English server translation file, and there does not need to be — English is the fallback layer under every locale. - -| Catalog | Covers | -|---|---| -| `DirectorMessages` (VolmLib) | Shared command framework text: parameter errors, argument prompts, help chrome | -| `IrisMessages` | Permission denials, unknown command, player-only, "not an Iris world", reload results, modded help chrome | -| `BukkitCommandMessages`, `BukkitCommandMessagesExtended` | Feedback from the Bukkit `/iris` command tree | -| `DirectorCommandMessages` | Per-command and per-parameter descriptions shown in `/iris help` | -| `ModdedCommandMessages`, `ModdedHelpMessages` | The Fabric/Forge/NeoForge command tree and its help pages | -| `RuntimeUiMessages`, `RuntimeProgressMessages`, `BukkitRuntimeMessages` | Pregen headers and boss bar titles, chunk job progress, runtime status lines | -| `PackDownloadMessages` | Pack download progress and results | -| `ClientUiMessages` | Client mod strings: Vision map, What overlay, pregen HUD stats, toasts, create-world gates | -| `BukkitUiMessages`, `DesktopUiMessages` | Bukkit inventory UI and desktop pregen window strings | - -Code resolves text through `IrisLanguage.text(...)` when color codes should survive, or `IrisLanguage.plain(...)` when they should be stripped. Argument-free `plain` calls are memoized per locale snapshot because HUD code calls them several times per frame; a locale reload publishes a new snapshot and throws the whole memo away. - -## Selecting a locale - -| Setting | Default | Location | -|---|---|---| -| `general.language` | `en_US` | `plugins/Iris/settings.json` (plugin) or `/settings.json` (mod) | - -The value must match `[A-Za-z0-9_-]+`. Anything else is rejected outright and the previously active locale keeps running. Both `/iris reload` and the automatic settings hotload re-read the setting and reload the locale; the command reports the requested and the active id so you can tell a successful switch from a silent no-op. - -On a successful load Iris logs `Loaded locale with N fallback entries.` That count is the number of catalog keys each overlay did not define, summed across overlays. A one-key override file therefore produces a very large number. It is informational, not an error. - -## Bundled server locales - -Complete translations ship inside the jar as `/languages/.json`. - -| Locale id | Language | -|---|---| -| `de_DE` | German | -| `es_ES` | Spanish | -| `fi_FI` | Finnish | -| `fr_FR` | French | -| `he_IL` | Hebrew | -| `it_IT` | Italian | -| `ja-JP` | Japanese (hyphen, not underscore — this one id is irregular) | -| `ko_KR` | Korean | -| `lt_LT` | Lithuanian | -| `nl_NL` | Dutch | -| `pl_PL` | Polish | -| `pt_PT` | Portuguese | -| `ru_RU` | Russian | -| `tr_TR` | Turkish | -| `vi_VI` | Vietnamese | -| `zh_CN` | Simplified Chinese | -| `zh_TW` | Traditional Chinese | - -A bundled file is capped at 2 MiB. If one of these ids is configured but its jar resource is missing, the load throws — that is a build defect, not an operator problem. An id outside this list with no bundle is not an error: Iris skips the bundled layer and every string falls through to English, which is what makes a fully custom locale possible from an override file alone. - -## Override files - -Path: `/languages/overrides/.json`, created as a folder on locale load. - -Overrides are partial by design. Define only the keys you want to change; the rest resolve from the bundled overlay, then from English. - -```json -{ - "locale": "de_DE", - "messages": { - "iris.command.unknown": "Unbekannter Iris-Befehl" - } -} -``` - -| Rule | Behavior | -|---|---| -| Root keys | Only `locale` and `messages`. Any other root key throws and the reload is rejected | -| `locale` | Optional. If present it must equal the filename's locale id after trimming, otherwise the file is rejected | -| Key existence | Every key must already exist in the catalog. An unknown key is an ERROR that rejects the whole file — you cannot mint new messages from an override | -| Value shapes | A string for text keys, a string array for lines keys, an object of plural forms for plural keys. Using the wrong shape rejects the file | -| Nesting | Nested objects flatten into dotted keys, so `{"iris": {"command": {"unknown": "..."}}}` is the same as `"iris.command.unknown"`. The exception is a plural key, where an object is read as the plural forms | -| Placeholders | The set of `{name}` tokens must match the English template exactly. A lines key must also match the English line count, and each line's placeholder set | -| Size | Max 2 MiB | -| Hotload | The file's path, mtime, and length are watched. A change reloads the locale on the next settings-hotload poll | - -Validation is all-or-nothing. A rejected reload leaves the previous locale fully intact and logs the first 12 errors plus a count of the remainder. - -## Resolution order - -For a non-`en_US` locale a key resolves as: operator override → bundled `/languages/.json` → English catalog default. For `en_US` the bundled layer is skipped entirely, so it is: operator override → English catalog default. - -Templates use `{name}` tokens. Arguments are classified as trusted or untrusted at the call site. Trusted arguments may carry color codes. Untrusted arguments — player names, world names, pack-authored strings, exception text — have legacy section codes stripped and have `&`, `<`, and `>` rewritten to lookalike characters so they cannot inject formatting. - -`&` codes in the template itself are translated to section-sign codes before send, for `0-9`, `a-f`, `k-o`, `r`, and `x`. - -## Client language assets - -The Minecraft client reads its own lang files from `assets/irisworldgen/lang/.json` inside the mod jar. These are not the server catalogs and are not affected by `general.language`. They currently define only the keybind category and the three key names: - -| Key | English | -|---|---| -| `key.categories.irisworldgen.iris` | Iris | -| `key.irisworldgen.toggle_pregen_hud` | Toggle Pregen HUD | -| `key.irisworldgen.open_vision_map` | Open Iris Vision Map | -| `key.irisworldgen.toggle_what_overlay` | Toggle Iris What Overlay | - -`en_us.json` is required; a translated file ships for every bundled locale. The Minecraft code is the server locale id with `-` replaced by `_` and lowercased, so `ja-JP` becomes `ja_jp`. - -Everything else the client draws — HUD stats, Vision map labels, What overlay rows, toasts — resolves through `IrisLanguage` and `ClientUiMessages` on whichever process renders it, not through these four keys. That is why a translated boss bar and an English keybind label can coexist. - -## Platforms - -Localization works the same on Bukkit-family and on Fabric/Forge/NeoForge, from the same catalogs and the same override file. Only the surfaces differ: the modded command tree uses `ModdedCommandMessages`/`ModdedHelpMessages`, the Bukkit tree uses the Bukkit catalogs, and each ignores the other's keys. Keybind lang assets apply only where the client mod is installed. See `30 - Platform Differences.md`. diff --git a/docs/09 - PlaceholderAPI.md b/docs/09 - PlaceholderAPI.md deleted file mode 100644 index d567a0e56..000000000 --- a/docs/09 - PlaceholderAPI.md +++ /dev/null @@ -1,185 +0,0 @@ -# 09 - PlaceholderAPI - -Iris registers a PlaceholderAPI expansion with id `iris` on Bukkit-family servers, publishing sixteen read-only values: one service flag, six terrain readings scoped to a player's position, and nine global pregeneration readings. It exists so scoreboard, chat, and HUD plugins can show Iris state without writing Java. Plugins that need the same data with real types and no string parsing should use `90 - API - Getting Started.md`, `91 - API - Terrain.md`, and `92 - API - World Events.md` instead. There is no PlaceholderAPI on Fabric/Forge/NeoForge; see `07 - Pregeneration.md` and `28 - Integrations.md` for the surrounding runtime. - -## Put an Iris value on a scoreboard - -Work outward: prove the placeholder resolves in PlaceholderAPI itself before you touch the plugin that will display it. Half of all "the placeholder is broken" reports are a formatting mistake in the consumer. - -Prerequisites: Bukkit-family Iris, PlaceholderAPI installed *before* Iris starts, a full server restart, and a player standing in a loaded Iris world. - -1. `/papi info iris` — the expansion must be listed with author `Volmit Software` and version `2.0.0`, along with all sixteen paths. If it is not listed at all, skip to the recovery table; nothing else will work. -2. `/papi parse me %iris_available%` — expect `true`. This only means Iris registered its terrain service, not that you are in an Iris world. -3. `/papi parse me %iris_world.available%` — expect `true`. This is the guard you will use in the board template. -4. `/papi parse me %iris_world.biome-key%` — expect a load key such as `desert/hot-dunes`. A `---` here means Iris has no reading for you yet; see the recovery table. -5. Start a job to test the pregen family: `/iris pregen start 352 center=0,0 gui=false`, then `/papi parse me %iris_pregen.percent%`. Expect a bare number between `0.00` and `100.00` with no percent sign. -6. `/iris pregen stop`, then `/papi parse me %iris_pregen.available%`. Expect `false`, and every other `pregen.*` key to read `---`. -7. Now paste the exact string you verified into the consumer plugin, wrap it in whatever guard that plugin offers, and reload it. - -Success is the board showing the same text `/papi parse` showed. If step 7 disagrees with step 4, the bug is in the consumer's template or refresh interval, not in Iris. - -### Recovery - -| Symptom | What actually happened | Fix | -|---|---|---| -| `/papi info iris` reports no such expansion | PlaceholderAPI was not enabled at the moment Iris ran its registration task, one tick after enable | Full server restart with both plugins present. There is no late retry on `PluginEnableEvent`, and `/papi reload` will not make Iris try again | -| The placeholder renders literally as `%iris_...%` | The path is not one Iris publishes. Unknown paths return null to PlaceholderAPI, which leaves the text alone | Copy an exact path from `/papi info iris` or the table below. Pre-2.0 underscore names are gone | -| A `world.*` key reads `---` | No player context (console or offline player), the player has no tracked position yet, the player is not in an Iris world, or the terrain service returned nothing for that column | Parse as a named online player who is standing in a loaded Iris world | -| `world.available` is `true` but the biome lags your movement | The per-player world view has a one-second TTL | Wait a second, or teleport — teleports publish immediately. Do not go looking for a bug in the consumer first | -| A `pregen.*` key reads `---` | No job snapshot is currently published | Guard the board template on `pregen.available` rather than testing the value keys for `---` | -| The board shows `47.5%%` or `47.5` with no sign | `pregen.percent` deliberately returns a bare number | Put the literal `%` in the consumer's format string | - -## Registration - -| Item | Value | -|---|---| -| Expansion id | `iris` | -| Version | `2.0.0` | -| Author | `Volmit Software` | -| Required plugin | `Iris` | -| Declared in `plugin.yml` | `softdepend: PlaceholderAPI` | -| `persist()` | `true` — the expansion survives `/papi reload` without restarting Iris | - -Iris checks `isPluginEnabled("PlaceholderAPI")` inside a task scheduled just after its own enable, and gives up silently if the answer is no. Registration also installs a listener; if that listener fails to attach, Iris unregisters the expansion again and logs a warning, so you never end up with an expansion publishing stale positions. - -Soft-depend only affects load order. It does not install or load PlaceholderAPI. - -## Value grammar - -| Rule | Detail | -|---|---| -| Path form | Dot-separated, lowercase `a-z`, `0-9`, and `-`. The path is lowercased before lookup, so `%iris_WORLD.BIOME%` resolves, but write it lowercase | -| Plain text only | No color codes, no unit suffixes, no `%` character in any value, `.` as the decimal separator, no thousands separators | -| Pack-name scrubbing | Section-sign sequences and `%` characters inside pack-authored names are removed before the value is returned, so a mischievous biome name cannot inject formatting into a scoreboard | -| Genuine zero | Returned as `0`, or `0.00` for two-decimal values. Never `---` | - -Every key answers in one of three ways: - -| Answer | When | What the board shows | -|---|---|---| -| The value | Known path, data available | The value | -| `---` | Known path, nothing to report right now | `---` | -| Null | Unknown path | The literal `%iris_...%` | - -Unknown paths stay visible on purpose. There is no blanket empty-string fallback that would hide a typo. - -## Key reference - -### World family - -Everything except `%iris_available%` needs an online player with a tracked position. - -| Placeholder | What it reports | -|---|---| -| `%iris_available%` | `true` when Iris has registered its terrain service on this server. Works from the console. Says nothing about the player's world | -| `%iris_world.available%` | `true` when the reading player's tracked position is in a world Iris generates. The guard for every other `world.*` key | -| `%iris_world.biome%` | Display name of the surface biome at the player's X/Z column, for example `Hot Desert Dunes` | -| `%iris_world.biome-key%` | Load key of that same biome, for example `desert/hot-dunes`. This is what a pack file is named after | -| `%iris_world.region%` | Display name of the region covering the player's X/Z | -| `%iris_world.region-key%` | Load key of that region | -| `%iris_world.dimension%` | Load key of the dimension the player's world generates from, for example `overworld`. This is the dimension file's key, which is usually but not necessarily the pack folder name | - -From the console, for an offline player, or before a player's first tracked position: `world.available` is `false` and the rest are `---`. - -### Pregeneration family - -One job runs per server, so these are global. Every player and the console see identical values. - -| Placeholder | What it reports | -|---|---| -| `%iris_pregen.available%` | `true` while a job snapshot is published. Use this as the guard | -| `%iris_pregen.world%` | Name of the world being pregenerated | -| `%iris_pregen.percent%` | Completion from `0.00` to `100.00`, two decimals, no `%` character | -| `%iris_pregen.eta%` | Whole seconds remaining | -| `%iris_pregen.eta-text%` | The same estimate formatted for humans: `45s` under a minute, `2m 5s` under an hour, `1h 30m` above it | -| `%iris_pregen.chunks%` | Chunks finished so far | -| `%iris_pregen.total%` | Chunks the job will generate in total | -| `%iris_pregen.chunks-per-second%` | Current generation rate, two decimals | -| `%iris_pregen.paused%` | `true` while the job is paused | - -The snapshot is republished on the `STARTED`, `TICK`, `PAUSED`, `RESUMED`, and `SAVING` pregen phases, and cleared on `COMPLETED` and `CANCELLED`. After the clear, `pregen.available` is `false` and every value key is `---`. Before the job has run long enough to estimate, `eta` reads `0` and `eta-text` reads `0s`. - -### Paths as `/papi info iris` prints them - -``` -available -pregen.available -pregen.chunks -pregen.chunks-per-second -pregen.eta -pregen.eta-text -pregen.paused -pregen.percent -pregen.total -pregen.world -world.available -world.biome -world.biome-key -world.dimension -world.region -world.region-key -``` - -Prefix each with `%iris_` and suffix with `%`. - -## What "surface" means, and what a board costs - -`world.biome`, `world.biome-key`, `world.region`, and `world.region-key` are **surface column** readings: whatever the generator places at ground level for that X/Z. Y is not part of the query. A player 60 blocks down in a cave still reads the surface biome overhead, not the cave biome. If you need the biome at an actual Y, that is a terrain API call, not a placeholder — see `91 - API - Terrain.md`. - -### When a position is published - -| Event | Timing | -|---|---| -| Walking (`PlayerMoveEvent`) | At most once per second, and skipped entirely while the player stays inside the same block column | -| Join, respawn, world change, portal, and every teleport — `/iris goto`, `/tp`, ender pearls, random-TP plugins | Immediately, bypassing the one-second interval | -| Quit | The player's tracked position and cached view are released | - -Because teleports publish immediately, a player who arrives somewhere and stands still never reads a stale column from where they came from. - -### View rebuild cost - -A player's world view is rebuilt at most once per second (`VIEW_TTL_MS = 1000`), and only when something actually reads a `world.*` key. Three consequences worth knowing before you design a board: - -- A board with six `world.*` keys costs one rebuild per player per second, not six. -- Values can trail a sprinting player by up to a second. -- A board nobody is reading costs nothing. Iris does not poll terrain in the background for this. - -Pregen values are not polled per player either — they come from one global snapshot updated by `IrisPregenerationEvent`. - -## Permissions - -No placeholder is permission-gated. Anything sensitive is simply not published: there is no seed key, no file path key, and no key that mutates engine state on read. - -## Failure policy - -| Situation | Result | -|---|---| -| Unknown path | Null to PlaceholderAPI, so the literal `%iris_...%` stays on screen | -| Known path, no data | `---` | -| No player context on a `world.*` key | `---`, and `world.available` is `false` | -| Player outside an Iris world | `---`, and `world.available` is `false` | -| Terrain service not registered | `---`, `world.available` is `false`, `%iris_available%` is `false` | -| No pregen job | `---`, and `pregen.available` is `false` | -| A resolver throws | `---`, plus one logged warning for that path. Logging stops after 64 distinct paths have warned | - -A key that threw is not quarantined. It keeps being called and keeps answering `---` until whatever was wrong resolves itself. - -## Migration from pre-2.0 keys - -The old underscore keys are gone with no aliases and no dual-accept window. They now render literally, which is deliberate — a silently empty scoreboard line is worse than a visibly broken one. - -| Old key | New key | Why | -|---|---|---| -| `%iris_biome_name%` | `%iris_world.biome%` | Dot grammar | -| `%iris_biome_id%` | `%iris_world.biome-key%` | `id` was always the load key; the name now says so | -| `%iris_region_name%` | `%iris_world.region%` | Dot grammar | -| `%iris_region_id%` | `%iris_world.region-key%` | Same as `biome_id` | -| `%iris_biome_file%` | removed | Leaked absolute server paths, and threw whenever the biome had no backing file | -| `%iris_region_file%` | removed | Same problem | -| `%iris_world_seed%` | removed | A scoreboard has no permission context. Read `IrisWorldInfo.seed()` from the terrain API if a plugin genuinely needs it | -| `%iris_terrain_height%` | removed | Reported generated height before objects and player edits, so it regularly disagreed with the block under the player's feet | -| `%iris_terrain_slope%` | removed | A pack-authoring diagnostic, far too expensive to run once per player per board refresh | -| `%iris_world_mode%` | removed | Studio versus production is not something a live board needs | -| `%iris_world_speed%` | removed | Mutated engine rate-window state as a side effect of being read. Use `%iris_pregen.chunks-per-second%` | - -One behavior change hides inside the renames: the old biome and region keys sampled two blocks above the player's feet, so they picked up cave and overhang biomes. The new keys are always the surface column. `%iris_world.dimension%` has no pre-2.0 equivalent. diff --git a/docs/10 - Studio & VSCode Schemas.md b/docs/10 - Studio & VSCode Schemas.md deleted file mode 100644 index bc7a833d5..000000000 --- a/docs/10 - Studio & VSCode Schemas.md +++ /dev/null @@ -1,279 +0,0 @@ -# 10 - Studio & VSCode Schemas - -Studio is the live pack-authoring loop: open a pack as a throwaway world, edit its JSON in an editor that autocompletes against schemas generated from the Java models, save, and watch the running engine rebuild itself. This page walks the loop end to end first, then documents the commands, the hotload rules, and how the schemas are produced. - -Related: see `04 - Commands & Permissions.md`, `05 - Concepts & Pack Layout.md`, `02 - Getting Started.md`, `11 - Dimensions.md`, `21 - Jigsaw Structures.md`, `25 - Pack Management.md`, `30 - Platform Differences.md`. - -## The edit loop - -Prerequisites: a writable packs directory, operator access on Bukkit or gamemaster access on a mod loader, and VSCode/Cursor (or IntelliJ) on the machine that holds the pack folder. Keep the server console visible — hotload reports success and failure there. - -### Bukkit-family - -1. **Create a project.** `/iris studio create name=tutorial` - Writes `packs/tutorial/` with a dimension, region, biome, generator, and a `tutorial.code-workspace`. The command reports the completed project path; creation runs asynchronously and may report that a restart is needed before the pack can be opened. -2. **Open it as a world.** `/iris studio open tutorial seed=1337` - You are teleported into a transient world generated from the live pack folder. A fixed seed matters — you will be comparing the same coordinates across reloads. -3. **Open the editor workspace.** `/iris studio vscode dimension=tutorial` - Generates `.iris/schema/*` if missing and opens the `*.code-workspace`. On a headless server nothing launches; copy the pack folder to your machine and open the workspace file yourself. - *Success condition:* typing `"` inside any object in `biomes/starter.json` offers field names, and hovering a field shows its description, type, and default value. If it does not, the workspace was not opened or the schemas were never written — run `/iris studio update dimension=tutorial`. -4. **Make one change.** Edit `packs/tutorial/biomes/starter.json` and change only its display `name`. Save once. -5. **Wait for the hotload result** in console before saving anything else. A failed hotload leaves the previous runtime active and reports the error; stacking more edits on top makes the first failure hard to find. -6. **Verify in fresh terrain.** Walk into chunks that have never generated and run `/iris what biome`. The new display name appears there. Hotload never rewrites blocks that already exist, so standing still and expecting the world to change is the usual false negative. -7. **Validate.** `/iris pack validate pack=tutorial` — no blocking errors. -8. **Close.** `/iris studio close` - -### Fabric / Forge / NeoForge - -Same loop, positional arguments, and the modded studio create always copies a template (`example` by default): - -1. `/iris studio create tutorial example` -2. `/iris studio open tutorial 1337` -3. `/iris studio vscode tutorial` -4. Trace the active dimension to one referenced biome, change one display or palette value, save once. -5. Wait for the hotload result, then enter newly generated terrain and check it with `/iris what biome`. -6. `/iris pack validate tutorial`, then `/iris studio close`. - -The loop passes when the editor binds the generated schema, hotload succeeds, validation reports no blocking errors, and newly generated chunks show the change. Create a production world only after that gate. - -A rejected height or dimension-type change is not evidence that hotload is broken — those are refused by design. See **Hotload rules** below. - -### When something goes wrong - -| Symptom | Meaning | Recovery | -|---|---|---| -| `open` reports startup validation pending, missing, failed, restart-required, or blocking pack errors | Datapacks or the pack graph cannot safely build an engine | Complete the requested restart or run the platform's `pack validate` form, fix the first blocking error, and retry; do not bypass validation | -| Save reports hotload failure | The new data or runtime build failed; the previous runtime may remain active | Fix the first console error and save again before making unrelated edits | -| Height, logical height, or dimension type change is rejected | The edit violates `IrisDimensionRuntimeContract` | Close Studio and reopen; on modded, restart when regenerated dimension-type datapacks require a registry reload | -| A valid change is invisible | The chunks you are standing in are already materialized, or the edited resource is unreachable from the active dimension | Move to new chunks; trace dimension → region → biome to confirm the resource is actually referenced; use `focus`/`focusRegion` or a buffet studio mode to isolate | -| No autocomplete, or resource keys are stale | Schemas were not generated or refreshed, or the editor never opened the workspace | Run `/iris studio update`, then open the pack's `.code-workspace`; on headless servers open it manually | -| The Studio world disappears after a restart | Studio worlds are transient and purged on purpose | Reopen the pack; `packs//` is the source of truth, not the world folder | - -## What Studio is - -| Concept | Behavior | -|---------|----------| -| Pack workspace | Packs live under the platform data directory in the folder named `packs` (`StudioSVC.WORKSPACE_NAME`) | -| Studio world | Opened from a pack dimension key; uses a studio chunk generator bound to the live pack folder with file watching | -| Hotload | Studio worlds only. A low-priority looper polls pack files; on change, `EngineHotloader` waits for already-admitted top-level Bukkit chunk stages, reloads the pack data, and rebuilds the engine runtime under exclusive generator control. Fair stage admission keeps later chunk stages behind the waiting transition, and Studio close uses the same drain boundary. Biome Buffet resolves its chunk focus and completes any required complex hotload under exclusive admission before that noise stage opens a generation session, then downgrades directly to one ordinary stage permit | -| Hotload contract | `IrisDimensionRuntimeContract` refuses hotload if the dimension type key, min height, total height, or logical height change | -| Non-studio worlds | No pack file watcher. Production worlds keep the pack snapshot installed at create or update time | - -Studio settings live in `settings.json` under `studio` (`IrisSettings.IrisSettingsStudio`): - -| Key | Default | Meaning | -|-----|---------|---------| -| `openVSCode` | `true` | When true and the JVM is not headless, `open`/`vscode` may launch the desktop opener on the pack's `*.code-workspace`. Set false on servers where a desktop launch would be pointless or unwanted | -| `entitySpawning` | `true` | Only affects Studio worlds. False stops Iris ambient entity spawning there; production worlds always spawn regardless of this key | -| `disableTimeAndWeather` | `true` | Present in the settings model but not read by any code path today | -| `autoStartDefaultStudio` | `false` | Present in the settings model but not read by any code path today | - -## Hotload rules - -- The watcher runs only when `PlatformChunkGenerator.isStudio()` is true, the world is not closing, and no Jigsaw Studio session is active. Jigsaw Studio deliberately suppresses ordinary pack-file hotload. -- On change: load a new `IrisData` from the same folder, reload the dimension key, check the hotload contract, build a new engine runtime, retire the previous data, refresh the workspace and schemas, reload datapacks when a platform world is bound, and broadcast a client studio-hotload toast on success or failure. -- `hotloadComplex` is a narrower rebuild that reconstructs `IrisComplex` without reopening the pack. -- A failed hotload rolls the runtime back where possible and reports the error. - -Four values are pinned for the life of the world and cannot hotload: the dimension type key (derived from the dimension load key), min height, total height, and logical height. Editing `dimensionHeight`, `logicalHeight`, `environment`, or the dimension file name means closing and reopening Studio. See `11 - Dimensions.md`. - -## Commands (Bukkit) - -Root: `/iris studio`, aliases `std` and `s`. Implemented by `CommandStudio` and `StudioSVC`. Keyed arguments; the first column shows the primary subcommand name. - -| Subcommand | Aliases | What it does | -|------------|---------|--------------| -| `open [seed=1337]` | `o` | Closes any open studio and opens the pack as a studio world. Blocked unless startup datapack validation is ready and the selected pack has a loadable validation result | -| `close` | `x` | Closes the active studio project and world | -| `create [name=studio] [template=]` | `+` | Creates a pack under `packs/` after startup validation is ready. With a template, that template must itself validate as loadable and is downloaded first if missing; without one, writes the starter skeleton below | -| `vscode [dimension=default]` | `vsc` | Opens the pack's VSCode workspace, generating it if missing | -| `update [dimension=default]` | | Rewrites `/.code-workspace` and queues regeneration of `.iris/schema/*` | -| `version [dimension=default]` | | Prints the dimension's `version` field | -| `pkg [dimension=default] [obfuscate=false] [minify=true]` | `package` | Compiles the pack into a distributable archive | -| `importvanilla [variants=3] [structures=true]` | `importv`, `iv` | Captures vanilla features and structures into the pack through Bukkit NMS | -| `scoreboard` | `board`, `sidebar`, `sb` | Toggles the studio debug scoreboard; player must be in the studio world | -| `noise [generator=] [seed=12345]` | `nmap` | Opens the external noise explorer GUI | -| `map [world=]` | `render` | Opens the external biome/terrain map GUI for an Iris world | -| `regions [radius=500]` | | Samples region rarity over a chunk spiral; player must be in an Iris world | -| `loot [fast=false] [add=true]` | | Opens a virtual chest showing loot tables for the block under the player | -| `profile [dimension=default]` | | Writes a pack performance profile report | -| `spawn` | `summon` | Spawns a pack entity definition at the player | -| `tpstudio` | `stp` | Teleports to the active studio world spawn in creative | -| `objects` | `find-objects` | Captures a nearby-chunk object placement report | - -Permissions and the full `/iris` tree: see `04 - Commands & Permissions.md`. - -## Commands (Modded) - -`/iris studio` on Fabric, Forge and NeoForge is implemented by `ModdedStudioCommands` with positional arguments. Supported: `create`/`+`, `package`/`pkg`, `version`, `regions`, `open`/`o`, `close`/`x`, `tpstudio`/`stp`, `status`, `vscode`/`vsc`, `update`, `noise`/`nmap`, `map`/`render`. - -`create` with no arguments creates a project named `studio` from the `example` template; `create ` uses the same template; `create