--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -179,42 +_,40 @@
     private static final Logger LOGGER = LogUtils.getLogger();
     private static final int EMPTY_TIME_NO_TICK = 300;
     private static final int MAX_SCHEDULED_TICKS_PER_TICK = 65536;
-    final List<ServerPlayer> players = Lists.newArrayList();
+    final List<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArrayList<>(); // Folia - region threading
     public final ServerChunkCache chunkSource;
     private final MinecraftServer server;
     public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type
     private int lastSpawnChunkRadius;
-    final EntityTickList entityTickList = new EntityTickList();
+    //final EntityTickList entityTickList = new EntityTickList(); // Folia - region threading
     // Paper - rewrite chunk system
     private final GameEventDispatcher gameEventDispatcher;
     public boolean noSave;
     private final SleepStatus sleepStatus;
     private int emptyTime;
     private final PortalForcer portalForcer;
-    private final LevelTicks<Block> blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded);
-    private final LevelTicks<Fluid> fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded);
-    private final PathTypeCache pathTypesByPosCache = new PathTypeCache();
-    final Set<Mob> navigatingMobs = new ObjectOpenHashSet<>();
+    //private final LevelTicks<Block> blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); // Folia - region threading
+    //private final LevelTicks<Fluid> fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded); // Folia - region threading
+    //private final PathTypeCache pathTypesByPosCache = new PathTypeCache(); // Folia - region threading
+    //final Set<Mob> navigatingMobs = new ObjectOpenHashSet<>(); // Folia - region threading
     volatile boolean isUpdatingNavigations;
     protected final Raids raids;
-    private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>();
-    private final List<BlockEventData> blockEventsToReschedule = new ArrayList<>(64);
-    private boolean handlingTick;
+    //private final ObjectLinkedOpenHashSet<BlockEventData> blockEvents = new ObjectLinkedOpenHashSet<>(); // Folia - region threading
+    //private final List<BlockEventData> blockEventsToReschedule = new ArrayList<>(64); // Folia - region threading
+    //private boolean handlingTick; // Folia - region threading
     private final List<CustomSpawner> customSpawners;
     @Nullable
     private EndDragonFight dragonFight;
-    final Int2ObjectMap<EnderDragonPart> dragonParts = new Int2ObjectOpenHashMap<>();
+    final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.entity.boss.EnderDragonPart> dragonParts = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); // Folia - region threading
     private final StructureManager structureManager;
     private final StructureCheck structureCheck;
-    private final boolean tickTime;
+    public final boolean tickTime; // Folia - region threading
     private final RandomSequences randomSequences;
 
     // CraftBukkit start
     public final LevelStorageSource.LevelStorageAccess levelStorageAccess;
     public final UUID uuid;
-    public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
-    public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
-    private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
+    // Folia - region threading - move to regionised world data
 
     public LevelChunk getChunkIfLoaded(int x, int z) {
         return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
@@ -242,6 +_,13 @@
         int minChunkZ = minBlockZ >> 4;
         int maxChunkZ = maxBlockZ >> 4;
 
+        // Folia start - region threading
+        // don't let players move into regions not owned
+        if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, minChunkX, minChunkZ, maxChunkX, maxChunkZ)) {
+            return false;
+        }
+        // Folia end - region threading
+
         ServerChunkCache chunkProvider = this.getChunkSource();
 
         for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
@@ -297,11 +_,7 @@
     private final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler;
     private long lastMidTickFailure;
     private long tickedBlocksOrFluids;
