From ae20fa17c9e747211193820d0a327434f105349d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Mon, 18 May 2026 10:05:23 -0400 Subject: [PATCH 1/8] Fix random CMEs from NightConfigWatchThrottler --- .../config/NightConfigWatchThrottler.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/forge/config/NightConfigWatchThrottler.java b/src/main/java/org/embeddedt/modernfix/forge/config/NightConfigWatchThrottler.java index a0283bd2..b75bfad1 100644 --- a/src/main/java/org/embeddedt/modernfix/forge/config/NightConfigWatchThrottler.java +++ b/src/main/java/org/embeddedt/modernfix/forge/config/NightConfigWatchThrottler.java @@ -22,7 +22,8 @@ public class NightConfigWatchThrottler { @SuppressWarnings("rawtypes") public static void throttle() { Map watchedDirs = ObfuscationReflectionHelper.getPrivateValue(FileWatcher.class, FileWatcher.defaultInstance(), "watchedDirs"); - ObfuscationReflectionHelper.setPrivateValue(FileWatcher.class, FileWatcher.defaultInstance(), new ForwardingMap() { + Thread launchThread = Thread.currentThread(); + Map watchedDirsWrapper = new ForwardingMap() { @Override protected Map delegate() { return watchedDirs; @@ -44,13 +45,24 @@ public class NightConfigWatchThrottler { public Iterator iterator() { // iterator() is called at the beginning of each iteration of the watch loop, // so it is a good spot to inject the delay. - LockSupport.parkNanos(DELAY); + if (Thread.currentThread() != launchThread) { + LockSupport.parkNanos(DELAY); + } return super.iterator(); } }; } return cachedValues; } - }, "watchedDirs"); + }; + // Force all classes related to the iterator to be loaded ahead of time. This is necessary to prevent + // a ConcurrentModificationException from being thrown inside ModLauncher when the NightConfig file + // watcher thread loads forwarding collection classes while the main thread is still mutating the + // launch plugin map. + //noinspection StatementWithEmptyBody + for (var ignored : watchedDirsWrapper.values()) { + + } + ObfuscationReflectionHelper.setPrivateValue(FileWatcher.class, FileWatcher.defaultInstance(), watchedDirsWrapper, "watchedDirs"); } } From afe3e09a27df65553e2feda427abfa04074ae09a Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 11:51:11 -0400 Subject: [PATCH 2/8] Add feature level system for mixins --- .../modernfix/annotation/FeatureLevel.java | 9 +++++++ .../annotation/RequiresFeatureLevel.java | 12 +++++++++ .../modernfix/core/ModernFixMixinPlugin.java | 6 +++++ .../core/config/ModernFixEarlyConfig.java | 26 +++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 annotations/src/main/java/org/embeddedt/modernfix/annotation/FeatureLevel.java create mode 100644 annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresFeatureLevel.java diff --git a/annotations/src/main/java/org/embeddedt/modernfix/annotation/FeatureLevel.java b/annotations/src/main/java/org/embeddedt/modernfix/annotation/FeatureLevel.java new file mode 100644 index 00000000..06aa3098 --- /dev/null +++ b/annotations/src/main/java/org/embeddedt/modernfix/annotation/FeatureLevel.java @@ -0,0 +1,9 @@ +package org.embeddedt.modernfix.annotation; + +public enum FeatureLevel { + GA, BETA; + + public boolean isAtLeast(FeatureLevel required) { + return this.ordinal() >= required.ordinal(); + } +} diff --git a/annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresFeatureLevel.java b/annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresFeatureLevel.java new file mode 100644 index 00000000..7ebd7787 --- /dev/null +++ b/annotations/src/main/java/org/embeddedt/modernfix/annotation/RequiresFeatureLevel.java @@ -0,0 +1,12 @@ +package org.embeddedt.modernfix.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface RequiresFeatureLevel { + FeatureLevel value() default FeatureLevel.GA; +} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index 05bf9147..aeda65ed 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -3,6 +3,7 @@ package org.embeddedt.modernfix.core; import com.google.common.collect.ImmutableSet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.embeddedt.modernfix.annotation.FeatureLevel; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; import org.embeddedt.modernfix.core.launchplugin.CoreLaunchPluginService; @@ -40,6 +41,11 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { this.logger.info("Loaded configuration file for ModernFix {}: {} options available, {} override(s) found", ModernFixPlatformHooks.INSTANCE.getVersionString(), config.getOptionCount(), config.getOptionOverrideCount()); + if(ModernFixEarlyConfig.ACTIVE_FEATURE_LEVEL != FeatureLevel.GA) { + this.logger.warn("ModernFix stability level is set to {}. Features at this level may be unstable or cause crashes.", + ModernFixEarlyConfig.ACTIVE_FEATURE_LEVEL); + } + config.getOptionMap().values().forEach(option -> { if (option.isOverridden()) { String source = "[unknown]"; diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index bebe4c6b..6e67bd7f 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -9,7 +9,9 @@ import org.apache.commons.lang3.SystemUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.annotation.FeatureLevel; import org.embeddedt.modernfix.annotation.IgnoreOutsideDev; +import org.embeddedt.modernfix.annotation.RequiresFeatureLevel; import org.embeddedt.modernfix.annotation.RequiresMod; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; @@ -65,6 +67,18 @@ public class ModernFixEarlyConfig { private static final String MIXIN_CLIENT_ONLY_DESC = Type.getDescriptor(ClientOnlyMixin.class); private static final String MIXIN_REQUIRES_MOD_DESC = Type.getDescriptor(RequiresMod.class); private static final String MIXIN_DEV_ONLY_DESC = Type.getDescriptor(IgnoreOutsideDev.class); + private static final String FEATURE_LEVEL_ANNOTATION_DESC = Type.getDescriptor(RequiresFeatureLevel.class); + + public static final FeatureLevel ACTIVE_FEATURE_LEVEL = resolveFeatureLevel(); + + private static FeatureLevel resolveFeatureLevel() { + String prop = System.getProperty("modernfix.stabilityLevel", "ga").toLowerCase(Locale.ROOT); + try { + return FeatureLevel.valueOf(prop); + } catch (IllegalArgumentException e) { + return FeatureLevel.GA; + } + } private static final Pattern PLATFORM_PREFIX = Pattern.compile("(forge|fabric|common)\\."); @@ -112,6 +126,7 @@ public class ModernFixEarlyConfig { return; boolean isMixin = false, isClientOnly = false, requiredModPresent = true, isDevOnly = false; String requiredModId = ""; + FeatureLevel requiredLevel = FeatureLevel.GA; for(AnnotationNode annotation : node.invisibleAnnotations) { if(Objects.equals(annotation.desc, MIXIN_DESC)) { isMixin = true; @@ -130,6 +145,15 @@ public class ModernFixEarlyConfig { } } else if(Objects.equals(annotation.desc, MIXIN_DEV_ONLY_DESC)) { isDevOnly = true; + } else if(Objects.equals(annotation.desc, FEATURE_LEVEL_ANNOTATION_DESC)) { + for(int i = 0; i < annotation.values.size(); i += 2) { + if(annotation.values.get(i).equals("value")) { + // ASM stores enum annotation values as String[]{typeDescriptor, constantName} + String[] enumVal = (String[]) annotation.values.get(i + 1); + requiredLevel = FeatureLevel.valueOf(enumVal[1]); + break; + } + } } } if(isMixin && (!isDevOnly || ModernFixPlatformHooks.INSTANCE.isDevEnv())) { @@ -138,6 +162,8 @@ public class ModernFixEarlyConfig { mixinsMissingMods.put(mixinClassName, requiredModId); else if(isClientOnly && !ModernFixPlatformHooks.INSTANCE.isClient()) mixinsMissingMods.put(mixinClassName, "[not client]"); + else if(!ACTIVE_FEATURE_LEVEL.isAtLeast(requiredLevel)) + mixinsMissingMods.put(mixinClassName, "[feature level: requires " + requiredLevel + "]"); String mixinCategoryName = "mixin." + mixinClassName.substring(0, mixinClassName.lastIndexOf('.')); mixinOptions.add(mixinCategoryName); } From 8213a720a3da109a5882989ddbd838088cff4821 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 11:56:45 -0400 Subject: [PATCH 3/8] Optimize TerraBlender using extended surface biome context Supersedes TerraBlenderFix --- build.gradle.kts | 1 + .../NamespacedSurfaceRuleSourceMixin.java | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java diff --git a/build.gradle.kts b/build.gradle.kts index e6dc06a9..82c336aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -129,6 +129,7 @@ dependencies { modCompileOnly("curse.maven:cofhcore-69162:5374122") modCompileOnly("curse.maven:resourcefullib-570073:5659871") modCompileOnly("curse.maven:kubejs-238086:5853326") + modCompileOnly("curse.maven:terrablender-563928:6290448") } tasks.named("jar") { diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java new file mode 100644 index 00000000..223a9cdb --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java @@ -0,0 +1,97 @@ +package org.embeddedt.modernfix.common.mixin.perf.optimize_surface_rules; + +import com.google.common.collect.ImmutableList; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import it.unimi.dsi.fastutil.objects.ObjectArraySet; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.SurfaceRules; +import org.embeddedt.modernfix.annotation.FeatureLevel; +import org.embeddedt.modernfix.annotation.RequiresFeatureLevel; +import org.embeddedt.modernfix.annotation.RequiresMod; +import org.embeddedt.modernfix.world.gen.ExtendedSurfaceContext; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import terrablender.worldgen.surface.NamespacedSurfaceRuleSource; + +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +@Mixin(NamespacedSurfaceRuleSource.class) +@RequiresMod("terrablender") +@RequiresFeatureLevel(FeatureLevel.BETA) +public class NamespacedSurfaceRuleSourceMixin { + @Shadow + @Final + private Map sources; + + @Shadow + @Final + private SurfaceRules.RuleSource base; + + /** + * @author embeddedt + * @reason Avoid doing an expensive biome lookup per block in cases where we can prove all biomes will be from a + * single namespace. This achieves much of the benefit of TerraBlenderFix without the compatibility issues. + */ + @Inject(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At("HEAD"), cancellable = true) + private void modernfix$fastApply(SurfaceRules.Context context, CallbackInfoReturnable cir, + @Share("possibleNamespaces") LocalRef> possibleNamespacesRef) { + var possibleBiomes = ((ExtendedSurfaceContext)(Object)context).mfix$getPossibleBiomes(); + if (possibleBiomes == null) { + return; + } + Set namespaces = mfix$findNamespaces(possibleBiomes); + possibleNamespacesRef.set(namespaces); + if (namespaces.size() != 1) { + return; + } + String singleNamespace = namespaces.iterator().next(); + // In a single namespace scenario, we can bypass the biome lookup and directly construct a sequence rule + SurfaceRules.RuleSource namespacedSource = this.sources.get(singleNamespace); + if (namespacedSource == null) { + // Sequence rule wrapper not required + cir.setReturnValue(this.base.apply(context)); + } else { + cir.setReturnValue(new SurfaceRules.SequenceRule(ImmutableList.of(namespacedSource.apply(context), this.base.apply(context)))); + } + } + + /** + * @author embeddedt + * @reason Even if we have to fall back to the namespaced source, avoid compiling surface rules for namespaces that + * will never be hit in the given chunk. + */ + @ModifyArg(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At(value = "INVOKE", target = "Ljava/util/Set;forEach(Ljava/util/function/Consumer;)V")) + private Consumer> mfix$filterConsumer(Consumer> originalConsumer, + @Share("possibleNamespaces") LocalRef> possibleNamespacesRef) { + var possibleNamespaces = possibleNamespacesRef.get(); + if (possibleNamespaces == null) { + return originalConsumer; + } + return entry -> { + if(possibleNamespaces.contains(entry.getKey())) { + originalConsumer.accept(entry); + } + }; + } + + private static Set mfix$findNamespaces(Set> possibleBiomes) { + if (possibleBiomes.size() == 1) { + return Set.of(possibleBiomes.iterator().next().location().getNamespace()); + } else { + var namespaces = new ObjectArraySet(4); + for (var key : possibleBiomes) { + namespaces.add(key.location().getNamespace()); + } + return Set.copyOf(namespaces); + } + } +} From 29ff5f152e1ede9905cd2a4b7cd1ab728f810690 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 11:58:36 -0400 Subject: [PATCH 4/8] Log the state of each mixin at DEBUG level --- .../modernfix/core/ModernFixMixinPlugin.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index aeda65ed..e75b4cb6 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -135,10 +135,17 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { } String mixin = mixinClassName.substring(MIXIN_PACKAGE_ROOT.length()); - if(!instance.isOptionEnabled(mixin)) + if(!instance.isOptionEnabled(mixin)) { + this.logger.debug("Skipping mixin {}: disabled by configuration", mixin); return false; + } String disabledBecauseMod = instance.config.getPermanentlyDisabledMixins().get(mixin); - return disabledBecauseMod == null; + if(disabledBecauseMod != null) { + this.logger.debug("Skipping mixin {}: disabled for mod compat ({})", mixin, disabledBecauseMod); + return false; + } + this.logger.debug("Applying mixin {}", mixin); + return true; } public boolean isOptionEnabled(String mixin) { From 85aab426c52baad57f55691f3724c5fd3edf8562 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 12:01:33 -0400 Subject: [PATCH 5/8] Fix mixin AP complaints --- .../NamespacedSurfaceRuleSourceMixin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java index 223a9cdb..be95eb37 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NamespacedSurfaceRuleSourceMixin.java @@ -41,7 +41,7 @@ public class NamespacedSurfaceRuleSourceMixin { * @reason Avoid doing an expensive biome lookup per block in cases where we can prove all biomes will be from a * single namespace. This achieves much of the benefit of TerraBlenderFix without the compatibility issues. */ - @Inject(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At("HEAD"), cancellable = true) + @Inject(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At("HEAD"), cancellable = true, remap = false) private void modernfix$fastApply(SurfaceRules.Context context, CallbackInfoReturnable cir, @Share("possibleNamespaces") LocalRef> possibleNamespacesRef) { var possibleBiomes = ((ExtendedSurfaceContext)(Object)context).mfix$getPossibleBiomes(); @@ -69,7 +69,7 @@ public class NamespacedSurfaceRuleSourceMixin { * @reason Even if we have to fall back to the namespaced source, avoid compiling surface rules for namespaces that * will never be hit in the given chunk. */ - @ModifyArg(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At(value = "INVOKE", target = "Ljava/util/Set;forEach(Ljava/util/function/Consumer;)V")) + @ModifyArg(method = "apply(Lnet/minecraft/world/level/levelgen/SurfaceRules$Context;)Lnet/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule;", at = @At(value = "INVOKE", target = "Ljava/util/Set;forEach(Ljava/util/function/Consumer;)V"), remap = false) private Consumer> mfix$filterConsumer(Consumer> originalConsumer, @Share("possibleNamespaces") LocalRef> possibleNamespacesRef) { var possibleNamespaces = possibleNamespacesRef.get(); From f4f596ca0c37d775a4277a27b5e838562323d1ef Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 12:50:21 -0400 Subject: [PATCH 6/8] Fix mixin failing at runtime due to missing AT --- src/main/resources/META-INF/accesstransformer.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index c7980896..e25fffd6 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -5,6 +5,7 @@ public net.minecraft.client.renderer.block.model.multipart.MultiPart f_111962_ public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl public net.minecraft.client.resources.model.ModelBakery$ModelBakerImpl (Lnet/minecraft/client/resources/model/ModelBakery;Ljava/util/function/BiFunction;Lnet/minecraft/resources/ResourceLocation;)V public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRule +public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRule (Ljava/util/List;)V public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRuleSource public net.minecraft.world.level.levelgen.SurfaceRules$SequenceRuleSource (Ljava/util/List;)V public net.minecraft.world.level.levelgen.SurfaceRules$TestRuleSource From 50cedfc6993d7ad69e86f46494f61cd637dd6f04 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 12:50:33 -0400 Subject: [PATCH 7/8] Fix stability level being impossible to override --- .../embeddedt/modernfix/core/config/ModernFixEarlyConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 6e67bd7f..4f9f0086 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -72,7 +72,7 @@ public class ModernFixEarlyConfig { public static final FeatureLevel ACTIVE_FEATURE_LEVEL = resolveFeatureLevel(); private static FeatureLevel resolveFeatureLevel() { - String prop = System.getProperty("modernfix.stabilityLevel", "ga").toLowerCase(Locale.ROOT); + String prop = System.getProperty("modernfix.stabilityLevel", "ga").toUpperCase(Locale.ROOT); try { return FeatureLevel.valueOf(prop); } catch (IllegalArgumentException e) { From f8d2425242e7865651133ac5d0e2d7cc257a1499 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 23 May 2026 12:50:48 -0400 Subject: [PATCH 8/8] Improve accuracy of possible biomes check --- .../NoiseBasedChunkGeneratorMixin.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java index 4791d620..ff265251 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/optimize_surface_rules/NoiseBasedChunkGeneratorMixin.java @@ -26,8 +26,15 @@ public class NoiseBasedChunkGeneratorMixin { @SuppressWarnings("unchecked") private static void mfix$accumulate(Set> chunkBiomes, LevelChunkSection section) { var palette = ((ExtendedPalettedContainer>)section.getBiomes()).mfix$getPalette(); - for (int i = 0; i < palette.getSize(); i++) { - chunkBiomes.add(palette.valueFor(i).unwrapKey().orElseThrow()); + if (palette.getSize() == 1) { + // No need to iterate the storage itself, as there can only be one value + chunkBiomes.add(palette.valueFor(0).unwrapKey().orElseThrow()); + } else { + // Use getAll() rather than raw palette iteration. PalettedContainer.recreate() seeds the new + // palette with Biomes.PLAINS (the initial default), leaving a stale palette entry even after + // fillBiomesFromNoise replaces all cells with real biomes. getAll() only visits entries that + // are actually referenced in the backing storage, so stale entries are correctly excluded. + section.getBiomes().getAll(holder -> chunkBiomes.add(holder.unwrapKey().orElseThrow())); } }