This commit is contained in:
Brian Neumann-Fopiano
2026-08-19 22:21:25 -04:00
parent 41b2b53ac8
commit fec655def4
79 changed files with 425 additions and 1675 deletions
@@ -117,7 +117,9 @@ public final class IrisWorldStorage {
throw new IllegalArgumentException("World identifier is invalid: " + requestedName);
}
} else {
key = keyFromName(requestedName, levelName);
// "iris worlds" and Multiverse print the Bukkit startup name (<level>_iris_<key>), so
// accept that form here instead of turning it into iris:<level>_iris_<key>.
key = keyFromConfiguredWorldName(requestedName, levelName);
}
if (!IRIS_NAMESPACE.equals(key.getNamespace()) || !key.getKey().matches("[a-z0-9_-]+")) {
@@ -1,10 +1,13 @@
package art.arcane.iris.core;
import art.arcane.iris.spi.IrisLogging;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.WorldCreator;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* WorldCreator.ofKey and WorldCreator#key are Paper-API-only. Once a call throws
@@ -12,31 +15,40 @@ import java.io.File;
* straight to the fallback. The fallback derives names/keys through IrisWorldStorage's
* current configured-name mapping so persistent Spigot worlds round-trip without changing
* their startup directory.
*
* <p>Paper names a keyed creator {@code <namespace>_<key>} and refuses a creator built from a name
* and a key together, but it names that same world {@code <level>_<namespace>_<key>} when it loads
* it out of {@code <level>/dimensions/<namespace>/<key>} on every later boot. A persistent world
* therefore has to be born under the startup name, which is what {@link #ofPersistentKey} does.</p>
*/
public final class WorldCreatorCompat {
private static volatile boolean keyedCreatorsUnavailable;
private static volatile boolean creatorNamesUnwritable;
private static volatile Field creatorNameField;
private WorldCreatorCompat() {
}
public static WorldCreator ofKey(NamespacedKey worldKey) {
return ofKey(worldKey, IrisWorldStorage.logicalName(worldKey));
}
public static WorldCreator ofKey(NamespacedKey worldKey, String fallbackWorldName) {
WorldCreator keyedCreator = keyedCreator(worldKey);
if (keyedCreator != null) {
return keyedCreator;
}
return new WorldCreator(fallbackWorldName);
return new WorldCreator(IrisWorldStorage.logicalName(worldKey));
}
public static WorldCreator ofKey(NamespacedKey worldKey, String worldName) {
String name = requireWorldName(worldName);
WorldCreator keyedCreator = keyedCreator(worldKey);
if (keyedCreator == null) {
return new WorldCreator(name);
}
renameCreator(keyedCreator, name);
return keyedCreator;
}
public static WorldCreator ofPersistentKey(NamespacedKey worldKey) {
WorldCreator keyedCreator = keyedCreator(worldKey);
if (keyedCreator != null) {
return keyedCreator;
}
return new WorldCreator(fallbackPersistentName(worldKey, IrisWorldStorage.levelRoot().getName()));
return ofKey(worldKey, persistentWorldName(worldKey, IrisWorldStorage.levelRoot().getName()));
}
public static File persistentDimensionRoot(NamespacedKey worldKey) {
@@ -75,18 +87,10 @@ public final class WorldCreatorCompat {
);
}
static String fallbackName(NamespacedKey worldKey, String levelName) {
return IrisWorldStorage.logicalName(worldKey, levelName);
}
static String fallbackPersistentName(NamespacedKey worldKey, String levelName) {
static String persistentWorldName(NamespacedKey worldKey, String levelName) {
return IrisWorldStorage.configuredWorldName(worldKey, levelName);
}
static NamespacedKey fallbackKey(String creatorName, String levelName) {
return IrisWorldStorage.keyFromConfiguredWorldName(creatorName, levelName);
}
private static WorldCreator keyedCreator(NamespacedKey worldKey) {
if (keyedCreatorsUnavailable) {
return null;
@@ -98,4 +102,42 @@ public final class WorldCreatorCompat {
return null;
}
}
/**
* WorldCreator rejects a name and a key passed together and derives the name from the key, so the
* only way to hand a keyed world its startup name at creation is to overwrite the creator's name.
* If that ever stops working the keyed creator is still correct except for the name, which is what
* shipped before, so the world is created rather than refused.
*/
private static void renameCreator(WorldCreator creator, String worldName) {
if (worldName.equals(creator.name()) || creatorNamesUnwritable) {
return;
}
try {
creatorNameField().set(creator, worldName);
} catch (ReflectiveOperationException | RuntimeException | Error e) {
creatorNamesUnwritable = true;
IrisLogging.warn("WorldCreator name is not writable; world %s is created as %s and renamed on the next boot.",
worldName, creator.name());
}
}
private static Field creatorNameField() throws ReflectiveOperationException {
Field cached = creatorNameField;
if (cached != null) {
return cached;
}
Field name = WorldCreator.class.getDeclaredField("name");
name.setAccessible(true);
creatorNameField = name;
return name;
}
private static String requireWorldName(String worldName) {
String name = Objects.requireNonNull(worldName, "worldName").trim();
if (name.isEmpty()) {
throw new IllegalArgumentException("World name cannot be empty.");
}
return name;
}
}
@@ -18,8 +18,11 @@
package art.arcane.iris.core.link;
import art.arcane.iris.core.IrisWorldStorage;
import art.arcane.iris.spi.IrisLogging;
import lombok.SneakyThrows;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.mvplugins.multiverse.core.MultiverseCoreApi;
import org.mvplugins.multiverse.core.utils.result.Attempt;
@@ -30,57 +33,146 @@ import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
import org.mvplugins.multiverse.core.world.reasons.RemoveFailureReason;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Multiverse indexes its world store by NamespacedKey and by the Bukkit world name it saw when the
* world was imported, and records that name as {@code legacy-world-name}. Iris creates and registers
* its worlds under the Paper startup name ({@code <level>_<namespace>_<key>}), so the name Multiverse
* sees at import is already the name it will see again after every restart, and it is the name every
* Iris unregistration uses.
*/
public class MultiverseCoreLink {
public boolean removeFromConfig(World world) {
return removeFromConfig(world.getName());
}
public boolean removeFromConfig(String world) {
/**
* Best-effort scrub for worlds Iris never registered (studio worlds). Multiverse not knowing the
* world is the normal case here, so a miss is not worth a warning.
*/
public boolean removeIfPresent(World world) {
String worldName = Objects.requireNonNull(world, "world").getName();
if (!isActive()) {
return false;
}
WorldManager manager = worldManager();
MultiverseWorld multiverseWorld = manager.getWorld(world).getOrElse((MultiverseWorld) null);
MultiverseWorld multiverseWorld = resolve(manager, worldName);
if (multiverseWorld == null) {
return false;
}
return remove(manager, multiverseWorld, worldName);
}
/**
* Authoritative unregistration for a world Iris registered. A miss leaves a ghost entry behind in
* Multiverse's worlds.yml, so it is reported.
*/
public boolean removeFromConfig(String configuredWorldName) {
String worldName = requireWorldName(configuredWorldName);
if (!isActive()) {
IrisLogging.debug("Multiverse is not enabled; skipped unregistering \"" + worldName + "\".");
return false;
}
WorldManager manager = worldManager();
MultiverseWorld multiverseWorld = resolve(manager, worldName);
if (multiverseWorld == null) {
IrisLogging.warn("Multiverse has no world registered as %s; its worlds.yml entry was left as-is.",
worldName);
return false;
}
return remove(manager, multiverseWorld, worldName);
}
@SneakyThrows
public void updateWorld(World bukkitWorld, String configuredWorldName, String pack) {
World world = Objects.requireNonNull(bukkitWorld, "bukkitWorld");
String worldName = requireWorldName(configuredWorldName);
if (!isActive()) {
return;
}
if (!worldName.equals(world.getName())) {
// Multiverse records the live name as legacy-world-name. A live name that is not the
// startup name makes it re-import the world next boot and collide with its own config key.
IrisLogging.warn("World %s is live as %s; Multiverse will record the live name.",
worldName, world.getName());
}
String generator = "Iris:" + pack;
WorldManager manager = worldManager();
MultiverseWorld multiverseWorld = manager.getWorld(world)
.orElse(() -> manager.getWorld(worldName))
.getOrElse(() -> {
// Import through the live world so Multiverse binds its own config key to the key
// Paper gave the world and records the startup name the world was created under.
ImportWorldOptions options = ImportWorldOptions.worldName(world.getName())
.generator(generator)
.environment(world.getEnvironment())
.useSpawnAdjust(false);
return manager.importWorld(options).get();
});
multiverseWorld.setAutoLoad(false);
if (!generator.equals(multiverseWorld.getGenerator())) {
setWorldConfigString(multiverseWorld, "setGenerator", generator);
}
manager.saveWorldsConfig().get();
}
private boolean remove(WorldManager manager, MultiverseWorld multiverseWorld, String worldName) {
Attempt<String, RemoveFailureReason> removal = manager.removeWorld(RemoveWorldOptions.world(multiverseWorld));
if (removal.isFailure()) {
throw new IllegalStateException("Multiverse refused to remove world \"" + world + "\": "
throw new IllegalStateException("Multiverse refused to remove world \"" + worldName + "\": "
+ removal.getFailureMessage());
}
manager.saveWorldsConfig().get();
return true;
}
@SneakyThrows
public void updateWorld(World bukkitWorld, String pack) {
if (!isActive()) {
return;
private MultiverseWorld resolve(WorldManager manager, String worldName) {
for (String candidate : lookupNames(worldName, IrisWorldStorage.levelRoot().getName())) {
MultiverseWorld multiverseWorld = manager.getWorld(candidate).getOrElse((MultiverseWorld) null);
if (multiverseWorld != null) {
return multiverseWorld;
}
}
String generator = "Iris:" + pack;
WorldManager manager = worldManager();
MultiverseWorld multiverseWorld = manager.getWorld(bukkitWorld).getOrElse(() -> {
ImportWorldOptions options = ImportWorldOptions.worldName(bukkitWorld.getName())
.generator(generator)
.environment(bukkitWorld.getEnvironment())
.useSpawnAdjust(false);
return manager.importWorld(options).get();
});
return null;
}
multiverseWorld.setAutoLoad(false);
if (!generator.equals(multiverseWorld.getGenerator())) {
Field field = MultiverseWorld.class.getDeclaredField("worldConfig");
field.setAccessible(true);
Object config = field.get(multiverseWorld);
config.getClass()
.getDeclaredMethod("setGenerator", String.class)
.invoke(config, generator);
/**
* worlds.yml files written by Iris builds that created keyed worlds under {@code <namespace>_<key>}
* still carry that name, so a startup name must also resolve through the Multiverse config key it
* encodes or those entries can never be removed.
*/
static List<String> lookupNames(String worldName, String levelName) {
List<String> candidates = new ArrayList<>(2);
candidates.add(worldName);
try {
NamespacedKey key = IrisWorldStorage.managedKeyFromName(worldName, levelName);
if (IrisWorldStorage.configuredWorldName(key, levelName).equals(worldName)) {
candidates.add(key.toString());
}
} catch (IllegalArgumentException ignored) {
// Not an Iris startup name; the plain name is the only identity Multiverse can have.
}
return List.copyOf(candidates);
}
manager.saveWorldsConfig().get();
private static void setWorldConfigString(MultiverseWorld world, String setter, String value) throws Exception {
Field field = MultiverseWorld.class.getDeclaredField("worldConfig");
field.setAccessible(true);
Object config = field.get(world);
Method method = config.getClass().getDeclaredMethod(setter, String.class);
method.setAccessible(true);
method.invoke(config, value);
}
private static String requireWorldName(String worldName) {
String name = Objects.requireNonNull(worldName, "worldName").trim();
if (name.isEmpty()) {
throw new IllegalArgumentException("World name cannot be empty.");
}
return name;
}
private WorldManager worldManager() {
@@ -750,7 +750,7 @@ public final class StudioOpenCoordinator {
for (World loadedWorld : loadedWorlds) {
CompletableFuture<Boolean> unload = J.sfut(() ->
IrisServices.get(MultiverseCoreLink.class)
.removeFromConfig(loadedWorld))
.removeIfPresent(loadedWorld))
.thenCompose(ignored -> WorldLifecycleService.get().unloadAsync(loadedWorld, false));
unloads.add(unload);
}
@@ -106,7 +106,7 @@ public final class WorldRuntimeControlService {
return false;
}
IrisServices.get(art.arcane.iris.core.link.MultiverseCoreLink.class).removeFromConfig(world);
IrisServices.get(art.arcane.iris.core.link.MultiverseCoreLink.class).removeIfPresent(world);
setIntGameRule(world, 0, "SPAWN_CHUNK_RADIUS", "spawnChunkRadius");
enableStudioEntitySpawning(world);
if (!IrisSettings.get().getStudio().isDisableTimeAndWeather()) {
@@ -331,16 +331,23 @@ public class IrisCreator {
reportCreationProgress(creationReporter, 0.84D, "register_world");
if (!studio && !benchmark) {
// bukkit.yml and Multiverse must agree on the startup name, or Multiverse re-imports the
// world on the next boot and collides with its own config key.
String registrationName = IrisWorldStorage.configuredWorldName(
worldKey,
IrisWorldStorage.levelRoot().getName()
);
BukkitWorldConfiguration.register(
BUKKIT_YML,
IrisWorldStorage.configuredWorldName(worldKey, IrisWorldStorage.levelRoot().getName()),
registrationName,
dimension,
seed
);
bukkitRegistered = true;
World createdWorld = world;
CompletableFuture<Void> multiverseRegistration = J.sfut(
() -> IrisServices.get(MultiverseCoreLink.class).updateWorld(createdWorld, dimension)
() -> IrisServices.get(MultiverseCoreLink.class)
.updateWorld(createdWorld, registrationName, dimension)
);
if (multiverseRegistration == null) {
throw new IrisException("Failed to schedule Multiverse registration for world \"" + name + "\".");
@@ -172,6 +172,18 @@ public class IrisWorldStorageTest {
() -> IrisWorldStorage.managedKeyFromName("iris:unsafe.world", "world"));
}
@Test
public void managedKeyAcceptsTheConfiguredBukkitStartupName() {
assertEquals(new NamespacedKey("iris", "mvtest"),
IrisWorldStorage.managedKeyFromName("world_iris_mvtest", "world"));
assertEquals(new NamespacedKey("iris", "mvtest"),
IrisWorldStorage.managedKeyFromName("survival_iris_mvtest", "survival"));
assertEquals(new NamespacedKey("iris", "iris_mvtest"),
IrisWorldStorage.managedKeyFromName("iris_mvtest", "world"));
assertEquals(new NamespacedKey("iris", "world_iris_mvtest"),
IrisWorldStorage.managedKeyFromName("world_iris_mvtest", "survival"));
}
@Test
public void explicitManagedIdentityDoesNotRequireServerLevelLookup() {
NamespacedKey worldKey = new NamespacedKey("iris", "probe");
@@ -1,9 +1,11 @@
package art.arcane.iris.core;
import org.bukkit.NamespacedKey;
import org.bukkit.WorldCreator;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class WorldCreatorCompatTest {
@Test
@@ -14,39 +16,48 @@ public class WorldCreatorCompatTest {
}
@Test
public void fallbackNameDerivesLogicalNameFromKey() {
assertEquals("compat_world", WorldCreatorCompat.fallbackName(new NamespacedKey("iris", "compat_world"), "world"));
assertEquals("world", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("overworld"), "world"));
assertEquals("world_nether", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("the_nether"), "world"));
assertEquals("world_the_end", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("the_end"), "world"));
public void keyedCreatorIsBornUnderTheRequestedStartupName() {
NamespacedKey key = new NamespacedKey("iris", "compat_world");
WorldCreator creator = WorldCreatorCompat.ofKey(key, "world_iris_compat_world");
assertEquals("world_iris_compat_world", creator.name());
assertEquals(key, creator.key());
}
@Test
public void persistentFallbackUsesExactConfiguredStartupName() {
public void keyedCreatorKeepsThePaperKeyNameWhenNoStartupNameIsGiven() {
NamespacedKey key = new NamespacedKey("iris", "compat_world");
WorldCreator creator = WorldCreatorCompat.ofKey(key);
assertNotEquals("world_iris_compat_world", creator.name());
assertEquals(key, creator.key());
}
@Test
public void persistentNameIsTheExactConfiguredStartupName() {
assertEquals(
"world_iris_compat_world",
WorldCreatorCompat.fallbackPersistentName(new NamespacedKey("iris", "compat_world"), "world")
WorldCreatorCompat.persistentWorldName(new NamespacedKey("iris", "compat_world"), "world")
);
}
@Test
public void fallbackKeyRoundTripsCreatorName() {
assertEquals(new NamespacedKey("iris", "compat_world"), WorldCreatorCompat.fallbackKey("compat_world", "world"));
assertEquals(NamespacedKey.minecraft("overworld"), WorldCreatorCompat.fallbackKey("world", "world"));
assertEquals(NamespacedKey.minecraft("the_nether"), WorldCreatorCompat.fallbackKey("world_nether", "world"));
assertEquals(
new NamespacedKey("iris", "compat_world"),
WorldCreatorCompat.fallbackKey("world_iris_compat_world", "world")
);
public void logicalNameDerivesTheLogicalNameFromKey() {
assertEquals("compat_world", IrisWorldStorage.logicalName(new NamespacedKey("iris", "compat_world"), "world"));
assertEquals("world", IrisWorldStorage.logicalName(NamespacedKey.minecraft("overworld"), "world"));
assertEquals("world_nether", IrisWorldStorage.logicalName(NamespacedKey.minecraft("the_nether"), "world"));
assertEquals("world_the_end", IrisWorldStorage.logicalName(NamespacedKey.minecraft("the_end"), "world"));
}
@Test
public void fallbackMappingIsStableAcrossRoundTrips() {
NamespacedKey key = new NamespacedKey("iris", "iris_world");
public void startupNameRoundTripsBackToTheWorldKey() {
NamespacedKey key = new NamespacedKey("iris", "compat_world");
String name = WorldCreatorCompat.fallbackName(key, "world");
String name = WorldCreatorCompat.persistentWorldName(key, "world");
assertEquals(key, WorldCreatorCompat.fallbackKey(name, "world"));
assertEquals(name, WorldCreatorCompat.fallbackName(key, "world"));
assertEquals("world_iris_compat_world", name);
assertEquals(key, IrisWorldStorage.keyFromConfiguredWorldName(name, "world"));
}
}
@@ -125,6 +125,22 @@ public class IrisWorldRemovalServiceTest {
);
}
@Test
public void removalAcceptsTheConfiguredStartupNameThatIrisWorldsPrints() throws Exception {
Path levelRoot = temporaryFolder.newFolder("world").toPath();
WorldRemovalPathPolicy.Target logical = WorldRemovalPathPolicy.resolve("moon", "world", levelRoot);
WorldRemovalPathPolicy.Target configured =
WorldRemovalPathPolicy.resolve("world_iris_moon", "world", levelRoot);
assertEquals(logical.worldKey(), configured.worldKey());
assertEquals("moon", configured.logicalName());
assertEquals(logical.worldDirectory(), configured.worldDirectory());
assertEquals(
"world_iris_moon",
IrisWorldRemovalService.bukkitConfigurationWorldName(configured)
);
}
@Test
public void diskInspectionReadsOnlyCanonicalPaperStartupSection() throws Exception {
Path levelRoot = temporaryFolder.newFolder("inspection-world").toPath();
@@ -0,0 +1,46 @@
package art.arcane.iris.core.lifecycle;
import art.arcane.iris.core.WorldCreatorCompat;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class WorldLifecycleRequestTest {
@Test
public void requestCarriesTheStartupNameTheCreatorWasBornWith() {
NamespacedKey worldKey = new NamespacedKey("iris", "mvtest");
WorldCreator creator = WorldCreatorCompat.ofKey(worldKey, "world_iris_mvtest")
.environment(World.Environment.NORMAL)
.seed(99L);
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(
creator,
false,
false,
WorldLifecycleCaller.CREATE);
assertEquals("world_iris_mvtest", request.worldName());
assertEquals(worldKey, request.worldKey());
}
@Test
public void rebuiltCreatorKeepsBothTheStartupNameAndTheWorldKey() {
NamespacedKey worldKey = new NamespacedKey("iris", "mvtest");
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(
WorldCreatorCompat.ofKey(worldKey, "world_iris_mvtest")
.environment(World.Environment.NORMAL)
.seed(99L),
false,
false,
WorldLifecycleCaller.CREATE);
WorldCreator rebuilt = request.toWorldCreator();
assertEquals("world_iris_mvtest", rebuilt.name());
assertEquals(worldKey, rebuilt.key());
assertEquals(99L, rebuilt.seed());
}
}
@@ -0,0 +1,58 @@
package art.arcane.iris.core.link;
import org.bukkit.World;
import org.junit.Test;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MultiverseCoreLinkTest {
@Test
public void multiverseRegistrationRequiresTheConfiguredWorldName() throws Exception {
Method updateWorld = MultiverseCoreLink.class
.getDeclaredMethod("updateWorld", World.class, String.class, String.class);
assertEquals(void.class, updateWorld.getReturnType());
assertFalse("updateWorld must not keep an overload that derives the name from the live world",
Arrays.stream(MultiverseCoreLink.class.getDeclaredMethods())
.anyMatch(method -> "updateWorld".equals(method.getName())
&& method.getParameterCount() == 2));
}
@Test
public void registrationDoesNotRewriteTheRecordedMultiverseName() throws Exception {
String source = Files.readString(Path.of("src/main/java/art/arcane/iris/core/link/MultiverseCoreLink.java"));
assertFalse("worlds are created under the startup name, so the recorded name needs no correction",
source.contains("setLegacyWorldName"));
assertTrue("Multiverse still has to be told the live world name it will record",
source.contains("ImportWorldOptions.worldName(world.getName())"));
}
@Test
public void lookupNamesAddTheIrisKeyForConfiguredStartupNames() {
assertEquals(
List.of("world_iris_mvtest", "iris:mvtest"),
MultiverseCoreLink.lookupNames("world_iris_mvtest", "world")
);
assertEquals(
List.of("survival_iris_mvtest", "iris:mvtest"),
MultiverseCoreLink.lookupNames("survival_iris_mvtest", "survival")
);
}
@Test
public void lookupNamesKeepNonConfiguredNamesUntouched() {
assertEquals(List.of("iris_mvtest"), MultiverseCoreLink.lookupNames("iris_mvtest", "world"));
assertEquals(List.of("world"), MultiverseCoreLink.lookupNames("world", "world"));
assertEquals(List.of("iris:mvtest"), MultiverseCoreLink.lookupNames("iris:mvtest", "world"));
assertEquals(List.of("iris_studio-demo"), MultiverseCoreLink.lookupNames("iris_studio-demo", "world"));
}
}
@@ -0,0 +1,37 @@
package art.arcane.iris.core.tools;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisCreatorMultiverseRegistrationContractTest {
@Test
public void bukkitAndMultiverseRegistrationShareOneConfiguredName() throws IOException {
String source = compact(Files.readString(Path.of(System.getProperty("iris.irisCreatorSource"))));
assertTrue("bukkit.yml must be registered under the shared configured name",
source.contains("BukkitWorldConfiguration.register(BUKKIT_YML,registrationName,dimension,seed)"));
assertTrue("Multiverse must be registered under the same configured name",
source.contains(".updateWorld(createdWorld,registrationName,dimension)"));
assertFalse("Multiverse must never be registered under the transient created-world name",
source.contains(".updateWorld(createdWorld,dimension)"));
}
@Test
public void multiverseRollbackUsesTheSameConfiguredName() throws IOException {
String source = compact(Files.readString(Path.of(System.getProperty("iris.irisCreatorSource"))));
assertTrue("rollback must unregister the name creation registered",
source.contains(".removeFromConfig(IrisWorldStorage.configuredWorldName(worldKey,"
+ "IrisWorldStorage.levelRoot().getName()))"));
}
private static String compact(String source) {
return source.replaceAll("\\s+", "");
}
}
@@ -14,7 +14,7 @@ import static org.junit.Assert.assertThrows;
public class WorldHandlerTest {
@Test
public void resolvesOwnedIrisWorldByLogicalNameAndCanonicalKey() throws DirectorParsingException {
World world = world("iris_irisworld", new NamespacedKey("iris", "irisworld"));
World world = world("world_iris_irisworld", new NamespacedKey("iris", "irisworld"));
WorldHandler handler = new TestWorldHandler(List.of(world));
assertSame(world, handler.parse("irisworld", false));