-    private final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = new ca.spottedleaf.moonrise.common.misc.NearbyPlayers((ServerLevel)(Object)this);
-    private static final ServerChunkCache.ChunkAndHolder[] EMPTY_CHUNK_AND_HOLDERS = new ServerChunkCache.ChunkAndHolder[0];
-    private final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> loadedChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);
-    private final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> tickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);
-    private final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> entityTickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);
+    // Folia - region threading - move to regionized data
 
     @Override
     public final LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ) {
@@ -359,7 +_,7 @@
 
     @Override
     public final int moonrise$getRegionChunkShift() {
-        return io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift();
+        return this.regioniser.sectionChunkShift; // Folia - region threading
     }
 
     @Override
@@ -460,22 +_,22 @@
 
     @Override
     public final ca.spottedleaf.moonrise.common.misc.NearbyPlayers moonrise$getNearbyPlayers() {
-        return this.nearbyPlayers;
+        return this.getCurrentWorldData().getNearbyPlayers(); // Folia - region threading
     }
 
     @Override
     public final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> moonrise$getLoadedChunks() {
-        return this.loadedChunks;
+        return this.getCurrentWorldData().getChunks(); // Folia - region threading
     }
 
     @Override
     public final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> moonrise$getTickingChunks() {
-        return this.tickingChunks;
+        return this.getCurrentWorldData().getTickingChunks(); // Folia - region threading
     }
 
     @Override
     public final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> moonrise$getEntityTickingChunks() {
-        return this.entityTickingChunks;
+        return this.getCurrentWorldData().getEntityTickingChunks(); // Folia - region threading
     }
 
     @Override
@@ -495,80 +_,85 @@
     // Paper end - rewrite chunk system
     // Paper start - chunk tick iteration
     private static final ServerChunkCache.ChunkAndHolder[] EMPTY_PLAYER_CHUNK_HOLDERS = new ServerChunkCache.ChunkAndHolder[0];
-    private final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> playerTickingChunks = new ca.spottedleaf.moonrise.common.list.ReferenceList<>(EMPTY_PLAYER_CHUNK_HOLDERS);
-    private final it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap playerTickingRequests = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap();
+    // Folia - region threading
 
     @Override
     public final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerChunkCache.ChunkAndHolder> moonrise$getPlayerTickingChunks() {
-        return this.playerTickingChunks;
+        throw new UnsupportedOperationException(); // Folia - region threading
     }
 
     @Override
     public final void moonrise$markChunkForPlayerTicking(final LevelChunk chunk) {
-        final ChunkPos pos = chunk.getPos();
-        if (!this.playerTickingRequests.containsKey(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(pos))) {
-            return;
-        }
-
-        this.playerTickingChunks.add(((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder());
+        // Folia - region threading
     }
 
     @Override
     public final void moonrise$removeChunkForPlayerTicking(final LevelChunk chunk) {
-        this.playerTickingChunks.remove(((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder());
+        // Folia - region threading
     }
 
     @Override
     public final void moonrise$addPlayerTickingRequest(final int chunkX, final int chunkZ) {
-        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot add ticking request async");
-
-        final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ);
-
-        if (this.playerTickingRequests.addTo(chunkKey, 1) != 0) {
-            // already added
-            return;
-        }
-
-        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler()
-            .chunkHolderManager.getChunkHolder(chunkKey);
-
-        if (chunkHolder == null || !chunkHolder.isTickingReady()) {
-            return;
-        }
-
-        this.playerTickingChunks.add(
-            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder()
-        );
+        // Folia - region threading
     }
 
     @Override
     public final void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ) {
-        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot remove ticking request async");
-
-        final long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ);
-        final int val = this.playerTickingRequests.addTo(chunkKey, -1);
-
-        if (val <= 0) {
-            throw new IllegalStateException("Negative counter");
-        }
-
-        if (val != 1) {
-            // still has at least one request
-            return;
-        }
-
-        final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler()
-            .chunkHolderManager.getChunkHolder(chunkKey);
-
-        if (chunkHolder == null || !chunkHolder.isTickingReady()) {
-            return;
-        }
-
-        this.playerTickingChunks.remove(
-            ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder()
-        );
+        // Folia - region threading
     }
     // Paper end - chunk tick iteration
+    // Folia start - region threading
+    public final io.papermc.paper.threadedregions.TickRegions tickRegions = new io.papermc.paper.threadedregions.TickRegions();
+    public final io.papermc.paper.threadedregions.ThreadedRegionizer<io.papermc.paper.threadedregions.TickRegions.TickRegionData, io.papermc.paper.threadedregions.TickRegions.TickRegionSectionData> regioniser;
+    {
+        this.regioniser = new io.papermc.paper.threadedregions.ThreadedRegionizer<>(
+                (int)Math.max(1L, (8L * 16L * 16L) / (1L << (2 * (io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift())))),
+                (1.0 / 6.0),
+                Math.max(1, 8 / (1 << io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift())),
+                1,
+                io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift(),
+                this,
+                this.tickRegions
+        );
+    }
+    public final io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData taskQueueRegionData = new io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData(this);
+    public static final int WORLD_INIT_NOT_CHECKED = 0;
+    public static final int WORLD_INIT_CHECKING = 1;
+    public static final int WORLD_INIT_CHECKED = 2;
+    public final java.util.concurrent.atomic.AtomicInteger checkInitialised = new java.util.concurrent.atomic.AtomicInteger(WORLD_INIT_NOT_CHECKED);
+    public ChunkPos randomSpawnSelection;
+
+    public static final record PendingTeleport(Entity.EntityTreeNode rootVehicle, Vec3 to) {}
+    private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<PendingTeleport> pendingTeleports = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
+
+    public void pushPendingTeleport(final PendingTeleport teleport) {
+        synchronized (this.pendingTeleports) {
+            this.pendingTeleports.add(teleport);
+        }
+    }
+
+    public boolean removePendingTeleport(final PendingTeleport teleport) {
+        synchronized (this.pendingTeleports) {
+            return this.pendingTeleports.remove(teleport);
+        }
+    }
+
+    public List<PendingTeleport> removeAllRegionTeleports() {
+        final List<PendingTeleport> ret = new ArrayList<>();
+
+        synchronized (this.pendingTeleports) {
+            for (final java.util.Iterator<net.minecraft.server.level.ServerLevel.PendingTeleport> iterator = this.pendingTeleports.iterator(); iterator.hasNext(); ) {
+                final PendingTeleport pendingTeleport = iterator.next();
+                if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, pendingTeleport.to())) {
+                    ret.add(pendingTeleport);
+                    iterator.remove();
+                }
+            }
+        }
+
+        return ret;
+    }
+    // Folia end - region threading
 
     public ServerLevel(
         MinecraftServer server,
@@ -633,7 +_,7 @@
         );
         this.chunkSource.getGeneratorState().ensureStructuresGenerated();
         this.portalForcer = new PortalForcer(this);
-        this.updateSkyBrightness();
+        //this.updateSkyBrightness(); // Folia - region threading - delay until first tick
         this.prepareWeather();
         this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize());
         this.raids = this.getDataStorage().computeIfAbsent(Raids.factory(this), Raids.getFileId(this.dimensionTypeRegistration()));
@@ -681,7 +_,14 @@
         this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler);
         // Paper end - rewrite chunk system
         this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
-    }
+        this.updateTickData(); // Folia - region threading - make sure it is initialised before ticked
+    }
+
+    // Folia start - region threading
+    public void updateTickData() {
+        this.tickData = new io.papermc.paper.threadedregions.RegionizedServer.WorldLevelData(this, this.serverLevelData.getGameTime(), this.serverLevelData.getDayTime());
+    }
+    // Folia end - region threading
 
     // Paper start
     @Override
