diff --git a/README.md b/README.md
index 3cfa440f..9176fba9 100644
--- a/README.md
+++ b/README.md
@@ -119,6 +119,7 @@ If these excellent projects hadn't appeared, Leaf wouldn't have become great.
• Luminol
• Nitori
• Moonrise (during 1.21.1)
+ • Sakura
diff --git a/leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch b/leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch
new file mode 100644
index 00000000..16a14fa4
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch
@@ -0,0 +1,81 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Samsuik
+Date: Thu, 1 May 2025 22:28:50 +0200
+Subject: [PATCH] Sakura: copy-EntityList-implementation-to-BasicEntityList
+
+
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+index b6af8da084c83ee38bb3ecea6a98feb0c1c74d2a..4311309f14c069f929ffe86bf0a91d7df3222828 100644
+--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java
+@@ -382,6 +382,13 @@ public final class ChunkEntitySlices {
+
+ private E[] storage;
+ private int size;
++ // Sakura start - use methods from EntityList
++ private it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap entityToIndex = null;
++ private void setupIndexMap() {
++ this.entityToIndex = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(2, 0.8f);
++ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
++ }
++ // Sakura end - use methods from EntityList
+
+ public BasicEntityList() {
+ this(0);
+@@ -402,6 +409,7 @@ public final class ChunkEntitySlices {
+ private void resize() {
+ if (this.storage == me.titaniumtown.ArrayConstants.emptyEntityArray) { // Gale - JettPack - reduce array allocations
+ this.storage = (E[])new Entity[DEFAULT_CAPACITY];
++ this.setupIndexMap(); // Sakura - use methods from EntityList
+ } else {
+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2);
+ }
+@@ -415,6 +423,7 @@ public final class ChunkEntitySlices {
+ } else {
+ this.storage[idx] = entity;
+ }
++ this.entityToIndex.put(entity.getId(), idx); // Sakura - use methods from EntityList
+ }
+
+ public int indexOf(final E entity) {
+@@ -430,24 +439,32 @@ public final class ChunkEntitySlices {
+ }
+
+ public boolean remove(final E entity) {
+- final int idx = this.indexOf(entity);
+- if (idx == -1) {
++ // Sakura start - use methods from EntityList
++ if (this.entityToIndex == null) {
+ return false;
+ }
+
+- final int size = --this.size;
+- final E[] storage = this.storage;
+- if (idx != size) {
+- System.arraycopy(storage, idx + 1, storage, idx, size - idx);
++ final int index = this.entityToIndex.remove(entity.getId());
++ if (index == Integer.MIN_VALUE) {
++ return false;
+ }
+
+- storage[size] = null;
++ // move the entity at the end to this index
++ final int endIndex = --this.size;
++ final E end = this.storage[endIndex];
++ if (index != endIndex) {
++ // not empty after this call
++ this.entityToIndex.put(end.getId(), index); // update index
++ }
++ this.storage[index] = end;
++ this.storage[endIndex] = null;
++ // Sakura end - use methods from EntityList
+
+ return true;
+ }
+
+ public boolean has(final E entity) {
+- return this.indexOf(entity) != -1;
++ return this.entityToIndex != null && this.entityToIndex.containsKey(entity.getId()); // Sakura - use methods from EntityList
+ }
+ }
+
diff --git a/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch b/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch
new file mode 100644
index 00000000..053f1b90
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch
@@ -0,0 +1,68 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Taiyou06
+Date: Thu, 1 May 2025 22:38:48 +0200
+Subject: [PATCH] Sakura: Optimise-check-inside-blocks-and-traverse-blocks
+
+
+diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
+index 4221e5322fa3a3ff6ab53946aa71d54144d2c4b2..4474639b0411236e208c542973084864365c544c 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -1670,6 +1670,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ private void checkInsideBlocks(List movements, Set blocksInside) {
+ if (this.isAffectedByBlocks()) {
+ LongSet set = this.visitedBlocks;
++ // Sakura start - optimise check inside blocks
++ int lastChunkX = Integer.MIN_VALUE;
++ int lastChunkZ = Integer.MIN_VALUE;
++ net.minecraft.world.level.chunk.ChunkAccess chunk = null;
++ // Sakura end - optimise check inside blocks
+
+ for (Entity.Movement movement : movements) {
+ Vec3 vec3 = movement.from();
+@@ -1681,7 +1686,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ return;
+ }
+
+- BlockState blockState = this.level().getBlockState(blockPos);
++ // Sakura start - optimise check inside blocks
++ final int chunkX = blockPos.getX() >> 4;
++ final int chunkZ = blockPos.getZ() >> 4;
++ if (chunk == null || chunkX != lastChunkX || chunkZ != lastChunkZ) {
++ chunk = this.level.getChunkIfLoadedImmediately(chunkX, chunkZ);
++ if (chunk == null) {
++ continue;
++ }
++ lastChunkX = chunkX;
++ lastChunkZ = chunkZ;
++ }
++ final BlockState blockState = chunk.getBlockState(blockPos);
++ // Sakura end - optimise check inside blocks
+ if (!blockState.isAir() && set.add(blockPos.asLong())) {
+ try {
+ VoxelShape entityInsideCollisionShape = blockState.getEntityInsideCollisionShape(this.level(), blockPos);
+diff --git a/net/minecraft/world/level/BlockGetter.java b/net/minecraft/world/level/BlockGetter.java
+index 91865d7e78e15cc643a65de03045b90a52d6ec2a..03f82e60528738e89f195cfc59094f53156f5370 100644
+--- a/net/minecraft/world/level/BlockGetter.java
++++ b/net/minecraft/world/level/BlockGetter.java
+@@ -214,10 +214,18 @@ public interface BlockGetter extends LevelHeightAccessor {
+
+ static Iterable boxTraverseBlocks(Vec3 oldPosition, Vec3 position, AABB boundingBox) {
+ Vec3 vec3 = position.subtract(oldPosition);
+- Iterable iterable = BlockPos.betweenClosed(boundingBox);
++ // Sakura start - optimise check inside blocks
+ if (vec3.lengthSqr() < Mth.square(0.99999F)) {
+- return iterable;
++ return org.dreeam.leaf.util.map.BlockPosIterator.iterable(boundingBox);
+ } else {
++ final boolean xZero = vec3.x() == 0.0;
++ final boolean yZero = vec3.y() == 0.0;
++ final boolean zZero = vec3.z() == 0.0;
++ if (xZero && yZero || yZero && zZero || xZero && zZero) {
++ return org.dreeam.leaf.util.map.BlockPosIterator.traverseArea(vec3, boundingBox);
++ }
++ Iterable iterable = BlockPos.betweenClosed(boundingBox);
++ // Sakura end - optimise check inside blocks
+ Set set = new ObjectLinkedOpenHashSet<>();
+ Vec3 minPosition = boundingBox.getMinPosition();
+ Vec3 vec31 = minPosition.subtract(vec3);
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java
new file mode 100644
index 00000000..bf269d54
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java
@@ -0,0 +1,69 @@
+package org.dreeam.leaf.util.map;
+
+import com.google.common.collect.AbstractIterator;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.BlockPos.MutableBlockPos;
+import net.minecraft.util.Mth;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
+public final class BlockPosIterator extends AbstractIterator {
+ private final int startX;
+ private final int startY;
+ private final int startZ;
+ private final int endX;
+ private final int endY;
+ private final int endZ;
+ private @Nullable MutableBlockPos pos = null;
+
+ public static Iterable iterable(AABB bb) {
+ return () -> new BlockPosIterator(bb);
+ }
+
+ public static Iterable traverseArea(Vec3 vec, AABB boundingBox) {
+ double toTravel = Math.min(16.0 / vec.length(), 1.0);
+ Vec3 movement = vec.scale(toTravel);
+ AABB fromBB = boundingBox.move(-vec.x, -vec.y, -vec.z);
+ AABB searchArea = fromBB.expandTowards(movement);
+ return org.dreeam.leaf.util.map.BlockPosIterator.iterable(searchArea);
+ }
+
+ public BlockPosIterator(AABB bb) {
+ this.startX = Mth.floor(bb.minX);
+ this.startY = Mth.floor(bb.minY);
+ this.startZ = Mth.floor(bb.minZ);
+ this.endX = Mth.floor(bb.maxX);
+ this.endY = Mth.floor(bb.maxY);
+ this.endZ = Mth.floor(bb.maxZ);
+ }
+
+ @Override
+ protected BlockPos computeNext() {
+ MutableBlockPos pos = this.pos;
+ if (pos == null) {
+ return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ);
+ } else {
+ int x = pos.getX();
+ int y = pos.getY();
+ int z = pos.getZ();
+
+ if (y < this.endY) {
+ y += 1;
+ } else if (x < this.endX) {
+ x += 1;
+ y = this.startY;
+ } else if (z < this.endZ) {
+ z += 1;
+ x = this.startX;
+ } else {
+ return this.endOfData();
+ }
+
+ pos.set(x, y, z);
+ return pos;
+ }
+ }
+}