--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -61,18 +_,14 @@
     public final ServerChunkCache.MainThreadExecutor mainThreadProcessor;
     public final ChunkMap chunkMap;
     private final DimensionDataStorage dataStorage;
-    private long lastInhabitedUpdate;
+    //private long lastInhabitedUpdate; // Folia - region threading
     public boolean spawnEnemies = true;
     public boolean spawnFriendlies = true;
     private static final int CACHE_SIZE = 4;
     private final long[] lastChunkPos = new long[4];
     private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4];
     private final ChunkAccess[] lastChunk = new ChunkAccess[4];
-    private final List<LevelChunk> tickingChunks = new ArrayList<>();
-    private final Set<ChunkHolder> chunkHoldersToBroadcast = new ReferenceOpenHashSet<>();
-    @Nullable
-    @VisibleForDebug
-    private NaturalSpawner.SpawnState lastSpawnState;
+    // Folia - moved to regionised world data
     // Paper start
     private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
     public int getFullChunksCount() {
@@ -98,6 +_,11 @@
     }
 
     private ChunkAccess syncLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus) {
+        // Folia start - region threading
+        if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) {
+            ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, chunkX, chunkZ, "Cannot asynchronously load chunks");
+        }
+        // Folia end - region threading
         final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler();
         final CompletableFuture<ChunkAccess> completable = new CompletableFuture<>();
         chunkTaskScheduler.scheduleChunkLoad(
@@ -355,6 +_,7 @@
     }
 
     public CompletableFuture<ChunkResult<ChunkAccess>> getChunkFuture(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) {
+        if (true) throw new UnsupportedOperationException(); // Folia - region threading
         boolean flag = Thread.currentThread() == this.mainThread;
         CompletableFuture<ChunkResult<ChunkAccess>> chunkFutureMainThread;
         if (flag) {
@@ -502,14 +_,15 @@
     }
 
     private void tickChunks() {
-        long gameTime = this.level.getGameTime();
-        long l = gameTime - this.lastInhabitedUpdate;
-        this.lastInhabitedUpdate = gameTime;
+        io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData(); // Folia - region threading
+        //long gameTime = this.level.getGameTime(); // Folia - region threading
+        long l = 1L; // Folia - region threading
+        //this.lastInhabitedUpdate = gameTime; // Folia - region threading
         if (!this.level.isDebug()) {
             ProfilerFiller profilerFiller = Profiler.get();
             profilerFiller.push("pollingChunks");
             if (this.level.tickRateManager().runsNormally()) {
-                List<LevelChunk> list = this.tickingChunks;
+                List<LevelChunk> list = regionizedWorldData.temporaryChunkTickList; // Folia - region threading
 
                 try {
                     profilerFiller.push("filteringTickingChunks");
@@ -532,23 +_,24 @@
     }
 
     private void broadcastChangedChunks(ProfilerFiller profiler) {
+        io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData(); // Folia - region threading
         profiler.push("broadcast");
 
-        for (ChunkHolder chunkHolder : this.chunkHoldersToBroadcast) {
+        for (ChunkHolder chunkHolder : regionizedWorldData.chunkHoldersToBroadcast) { // Folia - region threading - note: do not need to thread check, as getChunkToSend is only non-null when the chunkholder is loaded
             LevelChunk tickingChunk = chunkHolder.getChunkToSend(); // Paper - rewrite chunk system
             if (tickingChunk != null) {
                 chunkHolder.broadcastChanges(tickingChunk);
             }
         }
 
-        this.chunkHoldersToBroadcast.clear();
+        regionizedWorldData.chunkHoldersToBroadcast.clear(); // Folia - region threading
         profiler.pop();
     }
 
     private void collectTickingChunks(List<LevelChunk> output) {
         // Paper start - chunk tick iteration optimisation
         final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> tickingChunks =
-            ((ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
+            this.level.getCurrentWorldData().getEntityTickingChunks(); // Folia - region threading
 
         final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
         final int size = tickingChunks.size();
@@ -569,13 +_,14 @@
     }
 
     private void tickChunks(ProfilerFiller profiler, long timeInhabited, List<LevelChunk> chunks) {
+        io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.level.getCurrentWorldData(); // Folia - region threading
         profiler.popPush("naturalSpawnCount");
         int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
         // Paper start - Optional per player mob spawns
         NaturalSpawner.SpawnState spawnState;
         if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled
             // re-set mob counts
-            for (ServerPlayer player : this.level.players) {
+            for (ServerPlayer player : this.level.getLocalPlayers()) { // Folia - region threading
                 // Paper start - per player mob spawning backoff
                 for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) {
                     player.mobCounts[ii] = 0;
@@ -588,26 +_,26 @@
                 }
                 // Paper end - per player mob spawning backoff
             }
-            spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true);
+            spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
         } else {
-            spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+            spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); // Folia - region threading - note: function only cares about loaded entities, doesn't need all
         }
         // Paper end - Optional per player mob spawns
-        this.lastSpawnState = spawnState;
+        regionizedWorldData.lastSpawnState = spawnState; // Folia - region threading
         profiler.popPush("spawnAndTick");
-        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+        boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.getLocalPlayers().isEmpty(); // CraftBukkit // Folia - region threading
         int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
         List<MobCategory> filteredSpawningCategories;
         if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
             // Paper start - PlayerNaturallySpawnCreaturesEvent
-            for (ServerPlayer entityPlayer : this.level.players()) {
+            for (ServerPlayer entityPlayer : this.level.getLocalPlayers()) { // Folia - region threading
                 int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
                 chunkRange = Math.min(chunkRange, 8);
                 entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
                 entityPlayer.playerNaturallySpawnedEvent.callEvent();
             }
             // Paper end - PlayerNaturallySpawnCreaturesEvent
-            boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
+            boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getRedstoneGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit // Folia - region threading
             filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit
         } else {
             filteredSpawningCategories = List.of();
@@ -673,18 +_,23 @@
         int sectionPosZ = SectionPos.blockToSectionCoord(pos.getZ());
         ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(ChunkPos.asLong(sectionPosX, sectionPosZ));
         if (visibleChunkIfPresent != null && visibleChunkIfPresent.blockChanged(pos)) {
-            this.chunkHoldersToBroadcast.add(visibleChunkIfPresent);
+            this.level.getCurrentWorldData().chunkHoldersToBroadcast.add(visibleChunkIfPresent); // Folia - region threading
         }
     }
 
     @Override
     public void onLightUpdate(LightLayer type, SectionPos pos) {
-        this.mainThreadProcessor.execute(() -> {
+        Runnable run = () -> { // Folia - region threading
             ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(pos.chunk().toLong());
             if (visibleChunkIfPresent != null && visibleChunkIfPresent.sectionLightChanged(type, pos.y())) {
-                this.chunkHoldersToBroadcast.add(visibleChunkIfPresent);
+                this.level.getCurrentWorldData().chunkHoldersToBroadcast.add(visibleChunkIfPresent); // Folia - region threading
             }
-        });
+        }; // Folia - region threading
+        // Folia start - region threading
+        io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(
+                this.level, pos.getX(), pos.getZ(), run
+        );
+        // Folia end - region threading
     }
 
     public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
@@ -766,7 +_,8 @@
     @Nullable
     @VisibleForDebug
     public NaturalSpawner.SpawnState getLastSpawnState() {
-        return this.lastSpawnState;
+        io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading
+        return worldData == null ? null : worldData.lastSpawnState; // Folia - region threading
     }
 
     public void removeTicketsOnClosing() {
@@ -775,7 +_,7 @@
 
     public void onChunkReadyToSend(ChunkHolder chunkHolder) {
         if (chunkHolder.hasChangesToBroadcast()) {
-            this.chunkHoldersToBroadcast.add(chunkHolder);
+            throw new UnsupportedOperationException(); // Folia - region threading
         }
     }
 
@@ -812,20 +_,76 @@
             return ServerChunkCache.this.mainThread;
         }
 
+        // Folia start - region threading
+        @Override
+        public <V> CompletableFuture<V> submit(Supplier<V> task) {
+            if (true) {
+                throw new UnsupportedOperationException();
+            }
+            return super.submit(task);
+        }
+
+        @Override
+        public CompletableFuture<Void> submit(Runnable task) {
+            if (true) {
+                throw new UnsupportedOperationException();
+            }
+            return super.submit(task);
+        }
+
+        @Override
+        public void schedule(Runnable runnable) {
+            if (true) {
+                throw new UnsupportedOperationException();
+            }
+            super.schedule(runnable);
+        }
+
+        @Override
+        public void executeBlocking(Runnable runnable) {
+            if (true) {
+                throw new UnsupportedOperationException();
+            }
+            super.executeBlocking(runnable);
+        }
+
+        @Override
+        public void execute(Runnable runnable) {
+            if (true) {
+                throw new UnsupportedOperationException();
+            }
+            super.execute(runnable);
+        }
+
+        @Override
+        public void executeIfPossible(Runnable runnable) {
+            if (true) {
+                throw new UnsupportedOperationException();
+            }
+            super.executeIfPossible(runnable);
+        }
+        // Folia end - region threading
+
         @Override
         protected void doRunTask(Runnable task) {
+            if (true) throw new UnsupportedOperationException(); // Folia - region threading
             Profiler.get().incrementCounter("runTask");
             super.doRunTask(task);
         }
 
         @Override
         public boolean pollTask() {
+            // Folia start - region threading
+            if (ServerChunkCache.this.level != io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData().world) {
+                throw new IllegalStateException("Polling tasks from non-owned region");
+            }
+            // Folia end - region threading
             // Paper start - rewrite chunk system
             final ServerChunkCache serverChunkCache = ServerChunkCache.this;
             if (serverChunkCache.runDistanceManagerUpdates()) {
                 return true;
             } else {
-                return super.pollTask() | ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)serverChunkCache.level).moonrise$getChunkTaskScheduler().executeMainThreadTask();
+                return io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegion().getData().getTaskQueueData().executeChunkTask(); // Folia - region threading
             }
             // Paper end - rewrite chunk system
         }