@@ -709,61 +_,39 @@
         return this.getChunkSource().getGenerator().getBiomeSource().getNoiseBiome(x, y, z, this.getChunkSource().randomState().sampler());
     }
 
+    @Override // Folia - region threading
     public StructureManager structureManager() {
         return this.structureManager;
     }
 
-    public void tick(BooleanSupplier hasTimeLeft) {
+    public void tick(BooleanSupplier hasTimeLeft, io.papermc.paper.threadedregions.TickRegions.TickRegionData region) { // Folia - regionised ticking
+        final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - regionised ticking
         ProfilerFiller profilerFiller = Profiler.get();
-        this.handlingTick = true;
+        regionizedWorldData.setHandlingTick(true); // Folia - regionised ticking
         TickRateManager tickRateManager = this.tickRateManager();
         boolean runsNormally = tickRateManager.runsNormally();
         if (runsNormally) {
             profilerFiller.push("world border");
-            this.getWorldBorder().tick();
+            //this.getWorldBorder().tick(); // Folia - regionised ticking
             profilerFiller.popPush("weather");
-            this.advanceWeatherCycle();
+            //this.advanceWeatherCycle(); // Folia - regionised ticking
             profilerFiller.pop();
         }
 
-        int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
-        if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) {
-            // Paper start - create time skip event - move up calculations
-            final long newDayTime = this.levelData.getDayTime() + 24000L;
-            org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(
-                this.getWorld(),
-                org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP,
-                (newDayTime - newDayTime % 24000L) - this.getDayTime()
-            );
-            // Paper end - create time skip event - move up calculations
-            if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
-                // Paper start - call time skip event if gamerule is enabled
-                // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime
-                // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param
-                if (event.callEvent()) {
-                    this.setDayTime(this.getDayTime() + event.getSkipAmount());
-                }
-                // Paper end - call time skip event if gamerule is enabled
-            }
-
-            if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled
-            if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
-                this.resetWeatherCycle();
-            }
-        }
-
-        this.updateSkyBrightness();
+        this.tickSleep(); // Folia - region threading - move into tickSleep
+
+        //this.updateSkyBrightness(); // Folia - region threading
         if (runsNormally) {
             this.tickTime();
         }
 
         profilerFiller.push("tickPending");
         if (!this.isDebug() && runsNormally) {
-            long l = this.getGameTime();
+            long l = regionizedWorldData.getRedstoneGameTime(); // Folia - region threading
             profilerFiller.push("blockTicks");
-            this.blockTicks.tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks
+            regionizedWorldData.getBlockLevelTicks().tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks // Folia - region ticking
             profilerFiller.popPush("fluidTicks");
-            this.fluidTicks.tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks
+            regionizedWorldData.getFluidLevelTicks().tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks // Folia - region ticking
             profilerFiller.pop();
         }
 
@@ -779,9 +_,9 @@
             this.runBlockEvents();
         }
 
-        this.handlingTick = false;
+        regionizedWorldData.setHandlingTick(false); // Folia - regionised ticking
         profilerFiller.pop();
-        boolean flag = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this
+        boolean flag = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this // Folia - unrestore this, we always need to tick empty worlds
         if (flag) {
             this.resetEmptyTime();
         }
@@ -789,19 +_,29 @@
         if (flag || this.emptyTime++ < 300) {
             profilerFiller.push("entities");
             if (this.dragonFight != null && runsNormally) {
+                if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, this.dragonFight.origin)) { // Folia - region threading
                 profilerFiller.push("dragonFight");
                 this.dragonFight.tick();
                 profilerFiller.pop();
+                } else { // Folia start - region threading
+                    // try to load dragon fight
+                    ChunkPos fightCenter = new ChunkPos(this.dragonFight.origin);
+                    this.chunkSource.addTicketAtLevel(
+                        TicketType.UNKNOWN, fightCenter, ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL,
+                        fightCenter
+                    );
+                } // Folia end - region threading
             }
 
             io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR
