--- a/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+++ b/net/minecraft/world/level/levelgen/structure/StructureCheck.java
@@ -43,17 +43,110 @@
     private final Registry<Biome> biomes;
     private final Registry<Structure> structureConfigs;
     private final StructureTemplateManager structureTemplateManager;
-    private final ResourceKey<Level> dimension;
+    private final ResourceKey<net.minecraft.world.level.dimension.LevelStem> dimension; // Paper - fix missing CB diff
     private final ChunkGenerator chunkGenerator;
     private final RandomState randomState;
     private final LevelHeightAccessor heightAccessor;
     private final BiomeSource biomeSource;
     private final long seed;
     private final DataFixer fixerUpper;
-    private final Long2ObjectMap<Object2IntMap<Structure>> loadedChunks = new Long2ObjectOpenHashMap<>();
-    private final Map<Structure, Long2BooleanMap> featureChecks = new HashMap<>();
+    // Paper start - rewrite chunk system - synchronise this class
+    // additionally, make sure to purge entries from the maps so it does not leak memory
+    private static final int CHUNK_TOTAL_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups
+    private static final int PER_FEATURE_CHECK_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups
 
-    public StructureCheck(ChunkScanAccess chunkIoWorker, RegistryAccess registryManager, StructureTemplateManager structureTemplateManager, ResourceKey<Level> worldKey, ChunkGenerator chunkGenerator, RandomState noiseConfig, LevelHeightAccessor world, BiomeSource biomeSource, long seed, DataFixer dataFixer) {
+    private final SynchronisedLong2ObjectMap<Object2IntMap<Structure>> loadedChunksSafe = new SynchronisedLong2ObjectMap<>(CHUNK_TOTAL_LIMIT);
+    private final java.util.concurrent.ConcurrentHashMap<Structure, SynchronisedLong2BooleanMap> featureChecksSafe = new java.util.concurrent.ConcurrentHashMap<>();
+
+    private static final class SynchronisedLong2ObjectMap<V> {
+        private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<V> map = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>();
+        private final int limit;
+
+        public SynchronisedLong2ObjectMap(final int limit) {
+            this.limit = limit;
+        }
+
+        // must hold lock on map
+        private void purgeEntries() {
+            while (this.map.size() > this.limit) {
+                this.map.removeLast();
+            }
+        }
+
+        public V get(final long key) {
+            synchronized (this.map) {
+                return this.map.getAndMoveToFirst(key);
+            }
+        }
+
+        public V put(final long key, final V value) {
+            synchronized (this.map) {
+                final V ret = this.map.putAndMoveToFirst(key, value);
+                this.purgeEntries();
+                return ret;
+            }
+        }
+
+        public V compute(final long key, final java.util.function.BiFunction<? super Long, ? super V, ? extends V> remappingFunction) {
+            synchronized (this.map) {
+                // first, compute the value - if one is added, it will be at the last entry
+                this.map.compute(key, remappingFunction);
+                // move the entry to first, just in case it was added at last
+                final V ret = this.map.getAndMoveToFirst(key);
+                // now purge the last entries
+                this.purgeEntries();
+
+                return ret;
+            }
+        }
+    }
+
+    private static final class SynchronisedLong2BooleanMap {
+        private final it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap();
+        private final int limit;
+
+        public SynchronisedLong2BooleanMap(final int limit) {
+            this.limit = limit;
+        }
+
+        // must hold lock on map
+        private void purgeEntries() {
+            while (this.map.size() > this.limit) {
+                this.map.removeLastBoolean();
+            }
+        }
+
+        public boolean remove(final long key) {
+            synchronized (this.map) {
+                return this.map.remove(key);
+            }
+        }
+
+        // note:
+        public boolean getOrCompute(final long key, final it.unimi.dsi.fastutil.longs.Long2BooleanFunction ifAbsent) {
+            synchronized (this.map) {
+                if (this.map.containsKey(key)) {
+                    return this.map.getAndMoveToFirst(key);
+                }
+            }
+
+            final boolean put = ifAbsent.get(key);
+
+            synchronized (this.map) {
+                if (this.map.containsKey(key)) {
+                    return this.map.getAndMoveToFirst(key);
+                }
+                this.map.putAndMoveToFirst(key, put);
+
+                this.purgeEntries();
+
+                return put;
+            }
+        }
+    }
+    // Paper end - rewrite chunk system - synchronise this class
+
+    public StructureCheck(ChunkScanAccess chunkIoWorker, RegistryAccess registryManager, StructureTemplateManager structureTemplateManager, ResourceKey<net.minecraft.world.level.dimension.LevelStem> worldKey, ChunkGenerator chunkGenerator, RandomState noiseConfig, LevelHeightAccessor world, BiomeSource biomeSource, long seed, DataFixer dataFixer) { // Paper - fix missing CB diff
         this.storageAccess = chunkIoWorker;
         this.registryAccess = registryManager;
         this.structureTemplateManager = structureTemplateManager;
@@ -70,7 +163,7 @@
 
     public StructureCheckResult checkStart(ChunkPos pos, Structure type, boolean skipReferencedStructures) {
         long l = pos.toLong();
-        Object2IntMap<Structure> object2IntMap = this.loadedChunks.get(l);
+        Object2IntMap<Structure> object2IntMap = this.loadedChunksSafe.get(l); // Paper - rewrite chunk system - synchronise this class
         if (object2IntMap != null) {
             return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures);
         } else {
@@ -78,9 +171,9 @@
             if (structureCheckResult != null) {
                 return structureCheckResult;
             } else {
-                boolean bl = this.featureChecks.computeIfAbsent(type, (structure2) -> {
-                    return new Long2BooleanOpenHashMap();
-                }).computeIfAbsent(l, (chunkPos) -> {
+                boolean bl = this.featureChecksSafe.computeIfAbsent(type, (structure2) -> { // Paper - rewrite chunk system - synchronise this class
+                    return new SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT); // Paper - rewrite chunk system - synchronise this class
+                }).getOrCompute(l, (chunkPos) -> { // Paper - rewrite chunk system - synchronise this class
                     return this.canCreateStructure(pos, type);
                 });
                 return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED;
@@ -115,7 +208,7 @@
 
                 CompoundTag compoundTag2;
                 try {
-                    compoundTag2 = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, compoundTag, i);
+                    compoundTag2 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
                 } catch (Exception var12) {
                     LOGGER.warn("Failed to partially datafix chunk {}", pos, var12);
                     return StructureCheckResult.CHUNK_LOAD_NEEDED;
@@ -193,17 +286,26 @@
     }
 
     private void storeFullResults(long pos, Object2IntMap<Structure> referencesByStructure) {
-        this.loadedChunks.put(pos, deduplicateEmptyMap(referencesByStructure));
-        this.featureChecks.values().forEach((generationPossibilityByChunkPos) -> {
-            generationPossibilityByChunkPos.remove(pos);
-        });
+        // Paper start - rewrite chunk system - synchronise this class
+        this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure));
+        // once we insert into loadedChunks, we don't really need to be very careful about removing everything
+        // from this map, as everything that checks this map uses loadedChunks first
+        // so, one way or another it's a race condition that doesn't matter
+        for (SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) {
+            value.remove(pos);
+        }
+        // Paper end - rewrite chunk system - synchronise this class
     }
 
     public void incrementReference(ChunkPos pos, Structure structure) {
-        this.loadedChunks.compute(pos.toLong(), (posx, referencesByStructure) -> {
-            if (referencesByStructure == null || referencesByStructure.isEmpty()) {
+        this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Paper start - rewrite chunk system - synchronise this class
+            // make this COW so that we do not mutate state that may be currently in use
+            if (referencesByStructure == null) {
                 referencesByStructure = new Object2IntOpenHashMap<>();
+            } else {
+                referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap<Structure> fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure);
             }
+            // Paper end - rewrite chunk system - synchronise this class
 
             referencesByStructure.computeInt(structure, (feature, references) -> {
                 return references == null ? 1 : references + 1;
