diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java
index 5ccf22f50..b4fea6254 100644
--- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java
@@ -58,6 +58,7 @@ import art.arcane.iris.generation.block.BlockDataMergeSupport;
import art.arcane.iris.structure.object.IrisObjectRotation;
import art.arcane.iris.generation.block.TileData;
import art.arcane.iris.modded.api.ModdedCustomContentRegistry;
+import art.arcane.iris.modded.catalog.ModdedWorldgenCatalogs;
import art.arcane.iris.modded.command.IrisModdedCommands;
import art.arcane.iris.modded.command.ModdedGuiHost;
import art.arcane.iris.modded.command.ModdedObjectUndo;
@@ -173,6 +174,7 @@ public final class ModdedEngineBootstrap {
ModdedStartup.runOnce(server);
reconcileSpawn(server);
ModdedWorldCheck.serverStarted(server);
+ ModdedWorldgenCatalogs.capture(server);
}
public static void levelLoaded(NativeWorld world) {
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/CatalogCaptureContext.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/CatalogCaptureContext.java
new file mode 100644
index 000000000..1ab3a0512
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/CatalogCaptureContext.java
@@ -0,0 +1,32 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.catalog;
+
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModMetadata;
+
+import java.util.List;
+import java.util.Objects;
+
+public record CatalogCaptureContext(String minecraftVersion, String platform, long capturedAtEpochMillis, List mods) {
+ public CatalogCaptureContext {
+ Objects.requireNonNull(minecraftVersion, "minecraftVersion");
+ Objects.requireNonNull(platform, "platform");
+ mods = List.copyOf(Objects.requireNonNull(mods, "mods"));
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/ModdedWorldgenCatalogs.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/ModdedWorldgenCatalogs.java
new file mode 100644
index 000000000..5de99124e
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/ModdedWorldgenCatalogs.java
@@ -0,0 +1,139 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.catalog;
+
+import art.arcane.iris.modded.ModdedEngineBootstrap;
+import art.arcane.iris.modded.ModdedIrisLog;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogStore;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenScan;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.EncodeFailures;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class ModdedWorldgenCatalogs {
+ private static final AtomicReference LATEST = new AtomicReference<>();
+ private static final AtomicReference> RUNNING = new AtomicReference<>();
+
+ private ModdedWorldgenCatalogs() {
+ }
+
+ public static Optional latest() {
+ return Optional.ofNullable(LATEST.get());
+ }
+
+ public static Path catalogFile() {
+ return ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("catalog")
+ .resolve(WorldgenCatalogStore.FILE_NAME);
+ }
+
+ public static CompletableFuture capture(NativeModdedServer server) {
+ Objects.requireNonNull(server, "server");
+ CompletableFuture pending = new CompletableFuture<>();
+ CompletableFuture running = RUNNING.compareAndExchange(null, pending);
+ if (running != null) {
+ return running;
+ }
+ long startedAt = System.nanoTime();
+ NativeModdedLoader loader = ModdedEngineBootstrap.loader();
+ NativeWorldgenScan scan;
+ CatalogCaptureContext context;
+ try {
+ scan = NativeWorldgenScan.scan(server, loader);
+ context = new CatalogCaptureContext(loader.minecraftVersion(), loader.platformName(),
+ System.currentTimeMillis(), loader.mods());
+ } catch (RuntimeException failure) {
+ ModdedIrisLog.error("Iris could not read the worldgen registries for the catalog", failure);
+ finish(pending, null, failure);
+ return pending;
+ }
+ long scanMillis = elapsedMillis(startedAt);
+ Path file = catalogFile();
+ Thread worker = new Thread(() -> encodeAndWrite(new PendingCapture(scan, context, file, startedAt, scanMillis), pending),
+ "Iris Worldgen Catalog");
+ worker.setDaemon(true);
+ worker.start();
+ return pending;
+ }
+
+ private static void encodeAndWrite(PendingCapture capture, CompletableFuture pending) {
+ try {
+ NativeWorldgenSnapshot snapshot = capture.scan().encode();
+ WorldgenCatalog catalog = WorldgenCatalogAssembler.assemble(snapshot, capture.context());
+ WorldgenCatalogStore.write(capture.file(), catalog);
+ LATEST.set(catalog);
+ CaptureResult result = new CaptureResult(catalog, capture.file(), elapsedMillis(capture.startedAt()));
+ ModdedIrisLog.info("Iris worldgen catalog: {} namespaces, {} biomes, {} placed features, {} structures, "
+ + "{} dimension types, {} level stems -> {} ({}ms server thread, {}ms total)",
+ catalog.namespaces().size(), catalog.biomes().size(), catalog.placedFeatures().size(),
+ catalog.structures().size(), catalog.dimensionTypes().size(), catalog.levelStems().size(),
+ capture.file(), capture.scanMillis(), result.millis());
+ reportEncodeFailures(snapshot.failures());
+ finish(pending, result, null);
+ } catch (IOException failure) {
+ ModdedIrisLog.error("Iris could not write the worldgen catalog to " + capture.file(), failure);
+ finish(pending, null, new UncheckedIOException(failure));
+ } catch (RuntimeException failure) {
+ ModdedIrisLog.error("Iris could not build the worldgen catalog", failure);
+ finish(pending, null, failure);
+ }
+ }
+
+ private static void reportEncodeFailures(EncodeFailures failures) {
+ if (failures.count() == 0) {
+ return;
+ }
+ String first = failures.samples().isEmpty() ? "" : failures.samples().getFirst();
+ if (failures.firstException() != null) {
+ ModdedIrisLog.warn("Iris worldgen catalog left {} entries without JSON; first: {}", failures.count(), first,
+ failures.firstException());
+ return;
+ }
+ ModdedIrisLog.warn("Iris worldgen catalog left {} entries without JSON; first: {}", failures.count(), first);
+ }
+
+ private static void finish(CompletableFuture pending, CaptureResult result, RuntimeException failure) {
+ RUNNING.compareAndSet(pending, null);
+ if (failure != null) {
+ pending.completeExceptionally(failure);
+ return;
+ }
+ pending.complete(result);
+ }
+
+ private static long elapsedMillis(long startedAt) {
+ return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startedAt);
+ }
+
+ public record CaptureResult(WorldgenCatalog catalog, Path file, long millis) {
+ }
+
+ private record PendingCapture(NativeWorldgenScan scan, CatalogCaptureContext context, Path file, long startedAt, long scanMillis) {
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java
new file mode 100644
index 000000000..e04c78b57
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java
@@ -0,0 +1,146 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.catalog;
+
+import art.arcane.iris.pack.ingest.catalog.CatalogBiome;
+import art.arcane.iris.pack.ingest.catalog.CatalogDimensionType;
+import art.arcane.iris.pack.ingest.catalog.CatalogLevelStem;
+import art.arcane.iris.pack.ingest.catalog.CatalogNamespace;
+import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature;
+import art.arcane.iris.pack.ingest.catalog.CatalogSpawn;
+import art.arcane.iris.pack.ingest.catalog.CatalogStructure;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogFingerprint;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogIndex;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModMetadata;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenBiome;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenDimensionType;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenLevelStem;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenPlacedFeature;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenSpawn;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenStructure;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+public final class WorldgenCatalogAssembler {
+ private static final String MINECRAFT = "minecraft";
+
+ private WorldgenCatalogAssembler() {
+ }
+
+ public static WorldgenCatalog assemble(NativeWorldgenSnapshot snapshot, CatalogCaptureContext context) {
+ Objects.requireNonNull(snapshot, "snapshot");
+ Objects.requireNonNull(context, "context");
+ List biomes = biomes(snapshot.biomes());
+ List placedFeatures = placedFeatures(snapshot.placedFeatures());
+ List structures = structures(snapshot.structures());
+ List dimensionTypes = dimensionTypes(snapshot.dimensionTypes());
+ List levelStems = levelStems(snapshot.levelStems());
+ Map> registryKeys = new LinkedHashMap<>();
+ registryKeys.put("biome", biomes.stream().map(CatalogBiome::key).toList());
+ registryKeys.put("placed_feature", placedFeatures.stream().map(CatalogPlacedFeature::key).toList());
+ registryKeys.put("structure", structures.stream().map(CatalogStructure::key).toList());
+ registryKeys.put("dimension_type", dimensionTypes.stream().map(CatalogDimensionType::key).toList());
+ registryKeys.put("level_stem", levelStems.stream().map(CatalogLevelStem::key).toList());
+ List namespaces = namespaces(registryKeys, context);
+ return new WorldgenCatalog(WorldgenCatalog.FORMAT, context.minecraftVersion(), context.platform(),
+ context.capturedAtEpochMillis(), WorldgenCatalogFingerprint.of(namespaces, registryKeys), namespaces,
+ biomes, placedFeatures, structures, dimensionTypes, levelStems);
+ }
+
+ private static List namespaces(Map> registryKeys, CatalogCaptureContext context) {
+ Map namespaces = new TreeMap<>();
+ for (NativeModMetadata mod : context.mods()) {
+ namespaces.put(mod.id(), new CatalogNamespace(mod.id(), mod.displayName(), mod.version()));
+ }
+ for (List keys : registryKeys.values()) {
+ for (String key : keys) {
+ String namespace = WorldgenCatalogIndex.namespaceOf(key);
+ if (!namespaces.containsKey(namespace)) {
+ namespaces.put(namespace, fallbackNamespace(namespace, context));
+ }
+ }
+ }
+ if (!namespaces.containsKey(MINECRAFT)) {
+ namespaces.put(MINECRAFT, fallbackNamespace(MINECRAFT, context));
+ }
+ return List.copyOf(namespaces.values());
+ }
+
+ private static CatalogNamespace fallbackNamespace(String namespace, CatalogCaptureContext context) {
+ if (MINECRAFT.equals(namespace)) {
+ return new CatalogNamespace(MINECRAFT, "Minecraft", context.minecraftVersion());
+ }
+ return new CatalogNamespace(namespace, namespace, "");
+ }
+
+ private static List biomes(List biomes) {
+ List result = new ArrayList<>(biomes.size());
+ for (WorldgenBiome biome : biomes) {
+ List spawns = new ArrayList<>(biome.spawns().size());
+ for (WorldgenSpawn spawn : biome.spawns()) {
+ spawns.add(new CatalogSpawn(spawn.category(), spawn.entity(), spawn.weight(), spawn.minCount(), spawn.maxCount()));
+ }
+ result.add(new CatalogBiome(biome.key(), biome.temperature(), biome.downfall(), biome.hasPrecipitation(),
+ biome.tags(), biome.featureSteps(), spawns, biome.definition()));
+ }
+ return result;
+ }
+
+ private static List placedFeatures(List features) {
+ List result = new ArrayList<>(features.size());
+ for (WorldgenPlacedFeature feature : features) {
+ result.add(new CatalogPlacedFeature(feature.key(), feature.feature(), feature.featureType(),
+ feature.config(), feature.placement()));
+ }
+ return result;
+ }
+
+ private static List structures(List structures) {
+ List result = new ArrayList<>(structures.size());
+ for (WorldgenStructure structure : structures) {
+ result.add(new CatalogStructure(structure.key(), structure.type(), structure.step(),
+ structure.terrainAdaptation(), structure.biomeTag(), structure.biomes(), structure.structureSets()));
+ }
+ return result;
+ }
+
+ private static List dimensionTypes(List types) {
+ List result = new ArrayList<>(types.size());
+ for (WorldgenDimensionType type : types) {
+ result.add(new CatalogDimensionType(type.key(), type.minY(), type.height(), type.logicalHeight(), type.definition()));
+ }
+ return result;
+ }
+
+ private static List levelStems(List stems) {
+ List result = new ArrayList<>(stems.size());
+ for (WorldgenLevelStem stem : stems) {
+ result.add(new CatalogLevelStem(stem.key(), stem.dimensionType(), stem.generatorType(),
+ stem.biomeSourceType(), stem.generator()));
+ }
+ return result;
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummary.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummary.java
new file mode 100644
index 000000000..26bdb24ea
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummary.java
@@ -0,0 +1,173 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.catalog;
+
+import art.arcane.iris.pack.ingest.catalog.CatalogBiome;
+import art.arcane.iris.pack.ingest.catalog.CatalogDimensionType;
+import art.arcane.iris.pack.ingest.catalog.CatalogLevelStem;
+import art.arcane.iris.pack.ingest.catalog.CatalogNamespace;
+import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature;
+import art.arcane.iris.pack.ingest.catalog.CatalogStructure;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogIndex;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+public final class WorldgenCatalogSummary {
+ private static final String VANILLA_PREFIX = "minecraft:";
+
+ private WorldgenCatalogSummary() {
+ }
+
+ public static List namespaces(WorldgenCatalog catalog) {
+ Map builders = new TreeMap<>();
+ for (CatalogNamespace namespace : catalog.namespaces()) {
+ builders.put(namespace.namespace(), new Builder(namespace));
+ }
+ collect(builders, catalog.biomes(), CatalogBiome::key, Builder::biomes);
+ collect(builders, catalog.placedFeatures(), CatalogPlacedFeature::key, Builder::placedFeatures);
+ collect(builders, catalog.structures(), CatalogStructure::key, Builder::structures);
+ collect(builders, catalog.dimensionTypes(), CatalogDimensionType::key, Builder::dimensionTypes);
+ collect(builders, catalog.levelStems(), CatalogLevelStem::key, Builder::levelStems);
+ for (CatalogPlacedFeature feature : catalog.placedFeatures()) {
+ builder(builders, WorldgenCatalogIndex.namespaceOf(feature.key())).featureTypes()
+ .merge(feature.featureType(), 1, Integer::sum);
+ }
+ List summaries = new ArrayList<>(builders.size());
+ for (Builder builder : builders.values()) {
+ summaries.add(builder.build());
+ }
+ return summaries;
+ }
+
+ public static Optional namespace(WorldgenCatalog catalog, String namespace) {
+ for (NamespaceSummary summary : namespaces(catalog)) {
+ if (summary.namespace().namespace().equals(namespace)) {
+ return Optional.of(summary);
+ }
+ }
+ return Optional.empty();
+ }
+
+ public static String featureTypes(List types, int limit) {
+ List labels = new ArrayList<>(Math.min(types.size(), limit));
+ for (int index = 0; index < types.size() && index < limit; index++) {
+ TypeCount type = types.get(index);
+ labels.add(shortType(type.type()) + " " + type.count());
+ }
+ return withRemainder(String.join(", ", labels), types.size() - labels.size());
+ }
+
+ public static String limited(List keys, String namespace, int limit) {
+ List labels = new ArrayList<>(Math.min(keys.size(), limit));
+ String ownPrefix = namespace + ":";
+ for (int index = 0; index < keys.size() && index < limit; index++) {
+ String key = keys.get(index);
+ labels.add(key.startsWith(ownPrefix) ? key.substring(ownPrefix.length()) : key);
+ }
+ return withRemainder(String.join(", ", labels), keys.size() - labels.size());
+ }
+
+ private static String shortType(String type) {
+ return type.startsWith(VANILLA_PREFIX) ? type.substring(VANILLA_PREFIX.length()) : type;
+ }
+
+ private static String withRemainder(String joined, int remainder) {
+ return remainder > 0 ? joined + " (+" + remainder + " more)" : joined;
+ }
+
+ private static void collect(Map builders, List entries, Function key,
+ Function> target) {
+ for (T entry : entries) {
+ String entryKey = key.apply(entry);
+ target.apply(builder(builders, WorldgenCatalogIndex.namespaceOf(entryKey))).add(entryKey);
+ }
+ }
+
+ private static Builder builder(Map builders, String namespace) {
+ return builders.computeIfAbsent(namespace, (String missing) -> new Builder(new CatalogNamespace(missing, missing, "")));
+ }
+
+ public record TypeCount(String type, int count) {
+ public TypeCount {
+ Objects.requireNonNull(type, "type");
+ }
+ }
+
+ public record NamespaceSummary(
+ CatalogNamespace namespace,
+ List biomes,
+ List placedFeatures,
+ List featureTypes,
+ List structures,
+ List dimensionTypes,
+ List levelStems) {
+ public NamespaceSummary {
+ Objects.requireNonNull(namespace, "namespace");
+ biomes = List.copyOf(biomes);
+ placedFeatures = List.copyOf(placedFeatures);
+ featureTypes = List.copyOf(featureTypes);
+ structures = List.copyOf(structures);
+ dimensionTypes = List.copyOf(dimensionTypes);
+ levelStems = List.copyOf(levelStems);
+ }
+
+ public boolean hasContent() {
+ return !biomes.isEmpty() || !placedFeatures.isEmpty() || !structures.isEmpty()
+ || !dimensionTypes.isEmpty() || !levelStems.isEmpty();
+ }
+ }
+
+ private record Builder(
+ CatalogNamespace namespace,
+ List biomes,
+ List placedFeatures,
+ Map featureTypes,
+ List structures,
+ List dimensionTypes,
+ List levelStems) {
+ private Builder(CatalogNamespace namespace) {
+ this(namespace, new ArrayList<>(), new ArrayList<>(), new TreeMap<>(), new ArrayList<>(), new ArrayList<>(),
+ new ArrayList<>());
+ }
+
+ private NamespaceSummary build() {
+ List types = new ArrayList<>(featureTypes.size());
+ for (Map.Entry entry : featureTypes.entrySet()) {
+ types.add(new TypeCount(entry.getKey(), entry.getValue()));
+ }
+ types.sort(Comparator.comparingInt(TypeCount::count).reversed().thenComparing(TypeCount::type));
+ return new NamespaceSummary(namespace, sorted(biomes), sorted(placedFeatures), types, sorted(structures),
+ sorted(dimensionTypes), sorted(levelStems));
+ }
+
+ private static List sorted(List keys) {
+ List copy = new ArrayList<>(keys);
+ copy.sort(null);
+ return copy;
+ }
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModIngestArguments.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModIngestArguments.java
new file mode 100644
index 000000000..03b885d11
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModIngestArguments.java
@@ -0,0 +1,129 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.command;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+record ModIngestArguments(String sourcePack, String targetPack, String dimension, Set namespaces, boolean replace) {
+ static final List OPTION_SUGGESTIONS = List.of("dimension=", "namespaces=", "replace=true");
+ private static final Pattern PACK = Pattern.compile("[A-Za-z0-9_.-]+");
+ private static final Pattern TARGET_PACK = Pattern.compile("[A-Za-z0-9_-]+");
+ private static final Pattern DIMENSION = Pattern.compile("[A-Za-z0-9_/.-]+");
+ private static final Pattern NAMESPACE = Pattern.compile("[a-z0-9_.-]+");
+ private static final String DIMENSION_KEY = "dimension";
+ private static final String NAMESPACES_KEY = "namespaces";
+ private static final String REPLACE_KEY = "replace";
+
+ ModIngestArguments {
+ Objects.requireNonNull(sourcePack, "sourcePack");
+ Objects.requireNonNull(targetPack, "targetPack");
+ Objects.requireNonNull(dimension, "dimension");
+ namespaces = Set.copyOf(Objects.requireNonNull(namespaces, "namespaces"));
+ }
+
+ static ModIngestArguments parse(String raw) {
+ String trimmed = raw == null ? "" : raw.trim();
+ String[] tokens = trimmed.isEmpty() ? new String[0] : trimmed.split("\\s+");
+ String source = null;
+ String target = null;
+ Map options = new HashMap<>(4);
+ for (String token : tokens) {
+ int equals = token.indexOf('=');
+ if (equals < 0) {
+ if (source == null) {
+ source = token;
+ } else if (target == null) {
+ target = token;
+ } else {
+ throw new IllegalArgumentException("unexpected argument '" + token + "'");
+ }
+ continue;
+ }
+ String key = token.substring(0, equals);
+ if (!key.equals(DIMENSION_KEY) && !key.equals(NAMESPACES_KEY) && !key.equals(REPLACE_KEY)) {
+ throw new IllegalArgumentException("unknown option '" + key + "'");
+ }
+ if (options.putIfAbsent(key, token.substring(equals + 1)) != null) {
+ throw new IllegalArgumentException("option '" + key + "' given twice");
+ }
+ }
+ if (source == null) {
+ throw new IllegalArgumentException("missing source pack");
+ }
+ if (target == null) {
+ throw new IllegalArgumentException("missing target pack");
+ }
+ requirePack(source);
+ requirePack(target);
+ if (!TARGET_PACK.matcher(target).matches()) {
+ throw new IllegalArgumentException("invalid target pack name '" + target + "'");
+ }
+ if (source.equals(target)) {
+ throw new IllegalArgumentException("source and target must be different packs");
+ }
+ String dimension = options.getOrDefault(DIMENSION_KEY, source);
+ if (!DIMENSION.matcher(dimension).matches() || dimension.contains("..")) {
+ throw new IllegalArgumentException("invalid dimension key '" + dimension + "'");
+ }
+ return new ModIngestArguments(source, target, dimension, namespaces(options.get(NAMESPACES_KEY)),
+ replace(options.get(REPLACE_KEY)));
+ }
+
+ private static void requirePack(String pack) {
+ if (!PACK.matcher(pack).matches() || pack.contains("..")) {
+ throw new IllegalArgumentException("invalid pack name '" + pack + "'");
+ }
+ }
+
+ private static Set namespaces(String raw) {
+ if (raw == null) {
+ return Set.of();
+ }
+ Set namespaces = new LinkedHashSet<>();
+ for (String part : raw.split(",")) {
+ if (part.isEmpty()) {
+ continue;
+ }
+ if (!NAMESPACE.matcher(part).matches()) {
+ throw new IllegalArgumentException("invalid namespace '" + part + "'");
+ }
+ namespaces.add(part);
+ }
+ if (namespaces.isEmpty()) {
+ throw new IllegalArgumentException("namespaces must not be empty");
+ }
+ return namespaces;
+ }
+
+ private static boolean replace(String raw) {
+ if (raw == null || raw.equals("false")) {
+ return false;
+ }
+ if (raw.equals("true")) {
+ return true;
+ }
+ throw new IllegalArgumentException("replace must be true or false");
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCatalogCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCatalogCommands.java
new file mode 100644
index 000000000..c8560f906
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCatalogCommands.java
@@ -0,0 +1,313 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.command;
+
+import art.arcane.iris.localization.IrisLanguage;
+import art.arcane.iris.modded.ModdedIrisLog;
+import art.arcane.iris.modded.catalog.ModdedWorldgenCatalogs;
+import art.arcane.iris.modded.catalog.ModdedWorldgenCatalogs.CaptureResult;
+import art.arcane.iris.modded.catalog.WorldgenCatalogSummary;
+import art.arcane.iris.modded.catalog.WorldgenCatalogSummary.NamespaceSummary;
+import art.arcane.iris.modded.localization.ModdedCatalogMessages;
+import art.arcane.iris.pack.PackDirectoryResolver;
+import art.arcane.iris.pack.ingest.ModIngestReport;
+import art.arcane.iris.pack.ingest.ModIngestRequest;
+import art.arcane.iris.pack.ingest.ModIngester;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandRegistration;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
+import art.arcane.volmlib.util.localization.MessageArgument;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.suggestion.SuggestionProvider;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+
+final class ModdedCatalogCommands {
+ private static final Predicate GATE = NativeCommandRegistration.GAMEMASTERS;
+ private static final int LIST_FEATURE_TYPES = 3;
+ private static final int DETAIL_LIMIT = 24;
+ private static final String NONE = "-";
+ private static final AtomicReference NAMESPACE_CACHE = new AtomicReference<>();
+ private static final SuggestionProvider NAMESPACES = (CommandContext context,
+ SuggestionsBuilder builder) ->
+ NativeCommandRegistration.suggest(contentNamespaces(), builder);
+ private static final SuggestionProvider INGEST_ARGUMENTS = ModdedCatalogCommands::suggestIngestArguments;
+
+ private ModdedCatalogCommands() {
+ }
+
+ static LiteralArgumentBuilder catalogTree() {
+ return NativeCommandRegistration.literal("catalog").requires(GATE)
+ .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))
+ .then(NativeCommandRegistration.literal("refresh")
+ .executes(ModdedCommandTree.localized((CommandContext context) -> refresh(context.getSource()))))
+ .then(NativeCommandRegistration.argument("namespace", StringArgumentType.word()).suggests(NAMESPACES)
+ .executes(ModdedCommandTree.localized((CommandContext context) ->
+ detail(context.getSource(), StringArgumentType.getString(context, "namespace")))));
+ }
+
+ static LiteralArgumentBuilder ingestTree() {
+ return NativeCommandRegistration.literal("ingest").requires(GATE)
+ .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "ingest")))
+ .then(NativeCommandRegistration.literal("mods")
+ .executes(ModdedCommandTree.localized((CommandContext context) -> ingest(context.getSource(), "")))
+ .then(NativeCommandRegistration.argument("arguments", StringArgumentType.greedyString()).suggests(INGEST_ARGUMENTS)
+ .executes(ModdedCommandTree.localized((CommandContext context) ->
+ ingest(context.getSource(), StringArgumentType.getString(context, "arguments"))))));
+ }
+
+ private static int list(NativeCommandSource source) {
+ Optional latest = ModdedWorldgenCatalogs.latest();
+ if (latest.isEmpty()) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.MISSING));
+ return 0;
+ }
+ WorldgenCatalog catalog = latest.get();
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.HEADER,
+ MessageArgument.untrusted("name", catalog.platform()),
+ MessageArgument.untrusted("version", catalog.minecraftVersion()),
+ MessageArgument.untrusted("count", catalog.namespaces().size()),
+ MessageArgument.untrusted("value", catalog.biomes().size()),
+ MessageArgument.untrusted("value2", catalog.placedFeatures().size()),
+ MessageArgument.untrusted("value3", catalog.structures().size()),
+ MessageArgument.untrusted("value4", catalog.dimensionTypes().size()),
+ MessageArgument.untrusted("value5", catalog.levelStems().size())));
+ int quiet = 0;
+ for (NamespaceSummary summary : WorldgenCatalogSummary.namespaces(catalog)) {
+ if (!summary.hasContent()) {
+ quiet++;
+ continue;
+ }
+ IrisModdedCommands.ok(source, namespaceLine(summary));
+ }
+ if (quiet > 0) {
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.QUIET, MessageArgument.untrusted("count", quiet)));
+ }
+ return 1;
+ }
+
+ private static int detail(NativeCommandSource source, String namespace) {
+ Optional latest = ModdedWorldgenCatalogs.latest();
+ if (latest.isEmpty()) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.MISSING));
+ return 0;
+ }
+ Optional found = WorldgenCatalogSummary.namespace(latest.get(), namespace);
+ if (found.isEmpty() || !found.get().hasContent()) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.UNKNOWN_NAMESPACE,
+ MessageArgument.untrusted("name", namespace)));
+ return 0;
+ }
+ NamespaceSummary summary = found.get();
+ IrisModdedCommands.ok(source, namespaceLine(summary));
+ if (!summary.biomes().isEmpty()) {
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.DETAIL_BIOMES,
+ MessageArgument.untrusted("count", summary.biomes().size()),
+ MessageArgument.untrusted("value", WorldgenCatalogSummary.limited(summary.biomes(), namespace, DETAIL_LIMIT))));
+ }
+ if (!summary.placedFeatures().isEmpty()) {
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.DETAIL_FEATURES,
+ MessageArgument.untrusted("count", summary.placedFeatures().size()),
+ MessageArgument.untrusted("value", WorldgenCatalogSummary.featureTypes(summary.featureTypes(), DETAIL_LIMIT))));
+ }
+ if (!summary.structures().isEmpty()) {
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.DETAIL_STRUCTURES,
+ MessageArgument.untrusted("count", summary.structures().size()),
+ MessageArgument.untrusted("value", WorldgenCatalogSummary.limited(summary.structures(), namespace, DETAIL_LIMIT))));
+ }
+ if (!summary.dimensionTypes().isEmpty() || !summary.levelStems().isEmpty()) {
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.DETAIL_DIMENSIONS,
+ MessageArgument.untrusted("value", orNone(WorldgenCatalogSummary.limited(summary.dimensionTypes(), namespace, DETAIL_LIMIT))),
+ MessageArgument.untrusted("value2", orNone(WorldgenCatalogSummary.limited(summary.levelStems(), namespace, DETAIL_LIMIT)))));
+ }
+ return 1;
+ }
+
+ private static int refresh(NativeCommandSource source) {
+ NativeModdedServer server = source.server();
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.REFRESHING));
+ ModdedWorldgenCatalogs.capture(server).whenComplete((CaptureResult result, Throwable failure) ->
+ source.execute(() -> reportRefresh(source, result, failure)));
+ return 1;
+ }
+
+ private static void reportRefresh(NativeCommandSource source, CaptureResult result, Throwable failure) {
+ if (failure != null) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.REFRESH_FAILED,
+ MessageArgument.untrusted("reason", reason(failure))));
+ return;
+ }
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.REFRESHED,
+ MessageArgument.untrusted("duration", result.millis() + " ms"),
+ MessageArgument.untrusted("path", result.file().toString())));
+ }
+
+ private static int ingest(NativeCommandSource source, String raw) {
+ ModIngestArguments arguments;
+ try {
+ arguments = ModIngestArguments.parse(raw);
+ } catch (IllegalArgumentException invalid) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_INVALID,
+ MessageArgument.untrusted("reason", invalid.getMessage())));
+ return 0;
+ }
+ Optional catalog = ModdedWorldgenCatalogs.latest();
+ if (catalog.isEmpty()) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.MISSING));
+ return 0;
+ }
+ File packsRoot = ModdedPackCommands.packsRoot();
+ File sourcePack = PackDirectoryResolver.resolveExisting(packsRoot, arguments.sourcePack());
+ if (sourcePack == null) {
+ IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_PACK_MISSING,
+ MessageArgument.untrusted("pack", arguments.sourcePack()),
+ MessageArgument.untrusted("path", packsRoot.getAbsolutePath())));
+ return 0;
+ }
+ Path targetPack = packsRoot.toPath().toAbsolutePath().normalize().resolve(arguments.targetPack());
+ ModIngestRequest request = new ModIngestRequest(sourcePack.toPath(), targetPack, arguments.dimension(),
+ catalog.get(), arguments.namespaces(), arguments.replace());
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_STARTED,
+ MessageArgument.untrusted("source", arguments.sourcePack()),
+ MessageArgument.untrusted("target", arguments.targetPack()),
+ MessageArgument.untrusted("dimension", arguments.dimension())));
+ Thread worker = new Thread(() -> runIngest(source, request), "Iris Mod Ingest");
+ worker.setDaemon(true);
+ worker.start();
+ return 1;
+ }
+
+ private static void runIngest(NativeCommandSource source, ModIngestRequest request) {
+ try {
+ ModIngestReport report = ModIngester.ingest(request);
+ source.execute(() -> reportIngest(source, report));
+ } catch (FileAlreadyExistsException exists) {
+ source.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_TARGET_EXISTS,
+ MessageArgument.untrusted("target", request.targetPack().getFileName().toString()))));
+ } catch (IOException | RuntimeException failure) {
+ ModdedIrisLog.error("Iris mod ingest into " + request.targetPack() + " failed", failure);
+ String reason = reason(failure);
+ source.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_FAILED,
+ MessageArgument.untrusted("reason", reason))));
+ }
+ }
+
+ private static void reportIngest(NativeCommandSource source, ModIngestReport report) {
+ for (String line : report.summaryLines()) {
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_LINE, MessageArgument.untrusted("summary", line)));
+ }
+ IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCatalogMessages.INGEST_COMPLETE,
+ MessageArgument.untrusted("path", report.targetPack().toString())));
+ }
+
+ private static String namespaceLine(NamespaceSummary summary) {
+ String version = summary.namespace().version().isEmpty() ? NONE : summary.namespace().version();
+ return IrisLanguage.plain(ModdedCatalogMessages.NAMESPACE,
+ MessageArgument.untrusted("name", summary.namespace().namespace()),
+ MessageArgument.untrusted("version", version),
+ MessageArgument.untrusted("value", summary.biomes().size()),
+ MessageArgument.untrusted("value2", summary.placedFeatures().size()),
+ MessageArgument.untrusted("detail", orNone(WorldgenCatalogSummary.featureTypes(summary.featureTypes(), LIST_FEATURE_TYPES))),
+ MessageArgument.untrusted("value3", summary.structures().size()),
+ MessageArgument.untrusted("value4", summary.dimensionTypes().size()),
+ MessageArgument.untrusted("value5", summary.levelStems().size()));
+ }
+
+ private static String orNone(String value) {
+ return value.isEmpty() ? NONE : value;
+ }
+
+ private static String reason(Throwable failure) {
+ Throwable cause = failure instanceof CompletionException && failure.getCause() != null ? failure.getCause() : failure;
+ String message = cause.getMessage();
+ return message == null || message.isBlank() ? cause.getClass().getSimpleName() : message;
+ }
+
+ private static List contentNamespaces() {
+ Optional latest = ModdedWorldgenCatalogs.latest();
+ if (latest.isEmpty()) {
+ return List.of();
+ }
+ NamespaceCache cached = NAMESPACE_CACHE.get();
+ if (cached != null && cached.catalog() == latest.get()) {
+ return cached.namespaces();
+ }
+ List namespaces = new ArrayList<>();
+ for (NamespaceSummary summary : WorldgenCatalogSummary.namespaces(latest.get())) {
+ if (summary.hasContent()) {
+ namespaces.add(summary.namespace().namespace());
+ }
+ }
+ NamespaceCache created = new NamespaceCache(latest.get(), List.copyOf(namespaces));
+ NAMESPACE_CACHE.set(created);
+ return created.namespaces();
+ }
+
+ private static CompletableFuture suggestIngestArguments(CommandContext context,
+ SuggestionsBuilder builder) {
+ String remaining = builder.getRemaining();
+ int lastSpace = remaining.lastIndexOf(' ');
+ int bareArguments = 0;
+ List usedOptions = new ArrayList<>(3);
+ for (String token : remaining.substring(0, Math.max(0, lastSpace)).trim().split("\\s+")) {
+ if (token.isEmpty()) {
+ continue;
+ }
+ int equals = token.indexOf('=');
+ if (equals < 0) {
+ bareArguments++;
+ } else {
+ usedOptions.add(token.substring(0, equals + 1));
+ }
+ }
+ SuggestionsBuilder current = builder.createOffset(builder.getStart() + lastSpace + 1);
+ if (bareArguments < 2) {
+ List packs = new ArrayList<>();
+ for (File pack : PackDirectoryResolver.listVisiblePackDirectories(ModdedPackCommands.packsRoot())) {
+ packs.add(pack.getName());
+ }
+ return NativeCommandRegistration.suggest(packs, current);
+ }
+ List options = new ArrayList<>(ModIngestArguments.OPTION_SUGGESTIONS.size());
+ for (String option : ModIngestArguments.OPTION_SUGGESTIONS) {
+ if (!usedOptions.contains(option.substring(0, option.indexOf('=') + 1))) {
+ options.add(option);
+ }
+ }
+ return NativeCommandRegistration.suggest(options, current);
+ }
+
+ private record NamespaceCache(WorldgenCatalog catalog, List namespaces) {
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java
index 36474a93f..faf6b395d 100644
--- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java
@@ -20,6 +20,7 @@ package art.arcane.iris.modded.command;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.IrisMessages;
+import art.arcane.iris.modded.localization.ModdedCatalogMessages;
import art.arcane.iris.modded.localization.ModdedHelpMessages;
import art.arcane.volmlib.util.director.help.DirectorHelpMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
@@ -68,6 +69,8 @@ final class ModdedCommandHelp {
Entry.command("debug", "", ModdedHelpMessages.COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON),
Entry.command("reload", "", ModdedHelpMessages.COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S),
Entry.command("download", "> [overwrite=true]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"),
+ Entry.command("catalog", "[namespace|refresh]", ModdedCatalogMessages.HELP_CATALOG),
+ Entry.group("ingest", ModdedCatalogMessages.HELP_INGEST),
Entry.command("metrics", "", ModdedHelpMessages.COMMAND_METRICS_PRINT_GENERATION_METRICS_FOR_YOUR_CURRENT_IRIS_DIMENSION, "measure"),
Entry.command("regen", "[radius]", ModdedHelpMessages.COMMAND_REGEN_DELETE_AND_REGENERATE_NEARBY_CHUNKS_IN_PLACE, "rg"),
Entry.group("pregen", ModdedHelpMessages.GROUP_PREGEN_PREGENERATE_AN_IRIS_DIMENSION, "pregenerate"),
@@ -165,6 +168,9 @@ final class ModdedCommandHelp {
Entry.command("compat", "[pack]", ModdedHelpMessages.COMMAND_COMPAT_SHOW_CONTENT_THAT_CANNOT_GENERATE_ON_THIS_MINECRAFT_VERSION, "cp")
));
SECTIONS.put("pk", SECTIONS.get("pack"));
+ SECTIONS.put("ingest", List.of(
+ Entry.command("mods", " [dimension=] [namespaces=] [replace=true]", ModdedCatalogMessages.HELP_INGEST_MODS)
+ ));
SECTIONS.put("world", List.of(
Entry.command("enable", " [seed|random]", ModdedHelpMessages.COMMAND_ENABLE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_AT_RUNTIME_DOWNLOADS_THE_PACK, "create"),
Entry.command("replace-overworld", " [seed|random]", ModdedHelpMessages.COMMAND_REPLACE_OVERWORLD_INJECT_AN_IRIS_PRIMARY_WORLD_AND_ROUTE_PLAYERS_THERE_INSTEAD_OF_THE),
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java
index b8120e1ec..389baf91c 100644
--- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java
@@ -114,6 +114,9 @@ final class ModdedCommandTree {
root.then(downloadTree("download"));
root.then(downloadTree("dl"));
+ root.then(ModdedCatalogCommands.catalogTree());
+ root.then(ModdedCatalogCommands.ingestTree());
+
root.then(metricsTree("metrics"));
root.then(metricsTree("measure"));
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedCatalogMessages.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedCatalogMessages.java
new file mode 100644
index 000000000..5482f9183
--- /dev/null
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedCatalogMessages.java
@@ -0,0 +1,150 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.localization;
+
+import art.arcane.volmlib.util.localization.MessageKey;
+import art.arcane.volmlib.util.localization.TextKey;
+
+import java.util.List;
+
+public final class ModdedCatalogMessages {
+ public static final TextKey MISSING = TextKey.of(
+ "iris.modded.catalog.missing",
+ "No worldgen catalog has been captured yet. Run /iris catalog refresh."
+ );
+ public static final TextKey HEADER = TextKey.of(
+ "iris.modded.catalog.header",
+ "Worldgen catalog ({name} {version}): {count} namespaces, {value} biomes, {value2} placed features, "
+ + "{value3} structures, {value4} dimension types, {value5} level stems"
+ );
+ public static final TextKey NAMESPACE = TextKey.of(
+ "iris.modded.catalog.namespace",
+ "{name} {version}: {value} biomes, {value2} placed features ({detail}), {value3} structures, "
+ + "{value4} dimension types, {value5} level stems"
+ );
+ public static final TextKey QUIET = TextKey.of(
+ "iris.modded.catalog.quiet",
+ "{count} more loaded mods have no worldgen content."
+ );
+ public static final TextKey UNKNOWN_NAMESPACE = TextKey.of(
+ "iris.modded.catalog.unknown_namespace",
+ "The worldgen catalog has no content from namespace {name}."
+ );
+ public static final TextKey DETAIL_BIOMES = TextKey.of(
+ "iris.modded.catalog.detail_biomes",
+ "Biomes ({count}): {value}"
+ );
+ public static final TextKey DETAIL_FEATURES = TextKey.of(
+ "iris.modded.catalog.detail_features",
+ "Placed features by type ({count}): {value}"
+ );
+ public static final TextKey DETAIL_STRUCTURES = TextKey.of(
+ "iris.modded.catalog.detail_structures",
+ "Structures ({count}): {value}"
+ );
+ public static final TextKey DETAIL_DIMENSIONS = TextKey.of(
+ "iris.modded.catalog.detail_dimensions",
+ "Dimension types: {value}; level stems: {value2}"
+ );
+ public static final TextKey REFRESHING = TextKey.of(
+ "iris.modded.catalog.refreshing",
+ "Capturing the worldgen catalog..."
+ );
+ public static final TextKey REFRESHED = TextKey.of(
+ "iris.modded.catalog.refreshed",
+ "Worldgen catalog captured in {duration}: {path}"
+ );
+ public static final TextKey REFRESH_FAILED = TextKey.of(
+ "iris.modded.catalog.refresh_failed",
+ "Worldgen catalog capture failed: {reason}"
+ );
+ public static final TextKey INGEST_INVALID = TextKey.of(
+ "iris.modded.catalog.ingest_invalid",
+ "Invalid arguments: {reason}. Usage: /iris ingest mods [dimension=] "
+ + "[namespaces=] [replace=true]"
+ );
+ public static final TextKey INGEST_PACK_MISSING = TextKey.of(
+ "iris.modded.catalog.ingest_pack_missing",
+ "Pack '{pack}' was not found under {path}."
+ );
+ public static final TextKey INGEST_STARTED = TextKey.of(
+ "iris.modded.catalog.ingest_started",
+ "Ingesting mod worldgen from '{source}' into '{target}' (dimension {dimension})..."
+ );
+ public static final TextKey INGEST_FAILED = TextKey.of(
+ "iris.modded.catalog.ingest_failed",
+ "Mod ingest failed: {reason}"
+ );
+ public static final TextKey INGEST_TARGET_EXISTS = TextKey.of(
+ "iris.modded.catalog.ingest_target_exists",
+ "Target pack '{target}' already exists. Add replace=true to overwrite it."
+ );
+ public static final TextKey INGEST_LINE = TextKey.of(
+ "iris.modded.catalog.ingest_line",
+ " {summary}"
+ );
+ public static final TextKey INGEST_COMPLETE = TextKey.of(
+ "iris.modded.catalog.ingest_complete",
+ "Mod ingest finished: {path}"
+ );
+ public static final TextKey HELP_CATALOG = TextKey.of(
+ "iris.modded.help.entry.command.catalog",
+ "List the worldgen content of loaded mods, show one namespace, or capture it again"
+ );
+ public static final TextKey HELP_INGEST = TextKey.of(
+ "iris.modded.help.entry.group.ingest",
+ "Build Iris packs from the worldgen content of loaded mods"
+ );
+ public static final TextKey HELP_INGEST_MODS = TextKey.of(
+ "iris.modded.help.entry.command.ingest_mods",
+ "Copy a source pack into a new target pack and adopt the worldgen of loaded mods"
+ );
+
+ private static final List KEYS = List.of(
+ MISSING,
+ HEADER,
+ NAMESPACE,
+ QUIET,
+ UNKNOWN_NAMESPACE,
+ DETAIL_BIOMES,
+ DETAIL_FEATURES,
+ DETAIL_STRUCTURES,
+ DETAIL_DIMENSIONS,
+ REFRESHING,
+ REFRESHED,
+ REFRESH_FAILED,
+ INGEST_INVALID,
+ INGEST_PACK_MISSING,
+ INGEST_STARTED,
+ INGEST_FAILED,
+ INGEST_TARGET_EXISTS,
+ INGEST_LINE,
+ INGEST_COMPLETE,
+ HELP_CATALOG,
+ HELP_INGEST,
+ HELP_INGEST_MODS
+ );
+
+ private ModdedCatalogMessages() {
+ }
+
+ public static List keys() {
+ return KEYS;
+ }
+}
diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedMessageContributor.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedMessageContributor.java
index 9a738a938..4a9654994 100644
--- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedMessageContributor.java
+++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/localization/ModdedMessageContributor.java
@@ -12,6 +12,7 @@ public final class ModdedMessageContributor implements IrisMessageContributor {
List keys = new ArrayList<>();
keys.addAll(ModdedCommandMessages.keys());
keys.addAll(ModdedHelpMessages.keys());
+ keys.addAll(ModdedCatalogMessages.keys());
keys.addAll(ClientUiMessages.keys());
return List.copyOf(keys);
}
diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java
index 8fc4445a9..9351cda28 100644
--- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java
+++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java
@@ -1,5 +1,6 @@
package art.arcane.iris.modded;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModMetadata;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
@@ -20,6 +21,7 @@ import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.nio.file.Path;
+import java.util.List;
import static org.junit.Assert.assertEquals;
@@ -45,6 +47,11 @@ public class ModdedPlatformPathsTest {
return "0.0.0";
}
+ @Override
+ public List mods() {
+ return List.of();
+ }
+
@Override
public NativeModdedServer currentServer() {
return null;
diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java
new file mode 100644
index 000000000..5641b0e42
--- /dev/null
+++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java
@@ -0,0 +1,129 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.catalog;
+
+import art.arcane.iris.pack.ingest.catalog.CatalogBiome;
+import art.arcane.iris.pack.ingest.catalog.CatalogLevelStem;
+import art.arcane.iris.pack.ingest.catalog.CatalogNamespace;
+import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogFingerprint;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModMetadata;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.EncodeFailures;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenBiome;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenDimensionType;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenLevelStem;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenPlacedFeature;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenSpawn;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenStructure;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class WorldgenCatalogAssemblerTest {
+ private static final CatalogCaptureContext CONTEXT = new CatalogCaptureContext("26.3", "neoforge", 1234L, List.of(
+ new NativeModMetadata("minecraft", "Minecraft", "26.3"),
+ new NativeModMetadata("examplemod", "Example Mod", "1.0.0"),
+ new NativeModMetadata("quietmod", "Quiet Mod", "2.0.0")));
+
+ @Test
+ public void namespacesCombineLoaderModsWithRegistryKeyNamespaces() {
+ WorldgenCatalog catalog = WorldgenCatalogAssembler.assemble(snapshot(), CONTEXT);
+
+ assertEquals(List.of(
+ new CatalogNamespace("datapack", "datapack", ""),
+ new CatalogNamespace("examplemod", "Example Mod", "1.0.0"),
+ new CatalogNamespace("minecraft", "Minecraft", "26.3"),
+ new CatalogNamespace("quietmod", "Quiet Mod", "2.0.0")), catalog.namespaces());
+ }
+
+ @Test
+ public void minecraftNamespaceFallsBackToTheRunningVersion() {
+ CatalogCaptureContext context = new CatalogCaptureContext("26.3", "forge", 1L, List.of());
+
+ WorldgenCatalog catalog = WorldgenCatalogAssembler.assemble(snapshot(), context);
+
+ assertEquals(new CatalogNamespace("minecraft", "Minecraft", "26.3"), catalog.namespaces().get(2));
+ }
+
+ @Test
+ public void recordsAreCopiedWithTheirJson() {
+ NativeWorldgenSnapshot snapshot = snapshot();
+
+ WorldgenCatalog catalog = WorldgenCatalogAssembler.assemble(snapshot, CONTEXT);
+
+ assertEquals(WorldgenCatalog.FORMAT, catalog.format());
+ assertEquals("26.3", catalog.minecraftVersion());
+ assertEquals("neoforge", catalog.platform());
+ assertEquals(1234L, catalog.capturedAtEpochMillis());
+ CatalogBiome glade = catalog.biomes().getFirst();
+ assertEquals("examplemod:glade", glade.key());
+ assertEquals(0.7F, glade.temperature(), 0F);
+ assertEquals(0.4F, glade.downfall(), 0F);
+ assertEquals(List.of(List.of(), List.of("examplemod:ore_glimmer")), glade.featureSteps());
+ assertEquals("creature", glade.spawns().getFirst().category());
+ assertSame(snapshot.biomes().getFirst().definition(), glade.definition());
+ CatalogPlacedFeature ore = catalog.placedFeatures().getFirst();
+ assertEquals("minecraft:ore", ore.featureType());
+ assertSame(snapshot.placedFeatures().getFirst().config(), ore.config());
+ CatalogLevelStem stem = catalog.levelStems().getFirst();
+ assertEquals("minecraft:multi_noise", stem.biomeSourceType());
+ assertEquals("examplemod:towers", catalog.structures().getFirst().structureSets().getFirst());
+ assertEquals(384, catalog.dimensionTypes().getFirst().height());
+ }
+
+ @Test
+ public void fingerprintCoversNamespacesAndEveryRegistryKey() {
+ WorldgenCatalog catalog = WorldgenCatalogAssembler.assemble(snapshot(), CONTEXT);
+
+ String expected = WorldgenCatalogFingerprint.of(catalog.namespaces(), Map.of(
+ "biome", List.of("datapack:dunes", "examplemod:glade"),
+ "placed_feature", List.of("examplemod:ore_glimmer"),
+ "structure", List.of("examplemod:tower"),
+ "dimension_type", List.of("minecraft:overworld"),
+ "level_stem", List.of("minecraft:overworld")));
+ assertEquals(expected, catalog.contentFingerprint());
+ }
+
+ static NativeWorldgenSnapshot snapshot() {
+ JsonObject definition = new JsonObject();
+ definition.add("downfall", new JsonPrimitive(0.4F));
+ JsonObject config = new JsonObject();
+ config.add("type", new JsonPrimitive("minecraft:ore"));
+ return new NativeWorldgenSnapshot(
+ List.of(new WorldgenBiome("examplemod:glade", 0.7F, 0.4F, true, List.of("minecraft:is_overworld"),
+ List.of(List.of(), List.of("examplemod:ore_glimmer")),
+ List.of(new WorldgenSpawn("creature", "minecraft:sheep", 12, 2, 4)), definition),
+ new WorldgenBiome("datapack:dunes", 2.0F, 0F, false, List.of(), List.of(), List.of(), null)),
+ List.of(new WorldgenPlacedFeature("examplemod:ore_glimmer", "examplemod:ore_glimmer", "minecraft:ore", config, null)),
+ List.of(new WorldgenStructure("examplemod:tower", "minecraft:jigsaw", "surface_structures", "beard_thin",
+ "examplemod:has_tower", List.of("examplemod:glade"), List.of("examplemod:towers"))),
+ List.of(new WorldgenDimensionType("minecraft:overworld", -64, 384, 384, null)),
+ List.of(new WorldgenLevelStem("minecraft:overworld", "minecraft:overworld", "minecraft:noise",
+ "minecraft:multi_noise", null)),
+ new EncodeFailures(0, List.of(), null));
+ }
+}
diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java
new file mode 100644
index 000000000..583cbe162
--- /dev/null
+++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java
@@ -0,0 +1,117 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.catalog;
+
+import art.arcane.iris.modded.catalog.WorldgenCatalogSummary.NamespaceSummary;
+import art.arcane.iris.modded.catalog.WorldgenCatalogSummary.TypeCount;
+import art.arcane.iris.pack.ingest.catalog.CatalogBiome;
+import art.arcane.iris.pack.ingest.catalog.CatalogDimensionType;
+import art.arcane.iris.pack.ingest.catalog.CatalogLevelStem;
+import art.arcane.iris.pack.ingest.catalog.CatalogNamespace;
+import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature;
+import art.arcane.iris.pack.ingest.catalog.CatalogStructure;
+import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class WorldgenCatalogSummaryTest {
+ @Test
+ public void summariesCountEveryRegistryPerNamespace() {
+ List summaries = WorldgenCatalogSummary.namespaces(catalog());
+
+ assertEquals(List.of("examplemod", "minecraft", "quietmod"),
+ summaries.stream().map((NamespaceSummary summary) -> summary.namespace().namespace()).toList());
+ NamespaceSummary example = summaries.getFirst();
+ assertEquals(List.of("examplemod:glade", "examplemod:marsh"), example.biomes());
+ assertEquals(6, example.placedFeatures().size());
+ assertEquals(List.of("examplemod:tower"), example.structures());
+ assertEquals(List.of("examplemod:sky"), example.dimensionTypes());
+ assertEquals(List.of("examplemod:sky"), example.levelStems());
+ assertTrue(example.hasContent());
+ assertFalse(summaries.get(2).hasContent());
+ }
+
+ @Test
+ public void featureTypesAreRankedByCountThenName() {
+ NamespaceSummary example = WorldgenCatalogSummary.namespace(catalog(), "examplemod").orElseThrow();
+
+ assertEquals(List.of(new TypeCount("minecraft:ore", 3), new TypeCount("minecraft:random_patch", 2),
+ new TypeCount("examplemod:big_tree", 1)), example.featureTypes());
+ assertEquals("ore 3, random_patch 2 (+1 more)", WorldgenCatalogSummary.featureTypes(example.featureTypes(), 2));
+ assertEquals("ore 3, random_patch 2, examplemod:big_tree 1",
+ WorldgenCatalogSummary.featureTypes(example.featureTypes(), 5));
+ }
+
+ @Test
+ public void unknownNamespaceHasNoSummary() {
+ Optional missing = WorldgenCatalogSummary.namespace(catalog(), "missingmod");
+
+ assertTrue(missing.isEmpty());
+ }
+
+ @Test
+ public void keyNamespacesWithoutModMetadataStillSummarize() {
+ WorldgenCatalog catalog = new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 1L, "x",
+ List.of(), List.of(biome("datapack:dunes")), List.of(), List.of(), List.of(), List.of());
+
+ NamespaceSummary summary = WorldgenCatalogSummary.namespace(catalog, "datapack").orElseThrow();
+
+ assertEquals(new CatalogNamespace("datapack", "datapack", ""), summary.namespace());
+ assertEquals(List.of("datapack:dunes"), summary.biomes());
+ }
+
+ @Test
+ public void limitedListsShortenOwnKeysAndCountTheRest() {
+ assertEquals("glade, marsh (+1 more)",
+ WorldgenCatalogSummary.limited(List.of("examplemod:glade", "examplemod:marsh", "examplemod:moor"), "examplemod", 2));
+ assertEquals("glade, minecraft:plains",
+ WorldgenCatalogSummary.limited(List.of("examplemod:glade", "minecraft:plains"), "examplemod", 5));
+ assertEquals("", WorldgenCatalogSummary.limited(List.of(), "examplemod", 5));
+ }
+
+ private static WorldgenCatalog catalog() {
+ return new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "neoforge", 1L, "x",
+ List.of(new CatalogNamespace("examplemod", "Example Mod", "1.0.0"),
+ new CatalogNamespace("minecraft", "Minecraft", "26.3"),
+ new CatalogNamespace("quietmod", "Quiet Mod", "2.0.0")),
+ List.of(biome("examplemod:marsh"), biome("examplemod:glade"), biome("minecraft:plains")),
+ List.of(feature("examplemod:ore_a", "minecraft:ore"), feature("examplemod:ore_b", "minecraft:ore"),
+ feature("examplemod:ore_c", "minecraft:ore"), feature("examplemod:patch_a", "minecraft:random_patch"),
+ feature("examplemod:patch_b", "minecraft:random_patch"), feature("examplemod:tree", "examplemod:big_tree"),
+ feature("minecraft:ore_iron", "minecraft:ore")),
+ List.of(new CatalogStructure("examplemod:tower", "minecraft:jigsaw", "surface_structures", "none", "",
+ List.of(), List.of())),
+ List.of(new CatalogDimensionType("examplemod:sky", 0, 256, 256, null)),
+ List.of(new CatalogLevelStem("examplemod:sky", "examplemod:sky", "minecraft:noise", "minecraft:fixed", null)));
+ }
+
+ private static CatalogBiome biome(String key) {
+ return new CatalogBiome(key, 0.5F, 0.5F, true, List.of(), List.of(), List.of(), null);
+ }
+
+ private static CatalogPlacedFeature feature(String key, String type) {
+ return new CatalogPlacedFeature(key, key, type, null, null);
+ }
+}
diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModIngestArgumentsTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModIngestArgumentsTest.java
new file mode 100644
index 000000000..c90dcca25
--- /dev/null
+++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModIngestArgumentsTest.java
@@ -0,0 +1,81 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.command;
+
+import org.junit.Test;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class ModIngestArgumentsTest {
+ @Test
+ public void requiredPacksBindPositionallyAndOptionalsDefault() {
+ ModIngestArguments arguments = ModIngestArguments.parse("overworld modded_overworld");
+
+ assertEquals("overworld", arguments.sourcePack());
+ assertEquals("modded_overworld", arguments.targetPack());
+ assertEquals("overworld", arguments.dimension());
+ assertEquals(Set.of(), arguments.namespaces());
+ assertFalse(arguments.replace());
+ }
+
+ @Test
+ public void keyedOptionalsAreParsedInAnyOrder() {
+ ModIngestArguments arguments = ModIngestArguments.parse(
+ " overworld modded replace=true namespaces=biomesoplenty,terralith dimension=overworld_alt ");
+
+ assertEquals("overworld_alt", arguments.dimension());
+ assertEquals(Set.of("biomesoplenty", "terralith"), arguments.namespaces());
+ assertTrue(arguments.replace());
+ }
+
+ @Test
+ public void missingTargetIsRejected() {
+ IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
+ () -> ModIngestArguments.parse("overworld"));
+
+ assertEquals("missing target pack", failure.getMessage());
+ }
+
+ @Test
+ public void strayPositionalsAndUnknownKeysAreRejected() {
+ assertEquals("unexpected argument 'extra'", reason("overworld modded extra"));
+ assertEquals("unknown option 'seed'", reason("overworld modded seed=1"));
+ assertEquals("option 'replace' given twice", reason("overworld modded replace=true replace=false"));
+ }
+
+ @Test
+ public void valuesAreValidated() {
+ assertEquals("replace must be true or false", reason("overworld modded replace=yes"));
+ assertEquals("invalid pack name '../evil'", reason("../evil modded"));
+ assertEquals("invalid target pack name 'modded.v2'", reason("overworld modded.v2"));
+ assertEquals("invalid dimension key 'a..b'", reason("overworld modded dimension=a..b"));
+ assertEquals("invalid namespace 'Bad'", reason("overworld modded namespaces=ok,Bad"));
+ assertEquals("namespaces must not be empty", reason("overworld modded namespaces=,"));
+ assertEquals("source and target must be different packs", reason("overworld overworld"));
+ }
+
+ private static String reason(String raw) {
+ return assertThrows(IllegalArgumentException.class, () -> ModIngestArguments.parse(raw)).getMessage();
+ }
+}
diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedCatalogCommandsTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedCatalogCommandsTest.java
new file mode 100644
index 000000000..434fd750b
--- /dev/null
+++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedCatalogCommandsTest.java
@@ -0,0 +1,73 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.modded.command;
+
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandRegistration;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.tree.CommandNode;
+import net.minecraft.SharedConstants;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.server.Bootstrap;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ModdedCatalogCommandsTest {
+ @BeforeClass
+ public static void bootstrap() {
+ SharedConstants.tryDetectVersion();
+ Bootstrap.bootStrap();
+ }
+
+ @Test
+ public void catalogAndIngestCommandsAreRegistered() {
+ CommandDispatcher dispatcher = new CommandDispatcher<>();
+ IrisModdedCommands.register(new NativeCommandRegistration(dispatcher));
+
+ CommandNode iris = child(dispatcher.getRoot(), "iris");
+ CommandNode catalog = child(iris, "catalog");
+ assertNotNull(catalog.getCommand());
+ assertNotNull(child(catalog, "refresh").getCommand());
+ assertNotNull(child(catalog, "namespace").getCommand());
+ CommandNode ingest = child(iris, "ingest");
+ assertNotNull(ingest.getCommand());
+ CommandNode mods = child(ingest, "mods");
+ assertNotNull(child(mods, "arguments").getCommand());
+ }
+
+ @Test
+ public void helpDocumentsCatalogAndIngest() {
+ assertTrue(ModdedCommandHelp.documents("", "catalog"));
+ assertTrue(ModdedCommandHelp.documents("", "ingest"));
+ assertTrue(ModdedCommandHelp.documents("ingest", "mods"));
+ List lines = ModdedCommandHelp.consoleLines("ingest");
+ assertTrue(lines.stream().anyMatch((String line) ->
+ line.startsWith("/iris ingest mods [dimension=] [namespaces=] [replace=true] - ")));
+ }
+
+ private static CommandNode child(CommandNode parent, String name) {
+ CommandNode child = parent.getChild(name);
+ assertNotNull(name, child);
+ return child;
+ }
+}
diff --git a/adapters/modded-common/src/test/java/art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeWorldgenScanTest.java b/adapters/modded-common/src/test/java/art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeWorldgenScanTest.java
new file mode 100644
index 000000000..10dab083c
--- /dev/null
+++ b/adapters/modded-common/src/test/java/art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeWorldgenScanTest.java
@@ -0,0 +1,129 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.volmlib.nativelib.minecraft26_2.modded;
+
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenBiome;
+import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenPlacedFeature;
+import com.google.gson.JsonParser;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.Lifecycle;
+import net.minecraft.SharedConstants;
+import net.minecraft.core.Holder;
+import net.minecraft.core.MappedRegistry;
+import net.minecraft.core.RegistrationInfo;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.resources.Identifier;
+import net.minecraft.resources.RegistryOps;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.server.Bootstrap;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.dimension.DimensionType;
+import net.minecraft.world.level.levelgen.GenerationStep;
+import net.minecraft.world.level.levelgen.carver.WorldCarver;
+import net.minecraft.world.level.levelgen.feature.Feature;
+import net.minecraft.world.level.levelgen.placement.PlacedFeature;
+import net.minecraft.world.level.levelgen.structure.Structure;
+import net.minecraft.world.level.levelgen.structure.StructureSet;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class NativeWorldgenScanTest {
+ private static final String BIOME_JSON = "{\"has_precipitation\":true,\"temperature\":0.8,\"downfall\":0.4,"
+ + "\"effects\":{\"water_color\":\"#3f76e4\"},\"carvers\":[],"
+ + "\"features\":[[],[],[],[],[],[],[\"examplemod:ore_glimmer\"]]}";
+
+ @BeforeClass
+ public static void bootstrap() {
+ SharedConstants.tryDetectVersion();
+ Bootstrap.bootStrap();
+ }
+
+ @Test
+ public void scanReadsLiveFeatureStepsAndEncodesDefinitions() {
+ NativeWorldgenSnapshot snapshot = NativeWorldgenScan.scan(server(), loader()).encode();
+
+ WorldgenBiome biome = snapshot.biomes().getFirst();
+ assertEquals("examplemod:glade", biome.key());
+ assertEquals(0.8F, biome.temperature(), 0F);
+ assertEquals(0.4F, biome.downfall(), 0.0001F);
+ assertTrue(biome.hasPrecipitation());
+ assertEquals(GenerationStep.Decoration.values().length, biome.featureSteps().size());
+ assertEquals(List.of("examplemod:ore_glimmer"),
+ biome.featureSteps().get(GenerationStep.Decoration.UNDERGROUND_ORES.ordinal()));
+ assertNotNull(biome.definition());
+ assertEquals(0.4F, biome.definition().getAsJsonObject().get("downfall").getAsFloat(), 0.0001F);
+ }
+
+ @Test
+ public void unencodableFeaturesStayInTheCatalogWithoutJson() {
+ NativeWorldgenSnapshot snapshot = NativeWorldgenScan.scan(server(), loader()).encode();
+
+ WorldgenPlacedFeature feature = snapshot.placedFeatures().getFirst();
+ assertEquals("examplemod:ore_glimmer", feature.key());
+ assertEquals("", feature.feature());
+ assertNull(feature.config());
+ assertTrue(feature.placement().isJsonArray());
+ assertEquals(1, snapshot.failures().count());
+ assertTrue(snapshot.failures().samples().getFirst().startsWith("feature of examplemod:ore_glimmer"));
+ assertTrue(snapshot.levelStems().isEmpty());
+ }
+
+ private static NativeModdedServer server() {
+ MappedRegistry features = new MappedRegistry<>(Registries.PLACED_FEATURE, Lifecycle.stable());
+ features.register(ResourceKey.create(Registries.PLACED_FEATURE, Identifier.parse("examplemod:ore_glimmer")),
+ new PlacedFeature(Holder.direct(mock(Feature.class)), List.of()), RegistrationInfo.BUILT_IN);
+ features.freeze();
+ MappedRegistry carvers = new MappedRegistry<>(Registries.CARVER, Lifecycle.stable());
+ carvers.freeze();
+ MappedRegistry biomes = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
+ MappedRegistry structures = new MappedRegistry<>(Registries.STRUCTURE, Lifecycle.stable());
+ structures.freeze();
+ MappedRegistry structureSets = new MappedRegistry<>(Registries.STRUCTURE_SET, Lifecycle.stable());
+ structureSets.freeze();
+ MappedRegistry dimensionTypes = new MappedRegistry<>(Registries.DIMENSION_TYPE, Lifecycle.stable());
+ dimensionTypes.freeze();
+ RegistryAccess access = new RegistryAccess.ImmutableRegistryAccess(
+ List.of(biomes, features, carvers, structures, structureSets, dimensionTypes));
+ Biome biome = Biome.DIRECT_CODEC.parse(RegistryOps.create(JsonOps.INSTANCE, access),
+ JsonParser.parseString(BIOME_JSON)).getOrThrow();
+ biomes.register(ResourceKey.create(Registries.BIOME, Identifier.parse("examplemod:glade")), biome, RegistrationInfo.BUILT_IN);
+ biomes.freeze();
+ MinecraftServer server = mock(MinecraftServer.class);
+ when(server.registryAccess()).thenReturn(access.freeze());
+ return NativeModdedServer.fromHandle(server);
+ }
+
+ private static NativeModdedLoader loader() {
+ NativeModdedLoader loader = mock(NativeModdedLoader.class);
+ when(loader.unmodifiedBiome(any())).thenAnswer(invocation -> invocation.getArgument(0));
+ return loader;
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogFingerprint.java b/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogFingerprint.java
new file mode 100644
index 000000000..3597e84bd
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogFingerprint.java
@@ -0,0 +1,66 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.pack.ingest.catalog;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+public final class WorldgenCatalogFingerprint {
+ private WorldgenCatalogFingerprint() {
+ }
+
+ public static String of(List namespaces, Map> registryKeys) {
+ Objects.requireNonNull(namespaces, "namespaces");
+ Objects.requireNonNull(registryKeys, "registryKeys");
+ MessageDigest digest = sha256();
+ List versions = new ArrayList<>(namespaces.size());
+ for (CatalogNamespace namespace : namespaces) {
+ versions.add(namespace.namespace() + ":" + namespace.version());
+ }
+ update(digest, "namespaces", versions);
+ for (Map.Entry> registry : new TreeMap<>(registryKeys).entrySet()) {
+ update(digest, "registry " + registry.getKey(), new ArrayList<>(registry.getValue()));
+ }
+ return HexFormat.of().formatHex(digest.digest());
+ }
+
+ private static void update(MessageDigest digest, String section, List values) {
+ values.sort(null);
+ digest.update((section + "\n").getBytes(StandardCharsets.UTF_8));
+ for (String value : values) {
+ digest.update((value + "\n").getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ private static MessageDigest sha256() {
+ try {
+ return MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException missing) {
+ throw new IllegalStateException("SHA-256 is unavailable", missing);
+ }
+ }
+}
diff --git a/core/src/main/resources/languages/de_DE.toml b/core/src/main/resources/languages/de_DE.toml
index 55472401e..25189f6f8 100644
--- a/core/src/main/resources/languages/de_DE.toml
+++ b/core/src/main/resources/languages/de_DE.toml
@@ -361,6 +361,9 @@ operator_instruction = "Führe {command} in der Konsole aus oder aktiviere Cheat
"entry.command.debug" = "Iris-Debugprotokollierung umschalten und iris.json speichern"
"entry.command.reload" = "iris.json neu laden; die Datei wird außerdem automatisch alle 3 Sekunden direkt neu geladen"
"entry.command.download" = "Ein Pack-Projekt herunterladen"
+"entry.command.catalog" = "Worldgen-Inhalte geladener Mods auflisten, einen Namespace anzeigen oder neu erfassen"
+"entry.group.ingest" = "Iris-Packs aus den Worldgen-Inhalten geladener Mods erstellen"
+"entry.command.ingest_mods" = "Ein Quell-Pack in ein neues Ziel-Pack kopieren und das Worldgen geladener Mods übernehmen"
"entry.command.metrics" = "Generierungsmetriken der aktuellen Iris-Dimension anzeigen"
"entry.command.regen" = "Löschen und regenerieren Sie nahe gelegene Chunks an Ort und Stelle"
"entry.group.pregen" = "Eine Iris-Dimension vorgenerieren"
@@ -455,6 +458,27 @@ operator_instruction = "Führe {command} in der Konsole aus oder aktiviere Cheat
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Inhalte anzeigen, die in dieser Minecraft-Version nicht generiert werden können"
+[iris.modded.catalog]
+missing = "Es wurde noch kein Worldgen-Katalog erfasst. Führe /iris catalog refresh aus."
+header = "Worldgen-Katalog ({name} {version}): {count} Namespaces, {value} Biome, {value2} platzierte Features, {value3} Strukturen, {value4} Dimensionstypen, {value5} Level-Stems"
+namespace = "{name} {version}: {value} Biome, {value2} platzierte Features ({detail}), {value3} Strukturen, {value4} Dimensionstypen, {value5} Level-Stems"
+quiet = "{count} weitere geladene Mods haben keine Worldgen-Inhalte."
+unknown_namespace = "Der Worldgen-Katalog enthält keine Inhalte aus dem Namespace {name}."
+detail_biomes = "Biome ({count}): {value}"
+detail_features = "Platzierte Features nach Typ ({count}): {value}"
+detail_structures = "Strukturen ({count}): {value}"
+detail_dimensions = "Dimensionstypen: {value}; Level-Stems: {value2}"
+refreshing = "Worldgen-Katalog wird erfasst..."
+refreshed = "Worldgen-Katalog in {duration} erfasst: {path}"
+refresh_failed = "Erfassung des Worldgen-Katalogs fehlgeschlagen: {reason}"
+ingest_invalid = "Ungültige Argumente: {reason}. Verwendung: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Pack '{pack}' wurde unter {path} nicht gefunden."
+ingest_started = "Übernehme Mod-Worldgen aus '{source}' in '{target}' (Dimension {dimension})..."
+ingest_failed = "Mod-Übernahme fehlgeschlagen: {reason}"
+ingest_target_exists = "Ziel-Pack '{target}' existiert bereits. Füge replace=true hinzu, um es zu überschreiben."
+ingest_line = " {summary}"
+ingest_complete = "Mod-Übernahme abgeschlossen: {path}"
+
[iris.modded.command]
"properties.none" = "Eigenschaften: (keine)"
"properties.list" = "Eigenschaften: {properties}"
diff --git a/core/src/main/resources/languages/es_ES.toml b/core/src/main/resources/languages/es_ES.toml
index 70b48a67d..f126ae8d7 100644
--- a/core/src/main/resources/languages/es_ES.toml
+++ b/core/src/main/resources/languages/es_ES.toml
@@ -361,6 +361,9 @@ operator_instruction = "Ejecuta {command} desde la consola (o activa los trucos
"entry.command.debug" = "Alternar el registro de depuración de Iris y guardar iris.json"
"entry.command.reload" = "Recargar iris.json (también se recarga en caliente automáticamente cada 3s)"
"entry.command.download" = "Descargar un proyecto de pack"
+"entry.command.catalog" = "Listar el contenido de generación de los mods cargados, mostrar un espacio de nombres o volver a capturarlo"
+"entry.group.ingest" = "Crear packs de Iris a partir del contenido de generación de los mods cargados"
+"entry.command.ingest_mods" = "Copiar un pack de origen en un nuevo pack de destino e incorporar la generación de los mods cargados"
"entry.command.metrics" = "Mostrar métricas de generación de tu dimensión de Iris actual"
"entry.command.regen" = "Eliminar y regenerar chunks cercanos en el mismo lugar"
"entry.group.pregen" = "Pregenerar una dimensión de Iris"
@@ -455,6 +458,27 @@ operator_instruction = "Ejecuta {command} desde la consola (o activa los trucos
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Muestra el contenido que no puede generarse en esta versión de Minecraft"
+[iris.modded.catalog]
+missing = "Todavía no se ha capturado ningún catálogo de generación de mundo. Ejecuta /iris catalog refresh."
+header = "Catálogo de generación ({name} {version}): {count} espacios de nombres, {value} biomas, {value2} features colocadas, {value3} estructuras, {value4} tipos de dimensión, {value5} level stems"
+namespace = "{name} {version}: {value} biomas, {value2} features colocadas ({detail}), {value3} estructuras, {value4} tipos de dimensión, {value5} level stems"
+quiet = "Otros {count} mods cargados no tienen contenido de generación de mundo."
+unknown_namespace = "El catálogo de generación no tiene contenido del espacio de nombres {name}."
+detail_biomes = "Biomas ({count}): {value}"
+detail_features = "Features colocadas por tipo ({count}): {value}"
+detail_structures = "Estructuras ({count}): {value}"
+detail_dimensions = "Tipos de dimensión: {value}; level stems: {value2}"
+refreshing = "Capturando el catálogo de generación de mundo..."
+refreshed = "Catálogo de generación capturado en {duration}: {path}"
+refresh_failed = "Error al capturar el catálogo de generación: {reason}"
+ingest_invalid = "Argumentos no válidos: {reason}. Uso: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "No se encontró el pack '{pack}' en {path}."
+ingest_started = "Incorporando la generación de mods de '{source}' a '{target}' (dimensión {dimension})..."
+ingest_failed = "La incorporación de mods falló: {reason}"
+ingest_target_exists = "El pack de destino '{target}' ya existe. Añade replace=true para sobrescribirlo."
+ingest_line = " {summary}"
+ingest_complete = "Incorporación de mods terminada: {path}"
+
[iris.modded.command]
"properties.none" = "Propiedades: (ninguno)"
"properties.list" = "Propiedades: {properties}"
diff --git a/core/src/main/resources/languages/fi_FI.toml b/core/src/main/resources/languages/fi_FI.toml
index 5616b83eb..326890358 100644
--- a/core/src/main/resources/languages/fi_FI.toml
+++ b/core/src/main/resources/languages/fi_FI.toml
@@ -366,6 +366,9 @@ operator_instruction = "Juokse {command} konsolilta (tai mahdollista huijaukset
"entry.command.debug" = "Vaihda Iris vianetsintä ja tallenna iris.json"
"entry.command.reload" = "Lataa uudelleen iris.json (myös hotloaded automaattisesti joka 3.s)"
"entry.command.download" = "Lataa pakettiprojekti"
+"entry.command.catalog" = "Listaa ladattujen modien maailmangenerointisisältö, näytä yksi nimiavaruus tai kerää se uudelleen"
+"entry.group.ingest" = "Luo Iris-paketteja ladattujen modien maailmangenerointisisällöstä"
+"entry.command.ingest_mods" = "Kopioi lähdepaketti uudeksi kohdepaketiksi ja ota ladattujen modien maailmangenerointi käyttöön"
"entry.command.metrics" = "Tulosta nykyisen sukupolven mittareita Iris ulottuvuus"
"entry.command.regen" = "Poista ja uudista lähistön palaset paikoilleen"
"entry.group.pregen" = "Esiaste Iris ulottuvuus"
@@ -460,6 +463,27 @@ operator_instruction = "Juokse {command} konsolilta (tai mahdollista huijaukset
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Näytä sisältö, jota ei voi luoda tässä Minecraft-versiossa"
+[iris.modded.catalog]
+missing = "Maailmangeneroinnin luetteloa ei ole vielä kerätty. Suorita /iris catalog refresh."
+header = "Maailmangeneroinnin luettelo ({name} {version}): {count} nimiavaruutta, {value} biomia, {value2} sijoitettua featurea, {value3} rakennelmaa, {value4} ulottuvuustyyppiä, {value5} level stemiä"
+namespace = "{name} {version}: {value} biomia, {value2} sijoitettua featurea ({detail}), {value3} rakennelmaa, {value4} ulottuvuustyyppiä, {value5} level stemiä"
+quiet = "{count} muulla ladatulla modilla ei ole maailmangenerointisisältöä."
+unknown_namespace = "Maailmangeneroinnin luettelossa ei ole sisältöä nimiavaruudesta {name}."
+detail_biomes = "Biomit ({count}): {value}"
+detail_features = "Sijoitetut featuret tyypeittäin ({count}): {value}"
+detail_structures = "Rakennelmat ({count}): {value}"
+detail_dimensions = "Ulottuvuustyypit: {value}; level stemit: {value2}"
+refreshing = "Kerätään maailmangeneroinnin luetteloa..."
+refreshed = "Maailmangeneroinnin luettelo kerätty ajassa {duration}: {path}"
+refresh_failed = "Maailmangeneroinnin luettelon kerääminen epäonnistui: {reason}"
+ingest_invalid = "Virheelliset argumentit: {reason}. Käyttö: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Pakettia '{pack}' ei löytynyt kohteesta {path}."
+ingest_started = "Tuodaan modien maailmangenerointi paketista '{source}' pakettiin '{target}' (ulottuvuus {dimension})..."
+ingest_failed = "Modien tuonti epäonnistui: {reason}"
+ingest_target_exists = "Kohdepaketti '{target}' on jo olemassa. Lisää replace=true korvataksesi sen."
+ingest_line = " {summary}"
+ingest_complete = "Modien tuonti valmis: {path}"
+
[iris.modded.command]
"properties.none" = "Ominaisuudet: (ei)"
"properties.list" = "Ominaisuudet: {properties}"
diff --git a/core/src/main/resources/languages/fr_FR.toml b/core/src/main/resources/languages/fr_FR.toml
index 20e4834fd..dd56126f1 100644
--- a/core/src/main/resources/languages/fr_FR.toml
+++ b/core/src/main/resources/languages/fr_FR.toml
@@ -361,6 +361,9 @@ operator_instruction = "Exécutez {command} depuis la console (ou activez les co
"entry.command.debug" = "Basculer les journaux de débogage Iris et enregistrer iris.json"
"entry.command.reload" = "Recharger iris.json (également rechargé à chaud automatiquement toutes les 3s)"
"entry.command.download" = "Télécharger un projet de pack"
+"entry.command.catalog" = "Lister le contenu de génération des mods chargés, afficher un espace de noms ou le capturer à nouveau"
+"entry.group.ingest" = "Créer des packs Iris à partir du contenu de génération des mods chargés"
+"entry.command.ingest_mods" = "Copier un pack source dans un nouveau pack cible et y intégrer la génération des mods chargés"
"entry.command.metrics" = "Afficher les métriques de génération de votre dimension Iris actuelle"
"entry.command.regen" = "Supprimer et régénérer sur place les chunks proches"
"entry.group.pregen" = "Prégénérer une dimension Iris"
@@ -455,6 +458,27 @@ operator_instruction = "Exécutez {command} depuis la console (ou activez les co
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Afficher le contenu qui ne peut pas être généré sur cette version de Minecraft"
+[iris.modded.catalog]
+missing = "Aucun catalogue de génération n'a encore été capturé. Exécutez /iris catalog refresh."
+header = "Catalogue de génération ({name} {version}) : {count} espaces de noms, {value} biomes, {value2} features placées, {value3} structures, {value4} types de dimension, {value5} level stems"
+namespace = "{name} {version} : {value} biomes, {value2} features placées ({detail}), {value3} structures, {value4} types de dimension, {value5} level stems"
+quiet = "{count} autres mods chargés n'ont aucun contenu de génération."
+unknown_namespace = "Le catalogue de génération ne contient rien de l'espace de noms {name}."
+detail_biomes = "Biomes ({count}) : {value}"
+detail_features = "Features placées par type ({count}) : {value}"
+detail_structures = "Structures ({count}) : {value}"
+detail_dimensions = "Types de dimension : {value} ; level stems : {value2}"
+refreshing = "Capture du catalogue de génération..."
+refreshed = "Catalogue de génération capturé en {duration} : {path}"
+refresh_failed = "Échec de la capture du catalogue de génération : {reason}"
+ingest_invalid = "Arguments invalides : {reason}. Utilisation : /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Le pack '{pack}' est introuvable dans {path}."
+ingest_started = "Intégration de la génération des mods de '{source}' vers '{target}' (dimension {dimension})..."
+ingest_failed = "L'intégration des mods a échoué : {reason}"
+ingest_target_exists = "Le pack cible '{target}' existe déjà. Ajoutez replace=true pour le remplacer."
+ingest_line = " {summary}"
+ingest_complete = "Intégration des mods terminée : {path}"
+
[iris.modded.command]
"properties.none" = "Propriétés: (aucune)"
"properties.list" = "Propriétés : {properties}"
diff --git a/core/src/main/resources/languages/he_IL.toml b/core/src/main/resources/languages/he_IL.toml
index 92ce65eb7..d1b60c580 100644
--- a/core/src/main/resources/languages/he_IL.toml
+++ b/core/src/main/resources/languages/he_IL.toml
@@ -366,6 +366,9 @@ operator_instruction = "לרוץ {command} מן הקונסולה (או לאפש
"entry.command.debug" = "עקבו אחרי Iris debug bug , שמירה iris.json"
"entry.command.reload" = "המונחים טעינה מחדש iris.json (גם חם כל 3s)"
"entry.command.download" = "הורד פרויקט חבילה"
+"entry.command.catalog" = "הצג את תוכן יצירת העולם של המודים הטעונים, הצג מרחב שמות אחד או אסוף אותו מחדש"
+"entry.group.ingest" = "בנה חבילות Iris מתוכן יצירת העולם של המודים הטעונים"
+"entry.command.ingest_mods" = "העתק חבילת מקור לחבילת יעד חדשה ואמץ את יצירת העולם של המודים הטעונים"
"entry.command.metrics" = "הדפסת דור מדדים עבור הנוכחי שלך Iris מימד"
"entry.command.regen" = "למחוק ולחדש חלקים סמוכים במקום"
"entry.group.pregen" = "עקבו אחרי a Iris מימד"
@@ -460,6 +463,27 @@ operator_instruction = "לרוץ {command} מן הקונסולה (או לאפש
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "הצג תוכן שלא ניתן ליצור בגרסת Minecraft הזו"
+[iris.modded.catalog]
+missing = "עדיין לא נאסף קטלוג יצירת עולם. הרץ /iris catalog refresh."
+header = "קטלוג יצירת עולם ({name} {version}): {count} מרחבי שמות, {value} ביומים, {value2} פיצ'רים ממוקמים, {value3} מבנים, {value4} סוגי ממדים, {value5} level stems"
+namespace = "{name} {version}: {value} ביומים, {value2} פיצ'רים ממוקמים ({detail}), {value3} מבנים, {value4} סוגי ממדים, {value5} level stems"
+quiet = "ל-{count} מודים טעונים נוספים אין תוכן יצירת עולם."
+unknown_namespace = "בקטלוג יצירת העולם אין תוכן ממרחב השמות {name}."
+detail_biomes = "ביומים ({count}): {value}"
+detail_features = "פיצ'רים ממוקמים לפי סוג ({count}): {value}"
+detail_structures = "מבנים ({count}): {value}"
+detail_dimensions = "סוגי ממדים: {value}; level stems: {value2}"
+refreshing = "אוסף את קטלוג יצירת העולם..."
+refreshed = "קטלוג יצירת העולם נאסף תוך {duration}: {path}"
+refresh_failed = "איסוף קטלוג יצירת העולם נכשל: {reason}"
+ingest_invalid = "ארגומנטים לא תקינים: {reason}. שימוש: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "החבילה '{pack}' לא נמצאה תחת {path}."
+ingest_started = "מייבא את יצירת העולם של המודים מ-'{source}' אל '{target}' (ממד {dimension})..."
+ingest_failed = "ייבוא המודים נכשל: {reason}"
+ingest_target_exists = "חבילת היעד '{target}' כבר קיימת. הוסף replace=true כדי לדרוס אותה."
+ingest_line = " {summary}"
+ingest_complete = "ייבוא המודים הסתיים: {path}"
+
[iris.modded.command]
"properties.none" = "נכסים: (אין)"
"properties.list" = "נכסים: {properties}"
diff --git a/core/src/main/resources/languages/it_IT.toml b/core/src/main/resources/languages/it_IT.toml
index 898f5b296..b7cc510c8 100644
--- a/core/src/main/resources/languages/it_IT.toml
+++ b/core/src/main/resources/languages/it_IT.toml
@@ -361,6 +361,9 @@ operator_instruction = "Esegui {command} dalla console oppure abilita i trucchi
"entry.command.debug" = "Attiva o disattiva la registrazione di debug Iris e salva iris.json"
"entry.command.reload" = "Ricarica iris.json; il file viene anche ricaricato automaticamente ogni 3 secondi"
"entry.command.download" = "Scarica un progetto Pack"
+"entry.command.catalog" = "Elenca i contenuti di generazione delle mod caricate, mostra un namespace o acquisiscilo di nuovo"
+"entry.group.ingest" = "Crea pack Iris dai contenuti di generazione delle mod caricate"
+"entry.command.ingest_mods" = "Copia un pack di origine in un nuovo pack di destinazione e adotta la generazione delle mod caricate"
"entry.command.metrics" = "Mostra le metriche di generazione della dimensione Iris corrente"
"entry.command.regen" = "Eliminare e rigenerare i chunk vicini al posto"
"entry.group.pregen" = "Pregenera una dimensione Iris"
@@ -455,6 +458,27 @@ operator_instruction = "Esegui {command} dalla console oppure abilita i trucchi
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Mostra i contenuti che non possono essere generati su questa versione di Minecraft"
+[iris.modded.catalog]
+missing = "Nessun catalogo di generazione è stato ancora acquisito. Esegui /iris catalog refresh."
+header = "Catalogo di generazione ({name} {version}): {count} namespace, {value} biomi, {value2} feature piazzate, {value3} strutture, {value4} tipi di dimensione, {value5} level stem"
+namespace = "{name} {version}: {value} biomi, {value2} feature piazzate ({detail}), {value3} strutture, {value4} tipi di dimensione, {value5} level stem"
+quiet = "Altre {count} mod caricate non hanno contenuti di generazione."
+unknown_namespace = "Il catalogo di generazione non contiene nulla dal namespace {name}."
+detail_biomes = "Biomi ({count}): {value}"
+detail_features = "Feature piazzate per tipo ({count}): {value}"
+detail_structures = "Strutture ({count}): {value}"
+detail_dimensions = "Tipi di dimensione: {value}; level stem: {value2}"
+refreshing = "Acquisizione del catalogo di generazione..."
+refreshed = "Catalogo di generazione acquisito in {duration}: {path}"
+refresh_failed = "Acquisizione del catalogo di generazione non riuscita: {reason}"
+ingest_invalid = "Argomenti non validi: {reason}. Uso: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Il pack '{pack}' non è stato trovato in {path}."
+ingest_started = "Importazione della generazione delle mod da '{source}' a '{target}' (dimensione {dimension})..."
+ingest_failed = "Importazione delle mod non riuscita: {reason}"
+ingest_target_exists = "Il pack di destinazione '{target}' esiste già. Aggiungi replace=true per sovrascriverlo."
+ingest_line = " {summary}"
+ingest_complete = "Importazione delle mod completata: {path}"
+
[iris.modded.command]
"properties.none" = "Proprietà: (nessuna)"
"properties.list" = "Proprietà: {properties}"
diff --git a/core/src/main/resources/languages/ja-JP.toml b/core/src/main/resources/languages/ja-JP.toml
index 2f3d4fc21..99e6c926c 100644
--- a/core/src/main/resources/languages/ja-JP.toml
+++ b/core/src/main/resources/languages/ja-JP.toml
@@ -364,6 +364,9 @@ operator_instruction = "コンソールから {command} を実行してくださ
"entry.command.debug" = "Iris のデバッグログを切り替えて iris.json を保存します"
"entry.command.reload" = "iris.json を再読み込みします(3s ごとの自動ホットロードにも対応)"
"entry.command.download" = "パックプロジェクトをダウンロードします"
+"entry.command.catalog" = "読み込まれた Mod のワールド生成コンテンツを一覧表示し、1 つの名前空間を表示し、または再取得します"
+"entry.group.ingest" = "読み込まれた Mod のワールド生成コンテンツから Iris パックを作成します"
+"entry.command.ingest_mods" = "元のパックを新しいパックにコピーし、読み込まれた Mod のワールド生成を取り込みます"
"entry.command.metrics" = "現在の Iris ディメンションの生成メトリクスを表示します"
"entry.command.regen" = "周囲のチャンクを削除し、その場で再生成します"
"entry.group.pregen" = "Iris ディメンションを事前生成します"
@@ -458,6 +461,27 @@ operator_instruction = "コンソールから {command} を実行してくださ
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "この Minecraft バージョンでは生成できない内容を表示します"
+[iris.modded.catalog]
+missing = "ワールド生成カタログはまだ取得されていません。/iris catalog refresh を実行してください。"
+header = "ワールド生成カタログ ({name} {version}): 名前空間 {count}、バイオーム {value}、配置済みフィーチャー {value2}、構造物 {value3}、ディメンションタイプ {value4}、レベルステム {value5}"
+namespace = "{name} {version}: バイオーム {value}、配置済みフィーチャー {value2} ({detail})、構造物 {value3}、ディメンションタイプ {value4}、レベルステム {value5}"
+quiet = "ほかに読み込まれた {count} 個の Mod にはワールド生成コンテンツがありません。"
+unknown_namespace = "ワールド生成カタログには名前空間 {name} のコンテンツがありません。"
+detail_biomes = "バイオーム ({count}): {value}"
+detail_features = "種類別の配置済みフィーチャー ({count}): {value}"
+detail_structures = "構造物 ({count}): {value}"
+detail_dimensions = "ディメンションタイプ: {value}、レベルステム: {value2}"
+refreshing = "ワールド生成カタログを取得しています..."
+refreshed = "ワールド生成カタログを {duration} で取得しました: {path}"
+refresh_failed = "ワールド生成カタログの取得に失敗しました: {reason}"
+ingest_invalid = "引数が無効です: {reason}。使い方: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "パック '{pack}' が {path} に見つかりません。"
+ingest_started = "'{source}' から '{target}' へ Mod のワールド生成を取り込んでいます (ディメンション {dimension})..."
+ingest_failed = "Mod の取り込みに失敗しました: {reason}"
+ingest_target_exists = "出力先パック '{target}' はすでに存在します。上書きするには replace=true を追加してください。"
+ingest_line = " {summary}"
+ingest_complete = "Mod の取り込みが完了しました: {path}"
+
[iris.modded.command]
"properties.none" = "プロパティ:(なし)"
"properties.list" = "プロパティ: {properties}"
diff --git a/core/src/main/resources/languages/ko_KR.toml b/core/src/main/resources/languages/ko_KR.toml
index 967099f9d..c417cfeb9 100644
--- a/core/src/main/resources/languages/ko_KR.toml
+++ b/core/src/main/resources/languages/ko_KR.toml
@@ -364,6 +364,9 @@ operator_instruction = "콘솔에서 {command}을(를) 실행하세요(싱글
"entry.command.debug" = "Iris 디버그 로깅을 전환하고 iris.json을 저장합니다"
"entry.command.reload" = "iris.json을 다시 로드합니다(3s마다 자동 핫로드도 수행)"
"entry.command.download" = "팩 프로젝트를 다운로드합니다"
+"entry.command.catalog" = "로드된 모드의 월드 생성 콘텐츠를 나열하거나, 네임스페이스 하나를 표시하거나, 다시 수집합니다"
+"entry.group.ingest" = "로드된 모드의 월드 생성 콘텐츠로 Iris 팩을 만듭니다"
+"entry.command.ingest_mods" = "원본 팩을 새 대상 팩으로 복사하고 로드된 모드의 월드 생성을 적용합니다"
"entry.command.metrics" = "현재 Iris 차원의 생성 측정값을 표시합니다"
"entry.command.regen" = "주변 청크를 삭제하고 제자리에서 다시 생성합니다"
"entry.group.pregen" = "Iris 차원을 사전 생성합니다"
@@ -458,6 +461,27 @@ operator_instruction = "콘솔에서 {command}을(를) 실행하세요(싱글
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "이 Minecraft 버전에서 생성할 수 없는 콘텐츠를 표시합니다"
+[iris.modded.catalog]
+missing = "아직 월드 생성 카탈로그가 수집되지 않았습니다. /iris catalog refresh를 실행하세요."
+header = "월드 생성 카탈로그 ({name} {version}): 네임스페이스 {count}개, 바이옴 {value}개, 배치된 지형지물 {value2}개, 구조물 {value3}개, 차원 유형 {value4}개, 레벨 스템 {value5}개"
+namespace = "{name} {version}: 바이옴 {value}개, 배치된 지형지물 {value2}개 ({detail}), 구조물 {value3}개, 차원 유형 {value4}개, 레벨 스템 {value5}개"
+quiet = "로드된 다른 모드 {count}개에는 월드 생성 콘텐츠가 없습니다."
+unknown_namespace = "월드 생성 카탈로그에 네임스페이스 {name}의 콘텐츠가 없습니다."
+detail_biomes = "바이옴 ({count}): {value}"
+detail_features = "유형별 배치된 지형지물 ({count}): {value}"
+detail_structures = "구조물 ({count}): {value}"
+detail_dimensions = "차원 유형: {value}; 레벨 스템: {value2}"
+refreshing = "월드 생성 카탈로그를 수집하는 중..."
+refreshed = "월드 생성 카탈로그를 {duration} 만에 수집했습니다: {path}"
+refresh_failed = "월드 생성 카탈로그 수집에 실패했습니다: {reason}"
+ingest_invalid = "잘못된 인수: {reason}. 사용법: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "{path}에서 팩 '{pack}'을(를) 찾을 수 없습니다."
+ingest_started = "'{source}'에서 '{target}'(으)로 모드 월드 생성을 가져오는 중 (차원 {dimension})..."
+ingest_failed = "모드 가져오기에 실패했습니다: {reason}"
+ingest_target_exists = "대상 팩 '{target}'이(가) 이미 있습니다. 덮어쓰려면 replace=true를 추가하세요."
+ingest_line = " {summary}"
+ingest_complete = "모드 가져오기 완료: {path}"
+
[iris.modded.command]
"properties.none" = "속성: (없음)"
"properties.list" = "속성: {properties}"
diff --git a/core/src/main/resources/languages/lt_LT.toml b/core/src/main/resources/languages/lt_LT.toml
index 9fdbf193e..2f737936e 100644
--- a/core/src/main/resources/languages/lt_LT.toml
+++ b/core/src/main/resources/languages/lt_LT.toml
@@ -366,6 +366,9 @@ operator_instruction = "Vykdyti {command} iš konsolės (arba leidžia cheats į
"entry.command.debug" = "Perjungti Iris Derinti prisijungimą ir įrašymą iris.json"
"entry.command.reload" = "Įkelti iš naujo iris.json (taip pat įkeliamas automatiškai kas 3s)"
"entry.command.download" = "Parsisiųsti paketo projektą"
+"entry.command.catalog" = "Išvardyti įkeltų modų pasaulio generavimo turinį, parodyti vieną vardų sritį arba surinkti jį iš naujo"
+"entry.group.ingest" = "Kurti Iris paketus iš įkeltų modų pasaulio generavimo turinio"
+"entry.command.ingest_mods" = "Nukopijuoti šaltinio paketą į naują paskirties paketą ir perimti įkeltų modų pasaulio generavimą"
"entry.command.metrics" = "Spausdinti kartos metrikos savo dabartinę Iris matmuo"
"entry.command.regen" = "Pašalinti ir regeneruoti netoliese gabaliukus vietoje"
"entry.group.pregen" = "Regeneruoti Iris matmuo"
@@ -460,6 +463,27 @@ operator_instruction = "Vykdyti {command} iš konsolės (arba leidžia cheats į
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Rodyti turinį, kurio negalima sugeneruoti šioje Minecraft versijoje"
+[iris.modded.catalog]
+missing = "Pasaulio generavimo katalogas dar nesurinktas. Paleisk /iris catalog refresh."
+header = "Pasaulio generavimo katalogas ({name} {version}): vardų sritys {count}, biomai {value}, išdėstyti elementai {value2}, struktūros {value3}, dimensijų tipai {value4}, lygio stiebai {value5}"
+namespace = "{name} {version}: biomai {value}, išdėstyti elementai {value2} ({detail}), struktūros {value3}, dimensijų tipai {value4}, lygio stiebai {value5}"
+quiet = "Dar {count} įkeltų modų neturi pasaulio generavimo turinio."
+unknown_namespace = "Pasaulio generavimo kataloge nėra turinio iš vardų srities {name}."
+detail_biomes = "Biomai ({count}): {value}"
+detail_features = "Išdėstyti elementai pagal tipą ({count}): {value}"
+detail_structures = "Struktūros ({count}): {value}"
+detail_dimensions = "Dimensijų tipai: {value}; lygio stiebai: {value2}"
+refreshing = "Renkamas pasaulio generavimo katalogas..."
+refreshed = "Pasaulio generavimo katalogas surinktas per {duration}: {path}"
+refresh_failed = "Nepavyko surinkti pasaulio generavimo katalogo: {reason}"
+ingest_invalid = "Netinkami argumentai: {reason}. Naudojimas: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Paketas '{pack}' nerastas {path}."
+ingest_started = "Perkeliamas modų pasaulio generavimas iš '{source}' į '{target}' (dimensija {dimension})..."
+ingest_failed = "Modų perkėlimas nepavyko: {reason}"
+ingest_target_exists = "Paskirties paketas '{target}' jau egzistuoja. Pridėk replace=true, kad jį perrašytum."
+ingest_line = " {summary}"
+ingest_complete = "Modų perkėlimas baigtas: {path}"
+
[iris.modded.command]
"properties.none" = "Savybės: (nėra)"
"properties.list" = "Savybės: {properties}"
diff --git a/core/src/main/resources/languages/nl_NL.toml b/core/src/main/resources/languages/nl_NL.toml
index 3817f6169..b69549c0a 100644
--- a/core/src/main/resources/languages/nl_NL.toml
+++ b/core/src/main/resources/languages/nl_NL.toml
@@ -361,6 +361,9 @@ operator_instruction = "Start {command} vanaf de console (of schakel bedrog in s
"entry.command.debug" = "Schakelen Iris debug loggen en opslaan iris.json"
"entry.command.reload" = "Herladen iris.json (ook automatisch gehotload elke 3s)"
"entry.command.download" = "Een pakketproject downloaden"
+"entry.command.catalog" = "De wereldgeneratie-inhoud van geladen mods tonen, één namespace weergeven of opnieuw vastleggen"
+"entry.group.ingest" = "Iris-packs maken van de wereldgeneratie-inhoud van geladen mods"
+"entry.command.ingest_mods" = "Een bronpack naar een nieuw doelpack kopiëren en de wereldgeneratie van geladen mods overnemen"
"entry.command.metrics" = "Generatiegegevens afdrukken voor uw huidige Iris dimensie"
"entry.command.regen" = "Verwijderen en regenereren nabijgelegen chunks in plaats"
"entry.group.pregen" = "Pregeneer een Iris dimensie"
@@ -455,6 +458,27 @@ operator_instruction = "Start {command} vanaf de console (of schakel bedrog in s
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Toon inhoud die niet kan worden gegenereerd op deze Minecraft-versie"
+[iris.modded.catalog]
+missing = "Er is nog geen wereldgeneratiecatalogus vastgelegd. Voer /iris catalog refresh uit."
+header = "Wereldgeneratiecatalogus ({name} {version}): {count} namespaces, {value} biomen, {value2} geplaatste features, {value3} structuren, {value4} dimensietypen, {value5} level stems"
+namespace = "{name} {version}: {value} biomen, {value2} geplaatste features ({detail}), {value3} structuren, {value4} dimensietypen, {value5} level stems"
+quiet = "{count} andere geladen mods hebben geen wereldgeneratie-inhoud."
+unknown_namespace = "De wereldgeneratiecatalogus bevat geen inhoud uit namespace {name}."
+detail_biomes = "Biomen ({count}): {value}"
+detail_features = "Geplaatste features per type ({count}): {value}"
+detail_structures = "Structuren ({count}): {value}"
+detail_dimensions = "Dimensietypen: {value}; level stems: {value2}"
+refreshing = "Wereldgeneratiecatalogus wordt vastgelegd..."
+refreshed = "Wereldgeneratiecatalogus vastgelegd in {duration}: {path}"
+refresh_failed = "Vastleggen van de wereldgeneratiecatalogus mislukt: {reason}"
+ingest_invalid = "Ongeldige argumenten: {reason}. Gebruik: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Pack '{pack}' is niet gevonden in {path}."
+ingest_started = "Wereldgeneratie van mods wordt overgenomen van '{source}' naar '{target}' (dimensie {dimension})..."
+ingest_failed = "Overnemen van mods mislukt: {reason}"
+ingest_target_exists = "Doelpack '{target}' bestaat al. Voeg replace=true toe om het te overschrijven."
+ingest_line = " {summary}"
+ingest_complete = "Overnemen van mods voltooid: {path}"
+
[iris.modded.command]
"properties.none" = "Eigenschappen: (geen)"
"properties.list" = "Eigenschappen: {properties}"
diff --git a/core/src/main/resources/languages/pl_PL.toml b/core/src/main/resources/languages/pl_PL.toml
index 32922cf6e..31cfbe159 100644
--- a/core/src/main/resources/languages/pl_PL.toml
+++ b/core/src/main/resources/languages/pl_PL.toml
@@ -366,6 +366,9 @@ operator_instruction = "Biegnij {command} z konsoli (lub włączyć cheats w sin
"entry.command.debug" = "Włączenie / wyłączenie Iris debugowanie logowania i zapisywanie iris.json"
"entry.command.reload" = "Wczytaj ponownie iris.json (także automatycznie naświetlane co 3)"
"entry.command.download" = "Pobierz projekt pakietu"
+"entry.command.catalog" = "Wyświetl zawartość generowania świata wczytanych modów, pokaż jedną przestrzeń nazw lub zbierz ją ponownie"
+"entry.group.ingest" = "Twórz paczki Iris z zawartości generowania świata wczytanych modów"
+"entry.command.ingest_mods" = "Skopiuj paczkę źródłową do nowej paczki docelowej i przejmij generowanie świata wczytanych modów"
"entry.command.metrics" = "Wydrukuj wskaźniki generacji dla bieżącego Iris wymiar"
"entry.command.regen" = "Usuń i zregeneruj pobliskie chunki w miejscu"
"entry.group.pregen" = "Przed Iris wymiar"
@@ -460,6 +463,27 @@ operator_instruction = "Biegnij {command} z konsoli (lub włączyć cheats w sin
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Pokaż zawartość, której nie można wygenerować w tej wersji Minecraft"
+[iris.modded.catalog]
+missing = "Katalog generowania świata nie został jeszcze zebrany. Uruchom /iris catalog refresh."
+header = "Katalog generowania świata ({name} {version}): przestrzenie nazw: {count}, biomy: {value}, umieszczone obiekty: {value2}, struktury: {value3}, typy wymiarów: {value4}, level stemy: {value5}"
+namespace = "{name} {version}: biomy: {value}, umieszczone obiekty: {value2} ({detail}), struktury: {value3}, typy wymiarów: {value4}, level stemy: {value5}"
+quiet = "Kolejne wczytane mody ({count}) nie mają zawartości generowania świata."
+unknown_namespace = "Katalog generowania świata nie zawiera nic z przestrzeni nazw {name}."
+detail_biomes = "Biomy ({count}): {value}"
+detail_features = "Umieszczone obiekty według typu ({count}): {value}"
+detail_structures = "Struktury ({count}): {value}"
+detail_dimensions = "Typy wymiarów: {value}; level stemy: {value2}"
+refreshing = "Zbieranie katalogu generowania świata..."
+refreshed = "Katalog generowania świata zebrany w {duration}: {path}"
+refresh_failed = "Nie udało się zebrać katalogu generowania świata: {reason}"
+ingest_invalid = "Nieprawidłowe argumenty: {reason}. Użycie: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Nie znaleziono paczki '{pack}' w {path}."
+ingest_started = "Przenoszenie generowania świata modów z '{source}' do '{target}' (wymiar {dimension})..."
+ingest_failed = "Przenoszenie modów nie powiodło się: {reason}"
+ingest_target_exists = "Paczka docelowa '{target}' już istnieje. Dodaj replace=true, aby ją nadpisać."
+ingest_line = " {summary}"
+ingest_complete = "Przenoszenie modów zakończone: {path}"
+
[iris.modded.command]
"properties.none" = "Właściwości: (brak)"
"properties.list" = "Właściwości: {properties}"
diff --git a/core/src/main/resources/languages/pt_PT.toml b/core/src/main/resources/languages/pt_PT.toml
index d2a4a1335..a304bee87 100644
--- a/core/src/main/resources/languages/pt_PT.toml
+++ b/core/src/main/resources/languages/pt_PT.toml
@@ -361,6 +361,9 @@ operator_instruction = "Executar {command} a partir do console (ou habilitar fra
"entry.command.debug" = "Alternar Iris debug registrar e salvar iris.json"
"entry.command.reload" = "Recarregar iris.json (também carregado automaticamente a cada 3s)"
"entry.command.download" = "Baixar um projeto de pacote"
+"entry.command.catalog" = "Listar o conteúdo de geração dos mods carregados, mostrar um namespace ou capturá-lo novamente"
+"entry.group.ingest" = "Criar packs Iris a partir do conteúdo de geração dos mods carregados"
+"entry.command.ingest_mods" = "Copiar um pack de origem para um novo pack de destino e adotar a geração dos mods carregados"
"entry.command.metrics" = "Imprimir métricas de geração para o seu atual Iris dimensão"
"entry.command.regen" = "Apagar e regenerar chunks próximos no lugar"
"entry.group.pregen" = "Pré- Gerar uma Iris dimensão"
@@ -455,6 +458,27 @@ operator_instruction = "Executar {command} a partir do console (ou habilitar fra
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Mostrar o conteúdo que não pode ser gerado nesta versão do Minecraft"
+[iris.modded.catalog]
+missing = "Ainda não foi capturado nenhum catálogo de geração de mundo. Executa /iris catalog refresh."
+header = "Catálogo de geração ({name} {version}): {count} namespaces, {value} biomas, {value2} features colocadas, {value3} estruturas, {value4} tipos de dimensão, {value5} level stems"
+namespace = "{name} {version}: {value} biomas, {value2} features colocadas ({detail}), {value3} estruturas, {value4} tipos de dimensão, {value5} level stems"
+quiet = "Mais {count} mods carregados não têm conteúdo de geração de mundo."
+unknown_namespace = "O catálogo de geração não tem conteúdo do namespace {name}."
+detail_biomes = "Biomas ({count}): {value}"
+detail_features = "Features colocadas por tipo ({count}): {value}"
+detail_structures = "Estruturas ({count}): {value}"
+detail_dimensions = "Tipos de dimensão: {value}; level stems: {value2}"
+refreshing = "A capturar o catálogo de geração de mundo..."
+refreshed = "Catálogo de geração capturado em {duration}: {path}"
+refresh_failed = "Falha ao capturar o catálogo de geração: {reason}"
+ingest_invalid = "Argumentos inválidos: {reason}. Utilização: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "O pack '{pack}' não foi encontrado em {path}."
+ingest_started = "A importar a geração de mods de '{source}' para '{target}' (dimensão {dimension})..."
+ingest_failed = "A importação de mods falhou: {reason}"
+ingest_target_exists = "O pack de destino '{target}' já existe. Adiciona replace=true para o substituir."
+ingest_line = " {summary}"
+ingest_complete = "Importação de mods concluída: {path}"
+
[iris.modded.command]
"properties.none" = "Propriedades: (nenhum)"
"properties.list" = "Propriedades: {properties}"
diff --git a/core/src/main/resources/languages/ru_RU.toml b/core/src/main/resources/languages/ru_RU.toml
index bb948faf5..edb2aeaf5 100644
--- a/core/src/main/resources/languages/ru_RU.toml
+++ b/core/src/main/resources/languages/ru_RU.toml
@@ -366,6 +366,9 @@ operator_instruction = "Беги. {command} от консоли (или вклю
"entry.command.debug" = "переключаться Iris Отладка и сохранение iris.json"
"entry.command.reload" = "Перезагрузить iris.json (также автоматически загружается каждые 3 секунды)"
"entry.command.download" = "Скачать пакетный проект"
+"entry.command.catalog" = "Показать генерацию мира загруженных модов, одно пространство имён или собрать каталог заново"
+"entry.group.ingest" = "Создать паки Iris из генерации мира загруженных модов"
+"entry.command.ingest_mods" = "Скопировать исходный пак в новый целевой пак и перенести в него генерацию мира загруженных модов"
"entry.command.metrics" = "Метрики генерации печати для вашего текущего Iris измерение"
"entry.command.regen" = "Удалить и регенерировать близлежащие чанки на месте"
"entry.group.pregen" = "Прегенерировать Iris измерение"
@@ -460,6 +463,27 @@ operator_instruction = "Беги. {command} от консоли (или вклю
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Показать содержимое, которое нельзя сгенерировать в этой версии Minecraft"
+[iris.modded.catalog]
+missing = "Каталог генерации мира ещё не собран. Выполните /iris catalog refresh."
+header = "Каталог генерации мира ({name} {version}): пространств имён {count}, биомов {value}, размещённых объектов {value2}, структур {value3}, типов измерений {value4}, level stem {value5}"
+namespace = "{name} {version}: биомов {value}, размещённых объектов {value2} ({detail}), структур {value3}, типов измерений {value4}, level stem {value5}"
+quiet = "Ещё {count} загруженных модов не содержат генерации мира."
+unknown_namespace = "В каталоге генерации мира нет содержимого из пространства имён {name}."
+detail_biomes = "Биомы ({count}): {value}"
+detail_features = "Размещённые объекты по типам ({count}): {value}"
+detail_structures = "Структуры ({count}): {value}"
+detail_dimensions = "Типы измерений: {value}; level stem: {value2}"
+refreshing = "Сбор каталога генерации мира..."
+refreshed = "Каталог генерации мира собран за {duration}: {path}"
+refresh_failed = "Не удалось собрать каталог генерации мира: {reason}"
+ingest_invalid = "Неверные аргументы: {reason}. Использование: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Пак '{pack}' не найден в {path}."
+ingest_started = "Перенос генерации мира модов из '{source}' в '{target}' (измерение {dimension})..."
+ingest_failed = "Не удалось перенести моды: {reason}"
+ingest_target_exists = "Целевой пак '{target}' уже существует. Добавьте replace=true, чтобы перезаписать его."
+ingest_line = " {summary}"
+ingest_complete = "Перенос модов завершён: {path}"
+
[iris.modded.command]
"properties.none" = "Свойства: (нет)"
"properties.list" = "Свойства: {properties}"
diff --git a/core/src/main/resources/languages/tr_TR.toml b/core/src/main/resources/languages/tr_TR.toml
index f53c5c664..04de8938b 100644
--- a/core/src/main/resources/languages/tr_TR.toml
+++ b/core/src/main/resources/languages/tr_TR.toml
@@ -366,6 +366,9 @@ operator_instruction = "{command} komutunu konsoldan çalıştırın (veya tek o
"entry.command.debug" = "değiştir Iris Debug login ve kaydet iris.json"
"entry.command.reload" = "Yeniden yükleme iris.json (Ayrıca her 3s otomatik olarak ısındı)"
"entry.command.download" = "Bir paket projesi indirin"
+"entry.command.catalog" = "Yüklü modların dünya oluşturma içeriğini listele, tek bir ad alanını göster veya yeniden topla"
+"entry.group.ingest" = "Yüklü modların dünya oluşturma içeriğinden Iris paketleri oluştur"
+"entry.command.ingest_mods" = "Bir kaynak paketi yeni bir hedef pakete kopyala ve yüklü modların dünya oluşturmasını uygula"
"entry.command.metrics" = "Mevcut nesil ölçümler için Iris boyut boyutu"
"entry.command.regen" = "Yakınlarda silme ve yenidenjenere"
"entry.group.pregen" = "Pregenerate a an Iris boyut boyutu"
@@ -460,6 +463,27 @@ operator_instruction = "{command} komutunu konsoldan çalıştırın (veya tek o
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Bu Minecraft sürümünde üretilemeyen içeriği göster"
+[iris.modded.catalog]
+missing = "Henüz bir dünya oluşturma kataloğu toplanmadı. /iris catalog refresh komutunu çalıştırın."
+header = "Dünya oluşturma kataloğu ({name} {version}): {count} ad alanı, {value} biyom, {value2} yerleştirilmiş özellik, {value3} yapı, {value4} boyut türü, {value5} level stem"
+namespace = "{name} {version}: {value} biyom, {value2} yerleştirilmiş özellik ({detail}), {value3} yapı, {value4} boyut türü, {value5} level stem"
+quiet = "Yüklü {count} mod daha dünya oluşturma içeriğine sahip değil."
+unknown_namespace = "Dünya oluşturma kataloğunda {name} ad alanından içerik yok."
+detail_biomes = "Biyomlar ({count}): {value}"
+detail_features = "Türe göre yerleştirilmiş özellikler ({count}): {value}"
+detail_structures = "Yapılar ({count}): {value}"
+detail_dimensions = "Boyut türleri: {value}; level stem'ler: {value2}"
+refreshing = "Dünya oluşturma kataloğu toplanıyor..."
+refreshed = "Dünya oluşturma kataloğu {duration} içinde toplandı: {path}"
+refresh_failed = "Dünya oluşturma kataloğu toplanamadı: {reason}"
+ingest_invalid = "Geçersiz argümanlar: {reason}. Kullanım: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "'{pack}' paketi {path} altında bulunamadı."
+ingest_started = "Mod dünya oluşturması '{source}' paketinden '{target}' paketine aktarılıyor (boyut {dimension})..."
+ingest_failed = "Mod aktarımı başarısız oldu: {reason}"
+ingest_target_exists = "Hedef paket '{target}' zaten var. Üzerine yazmak için replace=true ekleyin."
+ingest_line = " {summary}"
+ingest_complete = "Mod aktarımı tamamlandı: {path}"
+
[iris.modded.command]
"properties.none" = "Özellikler: (hiçbiri)"
"properties.list" = "Özellikler: {properties}"
diff --git a/core/src/main/resources/languages/vi_VI.toml b/core/src/main/resources/languages/vi_VI.toml
index c8ace9fa5..ee5a045d4 100644
--- a/core/src/main/resources/languages/vi_VI.toml
+++ b/core/src/main/resources/languages/vi_VI.toml
@@ -364,6 +364,9 @@ operator_instruction = "Chạy {command} từ bàn giao tiếp (hoặc kích ho
"entry.command.debug" = "Bật/ tắt Iris @ info/ rich iris.json"
"entry.command.reload" = "Nạp lại iris.json (đã nạp quá nóng tự động mỗi 3)"
"entry.command.download" = "Tải về dự án gói"
+"entry.command.catalog" = "Liệt kê nội dung tạo thế giới của các mod đã tải, hiển thị một không gian tên hoặc thu thập lại"
+"entry.group.ingest" = "Tạo gói Iris từ nội dung tạo thế giới của các mod đã tải"
+"entry.command.ingest_mods" = "Sao chép gói nguồn sang gói đích mới và áp dụng phần tạo thế giới của các mod đã tải"
"entry.command.metrics" = "Số đo cỡ ảnh hiện thời Iris Chiều rộng"
"entry.command.regen" = "Xóa và tái tạo các mẫu gần đó"
"entry.group.pregen" = "Tiền tố a Iris Chiều rộng"
@@ -458,6 +461,27 @@ operator_instruction = "Chạy {command} từ bàn giao tiếp (hoặc kích ho
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "Hiển thị nội dung không thể tạo trên phiên bản Minecraft này"
+[iris.modded.catalog]
+missing = "Chưa thu thập danh mục tạo thế giới nào. Hãy chạy /iris catalog refresh."
+header = "Danh mục tạo thế giới ({name} {version}): {count} không gian tên, {value} quần xã, {value2} feature đã đặt, {value3} công trình, {value4} loại chiều, {value5} level stem"
+namespace = "{name} {version}: {value} quần xã, {value2} feature đã đặt ({detail}), {value3} công trình, {value4} loại chiều, {value5} level stem"
+quiet = "{count} mod đã tải khác không có nội dung tạo thế giới."
+unknown_namespace = "Danh mục tạo thế giới không có nội dung từ không gian tên {name}."
+detail_biomes = "Quần xã ({count}): {value}"
+detail_features = "Feature đã đặt theo loại ({count}): {value}"
+detail_structures = "Công trình ({count}): {value}"
+detail_dimensions = "Loại chiều: {value}; level stem: {value2}"
+refreshing = "Đang thu thập danh mục tạo thế giới..."
+refreshed = "Đã thu thập danh mục tạo thế giới trong {duration}: {path}"
+refresh_failed = "Thu thập danh mục tạo thế giới thất bại: {reason}"
+ingest_invalid = "Đối số không hợp lệ: {reason}. Cách dùng: /iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "Không tìm thấy gói '{pack}' trong {path}."
+ingest_started = "Đang đưa phần tạo thế giới của mod từ '{source}' vào '{target}' (chiều {dimension})..."
+ingest_failed = "Đưa mod vào thất bại: {reason}"
+ingest_target_exists = "Gói đích '{target}' đã tồn tại. Thêm replace=true để ghi đè."
+ingest_line = " {summary}"
+ingest_complete = "Đã đưa mod vào xong: {path}"
+
[iris.modded.command]
"properties.none" = "Thuộc tính: (không có)"
"properties.list" = "Thuộc tính {properties}"
diff --git a/core/src/main/resources/languages/zh_CN.toml b/core/src/main/resources/languages/zh_CN.toml
index 4156dd8d7..c203d9c5a 100644
--- a/core/src/main/resources/languages/zh_CN.toml
+++ b/core/src/main/resources/languages/zh_CN.toml
@@ -364,6 +364,9 @@ operator_instruction = "请从控制台执行 {command}(或在单人游戏中
"entry.command.debug" = "切换 Iris 调试日志并保存 iris.json"
"entry.command.reload" = "重新加载 iris.json(也会每 3 秒自动热加载)"
"entry.command.download" = "下载包项目"
+"entry.command.catalog" = "列出已加载模组的世界生成内容、显示单个命名空间或重新采集"
+"entry.group.ingest" = "根据已加载模组的世界生成内容创建 Iris 包"
+"entry.command.ingest_mods" = "将源包复制为新的目标包,并采用已加载模组的世界生成"
"entry.command.metrics" = "显示当前 Iris 维度的生成指标"
"entry.command.regen" = "原地删除并重新生成附近区块"
"entry.group.pregen" = "预生成 Iris 维度"
@@ -458,6 +461,27 @@ operator_instruction = "请从控制台执行 {command}(或在单人游戏中
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "显示在当前 Minecraft 版本中无法生成的内容"
+[iris.modded.catalog]
+missing = "尚未采集世界生成目录。请运行 /iris catalog refresh。"
+header = "世界生成目录({name} {version}):{count} 个命名空间,{value} 个生物群系,{value2} 个已放置地物,{value3} 个结构,{value4} 个维度类型,{value5} 个 level stem"
+namespace = "{name} {version}:{value} 个生物群系,{value2} 个已放置地物({detail}),{value3} 个结构,{value4} 个维度类型,{value5} 个 level stem"
+quiet = "另有 {count} 个已加载的模组没有世界生成内容。"
+unknown_namespace = "世界生成目录中没有来自命名空间 {name} 的内容。"
+detail_biomes = "生物群系({count}):{value}"
+detail_features = "按类型的已放置地物({count}):{value}"
+detail_structures = "结构({count}):{value}"
+detail_dimensions = "维度类型:{value};level stem:{value2}"
+refreshing = "正在采集世界生成目录..."
+refreshed = "世界生成目录已在 {duration} 内采集完成:{path}"
+refresh_failed = "采集世界生成目录失败:{reason}"
+ingest_invalid = "参数无效:{reason}。用法:/iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "在 {path} 下未找到包 '{pack}'。"
+ingest_started = "正在将模组世界生成从 '{source}' 导入 '{target}'(维度 {dimension})..."
+ingest_failed = "模组导入失败:{reason}"
+ingest_target_exists = "目标包 '{target}' 已存在。添加 replace=true 以覆盖它。"
+ingest_line = " {summary}"
+ingest_complete = "模组导入完成:{path}"
+
[iris.modded.command]
"properties.none" = "属性:(无)"
"properties.list" = "属性 : {properties}"
diff --git a/core/src/main/resources/languages/zh_TW.toml b/core/src/main/resources/languages/zh_TW.toml
index 55cdb61cc..77533e883 100644
--- a/core/src/main/resources/languages/zh_TW.toml
+++ b/core/src/main/resources/languages/zh_TW.toml
@@ -364,6 +364,9 @@ operator_instruction = "請從主控台執行 {command}(或在單人遊戲中
"entry.command.debug" = "切換 Iris 偵錯記錄並儲存 iris.json"
"entry.command.reload" = "重新載入 iris.json(也會每 3 秒自動熱載入)"
"entry.command.download" = "下載包專案"
+"entry.command.catalog" = "列出已載入模組的世界生成內容、顯示單一命名空間或重新擷取"
+"entry.group.ingest" = "根據已載入模組的世界生成內容建立 Iris 包"
+"entry.command.ingest_mods" = "將來源包複製為新的目標包,並採用已載入模組的世界生成"
"entry.command.metrics" = "顯示目前 Iris 維度的生成指標"
"entry.command.regen" = "原地刪除並重新生成附近區塊"
"entry.group.pregen" = "預先生成 Iris 維度"
@@ -458,6 +461,27 @@ operator_instruction = "請從主控台執行 {command}(或在單人遊戲中
"entry.command.studio.objects" = "Explain why the Bukkit chunk object report is unavailable"
"entry.command.compat_content" = "顯示在目前 Minecraft 版本中無法生成的內容"
+[iris.modded.catalog]
+missing = "尚未擷取世界生成目錄。請執行 /iris catalog refresh。"
+header = "世界生成目錄({name} {version}):{count} 個命名空間,{value} 個生態域,{value2} 個已放置地物,{value3} 個結構,{value4} 個維度類型,{value5} 個 level stem"
+namespace = "{name} {version}:{value} 個生態域,{value2} 個已放置地物({detail}),{value3} 個結構,{value4} 個維度類型,{value5} 個 level stem"
+quiet = "另有 {count} 個已載入的模組沒有世界生成內容。"
+unknown_namespace = "世界生成目錄中沒有來自命名空間 {name} 的內容。"
+detail_biomes = "生態域({count}):{value}"
+detail_features = "依類型的已放置地物({count}):{value}"
+detail_structures = "結構({count}):{value}"
+detail_dimensions = "維度類型:{value};level stem:{value2}"
+refreshing = "正在擷取世界生成目錄..."
+refreshed = "世界生成目錄已在 {duration} 內擷取完成:{path}"
+refresh_failed = "擷取世界生成目錄失敗:{reason}"
+ingest_invalid = "參數無效:{reason}。用法:/iris ingest mods [dimension=] [namespaces=] [replace=true]"
+ingest_pack_missing = "在 {path} 下找不到包 '{pack}'。"
+ingest_started = "正在將模組世界生成從 '{source}' 匯入 '{target}'(維度 {dimension})..."
+ingest_failed = "模組匯入失敗:{reason}"
+ingest_target_exists = "目標包 '{target}' 已存在。加入 replace=true 以覆寫它。"
+ingest_line = " {summary}"
+ingest_complete = "模組匯入完成:{path}"
+
[iris.modded.command]
"properties.none" = "屬性:(無)"
"properties.list" = "屬性 : {properties}"
diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogFingerprintTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogFingerprintTest.java
new file mode 100644
index 000000000..773fdca1c
--- /dev/null
+++ b/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogFingerprintTest.java
@@ -0,0 +1,77 @@
+/*
+ * Iris is a World Generator for Minecraft Servers
+ * Copyright (c) 2026 Arcane Arts (Volmit Software)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package art.arcane.iris.pack.ingest.catalog;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+public class WorldgenCatalogFingerprintTest {
+ private static final List NAMESPACES = List.of(
+ new CatalogNamespace("minecraft", "Minecraft", "26.3"),
+ new CatalogNamespace("examplemod", "Example Mod", "1.0.0"));
+
+ @Test
+ public void fingerprintIsLowercaseSha256Hex() {
+ String fingerprint = WorldgenCatalogFingerprint.of(NAMESPACES, Map.of("biome", List.of("minecraft:plains")));
+
+ assertEquals(64, fingerprint.length());
+ assertTrue(fingerprint.matches("[0-9a-f]{64}"));
+ }
+
+ @Test
+ public void fingerprintIgnoresNamespaceAndKeyOrder() {
+ String ordered = WorldgenCatalogFingerprint.of(NAMESPACES, Map.of(
+ "biome", List.of("examplemod:glade", "minecraft:plains"),
+ "placed_feature", List.of("examplemod:ore_glimmer", "minecraft:ore_iron")));
+ String shuffled = WorldgenCatalogFingerprint.of(List.of(NAMESPACES.get(1), NAMESPACES.get(0)), Map.of(
+ "placed_feature", List.of("minecraft:ore_iron", "examplemod:ore_glimmer"),
+ "biome", List.of("minecraft:plains", "examplemod:glade")));
+
+ assertEquals(ordered, shuffled);
+ }
+
+ @Test
+ public void fingerprintChangesWithModVersion() {
+ Map> keys = Map.of("biome", List.of("examplemod:glade"));
+ String original = WorldgenCatalogFingerprint.of(NAMESPACES, keys);
+ String updated = WorldgenCatalogFingerprint.of(List.of(NAMESPACES.get(0),
+ new CatalogNamespace("examplemod", "Example Mod", "1.0.1")), keys);
+
+ assertNotEquals(original, updated);
+ }
+
+ @Test
+ public void fingerprintChangesWhenAKeyIsAddedOrMovesRegistry() {
+ String original = WorldgenCatalogFingerprint.of(NAMESPACES, Map.of(
+ "biome", List.of("examplemod:glade"), "structure", List.of()));
+ String added = WorldgenCatalogFingerprint.of(NAMESPACES, Map.of(
+ "biome", List.of("examplemod:glade", "examplemod:marsh"), "structure", List.of()));
+ String moved = WorldgenCatalogFingerprint.of(NAMESPACES, Map.of(
+ "biome", List.of(), "structure", List.of("examplemod:glade")));
+
+ assertNotEquals(original, added);
+ assertNotEquals(original, moved);
+ }
+}