-            this.entityTickList
-                .forEach(
+            regionizedWorldData // Folia - regionised ticking
+                .forEachTickingEntity( // Folia - regionised ticking
                     entity -> {
                         if (!entity.isRemoved()) {
                             if (!tickRateManager.isEntityFrozen(entity)) {
                                 profilerFiller.push("checkDespawn");
                                 entity.checkDespawn();
+                                if (entity.isRemoved()) return; // Folia - region threading - if we despawned, DON'T TICK IT!
                                 profilerFiller.pop();
                                 if (true) { // Paper - rewrite chunk system
                                     Entity vehicle = entity.getVehicle();
@@ -830,6 +_,36 @@
         profilerFiller.pop();
     }
 
+    // Folia start - region threading
+    public void tickSleep() {
+        int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
+        if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) {
+            // Paper start - create time skip event - move up calculations
+            final long newDayTime = this.levelData.getDayTime() + 24000L;
+            org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(
+                this.getWorld(),
+                org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP,
+                (newDayTime - newDayTime % 24000L) - this.getDayTime()
+            );
+            // Paper end - create time skip event - move up calculations
+            if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+                // Paper start - call time skip event if gamerule is enabled
+                // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime
+                // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param
+                if (event.callEvent()) {
+                    this.setDayTime(this.getDayTime() + event.getSkipAmount());
+                }
+                // Paper end - call time skip event if gamerule is enabled
+            }
+
+            if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled
+            if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
+                this.resetWeatherCycle();
+            }
+        }
+    }
+    // Folia end - region threading
+
     @Override
     public boolean shouldTickBlocksAt(long chunkPos) {
         // Paper start - rewrite chunk system
@@ -840,12 +_,13 @@
 
     protected void tickTime() {
         if (this.tickTime) {
-            long l = this.levelData.getGameTime() + 1L;
-            this.serverLevelData.setGameTime(l);
+            io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - region threading
+            long l = regionizedWorldData.getRedstoneGameTime() + 1L; // Folia - region threading
+            regionizedWorldData.setRedstoneGameTime(l); // Folia - region threading
             Profiler.get().push("scheduledFunctions");
-            this.serverLevelData.getScheduledEvents().tick(this.server, l);
+            //this.serverLevelData.getScheduledEvents().tick(this.server, l); // Folia - region threading - TODO any way to bring this in?
             Profiler.get().pop();
-            if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+            if (false && this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {  // Folia - region threading
                 this.setDayTime(this.levelData.getDayTime() + 1L);
             }
         }
@@ -863,16 +_,27 @@
 
     private void wakeUpAllPlayers() {
         this.sleepStatus.removeAllSleepers();
-        this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach(player -> player.stopSleepInBed(false, false));
+        // Folia start - region threading
+        this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList()).forEach((ServerPlayer entityplayer) -> {
+                    // Folia start - region threading
+                    entityplayer.getBukkitEntity().taskScheduler.schedule((ServerPlayer player) -> {
+                        if (player.level() != ServerLevel.this || !player.isSleeping()) {
+                            return;
+                        }
+                        player.stopSleepInBed(false, false);
+                    }, null, 1L);
+                }
+        );
+        // Folia end - region threading
     }
 
     // Paper start - optimise random ticking
-    private final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed());
+    private final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource.INSTANCE; // Folia - region threading
 
     private void optimiseRandomTick(final LevelChunk chunk, final int tickSpeed) {
         final LevelChunkSection[] sections = chunk.getSections();
         final int minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection((ServerLevel)(Object)this);
-        final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom;
+        final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = this.simpleRandom; // Folia - region threading
         final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294();
 
         final ChunkPos cpos = chunk.getPos();
@@ -919,7 +_,7 @@
     // Paper end - optimise random ticking
 
     public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
-        final ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom simpleRandom = this.simpleRandom; // Paper - optimise random ticking
+        final io.papermc.paper.threadedregions.util.SimpleThreadLocalRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Folia - region threading
         ChunkPos pos = chunk.getPos();
         boolean isRaining = this.isRaining();
         int minBlockX = pos.getMinBlockX();
@@ -1044,7 +_,7 @@
     }
 
     public boolean isHandlingTick() {
-        return this.handlingTick;
+        return this.getCurrentWorldData().isHandlingTick(); // Folia - regionised ticking
     }
 
     public boolean canSleepThroughNights() {
@@ -1070,6 +_,14 @@
     }
 
     public void updateSleepingPlayerList() {
+        // Folia start - region threading
+        if (!io.papermc.paper.threadedregions.RegionizedServer.isGlobalTickThread()) {
+            io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> {
+                ServerLevel.this.updateSleepingPlayerList();
+            });
+            return;
+        }
+        // Folia end - region threading
         if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) {
             this.announceSleepStatus();
         }
@@ -1080,7 +_,7 @@
         return this.server.getScoreboard();
     }
 
-    private void advanceWeatherCycle() {
+    public void advanceWeatherCycle() { // Folia - region threading - public
         boolean isRaining = this.isRaining();
         if (this.dimensionType().hasSkyLight()) {
             if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE)) {
@@ -1166,7 +_,8 @@
             this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
         }
         */
-        for (ServerPlayer player : this.players) {
+        ServerPlayer[] players = this.players.toArray(new ServerPlayer[0]); // Folia - region threading
+        for (ServerPlayer player : players) { // Folia - region threading
             if (player.level() == this) {
                 player.tickWeather();
             }
@@ -1174,13 +_,13 @@
 
         if (isRaining != this.isRaining()) {
             // Only send weather packets to those affected
-            for (ServerPlayer player : this.players) {
+            for (ServerPlayer player : players) { // Folia - region threading
                 if (player.level() == this) {
                     player.setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false);
                 }
             }
         }
-        for (ServerPlayer player : this.players) {
+        for (ServerPlayer player : players) { // Folia - region threading
             if (player.level() == this) {
                 player.updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
             }
@@ -1241,13 +_,10 @@
 
     // Paper start - log detailed entity tick information
     // TODO replace with varhandle
-    static final java.util.concurrent.atomic.AtomicReference<Entity> currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>();
+    // Folia - region threading
 
     public static List<Entity> getCurrentlyTickingEntities() {
-        Entity ticking = currentlyTickingEntity.get();
-        List<Entity> ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking });
-
-        return ret;
+        throw new UnsupportedOperationException(); // Folia - region threading
     }
     // Paper end - log detailed entity tick information
 
@@ -1255,9 +_,7 @@
         // Paper start - log detailed entity tick information
         ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
         try {
-            if (currentlyTickingEntity.get() == null) {
-                currentlyTickingEntity.lazySet(entity);
-            }
+            // Folia - region threading
             // Paper end - log detailed entity tick information
         entity.setOldPosAndRot();
         ProfilerFiller profilerFiller = Profiler.get();
@@ -1267,7 +_,16 @@
         final boolean isActive = io.papermc.paper.entity.activation.ActivationRange.checkIfActive(entity); // Paper - EAR 2
         if (isActive) { // Paper - EAR 2
         entity.tick();
-        entity.postTick(); // CraftBukkit
+        // Folia start - region threading
+        if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity)) {
+            // removed from region while ticking
+            return;
+        }
+        if (entity.handlePortal()) {
+            // portalled
+            return;
+        }
+        // Folia end - region threading
         } else {entity.inactiveTick();} // Paper - EAR 2
         profilerFiller.pop();
 
@@ -1276,9 +_,7 @@
         }
         // Paper start - log detailed entity tick information
         } finally {
-            if (currentlyTickingEntity.get() == entity) {
-                currentlyTickingEntity.lazySet(null);
-            }
+            // Folia - region threading
         }
         // Paper end - log detailed entity tick information
     }
