--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -82,6 +_,11 @@
         return this.apiScheduler;
     };
     // Paper end - Folia schedulers
+    // Folia start - region threading
+    public boolean isPurged() {
+        return this.taskScheduler.isRetired();
+    }
+    // Folia end - region threading
 
     public CraftEntity(final CraftServer server, final Entity entity) {
         this.server = server;
@@ -239,6 +_,11 @@
 
     @Override
     public boolean teleport(Location location, TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) {
+        // Folia start - region threading
+        if (true) {
+            throw new UnsupportedOperationException("Must use teleportAsync while in region threading");
+        }
+        // Folia end - region threading
         // Paper end
         Preconditions.checkArgument(location != null, "location cannot be null");
         location.checkFinite();
@@ -528,6 +_,7 @@
     }
 
     public Entity getHandle() {
+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.entity, "Accessing entity state off owning region's thread"); // Folia - region threading
         return this.entity;
     }
 
@@ -722,7 +_,7 @@
         ImmutableSet.Builder<Player> players = ImmutableSet.builder();
 
         ServerLevel world = ((CraftWorld) this.getWorld()).getHandle();
-        ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId());
+        ChunkMap.TrackedEntity entityTracker = this.getHandle().moonrise$getTrackedEntity(); // Folia - region threading
 
         if (entityTracker != null) {
             for (ServerPlayerConnection connection : entityTracker.seenBy) {
@@ -1026,7 +_,7 @@
         }
 
         ServerLevel world = ((CraftWorld) this.getWorld()).getHandle();
-        ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId());
+        ChunkMap.TrackedEntity entityTracker = this.getHandle().moonrise$getTrackedEntity(); // Folia - region threading
 
         if (entityTracker == null) {
             return;
@@ -1045,7 +_,7 @@
         }
 
         ServerLevel world = ((CraftWorld) this.getWorld()).getHandle();
-        ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId());
+        ChunkMap.TrackedEntity entityTracker = this.entity.moonrise$getTrackedEntity(); // Folia - region threading
 
         if (entityTracker == null) {
             return;
@@ -1079,29 +_,43 @@
         location.checkFinite();
         Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call.
 
-        net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle();
+        // Folia start - region threading
         java.util.concurrent.CompletableFuture<Boolean> ret = new java.util.concurrent.CompletableFuture<>();
+        java.util.function.Consumer<Entity> run = (Entity nmsEntity) -> {
+            boolean success = nmsEntity.teleportAsync(
+                ((CraftWorld)locationClone.getWorld()).getHandle(),
+                new net.minecraft.world.phys.Vec3(locationClone.getX(), locationClone.getY(), locationClone.getZ()),
+                locationClone.getYaw(), locationClone.getPitch(), net.minecraft.world.phys.Vec3.ZERO,
+                cause == null ? TeleportCause.UNKNOWN : cause,
+                Entity.TELEPORT_FLAG_LOAD_CHUNK | Entity.TELEPORT_FLAG_TELEPORT_PASSENGERS, // preserve behavior with old API: dismount the entity so it can teleport
+                (Entity entityTp) -> {
+                    ret.complete(Boolean.TRUE);
+                }
+            );
+            if (!success) {
+                ret.complete(Boolean.FALSE);
+            }
+        };
+        if (org.bukkit.Bukkit.isOwnedByCurrentRegion(this)) {
+            run.accept(this.getHandle());
+            return ret;
+        }
+        boolean scheduled = this.taskScheduler.schedule(
+            // success
+            run,
+            // retired
+            (Entity nmsEntity) -> {
+                ret.complete(Boolean.FALSE);
+            },
+            1L
+        );
 
-        world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()),
-            this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> {
-                net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
-                    final net.minecraft.server.level.ServerChunkCache chunkCache = world.getChunkSource();
-                    for (final net.minecraft.world.level.chunk.ChunkAccess chunk : list) {
-                        chunkCache.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
-                    }
-                    try {
-                        ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE);
-                    } catch (Throwable throwable) {
-                        if (throwable instanceof ThreadDeath) {
-                            throw (ThreadDeath)throwable;
-                        }
-                        net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable);
-                        ret.completeExceptionally(throwable);
-                    }
-                });
-            });
+        if (!scheduled) {
+            ret.complete(Boolean.FALSE);
+        }
 
         return ret;
+        // Folia end - region threading
     }
     // Paper end - more teleport API / async chunk API
 
@@ -1214,8 +_,7 @@
     // Paper start - tracked players API
     @Override
     public Set<org.bukkit.entity.Player> getTrackedPlayers() {
-        ServerLevel world = (net.minecraft.server.level.ServerLevel)this.entity.level();
-        ChunkMap.TrackedEntity tracker = world == null ? null : world.getChunkSource().chunkMap.entityMap.get(this.entity.getId());
+        ChunkMap.TrackedEntity tracker = this.entity.moonrise$getTrackedEntity(); // Folia - region threading
         if (tracker == null) {
             return java.util.Collections.emptySet();
         }
