--- a/net/minecraft/server/level/WorldGenRegion.java
+++ b/net/minecraft/server/level/WorldGenRegion.java
@@ -4,6 +4,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -43,6 +44,7 @@
 import net.minecraft.world.level.border.WorldBorder;
 import net.minecraft.world.level.chunk.ChunkAccess;
 import net.minecraft.world.level.chunk.ChunkSource;
+import net.minecraft.world.level.chunk.status.ChunkDependencies;
 import net.minecraft.world.level.chunk.status.ChunkStatus;
 import net.minecraft.world.level.chunk.status.ChunkStep;
 import net.minecraft.world.level.chunk.status.ChunkType;
@@ -61,6 +63,7 @@
 import org.slf4j.Logger;
 
 public class WorldGenRegion implements WorldGenLevel {
+
     private static final Logger LOGGER = LogUtils.getLogger();
     private final StaticCache2D<GenerationChunkHolder> cache;
     private final ChunkAccess center;
@@ -69,8 +72,12 @@
     private final LevelData levelData;
     private final RandomSource random;
     private final DimensionType dimensionType;
-    private final WorldGenTickAccess<Block> blockTicks = new WorldGenTickAccess<>(pos -> this.getChunk(pos).getBlockTicks());
-    private final WorldGenTickAccess<Fluid> fluidTicks = new WorldGenTickAccess<>(pos -> this.getChunk(pos).getFluidTicks());
+    private final WorldGenTickAccess<Block> blockTicks = new WorldGenTickAccess<>((blockposition) -> {
+        return this.getChunk(blockposition).getBlockTicks();
+    });
+    private final WorldGenTickAccess<Fluid> fluidTicks = new WorldGenTickAccess<>((blockposition) -> {
+        return this.getChunk(blockposition).getFluidTicks();
+    });
     private final BiomeManager biomeManager;
     private final ChunkStep generatingStep;
     @Nullable
@@ -78,6 +85,36 @@
     private final AtomicLong subTickCount = new AtomicLong();
     private static final ResourceLocation WORLDGEN_REGION_RANDOM = ResourceLocation.withDefaultNamespace("worldgen_region_random");
 
+    // Paper start - rewrite chunk system
+    /**
+     * During feature generation, light data is not initialised and will always return 15 in Starlight. Vanilla
+     * can possibly return 0 if partially initialised, which allows some mushroom blocks to generate.
+     * In general, the brightness value from the light engine should not be used until the chunk is ready. To emulate
+     * Vanilla behavior better, we return 0 as the brightness during world gen unless the target chunk is finished
+     * lighting.
+     */
+    @Override
+    public int getBrightness(final net.minecraft.world.level.LightLayer lightLayer, final BlockPos blockPos) {
+        final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4);
+        if (!chunk.isLightCorrect()) {
+            return 0;
+        }
+        return this.getLightEngine().getLayerListener(lightLayer).getLightValue(blockPos);
+    }
+
+    /**
+     * See above
+     */
+    @Override
+    public int getRawBrightness(final BlockPos blockPos, final int subtract) {
+        final ChunkAccess chunk = this.getChunk(blockPos.getX() >> 4, blockPos.getZ() >> 4);
+        if (!chunk.isLightCorrect()) {
+            return 0;
+        }
+        return this.getLightEngine().getRawBrightness(blockPos, subtract);
+    }
+    // Paper end - rewrite chunk system
+
     public WorldGenRegion(ServerLevel world, StaticCache2D<GenerationChunkHolder> chunks, ChunkStep generationStep, ChunkAccess centerPos) {
         this.generatingStep = generationStep;
         this.cache = chunks;
@@ -85,7 +122,7 @@
         this.level = world;
         this.seed = world.getSeed();
         this.levelData = world.getLevelData();
-        this.random = world.getChunkSource().randomState().getOrCreateRandomFactory(WORLDGEN_REGION_RANDOM).at(this.center.getPos().getWorldPosition());
+        this.random = world.getChunkSource().randomState().getOrCreateRandomFactory(WorldGenRegion.WORLDGEN_REGION_RANDOM).at(this.center.getPos().getWorldPosition());
         this.dimensionType = world.dimensionType();
         this.biomeManager = new BiomeManager(this, BiomeManager.obfuscateSeed(this.seed));
     }
@@ -111,45 +148,78 @@
     @Nullable
     @Override
     public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