@@ -1286,7 +_,7 @@
     private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2
         if (passengerEntity.isRemoved() || passengerEntity.getVehicle() != ridingEntity) {
             passengerEntity.stopRiding();
-        } else if (passengerEntity instanceof Player || this.entityTickList.contains(passengerEntity)) {
+        } else if (passengerEntity instanceof Player || this.getCurrentWorldData().hasEntityTickingEntity(passengerEntity)) { // Folia - region threading
             passengerEntity.setOldPosAndRot();
             passengerEntity.tickCount++;
             ProfilerFiller profilerFiller = Profiler.get();
@@ -1295,7 +_,16 @@
             // Paper start - EAR 2
             if (isActive) {
             passengerEntity.rideTick();
-            passengerEntity.postTick(); // CraftBukkit
+            // Folia start - region threading
+            if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(passengerEntity)) {
+                // removed from region while ticking
+                return;
+            }
+            if (passengerEntity.handlePortal()) {
+                // portalled
+                return;
+            }
+            // Folia end - region threading
             } else {
                 passengerEntity.setDeltaMovement(Vec3.ZERO);
                 passengerEntity.inactiveTick();
@@ -1369,19 +_,20 @@
         }
         // Paper end - add close param
 
-        // CraftBukkit start - moved from MinecraftServer.saveChunks
+        // Folia - move into saveLevelData
+    }
+
+    public void saveLevelData(boolean join) { // Folia - public
+        if (this.dragonFight != null) {
+            this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
+        }
+        // Folia start - moved into saveLevelData
         ServerLevel worldserver1 = this;
 
         this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
         this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess()));
         this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
-        // CraftBukkit end
-    }
-
-    private void saveLevelData(boolean join) {
-        if (this.dragonFight != null) {
-            this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
-        }
+        // Folia end - moved into saveLevelData
 
         DimensionDataStorage dataStorage = this.getChunkSource().getDataStorage();
         if (join) {
@@ -1437,6 +_,19 @@
         return list;
     }
 
+    // Folia start - region threading
+    @Nullable
+    public ServerPlayer getRandomLocalPlayer() {
+        List<ServerPlayer> list = this.getLocalPlayers();
+        list = new java.util.ArrayList<>(list);
+        list.removeIf((ServerPlayer player) -> {
+            return !player.isAlive();
+        });
+
+        return list.isEmpty() ? null : (ServerPlayer) list.get(this.random.nextInt(list.size()));
+    }
+    // Folia end - region threading
+
     @Nullable
     public ServerPlayer getRandomPlayer() {
         List<ServerPlayer> players = this.getPlayers(LivingEntity::isAlive);
@@ -1518,8 +_,8 @@
         } else {
             if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added
             // Paper start - capture all item additions to the world
-            if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) {
-                captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity);
+            if (this.getCurrentWorldData().captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { // Folia - region threading
+                this.getCurrentWorldData().captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); // Folia - region threading
                 return true;
             }
             // Paper end - capture all item additions to the world
@@ -1694,13 +_,14 @@
 
     @Override
     public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) {
-        if (this.isUpdatingNavigations) {
+        final io.papermc.paper.threadedregions.RegionizedWorldData regionizedWorldData = this.getCurrentWorldData(); // Folia - region threading
+        if (false && this.isUpdatingNavigations) { // Folia - region threading
             String string = "recursive call to sendBlockUpdated";
             Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated"));
         }
 
         this.getChunkSource().blockChanged(pos);
-        this.pathTypesByPosCache.invalidate(pos);
+        regionizedWorldData.pathTypesByPosCache.invalidate(pos); // Folia - region threading
         if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates
         VoxelShape collisionShape = oldState.getCollisionShape(this, pos);
         VoxelShape collisionShape1 = newState.getCollisionShape(this, pos);
@@ -1708,7 +_,8 @@
             List<PathNavigation> list = new ObjectArrayList<>();
 
             try { // Paper - catch CME see below why
-            for (Mob mob : this.navigatingMobs) {
+            for (java.util.Iterator<Mob> iterator = regionizedWorldData.getNavigatingMobs(); iterator.hasNext();) { // Folia - region threading
+                Mob mob = iterator.next(); // Folia - region threading
                 PathNavigation navigation = mob.getNavigation();
                 if (navigation.shouldRecomputePath(pos)) {
                     list.add(navigation);
@@ -1725,13 +_,13 @@
             // Paper end - catch CME see below why
 
             try {
-                this.isUpdatingNavigations = true;
+                //this.isUpdatingNavigations = true; // Folia - region threading
 
                 for (PathNavigation pathNavigation : list) {
                     pathNavigation.recomputePath();
                 }
             } finally {
-                this.isUpdatingNavigations = false;
+                //this.isUpdatingNavigations = false; // Folia - region threading
             }
         }
         } // Paper - option to disable pathfinding updates
@@ -1739,29 +_,29 @@
 
     @Override
     public void updateNeighborsAt(BlockPos pos, Block block) {
-        if (captureBlockStates) { return; } // Paper - Cancel all physics during placement
+        if (this.getCurrentWorldData().captureBlockStates) { return; } // Paper - Cancel all physics during placement // Folia - region threading
         this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null));
     }
 
     @Override
     public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) {
-        if (captureBlockStates) { return; } // Paper - Cancel all physics during placement
-        this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation);
+        if (this.getCurrentWorldData().captureBlockStates) { return; } // Paper - Cancel all physics during placement // Folia - region threading
+        this.getCurrentWorldData().neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); // Folia - region threading
     }
 
     @Override
     public void updateNeighborsAtExceptFromFacing(BlockPos pos, Block block, Direction facing, @Nullable Orientation orientation) {
-        this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation);
+        this.getCurrentWorldData().neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, facing, orientation); // Folia - region threading
     }
 
     @Override
     public void neighborChanged(BlockPos pos, Block block, @Nullable Orientation orientation) {
-        this.neighborUpdater.neighborChanged(pos, block, orientation);
+        this.getCurrentWorldData().neighborUpdater.neighborChanged(pos, block, orientation); // Folia - region threading
     }
 
     @Override
     public void neighborChanged(BlockState state, BlockPos pos, Block block, @Nullable Orientation orientation, boolean movedByPiston) {
-        this.neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston);
+        this.getCurrentWorldData().neighborUpdater.neighborChanged(state, pos, block, orientation, movedByPiston); // Folia - region threading
     }
 
     @Override
