Derived retained borrowed pack names

- Retained borrowed-type packs take their pack name from the scoped biome aliases recorded in their generation history instead of the dimension key
- Scoped legacy biome aliases of retained borrowed packs now match the definitions their worlds reference
- A retained pack whose recorded aliases name several packs is refused instead of staged with a guessed name
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-14 20:53:08 -04:00
1 parent 4244aa4ea7
commit a0c9d48601
3 files changed
+155 -1

No files matched your search

@@ -830,7 +830,7 @@ public final class ModdedForcedDatapack {
GenerationEpoch.DimensionContract contract = retained.epoch().dimensionContract();
Optional<ModdedWorldgenIds.ScopedDimension> scoped = ModdedWorldgenIds.scopedDimensionType(contract.dimensionTypeKey());
if (scoped.isEmpty()) {
return PackStageContext.retained(normalizedRoot, contract.dimensionKey(), retained.epoch());
return PackStageContext.retained(normalizedRoot, retainedPackName(retained.epoch(), normalizedRoot), retained.epoch());
}
ModdedWorldgenIds.ScopedDimension identity = scoped.get();
if (!identity.dimension().equals(contract.dimensionKey())) {
@@ -849,6 +849,16 @@ public final class ModdedForcedDatapack {
return PackStageContext.installed(normalizedRoot, packName, legacyDimensions);
}
static String retainedPackName(GenerationEpoch epoch, Path packRoot) throws IOException {
String dimensionKey = epoch.dimensionContract().dimensionKey();
Set<String> packs = ModdedWorldgenIds.aliasPacks(epoch.registryContract(), dimensionKey);
if (packs.size() > 1) {
throw new IOException("Retained Iris generation pack at " + packRoot
+ " records scoped biome aliases from several packs " + packs + ".");
}
return packs.isEmpty() ? dimensionKey : packs.iterator().next();
}
private static RetainedPack retainedPack(Path packRoot) throws IOException {
Path epochRoot = packRoot.getParent();
Path epochsRoot = epochRoot == null ? null : epochRoot.getParent();
@@ -2,12 +2,17 @@ package art.arcane.iris.modded;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.world.history.GenerationRegistryContract;
import art.arcane.iris.world.history.GenerationRegistryContract.PhysicalResourceKey;
import art.arcane.iris.world.history.GenerationRegistryContractFactory;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
public final class ModdedWorldgenIds {
private static final String NAMESPACE = "irisworldgen";
@@ -68,6 +73,41 @@ public final class ModdedWorldgenIds {
return Optional.of(new ScopedDimension(pack, dimension));
}
static Set<String> aliasPacks(GenerationRegistryContract contract, String dimension) {
Set<String> packs = new TreeSet<>();
for (PhysicalResourceKey key : contract.definitions().keySet()) {
if (!GenerationRegistryContractFactory.BIOME_REGISTRY.equals(key.registryKey())) {
continue;
}
Optional<ScopedDimension> scoped = scopedBiome(key.resourceKey());
if (scoped.isPresent() && scoped.get().dimension().equals(dimension)) {
packs.add(scoped.get().pack());
}
}
return packs;
}
static Optional<ScopedDimension> scopedBiome(String biomeRef) {
String prefix = NAMESPACE + ":";
if (biomeRef == null || !biomeRef.startsWith(prefix)) {
return Optional.empty();
}
String[] parts = biomeRef.substring(prefix.length()).split("/", 6);
if (parts.length != 6
|| !"packs".equals(parts[0])
|| !"dimensions".equals(parts[2])
|| !"biomes".equals(parts[4])
|| parts[5].isEmpty()) {
return Optional.empty();
}
String pack = decode(parts[1]);
String dimension = decode(parts[3]);
if (pack == null || dimension == null) {
return Optional.empty();
}
return Optional.of(new ScopedDimension(pack, dimension));
}
public static String displayName(String presetPath) {
String[] parts = presetPath.split("/");
if (parts.length != 5 || !"packs".equals(parts[0])
@@ -0,0 +1,104 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.iris.world.history.GenerationEpoch;
import art.arcane.iris.world.history.GenerationEpochContractFactory;
import art.arcane.iris.world.history.GenerationRegistryContract;
import art.arcane.iris.world.history.GenerationRegistryContract.PhysicalResourceKey;
import art.arcane.iris.world.history.GenerationRegistryContractFactory;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
public class ModdedRetainedPackNameTest {
private static final String FINGERPRINT = "11".repeat(32);
private static final Path PACK_ROOT = Path.of("/srv/world/dimensions/aether/the_aether/iris/generation/epochs/e/pack");
@Test
public void borrowedRetainedPackTakesItsNameFromTheRecordedScopedBiomeAliases() throws IOException {
GenerationEpoch epoch = epoch("aether:the_aether", "sky", List.of(
ModdedWorldgenIds.biomeRef("skypack", "sky", "meadow"),
"sky:meadow",
"iris:biomes/" + "ab".repeat(32)));
assertEquals("skypack", ModdedForcedDatapack.retainedPackName(epoch, PACK_ROOT));
assertEquals(List.of(ModdedWorldgenIds.biomeRef("skypack", "sky", "meadow"), "sky:meadow"),
ModdedWorldgenIds.legacyBiomeRefs(ModdedForcedDatapack.retainedPackName(epoch, PACK_ROOT), "sky", "meadow"));
}
@Test
public void scopedAliasesOfOtherDimensionsAreIgnored() throws IOException {
GenerationEpoch epoch = epoch("aether:the_aether", "sky", List.of(
ModdedWorldgenIds.biomeRef("otherpack", "ground", "meadow"),
ModdedWorldgenIds.biomeRef("skypack", "sky", "meadow")));
assertEquals("skypack", ModdedForcedDatapack.retainedPackName(epoch, PACK_ROOT));
}
@Test
public void borrowedRetainedPackWithoutScopedAliasesKeepsTheDimensionKey() throws IOException {
GenerationEpoch epoch = epoch("aether:the_aether", "sky", List.of("iris:biomes/" + "ab".repeat(32)));
assertEquals("sky", ModdedForcedDatapack.retainedPackName(epoch, PACK_ROOT));
}
@Test
public void aliasesFromSeveralPacksAreRefused() {
GenerationEpoch epoch = epoch("aether:the_aether", "sky", List.of(
ModdedWorldgenIds.biomeRef("skypack", "sky", "meadow"),
ModdedWorldgenIds.biomeRef("otherpack", "sky", "meadow")));
IOException refused = assertThrows(IOException.class, () -> ModdedForcedDatapack.retainedPackName(epoch, PACK_ROOT));
assertEquals(true, refused.getMessage().contains(PACK_ROOT.toString()));
}
static GenerationEpoch epoch(String dimensionTypeKey, String dimensionKey, List<String> biomeKeys) {
Map<PhysicalResourceKey, String> definitions = new TreeMap<>();
for (String biome : biomeKeys) {
definitions.put(new PhysicalResourceKey(GenerationRegistryContractFactory.BIOME_REGISTRY, biome), FINGERPRINT);
}
GenerationEpoch.DimensionContract dimension = new GenerationEpoch.DimensionContract(
dimensionKey,
dimensionTypeKey,
"NORMAL",
"OVERWORLD",
127,
0,
256,
256,
1D,
false,
"none",
0,
"0".repeat(64),
GenerationEpochContractFactory.CURRENT_DIMENSION_TYPE_FINGERPRINT_SCHEMA,
FINGERPRINT
);
return GenerationEpoch.create(new GenerationEpoch.Spec(FINGERPRINT, 1, 5L, 1, 1, 1, FINGERPRINT, dimension,
GenerationRegistryContract.fromDefinitions(definitions)));
}
}