-        int i = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
-        ChunkStatus chunkStatus = i >= this.generatingStep.directDependencies().size() ? null : this.generatingStep.directDependencies().get(i);
-        GenerationChunkHolder generationChunkHolder;
-        if (chunkStatus != null) {
-            generationChunkHolder = this.cache.get(chunkX, chunkZ);
-            if (leastStatus.isOrBefore(chunkStatus)) {
-                ChunkAccess chunkAccess = generationChunkHolder.getChunkIfPresentUnchecked(chunkStatus);
-                if (chunkAccess != null) {
-                    return chunkAccess;
+        int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
+        ChunkStatus chunkstatus1 = k >= this.generatingStep.directDependencies().size() ? null : this.generatingStep.directDependencies().get(k);
+        GenerationChunkHolder generationchunkholder;
+
+        if (chunkstatus1 != null) {
+            generationchunkholder = (GenerationChunkHolder) this.cache.get(chunkX, chunkZ);
+            if (leastStatus.isOrBefore(chunkstatus1)) {
+                ChunkAccess ichunkaccess = generationchunkholder.getChunkIfPresentUnchecked(chunkstatus1);
+
+                if (ichunkaccess != null) {
+                    return ichunkaccess;
                 }
             }
         } else {
-            generationChunkHolder = null;
+            generationchunkholder = null;
         }
 
-        CrashReport crashReport = CrashReport.forThrowable(
-            new IllegalStateException("Requested chunk unavailable during world generation"), "Exception generating new chunk"
-        );
-        CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk request details");
-        crashReportCategory.setDetail("Requested chunk", String.format(Locale.ROOT, "%d, %d", chunkX, chunkZ));
-        crashReportCategory.setDetail("Generating status", () -> this.generatingStep.targetStatus().getName());
-        crashReportCategory.setDetail("Requested status", leastStatus::getName);
-        crashReportCategory.setDetail(
-            "Actual status", () -> generationChunkHolder == null ? "[out of cache bounds]" : generationChunkHolder.getPersistedStatus().getName()
-        );
-        crashReportCategory.setDetail("Maximum allowed status", () -> chunkStatus == null ? "null" : chunkStatus.getName());
-        crashReportCategory.setDetail("Dependencies", this.generatingStep.directDependencies()::toString);
-        crashReportCategory.setDetail("Requested distance", i);
-        crashReportCategory.setDetail("Generating chunk", this.center.getPos()::toString);
-        throw new ReportedException(crashReport);
+        CrashReport crashreport = CrashReport.forThrowable(new IllegalStateException("Requested chunk unavailable during world generation"), "Exception generating new chunk");
+        CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk request details");
+
+        crashreportsystemdetails.setDetail("Requested chunk", (Object) String.format(Locale.ROOT, "%d, %d", chunkX, chunkZ));
+        crashreportsystemdetails.setDetail("Generating status", () -> {
+            return this.generatingStep.targetStatus().getName();
+        });
+        Objects.requireNonNull(leastStatus);
+        crashreportsystemdetails.setDetail("Requested status", leastStatus::getName);
+        crashreportsystemdetails.setDetail("Actual status", () -> {
+            return generationchunkholder == null ? "[out of cache bounds]" : generationchunkholder.getPersistedStatus().getName();
+        });
+        crashreportsystemdetails.setDetail("Maximum allowed status", () -> {
+            return chunkstatus1 == null ? "null" : chunkstatus1.getName();
+        });
+        ChunkDependencies chunkdependencies = this.generatingStep.directDependencies();
+
+        Objects.requireNonNull(chunkdependencies);
+        crashreportsystemdetails.setDetail("Dependencies", chunkdependencies::toString);
+        crashreportsystemdetails.setDetail("Requested distance", (Object) k);
+        ChunkPos chunkcoordintpair = this.center.getPos();
+
+        Objects.requireNonNull(chunkcoordintpair);
+        crashreportsystemdetails.setDetail("Generating chunk", chunkcoordintpair::toString);
+        throw new ReportedException(crashreport);
     }
 
     @Override
     public boolean hasChunk(int chunkX, int chunkZ) {
-        int i = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
-        return i < this.generatingStep.directDependencies().size();
+        int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ);
+
+        return k < this.generatingStep.directDependencies().size();
+    }
+
+    // Paper start - if loaded util
+    @Nullable
+    @Override
+    public ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
+        return this.getChunk(x, z, ChunkStatus.FULL, false);
+    }
+
+    @Override
+    public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+        return chunk == null ? null : chunk.getBlockState(blockposition);
     }
 
     @Override
+    public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+        ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4);
+        return chunk == null ? null : chunk.getFluidState(blockposition);
+    }
+    // Paper end
+
+    @Override
     public BlockState getBlockState(BlockPos pos) {
         return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos);
     }
