diff --git a/adapters/bukkit/nms/v26_3_R1/src/offline/java/art/arcane/iris/probe/HeadlessNativePlatform.java b/adapters/bukkit/nms/v26_3_R1/src/offline/java/art/arcane/iris/probe/HeadlessNativePlatform.java index 59e4d1374..942228c58 100644 --- a/adapters/bukkit/nms/v26_3_R1/src/offline/java/art/arcane/iris/probe/HeadlessNativePlatform.java +++ b/adapters/bukkit/nms/v26_3_R1/src/offline/java/art/arcane/iris/probe/HeadlessNativePlatform.java @@ -15,7 +15,6 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedBlockState; import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockResolver; import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeRegistryAccess; import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeGenerationRegistryImpl; -import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeWorldGenerationImpl; import art.arcane.volmlib.nativelib.terrain.NativeBiome; import art.arcane.volmlib.nativelib.terrain.NativeBlockProperty; import art.arcane.volmlib.nativelib.terrain.NativeBlockState; @@ -49,7 +48,7 @@ final class HeadlessNativePlatform implements IrisPlatform, PlatformRegistries, return access; } throw new IllegalStateException("Generation registry contract requires loaded native registries"); - }), "bukkit-generation-registry-v1", new NativeWorldGenerationImpl().generationRendererIdentity()); + }), "bukkit-generation-registry-v1"); nativeAccess = new NativeRegistryAccess(new NativeRegistryAccess.Configuration(registries, () -> { throw new UnsupportedOperationException("Headless reloadable registries are not loaded"); }, message -> { throw new IllegalStateException("Unavailable native registry: " + message); })); diff --git a/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/BareMobSpawnHistoryTest.java b/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/BareMobSpawnHistoryTest.java new file mode 100644 index 000000000..2adc1b3e7 --- /dev/null +++ b/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/BareMobSpawnHistoryTest.java @@ -0,0 +1,156 @@ +package art.arcane.iris.probe; + +import art.arcane.iris.generation.biome.IrisBiomeCustom; +import art.arcane.iris.generation.biome.IrisBiomeCustomSpawn; +import art.arcane.iris.generation.biome.IrisBiomeCustomSpawnType; +import art.arcane.iris.pack.datapack.v263.DataFixerV263; +import art.arcane.iris.spi.PlatformGenerationRegistry; +import art.arcane.iris.world.history.GenerationRegistryContract; +import art.arcane.iris.world.history.GenerationRegistryContractFactory; +import art.arcane.volmlib.util.collection.KList; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.bukkit.Bukkit; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +public final class BareMobSpawnHistoryTest { + private static final String LEGACY_RENDERER_IDENTITY = "bukkit-v26_3_R1-generated-registry-json-v1"; + private static final String NATURAL_MOB_SPAWNS = "minecraft:gameplay/natural_mob_spawns"; + private static final GenerationRegistryContract.PhysicalResourceKey INSTALLED = + new GenerationRegistryContract.PhysicalResourceKey( + GenerationRegistryContractFactory.BIOME_REGISTRY, + "iris:biomes/" + "c".repeat(64) + ); + private static HeadlessNativeRegistries loaded; + private static PlatformGenerationRegistry registry; + + @BeforeClass + public static void installOverlayRendering() throws IOException { + assertNull(Bukkit.getServer()); + loaded = HeadlessNativeRegistries.load(Map.of( + "data/iris/worldgen/biome/biomes/" + "c".repeat(64) + ".json", + spawningBiome().generateJson(new DataFixerV263()) + )); + registry = new HeadlessNativePlatform(new File("unused-native-platform"), loaded::registries) + .generationRegistry(); + } + + @AfterClass + public static void closeRegistries() throws IOException { + if (loaded != null) { + loaded.close(); + } + } + + @Test + public void bareMobSpawnHistoryValidatesAgainstTheInstalledOverlayRendering() throws IOException { + GenerationRegistryContract legacy = legacyContract(INSTALLED, spawningBiome()); + GenerationRegistryContract.GeneratedSource source = legacy.generatedSources().get(INSTALLED); + assertEquals(GenerationRegistryContract.FINGERPRINT_SCHEMA_VERSION_ONE, legacy.fingerprintSchema()); + assertEquals(LEGACY_RENDERER_IDENTITY, source.rendererIdentity()); + assertNotEquals( + JsonParser.parseString(source.sourceJson()), + JsonParser.parseString(registry.generatedDefinition( + INSTALLED.registryKey(), + INSTALLED.resourceKey() + ).value()) + ); + + GenerationRegistryContract available = GenerationRegistryContractFactory.captureRequiredDefinitions( + Set.of(legacy), + new DataFixerV263(), + registry + ); + + legacy.requireDefinitionsAvailableIn(available); + } + + @Test + public void bareMobSpawnHistoryStillRejectsDifferentSemantics() throws IOException { + GenerationRegistryContract changed = legacyContract(INSTALLED, spawningBiome().setTemperature(0.2D)); + + IOException failure = assertThrows(IOException.class, + () -> GenerationRegistryContractFactory.captureRequiredDefinitions( + Set.of(changed), + new DataFixerV263(), + registry + )); + + assertEquals("Historical generated registry definition changed for minecraft:worldgen/biome / " + + INSTALLED.resourceKey() + ".", failure.getMessage()); + } + + @Test + public void bareMobSpawnHistoryStillRejectsAMissingDefinition() throws IOException { + GenerationRegistryContract missing = legacyContract(new GenerationRegistryContract.PhysicalResourceKey( + GenerationRegistryContractFactory.BIOME_REGISTRY, + "iris:biomes/" + "d".repeat(64) + ), spawningBiome()); + + assertThrows(IOException.class, () -> GenerationRegistryContractFactory.captureRequiredDefinitions( + Set.of(missing), + new DataFixerV263(), + registry + )); + } + + private static GenerationRegistryContract legacyContract( + GenerationRegistryContract.PhysicalResourceKey key, + IrisBiomeCustom biome + ) { + JsonObject rendered = JsonParser.parseString(biome.generateJson(new DataFixerV263())).getAsJsonObject(); + JsonObject attributes = rendered.getAsJsonObject("attributes"); + attributes.add(NATURAL_MOB_SPAWNS, attributes.getAsJsonObject(NATURAL_MOB_SPAWNS).get("argument")); + String captured = registry.canonicalDefinition(key.registryKey(), key.resourceKey(), rendered.toString()) + .value(); + String semantic = GenerationRegistryContractFactory.customBiomeEffectiveSemanticJson(biome, null); + GenerationRegistryContract.GeneratedSource source = new GenerationRegistryContract.GeneratedSource( + GenerationRegistryContractFactory.CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA, + GenerationRegistryContractFactory.fingerprintCustomBiomeAuthoredDefinition(semantic), + semantic, + LEGACY_RENDERER_IDENTITY, + GenerationRegistryContractFactory.fingerprintDefinition( + key, + PlatformGenerationRegistry.Definition.exactJson(captured), + "generated" + ), + captured + ); + return GenerationRegistryContract.fromDefinitionsAndGeneratedSources( + Map.of(key, GenerationRegistryContractFactory.fingerprintGeneratedSemantic(key, source)), + Map.of(key, source) + ); + } + + private static IrisBiomeCustom spawningBiome() { + return new IrisBiomeCustom() + .setId("tester") + .setSpawnRarity(2) + .setSpawns(new KList<>( + new IrisBiomeCustomSpawn() + .setType("minecraft:zombie") + .setGroup(IrisBiomeCustomSpawnType.MONSTER) + .setMinCount(2) + .setMaxCount(4) + .setWeight(7), + new IrisBiomeCustomSpawn() + .setType("minecraft:cow") + .setGroup(IrisBiomeCustomSpawnType.CREATURE) + .setMinCount(3) + .setMaxCount(3) + .setWeight(5) + )); + } +} diff --git a/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/HeadlessGenerationRegistryTest.java b/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/HeadlessGenerationRegistryTest.java index 5ee4ddab4..e8ecedcb7 100644 --- a/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/HeadlessGenerationRegistryTest.java +++ b/adapters/bukkit/nms/v26_3_R1/src/test/java/art/arcane/iris/probe/HeadlessGenerationRegistryTest.java @@ -1,7 +1,6 @@ package art.arcane.iris.probe; import art.arcane.iris.spi.PlatformGenerationRegistry; -import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeWorldGenerationImpl; import net.minecraft.data.registries.VanillaRegistries; import org.bukkit.Bukkit; import org.junit.Test; @@ -22,8 +21,6 @@ public final class HeadlessGenerationRegistryTest { HeadlessNativeTestRegistries::get); PlatformGenerationRegistry registry = platform.generationRegistry(); assertEquals("bukkit-generation-registry-v1", registry.runtimeIdentity()); - assertEquals(new NativeWorldGenerationImpl().generationRendererIdentity(), - registry.generatedDefinitionRendererIdentity()); assertEquals(registry.canonicalDefinition("minecraft:worldgen/biome", "iris:native_test", biome), registry.generatedDefinition("minecraft:worldgen/biome", "iris:native_test")); assertEquals(registry.canonicalDefinition("minecraft:dimension_type", "iris:native_test", dimension), diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java index ff4d78ce0..93f887393 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java @@ -314,12 +314,13 @@ public final class ModdedForcedDatapack { } catch (NoSuchAlgorithmException missing) { throw new IllegalStateException("SHA-256 is unavailable", missing); } + DataVersion dataVersion = DataVersion.getRuntime(); digest.update((HASH_SALT + '|' + ModdedEngineBootstrap.loader().platformName() - + '|' + DataVersion.V26_3.getPackFormat() + '\n').getBytes(StandardCharsets.UTF_8)); + + '|' + dataVersion.getPackFormat() + '\n').getBytes(StandardCharsets.UTF_8)); String inputFingerprint = IrisDatapackCompiler.computeInputFingerprint( compilationPackRoots(), List.of(), - DataVersion.V26_3.get(), + dataVersion.get(), false ); digest.update(inputFingerprint.getBytes(StandardCharsets.UTF_8)); @@ -328,7 +329,7 @@ public final class ModdedForcedDatapack { private static void writeStagedPack(Path stagingDirectory) throws IOException { Map> seenBiomes = new LinkedHashMap<>(); - IDataFixer fixer = DataVersion.V26_3.get(); + IDataFixer fixer = DataVersion.getRuntime().get(); int packCount = 0; KList presetIds = new KList<>(); @@ -679,7 +680,7 @@ public final class ModdedForcedDatapack { } static void writePackMeta(Path packDirectory) throws IOException { - int packFormat = DataVersion.V26_3.getPackFormat(); + int packFormat = DataVersion.getRuntime().getPackFormat(); String json = "{\n" + " \"pack\": {\n" + " \"description\": \"Iris world generation biomes and dimension types for installed packs.\",\n" diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenerationRegistry.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenerationRegistry.java index 050d4b984..976e6bdfa 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenerationRegistry.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenerationRegistry.java @@ -9,12 +9,10 @@ import java.util.Objects; final class ModdedGenerationRegistry implements PlatformGenerationRegistry { private final NativeGenerationRegistry nativeRegistry; private final String runtimeIdentity; - private final String rendererIdentity; - ModdedGenerationRegistry(NativeGenerationRegistry nativeRegistry, Identity identity) { + ModdedGenerationRegistry(NativeGenerationRegistry nativeRegistry, String runtimeIdentity) { this.nativeRegistry = Objects.requireNonNull(nativeRegistry, "nativeRegistry"); - this.runtimeIdentity = requireText(identity.runtime(), "runtimeIdentity"); - this.rendererIdentity = requireText(identity.renderer(), "rendererIdentity"); + this.runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity"); } @Override @@ -22,11 +20,6 @@ final class ModdedGenerationRegistry implements PlatformGenerationRegistry { return runtimeIdentity; } - @Override - public String generatedDefinitionRendererIdentity() { - return rendererIdentity; - } - @Override public String customBiomeResourceKey(String identitySha256) { return PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(identitySha256); @@ -75,7 +68,4 @@ final class ModdedGenerationRegistry implements PlatformGenerationRegistry { } return required; } - - record Identity(String runtime, String renderer) { - } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java index 3d795bfd5..24309bd2d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java @@ -357,8 +357,7 @@ public final class ModdedPlatform implements IrisPlatform { throw new IllegalStateException("Minecraft server is not ready for generation registry capture."); } return new ModdedGenerationRegistry(NativeModdedServer.registryDefinitions(ModdedEngineBootstrap::currentServer), - new ModdedGenerationRegistry.Identity("modded-generation-registry-v1", - "modded-generated-registry-json-v1|minecraft:" + BuildConstants.MINECRAFT_VERSION)); + "modded-generation-registry-v1"); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java index 1ee890883..22a70e57d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java @@ -55,7 +55,7 @@ final class WorldCheckDimensionContract { } static Dimension expectedDimensionContract(IrisDimension dimension) { - JSONObject json = new JSONObject(dimension.getDimensionType().toJson(DataVersion.V26_3.get())); + JSONObject json = new JSONObject(dimension.getDimensionType().toJson(DataVersion.getRuntime().get())); return new Dimension( json.getInt("min_y"), json.getInt("height"), diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java index f58722f51..0d9df4119 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java @@ -165,7 +165,7 @@ public final class ModdedDatapackCommands { IrisDimension dimension = engine.getDimension(); String json; try { - json = dimension.getDimensionType().toJson(DataVersion.V26_3.get()); + json = dimension.getDimensionType().toJson(DataVersion.getRuntime().get()); } catch (Throwable e) { ModdedIrisLog.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_DIMENSION_TYPE_GENERATION_FAILED, MessageArgument.untrusted("value", level.name()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))); @@ -187,7 +187,7 @@ public final class ModdedDatapackCommands { } File mcmeta = new File(worldDatapacksFolder(server), WORLD_PACK_NAME + "/pack.mcmeta"); - int packFormat = DataVersion.V26_3.getPackFormat(); + int packFormat = DataVersion.getRuntime().getPackFormat(); String meta = "{\n" + " \"pack\": {\n" + " \"description\": \"Iris dimension types derived from the installed Iris packs.\",\n" diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java index ad1af8e24..8a016e11a 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java @@ -20,6 +20,7 @@ package art.arcane.iris.modded; import art.arcane.volmlib.util.collection.KSet; import art.arcane.volmlib.util.json.JSONObject; +import art.arcane.iris.pack.datapack.DataVersion; import art.arcane.iris.pack.datapack.v1217.DataFixerV1217; import art.arcane.iris.world.history.GenerationEpochContractFactory; import art.arcane.iris.world.history.GenerationRegistryContract; @@ -27,6 +28,8 @@ import art.arcane.iris.world.history.GenerationRegistryContractFactory; import art.arcane.iris.generation.biome.IrisCustomBiomeAliasResolver; import art.arcane.iris.generation.terrain.IrisDimension; import art.arcane.iris.generation.terrain.IrisDimensionType; +import art.arcane.iris.spi.IrisPlatform; +import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.PlatformGenerationRegistry; import java.lang.reflect.Constructor; import java.util.Set; @@ -48,6 +51,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ModdedForcedDatapackTest { @Test @@ -65,6 +70,29 @@ public class ModdedForcedDatapackTest { } } + @Test + public void metadataFollowsTheRuntimeDataVersionThatValidationUses() throws IOException { + IrisPlatform previous = IrisPlatforms.getOrNull(); + IrisPlatform runtime = mock(IrisPlatform.class); + when(runtime.minecraftVersion()).thenReturn("26.2"); + Path packDirectory = Files.createTempDirectory("iris-modded-runtime-pack-format"); + IrisPlatforms.unbind(); + IrisPlatforms.bind(runtime); + try { + ModdedForcedDatapack.writePackMeta(packDirectory); + JSONObject pack = new JSONObject(Files.readString(packDirectory.resolve("pack.mcmeta"))) + .getJSONObject("pack"); + assertEquals(DataVersion.getRuntime().getPackFormat(), pack.getInt("pack_format")); + assertEquals(DataVersion.V26_2.getPackFormat(), pack.getInt("pack_format")); + } finally { + IrisPlatforms.unbind(); + if (previous != null) { + IrisPlatforms.bind(previous); + } + deleteTree(packDirectory); + } + } + @Test public void retainedDimensionSourceRendersBeforeNativeRegistryExists() throws Exception { DataFixerV1217 fixer = new DataFixerV1217(); diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitBinding.java b/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitBinding.java index 2f55a4685..fdb74abaf 100644 --- a/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitBinding.java +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitBinding.java @@ -55,7 +55,7 @@ public final class BukkitBinding implements INMSBinding { @Override public PlatformGenerationRegistry generationRegistry() { - return new BukkitGenerationRegistry(registry, "bukkit-generation-registry-v1", generation.generationRendererIdentity()); + return new BukkitGenerationRegistry(registry, "bukkit-generation-registry-v1"); } @Override diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitGenerationRegistry.java b/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitGenerationRegistry.java index 52e0012cc..5c4c2049c 100644 --- a/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitGenerationRegistry.java +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/nms/BukkitGenerationRegistry.java @@ -9,13 +9,11 @@ import java.util.Objects; public record BukkitGenerationRegistry( NativeGenerationRegistry registry, - String runtimeIdentity, - String generatedDefinitionRendererIdentity + String runtimeIdentity ) implements PlatformGenerationRegistry { public BukkitGenerationRegistry { registry = Objects.requireNonNull(registry, "registry"); runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity"); - generatedDefinitionRendererIdentity = requireText(generatedDefinitionRendererIdentity, "rendererIdentity"); } @Override diff --git a/core/src/main/java/art/arcane/iris/world/history/GenerationRegistryContractFactory.java b/core/src/main/java/art/arcane/iris/world/history/GenerationRegistryContractFactory.java index c84f34c9d..2b035ab23 100644 --- a/core/src/main/java/art/arcane/iris/world/history/GenerationRegistryContractFactory.java +++ b/core/src/main/java/art/arcane/iris/world/history/GenerationRegistryContractFactory.java @@ -552,15 +552,14 @@ public final class GenerationRegistryContractFactory { for (Map.Entry entry : requiredContract.generatedSources().entrySet()) { - GenerationRegistryContract.GeneratedSource previous = generatedSources.get(entry.getKey()); + GenerationRegistryContract.GeneratedSource previous = generatedSources.putIfAbsent( + entry.getKey(), + entry.getValue() + ); if (previous != null && !sameGeneratedSemantic(previous, entry.getValue())) { throw new IOException("Retained generation epochs require conflicting generated sources for " + entry.getKey().registryKey() + " / " + entry.getKey().resourceKey() + "."); } - generatedSources.put( - entry.getKey(), - preferredGeneratedSource(previous, entry.getValue(), registry) - ); } } Map captured = new TreeMap<>(); @@ -596,18 +595,6 @@ public final class GenerationRegistryContractFactory { return GenerationRegistryContract.fromDefinitions(captured); } - public static String requireGeneratedSource( - GenerationRegistryContract contract, - GenerationRegistryContract.PhysicalResourceKey key - ) throws IOException { - return requireGeneratedSource( - contract, - key, - DataVersion.getRuntime().get(), - IrisPlatforms.get().registries().generationRegistry() - ); - } - public static String renderGeneratedSource( GenerationRegistryContract contract, GenerationRegistryContract.PhysicalResourceKey key, @@ -622,68 +609,9 @@ public final class GenerationRegistryContractFactory { + requiredKey.registryKey() + " / " + requiredKey.resourceKey() + "."); } requireGeneratedSemanticFingerprint(requiredKey, source, requiredContract.definitions().get(requiredKey)); - String sourceFingerprint = fingerprintDefinition(requiredKey, - PlatformGenerationRegistry.Definition.exactJson(source.sourceJson()), "generated"); - if (!source.renderedDefinitionSha256().equals(sourceFingerprint)) { - throw new IOException("Historical generated registry source changed for " - + requiredKey.registryKey() + " / " + requiredKey.resourceKey() + "."); - } return renderGeneratedSemantic(requiredKey, source, requiredFixer); } - public static String requireGeneratedSource( - GenerationRegistryContract contract, - GenerationRegistryContract.PhysicalResourceKey key, - IDataFixer fixer - ) throws IOException { - return requireGeneratedSource( - contract, - key, - fixer, - IrisPlatforms.get().registries().generationRegistry() - ); - } - - public static String requireGeneratedSource( - GenerationRegistryContract contract, - GenerationRegistryContract.PhysicalResourceKey key, - PlatformGenerationRegistry generationRegistry - ) throws IOException { - return requireGeneratedSource( - contract, - key, - DataVersion.getRuntime().get(), - generationRegistry - ); - } - - public static String requireGeneratedSource( - GenerationRegistryContract contract, - GenerationRegistryContract.PhysicalResourceKey key, - IDataFixer fixer, - PlatformGenerationRegistry generationRegistry - ) throws IOException { - GenerationRegistryContract requiredContract = Objects.requireNonNull(contract, "contract"); - GenerationRegistryContract.PhysicalResourceKey requiredKey = Objects.requireNonNull(key, "key"); - IDataFixer requiredFixer = Objects.requireNonNull(fixer, "fixer"); - PlatformGenerationRegistry registry = Objects.requireNonNull( - generationRegistry, - "generationRegistry" - ); - GenerationRegistryContract.GeneratedSource source = requiredContract.generatedSources().get(requiredKey); - if (source == null) { - throw new IOException("Historical generated registry source is missing for " - + requiredKey.registryKey() + " / " + requiredKey.resourceKey() + "."); - } - String expectedFingerprint = requiredContract.definitions().get(requiredKey); - if (expectedFingerprint == null) { - throw new IOException("Historical generated registry definition is missing for " - + requiredKey.registryKey() + " / " + requiredKey.resourceKey() + "."); - } - requireGeneratedSemanticFingerprint(requiredKey, source, expectedFingerprint); - return canonicalGeneratedDefinition(requiredKey, source, requiredFixer, registry).value(); - } - public static String fingerprintDefinition( GenerationRegistryContract.PhysicalResourceKey key, PlatformGenerationRegistry.Definition definition, @@ -783,7 +711,7 @@ public final class GenerationRegistryContractFactory { DIMENSION_TYPE_EFFECTIVE_SOURCE_SCHEMA, GenerationEpochContractFactory.fingerprintDimensionType(dimension.getDimensionType()), GenerationEpochContractFactory.dimensionTypeSemanticJson(dimension.getDimensionType()), - registry.generatedDefinitionRendererIdentity(), + rendererIdentity(fixer), canonicalDefinition ); } @@ -833,7 +761,7 @@ public final class GenerationRegistryContractFactory { CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA, generated.semanticFingerprint(), generated.semanticJson(), - registry.generatedDefinitionRendererIdentity(), + rendererIdentity(fixer), generated.definition() ); Set tags = biomeTags.computeIfAbsent(generated.physicalKey().resourceKey(), ignored -> new TreeSet<>()); @@ -882,7 +810,7 @@ public final class GenerationRegistryContractFactory { CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA, generated.semanticFingerprint(), generated.semanticJson(), - registry.generatedDefinitionRendererIdentity(), + rendererIdentity(fixer), generated.definition() ); } @@ -1087,32 +1015,9 @@ public final class GenerationRegistryContractFactory { } } - private static GenerationRegistryContract.GeneratedSource preferredGeneratedSource( - GenerationRegistryContract.GeneratedSource previous, - GenerationRegistryContract.GeneratedSource candidate, - PlatformGenerationRegistry registry - ) { - if (previous == null) { - return candidate; - } - String currentRenderer = requireText( - registry.generatedDefinitionRendererIdentity(), - "Generated registry renderer identity" - ); - boolean previousMatches = previous.rendererIdentity().equals(currentRenderer); - boolean candidateMatches = candidate.rendererIdentity().equals(currentRenderer); - if (previousMatches != candidateMatches) { - return candidateMatches ? candidate : previous; - } - return generatedSourceOrder(candidate).compareTo(generatedSourceOrder(previous)) < 0 - ? candidate - : previous; - } - - private static String generatedSourceOrder(GenerationRegistryContract.GeneratedSource source) { - return source.rendererIdentity() + '\u0000' - + source.renderedDefinitionSha256() + '\u0000' - + source.sourceJson(); + // Recorded for diagnostics only. Validation always re-renders the semantics with the current fixer. + private static String rendererIdentity(IDataFixer fixer) { + return fixer.getClass().getName(); } private static boolean sameGeneratedSemantic( @@ -1130,36 +1035,19 @@ public final class GenerationRegistryContractFactory { IDataFixer fixer, PlatformGenerationRegistry registry ) throws IOException { - String currentRenderer = requireText( - registry.generatedDefinitionRendererIdentity(), - "Generated registry renderer identity" - ); - String renderedSource; - if (source.rendererIdentity().equals(currentRenderer)) { - renderedSource = source.sourceJson(); - } else { - renderedSource = renderGeneratedSemantic(key, source, fixer); - } - PlatformGenerationRegistry.Definition definition; + String renderedSource = renderGeneratedSemantic(key, source, fixer); try { - definition = registry.canonicalDefinition( + PlatformGenerationRegistry.Definition definition = registry.canonicalDefinition( key.registryKey(), key.resourceKey(), renderedSource ); requireGeneratedDefinition(definition, "historical generated registry definition"); + return definition; } catch (RuntimeException failure) { throw new IOException("Historical generated registry source is incompatible for " + key.registryKey() + " / " + key.resourceKey() + ".", failure); } - if (source.rendererIdentity().equals(currentRenderer)) { - String renderedFingerprint = fingerprintDefinition(key, definition, "generated"); - if (!source.renderedDefinitionSha256().equals(renderedFingerprint)) { - throw new IOException("Historical generated registry source changed for " - + key.registryKey() + " / " + key.resourceKey() + "."); - } - } - return definition; } private static String renderGeneratedSemantic( diff --git a/core/src/test/java/art/arcane/iris/pack/datapack/IrisDatapackCompilerTest.java b/core/src/test/java/art/arcane/iris/pack/datapack/IrisDatapackCompilerTest.java index c3af8d245..d841a2202 100644 --- a/core/src/test/java/art/arcane/iris/pack/datapack/IrisDatapackCompilerTest.java +++ b/core/src/test/java/art/arcane/iris/pack/datapack/IrisDatapackCompilerTest.java @@ -675,11 +675,6 @@ public class IrisDatapackCompilerTest { return "bootstrap-test"; } - @Override - public String generatedDefinitionRendererIdentity() { - return "bootstrap-renderer-v1"; - } - @Override public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) { return "iris:" + dimensionTypeKey; diff --git a/core/src/test/java/art/arcane/iris/world/history/ArchivedRegistryCompilerTest.java b/core/src/test/java/art/arcane/iris/world/history/ArchivedRegistryCompilerTest.java index 7f21f2840..005349482 100644 --- a/core/src/test/java/art/arcane/iris/world/history/ArchivedRegistryCompilerTest.java +++ b/core/src/test/java/art/arcane/iris/world/history/ArchivedRegistryCompilerTest.java @@ -120,11 +120,6 @@ public class ArchivedRegistryCompilerTest { return "bootstrap-test"; } - @Override - public String generatedDefinitionRendererIdentity() { - return "bootstrap-renderer-v1"; - } - @Override public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) { return "iris:" + dimensionTypeKey; diff --git a/core/src/test/java/art/arcane/iris/world/history/GenerationRegistryContractFactoryTest.java b/core/src/test/java/art/arcane/iris/world/history/GenerationRegistryContractFactoryTest.java index bdd7e4177..0a70f0edf 100644 --- a/core/src/test/java/art/arcane/iris/world/history/GenerationRegistryContractFactoryTest.java +++ b/core/src/test/java/art/arcane/iris/world/history/GenerationRegistryContractFactoryTest.java @@ -8,7 +8,11 @@ import art.arcane.iris.pack.datapack.DataVersion; import art.arcane.iris.pack.datapack.IDataFixer; import art.arcane.iris.generation.biome.IrisBiome; import art.arcane.iris.generation.biome.IrisBiomeCustom; +import art.arcane.iris.generation.biome.IrisBiomeCustomCategory; +import art.arcane.iris.generation.biome.IrisBiomeCustomParticle; +import art.arcane.iris.generation.biome.IrisBiomeCustomPrecipType; import art.arcane.iris.generation.biome.IrisBiomeCustomSpawn; +import art.arcane.iris.generation.biome.IrisBiomeCustomSpawnType; import art.arcane.iris.generation.terrain.IrisDimension; import art.arcane.iris.generation.terrain.IrisDimensionType; import art.arcane.iris.generation.terrain.IrisDimensionTypeOptions; @@ -510,7 +514,7 @@ public class GenerationRegistryContractFactoryTest { GenerationRegistryContractFactory.CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA, semanticFingerprint, semantic, - registry.generatedDefinitionRendererIdentity(), + "test-renderer-v1", GenerationRegistryContractFactory.fingerprintDefinition( generated, definition, @@ -528,18 +532,18 @@ public class GenerationRegistryContractFactoryTest { GenerationRegistryContract available = GenerationRegistryContractFactory.captureRequiredDefinitions( Set.of(required), + firstFixer, registry ); required.requireDefinitionsAvailableIn(available); assertEquals( - source, - GenerationRegistryContractFactory.requireGeneratedSource( + JsonParser.parseString(source), + JsonParser.parseString(GenerationRegistryContractFactory.renderGeneratedSource( required, generated, - firstFixer, - registry - ) + firstFixer + )) ); registry.put(generated, PlatformGenerationRegistry.Definition.exactJson( @@ -547,33 +551,199 @@ public class GenerationRegistryContractFactoryTest { )); assertThrows( IOException.class, - () -> GenerationRegistryContractFactory.captureRequiredDefinitions(Set.of(required), registry) + () -> GenerationRegistryContractFactory.captureRequiredDefinitions( + Set.of(required), + firstFixer, + registry + ) ); registry.remove(generated); assertThrows( IOException.class, - () -> GenerationRegistryContractFactory.captureRequiredDefinitions(Set.of(required), registry) + () -> GenerationRegistryContractFactory.captureRequiredDefinitions( + Set.of(required), + firstFixer, + registry + ) + ); + } + + @Test + public void validatesTheCurrentRenderingWhenTheRendererChangedUnderTheSameIdentity() throws Exception { + FakeGenerationRegistry registry = new FakeGenerationRegistry("runtime-a"); + GenerationRegistryContract.PhysicalResourceKey generated = key( + GenerationRegistryContractFactory.BIOME_REGISTRY, + "iris:biomes/" + HASH_A + ); + IrisBiomeCustom customBiome = new IrisBiomeCustom().setId("mist").setTemperature(0.5D); + String semantic = GenerationRegistryContractFactory.customBiomeEffectiveSemanticJson(customBiome, null); + String capturedSource = customBiome.generateJson( + new TaggedBiomeFixer("first"), + new ContentGate(null, Map.of(), null) + ); + GenerationRegistryContract.GeneratedSource generatedSource = new GenerationRegistryContract.GeneratedSource( + GenerationRegistryContractFactory.CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA, + GenerationRegistryContractFactory.fingerprintCustomBiomeAuthoredDefinition(semantic), + semantic, + "test-renderer-v1", + GenerationRegistryContractFactory.fingerprintDefinition( + generated, + PlatformGenerationRegistry.Definition.exactJson(capturedSource), + "generated" + ), + capturedSource + ); + GenerationRegistryContract required = GenerationRegistryContract.fromDefinitionsAndGeneratedSources( + Map.of(generated, GenerationRegistryContractFactory.fingerprintGeneratedSemantic( + generated, + generatedSource + )), + Map.of(generated, generatedSource) + ); + IDataFixer currentFixer = new TaggedBiomeFixer("second"); + String installed = GenerationRegistryContractFactory.renderGeneratedSource(required, generated, currentFixer); + assertNotEquals(JsonParser.parseString(capturedSource), JsonParser.parseString(installed)); + registry.put(generated, PlatformGenerationRegistry.Definition.exactJson(installed)); + + GenerationRegistryContract available = GenerationRegistryContractFactory.captureRequiredDefinitions( + Set.of(required), + currentFixer, + registry ); - FakeGenerationRegistry changedRenderer = new FakeGenerationRegistry("runtime-b") { - @Override - public String generatedDefinitionRendererIdentity() { - return "test-renderer-v2"; - } - }; - IDataFixer secondFixer = new TaggedBiomeFixer("second"); - String rerendered = GenerationRegistryContractFactory.requireGeneratedSource( - required, - generated, - secondFixer, - changedRenderer + required.requireDefinitionsAvailableIn(available); + } + + @Test + public void semanticRenderingMatchesTheCapturedRenderingForEverySupportedFixer() throws Exception { + IrisData data = mock(IrisData.class); + when(data.getContentGate()).thenReturn(new ContentGate(null, Map.of(), null)); + IrisDimension dimension = new IrisDimension(); + dimension.setLoadKey("overworld"); + IrisBiomeCustom biome = richCustomBiome(); + IrisDimensionTypeOptions options = new IrisDimensionTypeOptions() + .coordinateScale(4.0) + .ambientLight(0.25F) + .fixedTime(18000L) + .cloudHeight(-96) + .monsterSpawnBlockLightLimit(11) + .ultrawarm(IrisDimensionTypeOptions.TriState.TRUE) + .natural(IrisDimensionTypeOptions.TriState.FALSE) + .piglinSafe(IrisDimensionTypeOptions.TriState.TRUE) + .respawnAnchorWorks(IrisDimensionTypeOptions.TriState.TRUE) + .bedWorks(IrisDimensionTypeOptions.TriState.FALSE) + .raids(IrisDimensionTypeOptions.TriState.FALSE) + .skylight(IrisDimensionTypeOptions.TriState.FALSE) + .ceiling(IrisDimensionTypeOptions.TriState.TRUE); + FakeGenerationRegistry registry = new FakeGenerationRegistry("runtime"); + GenerationRegistryContract.PhysicalResourceKey dimensionKey = key( + GenerationRegistryContractFactory.DIMENSION_TYPE_REGISTRY, + "iris:overworld_type" ); - assertTrue(rerendered.contains("second")); - assertNotEquals(source, rerendered); - changedRenderer.put(generated, PlatformGenerationRegistry.Definition.exactJson(rerendered)); - GenerationRegistryContract availableAfterUpgrade = GenerationRegistryContractFactory - .captureRequiredDefinitions(Set.of(required), secondFixer, changedRenderer); - required.requireDefinitionsAvailableIn(availableAfterUpgrade); + int fixers = 0; + for (DataVersion version : DataVersion.values()) { + if (version == DataVersion.UNSUPPORTED) { + continue; + } + fixers++; + IDataFixer fixer = version.get(); + GenerationRegistryContractFactory.CustomBiomeDefinition captured = GenerationRegistryContractFactory + .customBiomeDefinition(data, dimension, biome, fixer, registry); + assertCapturedRenderingRoundTrips( + version, + captured.physicalKey(), + GenerationRegistryContractFactory.CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA, + captured.semanticFingerprint(), + captured.semanticJson(), + captured.definition(), + fixer, + registry + ); + for (IDataFixer.Dimension base : IDataFixer.Dimension.values()) { + IrisDimensionType dimensionType = new IrisDimensionType(base, options, 192, 384, -128); + assertCapturedRenderingRoundTrips( + version, + dimensionKey, + GenerationRegistryContractFactory.DIMENSION_TYPE_EFFECTIVE_SOURCE_SCHEMA, + GenerationEpochContractFactory.fingerprintDimensionType(dimensionType), + GenerationEpochContractFactory.dimensionTypeSemanticJson(dimensionType), + registry.canonicalDefinition( + dimensionKey.registryKey(), + dimensionKey.resourceKey(), + dimensionType.toJson(fixer) + ), + fixer, + registry + ); + } + } + assertEquals(DataVersion.values().length - 1, fixers); + } + + private static void assertCapturedRenderingRoundTrips( + DataVersion version, + GenerationRegistryContract.PhysicalResourceKey key, + String sourceSchema, + String semanticFingerprint, + String semanticJson, + PlatformGenerationRegistry.Definition captured, + IDataFixer fixer, + PlatformGenerationRegistry registry + ) throws IOException { + GenerationRegistryContract.GeneratedSource source = new GenerationRegistryContract.GeneratedSource( + sourceSchema, + semanticFingerprint, + semanticJson, + "round-trip", + GenerationRegistryContractFactory.fingerprintDefinition(key, captured, "generated"), + captured.value() + ); + GenerationRegistryContract contract = GenerationRegistryContract.fromDefinitionsAndGeneratedSources( + Map.of(key, GenerationRegistryContractFactory.fingerprintGeneratedSemantic(key, source)), + Map.of(key, source) + ); + String rendered = GenerationRegistryContractFactory.renderGeneratedSource(contract, key, fixer); + assertEquals( + version + " " + key.resourceKey(), + JsonParser.parseString(captured.value()), + JsonParser.parseString(registry.canonicalDefinition( + key.registryKey(), + key.resourceKey(), + rendered + ).value()) + ); + } + + private static IrisBiomeCustom richCustomBiome() { + String[] entities = { + "minecraft:zombie", "minecraft:cow", "minecraft:bat", "minecraft:axolotl", + "minecraft:glow_squid", "minecraft:squid", "minecraft:cod", "minecraft:allay" + }; + IrisBiomeCustomSpawnType[] groups = IrisBiomeCustomSpawnType.values(); + KList spawns = new KList<>(); + for (int index = 0; index < groups.length; index++) { + spawns.add(new IrisBiomeCustomSpawn() + .setType(entities[index % entities.length]) + .setGroup(groups[index]) + .setWeight(index + 1) + .setMinCount(index + 1) + .setMaxCount(index % 2 == 0 ? index + 3 : index + 1)); + } + return new IrisBiomeCustom() + .setId("round_trip") + .setTemperature(1.25D) + .setHumidity(0.3D) + .setDownfallType(IrisBiomeCustomPrecipType.snow) + .setCategory(IrisBiomeCustomCategory.forest) + .setSpawnRarity(7) + .setSkyColor("#112233") + .setFogColor("445566") + .setWaterColor("#778899") + .setWaterFogColor("#aabbcc") + .setGrassColor("#55aa33") + .setFoliageColor("#335511") + .setAmbientParticle(new IrisBiomeCustomParticle().setParticle("ash").setRarity(80)) + .setSpawns(spawns); } @Test @@ -621,18 +791,11 @@ public class GenerationRegistryContractFactoryTest { Map.of(key, GenerationRegistryContractFactory.fingerprintGeneratedSemantic(key, generatedSource)), Map.of(key, generatedSource) ); - FakeGenerationRegistry upgraded = new FakeGenerationRegistry("runtime-b") { - @Override - public String generatedDefinitionRendererIdentity() { - return "test-renderer-v2"; - } - }; - String rerendered = GenerationRegistryContractFactory.requireGeneratedSource( + String rerendered = GenerationRegistryContractFactory.renderGeneratedSource( contract, key, - new TaggedBiomeFixer("second"), - upgraded + new TaggedBiomeFixer("second") ); assertTrue(rerendered.contains("second")); @@ -750,11 +913,6 @@ public class GenerationRegistryContractFactoryTest { return runtimeIdentity; } - @Override - public String generatedDefinitionRendererIdentity() { - return "test-renderer-v1"; - } - @Override public String customBiomeResourceKey(String identitySha256) { return "test:biomes/" + identitySha256; diff --git a/spi/src/main/java/art/arcane/iris/spi/PlatformGenerationRegistry.java b/spi/src/main/java/art/arcane/iris/spi/PlatformGenerationRegistry.java index 1a5241fb5..456c10859 100644 --- a/spi/src/main/java/art/arcane/iris/spi/PlatformGenerationRegistry.java +++ b/spi/src/main/java/art/arcane/iris/spi/PlatformGenerationRegistry.java @@ -11,8 +11,6 @@ public interface PlatformGenerationRegistry extends NativeGenerationRegistry { String runtimeIdentity(); - String generatedDefinitionRendererIdentity(); - default String customBiomeResourceKey(String identitySha256) { return contentAddressedCustomBiomeResourceKey(identitySha256); }