Skip to content
Merged

26.1 #44

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ae20fa1
Fix random CMEs from NightConfigWatchThrottler
embeddedt May 18, 2026
afe3e09
Add feature level system for mixins
embeddedt May 23, 2026
8213a72
Optimize TerraBlender using extended surface biome context
embeddedt May 23, 2026
29ff5f1
Log the state of each mixin at DEBUG level
embeddedt May 23, 2026
85aab42
Fix mixin AP complaints
embeddedt May 23, 2026
f4f596c
Fix mixin failing at runtime due to missing AT
embeddedt May 23, 2026
50cedfc
Fix stability level being impossible to override
embeddedt May 23, 2026
f8d2425
Improve accuracy of possible biomes check
embeddedt May 23, 2026
5d862d0
Merge branch '1.20' into 1.21.1
embeddedt May 23, 2026
7c45564
Fix potential stronghold cache corruption if player exits world too q…
embeddedt May 23, 2026
b62eb18
Avoid blocking chunk generation on concentric rings calculation where…
embeddedt May 23, 2026
538c52b
Run stronghold gen on dedicated thread pool
embeddedt May 23, 2026
62dbbea
Optimize ZIP resource packs significantly
embeddedt May 24, 2026
74f76f7
Improvements to ZipPackIndex
embeddedt May 24, 2026
494203e
Fix potential crash during worldgen with release_protochunks enabled
embeddedt May 24, 2026
12afd99
Merge branch '1.20' into 1.21.1
embeddedt May 25, 2026
33851c1
Fix ImposterProtoChunk leaking live block entities to worldgen
embeddedt May 25, 2026
f484823
Merge remote-tracking branch 'origin/1.20' into 1.21.1
embeddedt May 25, 2026
fb9dcf7
Improve ZipPackIndex
embeddedt May 29, 2026
e9bfd96
Fix Forge pack finder being injected multiple times into pack repository
embeddedt May 29, 2026
0ecee52
Fix Forge calling getResource on every loot table unnecessarily
embeddedt Jun 3, 2026
f1492cc
Allow ZipPackIndex to work with any byte channel
embeddedt Jun 5, 2026
0f94634
Remove the item stack reference thread
embeddedt Jun 7, 2026
ab98801
Add experimental KubeJS memory usage optimization
embeddedt Jun 7, 2026
d51b0f6
Fix an instance of vanilla leaking a BufferBuilder
embeddedt Jun 7, 2026
1bcb28a
Allow feature level requirement to be set at package level
embeddedt Jun 7, 2026
7fbfcf1
Remove error when missing_block_entities sees null BE
embeddedt Jun 8, 2026
b702a40
Merge branch '1.20' into 1.21.1
embeddedt Jun 9, 2026
292a6ae
Fix optimize_surface_rules breaking mods that provide custom BiomeMan…
embeddedt Jun 12, 2026
1174f86
Fix blast_search_trees not working as expected with custom creative s…
embeddedt Jun 15, 2026
3ce04c4
Improve emulation of genuinely missing models
embeddedt Jun 16, 2026
f8f1b09
Merge branch '1.20' into 1.21.1
embeddedt Jun 16, 2026
38824e6
Make generated capability providers more memory and JIT compilation f…
embeddedt Jun 26, 2026
b4ca7bd
Add simple utility script to find FeatureLevel-gated code
embeddedt Jun 27, 2026
526ab8f
Add support for config options that are not booleans
embeddedt Jun 28, 2026
1c62fe1
Make the active feature level customizable via modernfix-mixins.prope…
embeddedt Jun 28, 2026
523cbd6
Add guidance about stability level to config
embeddedt Jun 28, 2026
04fb856
Enable several more options when beta stability is selected
embeddedt Jun 28, 2026
8385b0d
Do not assume WorldGenRegion's center is the currently generating chunk
embeddedt Jun 28, 2026
4e4e070
Merge branch '1.20' into 1.21.1
embeddedt Jul 2, 2026
83e03ed
Merge branch '1.21.1' into 26.1
embeddedt Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.embeddedt.modernfix.annotation;