@@ -1851,7 +_,7 @@
         // CraftBukkit end
         ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles;
 
-        for (ServerPlayer serverPlayer : this.players) {
+        for (ServerPlayer serverPlayer : this.getLocalPlayers()) { // Folia - region thraeding
             if (serverPlayer.distanceToSqr(vec3) < 4096.0) {
                 Optional<Vec3> optional = Optional.ofNullable(serverExplosion.getHitPlayers().get(serverPlayer));
                 serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound));
@@ -1867,14 +_,17 @@
 
     @Override
     public void blockEvent(BlockPos pos, Block block, int eventID, int eventParam) {
-        this.blockEvents.add(new BlockEventData(pos, block, eventID, eventParam));
+        this.getCurrentWorldData().pushBlockEvent(new BlockEventData(pos, block, eventID, eventParam)); // Folia - regionised ticking
     }
 
     private void runBlockEvents() {
-        this.blockEventsToReschedule.clear();
+        List<BlockEventData> blockEventsToReschedule = new ArrayList<>(64); // Folia - regionised ticking
 
-        while (!this.blockEvents.isEmpty()) {
-            BlockEventData blockEventData = this.blockEvents.removeFirst();
+        // Folia start - regionised ticking
+        io.papermc.paper.threadedregions.RegionizedWorldData worldRegionData = this.getCurrentWorldData();
+        BlockEventData blockEventData;
+        while ((blockEventData = worldRegionData.removeFirstBlockEvent()) != null) {
+            // Folia end - regionised ticking
             if (this.shouldTickBlocksAt(blockEventData.pos())) {
                 if (this.doBlockEvent(blockEventData)) {
                     this.server
@@ -1890,11 +_,11 @@
                         );
                 }
             } else {
-                this.blockEventsToReschedule.add(blockEventData);
+                blockEventsToReschedule.add(blockEventData); // Folia - regionised ticking
             }
         }
 
-        this.blockEvents.addAll(this.blockEventsToReschedule);
+        worldRegionData.pushBlockEvents(blockEventsToReschedule); // Folia - regionised ticking
     }
 
     private boolean doBlockEvent(BlockEventData event) {
@@ -1904,12 +_,12 @@
 
     @Override
     public LevelTicks<Block> getBlockTicks() {
-        return this.blockTicks;
+        return this.getCurrentWorldData().getBlockLevelTicks(); // Folia - region ticking
     }
 
     @Override
     public LevelTicks<Fluid> getFluidTicks() {
-        return this.fluidTicks;
+        return this.getCurrentWorldData().getFluidLevelTicks(); // Folia - region ticking
     }
 
     @Nonnull
@@ -1962,7 +_,7 @@
         double zOffset,
         double speed
     ) {
-        return sendParticlesSource(this.players, sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed);
+        return sendParticlesSource(this.getLocalPlayers(), sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); // Folia - region threading
     }
     public <T extends ParticleOptions> int sendParticlesSource(
         List<ServerPlayer> receivers,
@@ -2045,12 +_,12 @@
     @Nullable
     public Entity getEntityOrPart(int id) {
         Entity entity = this.getEntities().get(id);
-        return entity != null ? entity : this.dragonParts.get(id);
+        return entity != null ? entity : this.dragonParts.get((long)id); // Folia - diff on change
     }
 
     @Override
     public Collection<EnderDragonPart> dragonParts() {
-        return this.dragonParts.values();
+        return this.dragonParts.values(); // Folia - diff on change
     }
 
     @Nullable
@@ -2105,6 +_,7 @@
         // Paper start - Call missing map initialize event and set id
         final DimensionDataStorage storage = this.getServer().overworld().getDataStorage();
 
+        synchronized (storage.cache) { // Folia - region threading
         final Optional<net.minecraft.world.level.saveddata.SavedData> cacheEntry = storage.cache.get(mapId.key());
         if (cacheEntry == null) { // Cache did not contain, try to load and may init
             final MapItemSavedData mapData = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache
@@ -2124,6 +_,7 @@
         }
 
         return null;
+        } // Folia - region threading
         // Paper end - Call missing map initialize event and set id
     }
 
@@ -2178,6 +_,7 @@
     }
 
     public boolean setChunkForced(int chunkX, int chunkZ, boolean add) {
+        io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread("Cannot modify force loaded chunks off of the global region"); // Folia - region threading
         ForcedChunksSavedData forcedChunksSavedData = this.getDataStorage().computeIfAbsent(ForcedChunksSavedData.factory(), "chunks");
         ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
         long packedChunkPos = chunkPos.toLong();
@@ -2185,7 +_,7 @@
         if (add) {
             flag = forcedChunksSavedData.getChunks().add(packedChunkPos);
             if (flag) {
-                this.getChunk(chunkX, chunkZ);
+                //this.getChunk(chunkX, chunkZ); // Folia - region threading - we must let the chunk load asynchronously
             }
         } else {
             flag = forcedChunksSavedData.getChunks().remove(packedChunkPos);
@@ -2210,11 +_,24 @@
         Optional<Holder<PoiType>> optional1 = PoiTypes.forState(newState);
         if (!Objects.equals(optional, optional1)) {
             BlockPos blockPos = pos.immutable();
-            optional.ifPresent(poiType -> this.getServer().execute(() -> {
+            // Folia start - region threading
+            optional.ifPresent(poiType -> {
+                Runnable run = () -> {
+                    // Folia end - region threading
                 this.getPoiManager().remove(blockPos);
                 DebugPackets.sendPoiRemovedPacket(this, blockPos);
-            }));
-            optional1.ifPresent(poiType -> this.getServer().execute(() -> {
+                // Folia start - region threading
+                };
+                // Folia start - region threading
+                io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(
+                    this, blockPos.getX() >> 4, blockPos.getZ() >> 4, run
+                );
+            });
+            // Folia end - region threading
+            // Folia start - region threading
+            optional1.ifPresent(poiType -> {
+                Runnable run = () -> {
+                    // Folia end - region threading
                 // Paper start - Remove stale POIs
                 if (optional.isEmpty() && this.getPoiManager().exists(blockPos, ignored -> true)) {
                     this.getPoiManager().remove(blockPos);
@@ -2222,7 +_,15 @@
                 // Paper end - Remove stale POIs
                 this.getPoiManager().add(blockPos, (Holder<PoiType>)poiType);
                 DebugPackets.sendPoiAddedPacket(this, blockPos);
-            }));
+                    // Folia start - region threading
+                };
+                // Folia start - region threading
+                io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueChunkTask(
+                        this, blockPos.getX() >> 4, blockPos.getZ() >> 4, run
+                );
+                // Folia end - region threading
+            });
+            // Folia end - region threading
         }
     }
 
@@ -2276,7 +_,7 @@
             }
 
             bufferedWriter.write(String.format(Locale.ROOT, "entities: %s\n", this.moonrise$getEntityLookup().getDebugInfo()));  // Paper - rewrite chunk system
-            bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size()));
+            //bufferedWriter.write(String.format(Locale.ROOT, "block_entity_tickers: %d\n", this.blockEntityTickers.size())); // Folia - region threading
             bufferedWriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count()));
             bufferedWriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count()));
             bufferedWriter.write("distance_manager: " + chunkMap.getDistanceManager().getDebugStatus() + "\n");