@@ -192,13 +262,16 @@
 
     @Override
     public boolean destroyBlock(BlockPos pos, boolean drop, @Nullable Entity breakingEntity, int maxUpdateDepth) {
-        BlockState blockState = this.getBlockState(pos);
-        if (blockState.isAir()) {
+        BlockState iblockdata = this.getBlockState(pos);
+
+        if (iblockdata.isAir()) {
             return false;
         } else {
-            if (drop) {
-                BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null;
-                Block.dropResources(blockState, this.level, pos, blockEntity, breakingEntity, ItemStack.EMPTY);
+            if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens
+            if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation
+                BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+
+                Block.dropResources(iblockdata, this.level, pos, tileentity, breakingEntity, ItemStack.EMPTY);
             }
 
             return this.setBlock(pos, Blocks.AIR.defaultBlockState(), 3, maxUpdateDepth);
@@ -208,66 +281,69 @@
     @Nullable
     @Override
     public BlockEntity getBlockEntity(BlockPos pos) {
-        ChunkAccess chunkAccess = this.getChunk(pos);
-        BlockEntity blockEntity = chunkAccess.getBlockEntity(pos);
-        if (blockEntity != null) {
-            return blockEntity;
+        ChunkAccess ichunkaccess = this.getChunk(pos);
+        BlockEntity tileentity = ichunkaccess.getBlockEntity(pos);
+
+        if (tileentity != null) {
+            return tileentity;
         } else {
-            CompoundTag compoundTag = chunkAccess.getBlockEntityNbt(pos);
-            BlockState blockState = chunkAccess.getBlockState(pos);
-            if (compoundTag != null) {
-                if ("DUMMY".equals(compoundTag.getString("id"))) {
-                    if (!blockState.hasBlockEntity()) {
+            CompoundTag nbttagcompound = ichunkaccess.getBlockEntityNbt(pos);
+            BlockState iblockdata = ichunkaccess.getBlockState(pos);
+
+            if (nbttagcompound != null) {
+                if ("DUMMY".equals(nbttagcompound.getString("id"))) {
+                    if (!iblockdata.hasBlockEntity()) {
                         return null;
                     }
 
-                    blockEntity = ((EntityBlock)blockState.getBlock()).newBlockEntity(pos, blockState);
+                    tileentity = ((EntityBlock) iblockdata.getBlock()).newBlockEntity(pos, iblockdata);
                 } else {
-                    blockEntity = BlockEntity.loadStatic(pos, blockState, compoundTag, this.level.registryAccess());
+                    tileentity = BlockEntity.loadStatic(pos, iblockdata, nbttagcompound, this.level.registryAccess());
                 }
 
-                if (blockEntity != null) {
-                    chunkAccess.setBlockEntity(blockEntity);
-                    return blockEntity;
+                if (tileentity != null) {
+                    ichunkaccess.setBlockEntity(tileentity);
+                    return tileentity;
                 }
             }
 
-            if (blockState.hasBlockEntity()) {
-                LOGGER.warn("Tried to access a block entity before it was created. {}", pos);
+            if (iblockdata.hasBlockEntity()) {
+                WorldGenRegion.LOGGER.warn("Tried to access a block entity before it was created. {}", pos);
             }
 
             return null;
         }
     }
 
+    private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls
     @Override
     public boolean ensureCanWrite(BlockPos pos) {
         int i = SectionPos.blockToSectionCoord(pos.getX());
         int j = SectionPos.blockToSectionCoord(pos.getZ());
-        ChunkPos chunkPos = this.getCenter();
-        int k = Math.abs(chunkPos.x - i);
-        int l = Math.abs(chunkPos.z - j);
+        ChunkPos chunkcoordintpair = this.getCenter();
+        int k = Math.abs(chunkcoordintpair.x - i);
+        int l = Math.abs(chunkcoordintpair.z - j);
+
         if (k <= this.generatingStep.blockStateWriteRadius() && l <= this.generatingStep.blockStateWriteRadius()) {
             if (this.center.isUpgrading()) {
-                LevelHeightAccessor levelHeightAccessor = this.center.getHeightAccessorForGeneration();
-                if (pos.getY() < levelHeightAccessor.getMinBuildHeight() || pos.getY() >= levelHeightAccessor.getMaxBuildHeight()) {
+                LevelHeightAccessor levelheightaccessor = this.center.getHeightAccessorForGeneration();
+
+                if (pos.getY() < levelheightaccessor.getMinBuildHeight() || pos.getY() >= levelheightaccessor.getMaxBuildHeight()) {
                     return false;
                 }
             }
 
             return true;
         } else {
-            Util.logAndPauseIfInIde(
-                "Detected setBlock in a far chunk ["
-                    + i
-                    + ", "
-                    + j
-                    + "], pos: "
-                    + pos
-                    + ", status: "
-                    + this.generatingStep.targetStatus()
-                    + (this.currentlyGenerating == null ? "" : ", currently generating: " + this.currentlyGenerating.get())
-            );
+            // Paper start - Buffer OOB setBlock calls
+            if (!hasSetFarWarned) {
+            Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get()));
+                hasSetFarWarned = true;
+                if (this.getServer() != null && this.getServer().isDebugging()) {
+                    io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call");
+                }
+            }
+            // Paper end - Buffer OOB setBlock calls
             return false;
         }
     }
@@ -277,30 +353,44 @@
         if (!this.ensureCanWrite(pos)) {
             return false;
         } else {
-            ChunkAccess chunkAccess = this.getChunk(pos);
-            BlockState blockState = chunkAccess.setBlockState(pos, state, false);
-            if (blockState != null) {
-                this.level.onBlockStateChange(pos, blockState, state);
+            ChunkAccess ichunkaccess = this.getChunk(pos);
+            BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper
+
+            if (iblockdata1 != null) {
+                this.level.onBlockStateChange(pos, iblockdata1, state);
             }
 
             if (state.hasBlockEntity()) {
-                if (chunkAccess.getPersistedStatus().getChunkType() == ChunkType.LEVELCHUNK) {
-                    BlockEntity blockEntity = ((EntityBlock)state.getBlock()).newBlockEntity(pos, state);
-                    if (blockEntity != null) {
-                        chunkAccess.setBlockEntity(blockEntity);
+                if (ichunkaccess.getPersistedStatus().getChunkType() == ChunkType.LEVELCHUNK) {
+                    BlockEntity tileentity = ((EntityBlock) state.getBlock()).newBlockEntity(pos, state);
+
+                    if (tileentity != null) {
+                        ichunkaccess.setBlockEntity(tileentity);
                     } else {
-                        chunkAccess.removeBlockEntity(pos);
+                        ichunkaccess.removeBlockEntity(pos);
                     }
                 } else {
-                    CompoundTag compoundTag = new CompoundTag();
-                    compoundTag.putInt("x", pos.getX());
-                    compoundTag.putInt("y", pos.getY());
-                    compoundTag.putInt("z", pos.getZ());
-                    compoundTag.putString("id", "DUMMY");
-                    chunkAccess.setBlockEntityNbt(compoundTag);
+                    // Paper start - Clear block entity before setting up a DUMMY block entity
+                    // The concept of removing a block entity when the block itself changes is generally lifted
+                    // from LevelChunk#setBlockState.
+                    // It is however to note that this may only run if the block actually changes.
+                    // Otherwise a chest block entity generated by a structure template that is later "updated" to
+                    // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750)
+                    // This logic is *also* found in LevelChunk#setBlockState.
+                    if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) {
+                        ichunkaccess.removeBlockEntity(pos);
+                    }
+                    // Paper end - Clear block entity before setting up a DUMMY block entity
+                    CompoundTag nbttagcompound = new CompoundTag();
+
+                    nbttagcompound.putInt("x", pos.getX());
+                    nbttagcompound.putInt("y", pos.getY());
+                    nbttagcompound.putInt("z", pos.getZ());
+                    nbttagcompound.putString("id", "DUMMY");
+                    ichunkaccess.setBlockEntityNbt(nbttagcompound);
                 }
-            } else if (blockState != null && blockState.hasBlockEntity()) {
-                chunkAccess.removeBlockEntity(pos);
+            } else if (iblockdata1 != null && iblockdata1.hasBlockEntity()) {
+                ichunkaccess.removeBlockEntity(pos);
             }
 
             if (state.hasPostProcess(this, pos)) {
@@ -317,8 +407,16 @@
 
     @Override
     public boolean addFreshEntity(Entity entity) {
+        // CraftBukkit start
+        return this.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+    }
+
+    @Override
+    public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+        // CraftBukkit end
         int i = SectionPos.blockToSectionCoord(entity.getBlockX());
         int j = SectionPos.blockToSectionCoord(entity.getBlockZ());
+
         this.getChunk(i, j).addEntity(entity);
         return true;
     }
@@ -338,6 +436,7 @@
         return false;
     }
 
+    /** @deprecated */
     @Deprecated
     @Override
     public ServerLevel getLevel() {
@@ -410,20 +509,16 @@
     }
 
     @Override
-    public void playSound(@Nullable Player source, BlockPos pos, SoundEvent sound, SoundSource category, float volume, float pitch) {
-    }
+    public void playSound(@Nullable Player source, BlockPos pos, SoundEvent sound, SoundSource category, float volume, float pitch) {}
 
     @Override
-    public void addParticle(ParticleOptions parameters, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
-    }
+    public void addParticle(ParticleOptions parameters, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {}
 
     @Override
-    public void levelEvent(@Nullable Player player, int eventId, BlockPos pos, int data) {
-    }
+    public void levelEvent(@Nullable Player player, int eventId, BlockPos pos, int data) {}
 
     @Override
-    public void gameEvent(Holder<GameEvent> event, Vec3 emitterPos, GameEvent.Context emitter) {
-    }
+    public void gameEvent(Holder<GameEvent> event, Vec3 emitterPos, GameEvent.Context emitter) {}
 
     @Override
     public DimensionType dimensionType() {