public enum FeatureLevel {
GA, BETA;

public boolean isAtLeast(FeatureLevel required) {
return this.ordinal() >= required.ordinal();
}
}
Original file line number Diff line number Diff line change
@@ -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, ElementType.PACKAGE})
public @interface RequiresFeatureLevel {
FeatureLevel value() default FeatureLevel.GA;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface RequiresMod {
String value() default "";
}
26 changes: 26 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,41 @@ dependencies {
compileOnly("curse.maven:cofhcore-69162:5374122")
compileOnly("curse.maven:resourcefullib-570073:5659871")
compileOnly("curse.maven:kubejs-238086:5853326")
compileOnly("curse.maven:terrablender-neoforge-940057:8046313")
}

tasks.named<Jar>("jar") {
from(embed.map { if (it.isDirectory) it else zipTree(it) })
}

val checkDanglingMixinPackageInfo by tasks.registering {
val mixinDir = file("src/main/java/org/embeddedt/modernfix/common/mixin")
inputs.dir(mixinDir)
doLast {
val dangling = mutableListOf<File>()
mixinDir.walkTopDown()
.filter { it.name == "package-info.java" }
.forEach { pkgInfo ->
val dir = pkgInfo.parentFile
val hasOtherJava = dir.listFiles { f -> f.name != "package-info.java" && f.extension == "java" }?.isNotEmpty() == true
val hasSubpackage = dir.listFiles { f -> f.isDirectory }?.isNotEmpty() == true
if (!hasOtherJava && !hasSubpackage) {
dangling += pkgInfo
}
}
if (dangling.isNotEmpty()) {
throw GradleException(
"Dangling package-info.java files found (no sibling classes or subpackages):\n" +
dangling.joinToString("\n") { " ${it.relativeTo(projectDir)}" }
)
}
}
}

// For the AP
tasks.withType<JavaCompile>().configureEach {
if (!name.lowercase().contains("test")) {
dependsOn(checkDanglingMixinPackageInfo)
options.compilerArgs.addAll(
listOf(
"-ArootProject.name=${rootProject.name}",
Expand Down
33 changes: 33 additions & 0 deletions scripts/feature-gates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Show FeatureLevel gates, excluding permanent infrastructure.
# If a level is given, show only gates at that level.
#
# Usage:
# ./scripts/feature-gates.sh # all FeatureLevel gates
# ./scripts/feature-gates.sh beta # only BETA gates
# ./scripts/feature-gates.sh ga # only GA gates

set -euo pipefail

# Permanent infrastructure — these are NOT gates to remove when promoting β → GA.
PERMANENT=(
"ModernFixMixinPlugin\.java.*!= FeatureLevel\.GA"
"ModernFixEarlyConfig\.java.*return FeatureLevel\.GA"
"ModernFixEarlyConfig\.java.*FeatureLevel requiredLevel = FeatureLevel\.GA"
)

join() { local IFS='|'; echo "$*"; }

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

cd "$REPO_ROOT"

if [ $# -ge 1 ]; then
LEVEL="${1^^}"
PATTERN="FeatureLevel\.${LEVEL}"
else
PATTERN="FeatureLevel\.[A-Z]+"
fi

git grep -rE "$PATTERN" src/ | grep -v -E "$(join "${PERMANENT[@]}")" || true
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,9 @@ private void makeBlockEntityIfNotExists(BlockState state, BlockPos.MutableBlockP
}

BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE);
String blockName = state.getBlock().toString();
if (blockEntity != null) {
if (ModernFix.LOGGER.isDebugEnabled()) {
ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
}
} else {
ModernFix.LOGGER.error("Block entity is missing for {} at {}, but could not be created", blockName, pos.toShortString());
if (blockEntity != null && ModernFix.LOGGER.isDebugEnabled()) {
String blockName = state.getBlock().toString();
ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import net.minecraft.TracingExecutor;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.*;
import net.minecraft.resources.RegistryOps;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Util;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.BiomeSource;
Expand All @@ -17,6 +20,8 @@
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.Redirect;

import java.lang.ref.SoftReference;
import java.nio.charset.StandardCharsets;
Expand All @@ -29,6 +34,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Mixin(ChunkGeneratorStructureState.class)
public class ChunkGeneratorMixin implements IChunkGenerator {
Expand All @@ -41,22 +48,24 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
private BiomeSource biomeSource;

private Path mfix$dimensionPath;
private RegistryAccess.Frozen mfix$registryAccess;
private MinecraftServer mfix$server;

private SoftReference<Map<String, List<ChunkPos>>> mfix$cachedPositions = new SoftReference<>(null);

private static final String CACHE_FILENAME = "mfix_stronghold_cache_v2.nbt";

@Override
public void mfix$setStrongholdCachePath(Path cachePath, RegistryAccess.Frozen registryAccess) {
public void mfix$setStrongholdCachePath(Path cachePath, MinecraftServer server) {
this.mfix$dimensionPath = cachePath;
this.mfix$registryAccess = registryAccess;
this.mfix$server = server;
}

@WrapMethod(method = "generateRingPositions")
private CompletableFuture<List<ChunkPos>> modernfix$cacheRingPositions(Holder<StructureSet> structureSet,
ConcentricRingsStructurePlacement placement,
Operation<CompletableFuture<List<ChunkPos>>> original) {
if (this.mfix$registryAccess == null || this.mfix$dimensionPath == null) {
ConcentricRingsStructurePlacement placement,
Operation<CompletableFuture<List<ChunkPos>>> original,
@Share("threadPool") LocalRef<TracingExecutor> threadPoolRef) {
if (this.mfix$server == null || this.mfix$dimensionPath == null) {
return original.call(structureSet, placement);
}

Expand All @@ -69,14 +78,35 @@ public class ChunkGeneratorMixin implements IChunkGenerator {
return CompletableFuture.completedFuture(List.copyOf(cached));
}

return original.call(structureSet, placement).thenApplyAsync(positions -> {
mfix$writeToCache(cacheKey, positions);
return positions;
}, Util.ioPool());
var server = this.mfix$server;
ExecutorService strongholdPool = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
threadPoolRef.set(new TracingExecutor(strongholdPool));
try {
return original.call(structureSet, placement).thenApplyAsync(positions -> {
// Skip write if server exited before we finished
if (server.isRunning()) {
mfix$writeToCache(cacheKey, positions);
}
return positions;
}, Util.ioPool());
} finally {
strongholdPool.shutdown();
}
}

/**
* @author embeddedt
* @reason Ring position calculation is often not required for initial chunk generation, but the tasks still occupy
* CPU time on the main worker pool and prevent higher priority work from progressing. To fix this we use a
* dedicated pool.
*/
@Redirect(method = "generateRingPositions", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;backgroundExecutor()Lnet/minecraft/TracingExecutor;"))
private TracingExecutor useDedicatedService(@Share("threadPool") LocalRef<TracingExecutor> threadPoolRef) {
return threadPoolRef.get();
}

private String mfix$makeCacheKey(ConcentricRingsStructurePlacement placement) {
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$registryAccess);
RegistryOps<Tag> ops = RegistryOps.create(NbtOps.INSTANCE, this.mfix$server.registryAccess());
String placementKey = ConcentricRingsStructurePlacement.CODEC.codec().encodeStart(ops, placement)
.result().map(Tag::toString).orElse(null);
String biomeSourceKey = BiomeSource.CODEC.encodeStart(ops, this.biomeSource)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.embeddedt.modernfix.common.mixin.perf.cache_strongholds;

import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import org.embeddedt.modernfix.annotation.FeatureLevel;
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(ConcentricRingsStructurePlacement.class)
@RequiresFeatureLevel(FeatureLevel.BETA)
public class ConcentricRingsStructurePlacementMixin {

@Shadow @Final private int distance;
@Shadow @Final private int spread;
@Shadow @Final private int count;

/**
* Maximum per-axis section displacement from the initial ring chunk after biome snapping.
*
* Vanilla calls findBiomeHorizontal with radius=112 blocks. In quart space this is ±28,
* and converting the selected quart back to section coordinates yields at most ±7 chunks
* per axis from the original (initialX, initialZ).
*/
@Unique private static final int MFIX_MAX_BIOME_SNAP_SECTIONS_PER_AXIS = 7;
/**
* Worst-case Euclidean error introduced by rounding:
* initialX/Z = round(cos(angle) * dist), round(sin(angle) * dist).
*/
@Unique private static final double MFIX_MAX_ROUNDING_ERROR = Math.sqrt(2.0) * 0.5;
/**
* Worst-case Euclidean biome-snap displacement when each axis can move by at most 7 chunks.
*/
@Unique private static final double MFIX_MAX_BIOME_SNAP_ERROR = MFIX_MAX_BIOME_SNAP_SECTIONS_PER_AXIS * Math.sqrt(2.0);
/**
* Total conservative positional slack (rounding + biome snap) applied to radial bounds.
*/
@Unique private static final double MFIX_MAX_POSITION_ERROR = MFIX_MAX_ROUNDING_ERROR + MFIX_MAX_BIOME_SNAP_ERROR;

/** Squared chunk-distance below which no ring position can ever land. */
@Unique private long mfix$innerRadiusSq;
/** Squared chunk-distance above which no ring position can ever land. */
@Unique private long mfix$outerRadiusSq;

/**
* Precomputes conservative radial bounds for vanilla's ring placement distance:
* {@code dist = 4*i + i*i1*6 + noise}, where {@code i=distance} and {@code i1=circle}.
*
* - Inner bound uses the minimum possible base term ({@code i1=0} => {@code 4*i}).
* - Outer bound uses the maximum reachable {@code i1} for this ({@code spread,count}) pair.
*
* Both bounds are expanded by {@link #MFIX_MAX_POSITION_ERROR} so we never reject a valid
* chunk produced by rounding and biome snapping.
*/
@Inject(
method = "<init>(Lnet/minecraft/core/Vec3i;Lnet/minecraft/world/level/levelgen/structure/placement/StructurePlacement$FrequencyReductionMethod;FILjava/util/Optional;IIILnet/minecraft/core/HolderSet;)V",
at = @At("RETURN")
)
private void mfix$computeRadiusBounds(CallbackInfo ci) {
double maxNoise = this.distance * 1.25; // (nextDouble() - 0.5) * (distance * 2.5)

// min(dist): 4*i + i*0*6 - maxNoise
double minDist = 4.0 * this.distance - maxNoise;
double safeInnerRadius = minDist - MFIX_MAX_POSITION_ERROR;
this.mfix$innerRadiusSq = (long)Math.max(0.0, Math.floor(safeInnerRadius * safeInnerRadius));

if (this.spread == 0) {
// Vanilla behavior becomes non-finite here (angle += 2π / 0), so keep only inner rejection.
this.mfix$outerRadiusSq = Long.MAX_VALUE;
return;
}

int maxCircle = this.mfix$computeMaxCircleIndex();
// max(dist): 4*i + i*maxCircle*6 + maxNoise
double maxDist = 4.0 * this.distance + (double)this.distance * maxCircle * 6.0 + maxNoise;
double safeOuterRadius = maxDist + MFIX_MAX_POSITION_ERROR;
this.mfix$outerRadiusSq = (long)Math.ceil(safeOuterRadius * safeOuterRadius);
}

/**
* Computes the highest ring index ({@code circle}) that vanilla can reach for this placement.
*
* This mirrors the spread/total update logic in
* {@link net.minecraft.world.level.chunk.ChunkGeneratorStructureState#generateRingPositions},
* but only tracks deterministic loop state (no RNG).
*/
@Unique
private int mfix$computeMaxCircleIndex() {
int ringSpread = this.spread;
int total = 0;
int circle = 0;

while (total + ringSpread < this.count) {
total += ringSpread;
circle++;
ringSpread += 2 * ringSpread / (circle + 1);
ringSpread = Math.min(ringSpread, this.count - total);
}

return circle;
}

/**
* @author embeddedt, GPT-5.3-Codex
* @reason Avoid calling getRingPositionsFor() when we know the current chunk lies outside the region where
* concentric placement can even happen. This is particularly helpful when creating new worlds, because we can
* avoid blocking on the slow noise computations within the spawn region around (0, 0).
*/
@Inject(method = "isPlacementChunk", at = @At("HEAD"), cancellable = true)
private void mfix$earlyRejectByRadius(ChunkGeneratorStructureState structureState, int x, int z,
CallbackInfoReturnable<Boolean> cir) {
long distSq = (long)x * x + (long)z * z;
if (distSq < this.mfix$innerRadiusSq || distSq > this.mfix$outerRadiusSq) {
cir.setReturnValue(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private void setCachePath(ChunkGeneratorStructureState instance, Operation<Void>
@Local(ordinal = 0, argsOnly = true) LevelStorageSource.LevelStorageAccess levelStorageAccess,
@Local(ordinal = 0, argsOnly = true) ResourceKey<Level> dimension,
@Local(ordinal = 0, argsOnly = true) MinecraftServer server) {
((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server.registryAccess());
((IChunkGenerator)instance).mfix$setStrongholdCachePath(levelStorageAccess.getDimensionPath(dimension), server);
original.call(instance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@RequiresFeatureLevel(FeatureLevel.BETA)
package org.embeddedt.modernfix.common.mixin.perf.compact_entity_models;

import org.embeddedt.modernfix.annotation.FeatureLevel;
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@RequiresFeatureLevel(FeatureLevel.BETA)
package org.embeddedt.modernfix.common.mixin.perf.dynamic_languages;

import org.embeddedt.modernfix.annotation.FeatureLevel;
import org.embeddedt.modernfix.annotation.RequiresFeatureLevel;
Loading
Loading