@@ -2346,7 +_,7 @@
     private void dumpBlockEntityTickers(Writer output) throws IOException {
         CsvOutput csvOutput = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(output);
 
-        for (TickingBlockEntity tickingBlockEntity : this.blockEntityTickers) {
+        for (TickingBlockEntity tickingBlockEntity : (Iterable<? extends net.minecraft.world.level.block.entity.TickingBlockEntity>)null) { // Folia - region threading
             BlockPos pos = tickingBlockEntity.getPos();
             csvOutput.writeRow(pos.getX(), pos.getY(), pos.getZ(), tickingBlockEntity.getType());
         }
@@ -2354,14 +_,14 @@
 
     @VisibleForTesting
     public void clearBlockEvents(BoundingBox boundingBox) {
-        this.blockEvents.removeIf(blockEventData -> boundingBox.isInside(blockEventData.pos()));
+        this.getCurrentWorldData().removeIfBlockEvents(blockEventData -> boundingBox.isInside(blockEventData.pos())); // Folia - regionised ticking
     }
 
     @Override
     public void blockUpdated(BlockPos pos, Block block) {
         if (!this.isDebug()) {
             // CraftBukkit start
-            if (this.populating) {
+            if (this.getCurrentWorldData().populating) { // Folia - region threading
                 return;
             }
             // CraftBukkit end
@@ -2410,8 +_,8 @@
             this.players.size(),
             this.moonrise$getEntityLookup().getDebugInfo(), // Paper - rewrite chunk system
             getTypeCount(this.moonrise$getEntityLookup().getAll(), entity -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()), // Paper - rewrite chunk system
-            this.blockEntityTickers.size(),
-            getTypeCount(this.blockEntityTickers, TickingBlockEntity::getType),
+            0, // Folia - region threading
+            "null", // Folia - region threading
             this.getBlockTicks().count(),
             this.getFluidTicks().count(),
             this.gatherChunkSourceStats()
@@ -2463,15 +_,15 @@
     }
 
     public void startTickingChunk(LevelChunk chunk) {
-        chunk.unpackTicks(this.getLevelData().getGameTime());
+        chunk.unpackTicks(this.getRedstoneGameTime()); // Folia - region threading
     }
 
     public void onStructureStartsAvailable(ChunkAccess chunk) {
-        this.server.execute(() -> this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()));
+        this.structureCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()); // Folia - region threading
     }
 
     public PathTypeCache getPathTypeCache() {
-        return this.pathTypesByPosCache;
+        return this.getCurrentWorldData().pathTypesByPosCache; // Folia - region threading
     }
 
     @Override
@@ -2489,7 +_,7 @@
         return this.moonrise$getAnyChunkIfLoaded(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkX(chunkPos), ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkZ(chunkPos)) != null; // Paper - rewrite chunk system
     }
 
-    private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
+    public boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { // Folia - region threaded - make public
         // Paper start - rewrite chunk system
         final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = this.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkPos);
         // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded
@@ -2581,7 +_,7 @@
     // Paper start - optimize redstone (Alternate Current)
     @Override
     public alternate.current.wire.WireHandler getWireHandler() {
-        return wireHandler;
+        return this.getCurrentWorldData().wireHandler; // Folia - region threading
     }
     // Paper end - optimize redstone (Alternate Current)
 
@@ -2592,18 +_,18 @@
 
         @Override
         public void onDestroyed(Entity entity) {
-            ServerLevel.this.getScoreboard().entityRemoved(entity);
+            // ServerLevel.this.getScoreboard().entityRemoved(entity); // Folia - region threading
         }
 
         @Override
         public void onTickingStart(Entity entity) {
             if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking
-            ServerLevel.this.entityTickList.add(entity);
+            ServerLevel.this.getCurrentWorldData().addEntityTickingEntity(entity); // Folia - region threading
         }
 
         @Override
         public void onTickingEnd(Entity entity) {
-            ServerLevel.this.entityTickList.remove(entity);
+            ServerLevel.this.getCurrentWorldData().removeEntityTickingEntity(entity); // Folia - region threading
             // Paper start - Reset pearls when they stop being ticked
             if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) {
                 pearl.cachedOwner = null;
@@ -2615,6 +_,7 @@
         @Override
         public void onTrackingStart(Entity entity) {
             org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
+            ServerLevel.this.getCurrentWorldData().addLoadedEntity(entity); // Folia - region threading
             // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true
             if (entity instanceof ServerPlayer serverPlayer) {
                 ServerLevel.this.players.add(serverPlayer);
@@ -2629,12 +_,12 @@
                     );
                 }
 
-                ServerLevel.this.navigatingMobs.add(mob);
+                ServerLevel.this.getCurrentWorldData().addNavigatingMob(mob); // Folia - region threading
             }
 
             if (entity instanceof EnderDragon enderDragon) {
                 for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) {
-                    ServerLevel.this.dragonParts.put(enderDragonPart.getId(), enderDragonPart);
+                    ServerLevel.this.dragonParts.put((long)enderDragonPart.getId(), enderDragonPart); // Folia - diff on change
                 }
             }
 
@@ -2657,18 +_,27 @@
         @Override
         public void onTrackingEnd(Entity entity) {
             org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
+            ServerLevel.this.getCurrentWorldData().removeLoadedEntity(entity); // Folia - region threading
             // Spigot start // TODO I don't think this is needed anymore
             if (entity instanceof Player player) {
                 for (final ServerLevel level : ServerLevel.this.getServer().getAllLevels()) {
-                    for (final Optional<net.minecraft.world.level.saveddata.SavedData> savedData : level.getDataStorage().cache.values()) {
+                    // Folia start - make map data thread-safe
+                    List<Optional<net.minecraft.world.level.saveddata.SavedData>> worldDataCache;
+                    synchronized (level.getDataStorage().cache) {
+                        worldDataCache = new java.util.ArrayList<>(level.getDataStorage().cache.values());
+                    }
+                    for (final Optional<net.minecraft.world.level.saveddata.SavedData> savedData : worldDataCache) {
+                        // Folia end - make map data thread-safe
                         if (savedData.isEmpty() || !(savedData.get() instanceof MapItemSavedData map)) {
                             continue;
                         }
 
+                        synchronized (map) { // Folia - make map data thread-safe
                         map.carriedByPlayers.remove(player);
                         if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) {
                             map.decorations.remove(player.getName().getString());
                         }
+                        } // Folia - make map data thread-safe
                     }
                 }
             }
@@ -2699,18 +_,19 @@
                     );
                 }
 
-                ServerLevel.this.navigatingMobs.remove(mob);
+                ServerLevel.this.getCurrentWorldData().removeNavigatingMob(mob); // Folia - region threading
             }
 
             if (entity instanceof EnderDragon enderDragon) {
                 for (EnderDragonPart enderDragonPart : enderDragon.getSubEntities()) {
-                    ServerLevel.this.dragonParts.remove(enderDragonPart.getId());
+                    ServerLevel.this.dragonParts.remove((long)enderDragonPart.getId()); // Folia - diff on change
                 }
             }
 
             entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
             // CraftBukkit start
             entity.valid = false;
+            // Folia - region threading - TODO THIS SHIT
             if (!(entity instanceof ServerPlayer)) {
                 for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players
                     player.getBukkitEntity().onEntityRemove(entity);
@@ -2738,11 +_,11 @@
     private long lagCompensationTick = MinecraftServer.SERVER_INIT;
 
     public long getLagCompensationTick() {
-        return this.lagCompensationTick;
+        return this.getCurrentWorldData().getLagCompensationTick(); // Folia - region threading
     }
 
     public void updateLagCompensationTick() {
-        this.lagCompensationTick = (System.nanoTime() - MinecraftServer.SERVER_INIT) / (java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(50L));
+        throw new UnsupportedOperationException(); // Folia - region threading
     }
     // Paper end - lag compensation
 }
