diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
index 2830d7ace..8c5147655 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
@@ -214,21 +214,17 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
 
     @Override
     public void onPlacedByWorld(final @NotNull World world, final @NotNull TilePosc tilePos) {
-        if (world.isClientSide) {
-            ParticleEmitterRange t = this.getParticleEmitterRange();
-            if (t != null) {
-                world.particleEmitters.add(tilePos, t);
-            }
+        ParticleEmitterRange t = this.getParticleEmitterRange();
+        if (t != null) {
+            world.particleEmitters.add(tilePos, t);
         }
     }
 
     @Override
     public void onRemoved(final @NotNull World world, final @NotNull TilePosc tilePos, final int data) {
-        if (world.isClientSide) {
-            ParticleEmitterRange t = this.getParticleEmitterRange();
-            if (t != null) {
-                world.particleEmitters.remove(tilePos);
-            }
+        ParticleEmitterRange t = this.getParticleEmitterRange();
+        if (t != null) {
+            world.particleEmitters.remove(tilePos);
         }
     }
 
@@ -250,11 +246,9 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     public void onPlacedByMob(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Side side, final @NotNull Mob mob, final double xHit, final double yHit) {
         this.onPlacedOnSide(world, tilePos, side, xHit, yHit);
 
-        if (world.isClientSide) {
-            ParticleEmitterRange t = this.getParticleEmitterRange();
-            if (t != null) {
-                world.particleEmitters.add(tilePos, t);
-            }
+        ParticleEmitterRange t = this.getParticleEmitterRange();
+        if (t != null) {
+            world.particleEmitters.add(tilePos, t);
         }
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java b/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java
index f45c1cad8..63b7c6496 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/TileEntityMovingPistonBlock.java
@@ -95,7 +95,7 @@ public class TileEntityMovingPistonBlock extends TileEntity implements IMovingBl
         this.initCollision();
     }
 
-    private void initCollision() {
+    private final void initCollision() {
         assert this.worldObj != null;
         assert this.worldObj.isChunkLoaded(new ChunkPos(this.tilePos));
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
index 8f2d70dfe..f8628f9ff 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
@@ -12,9 +12,10 @@ import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.particle.ParticleEmitterRange;
+import net.minecraft.core.world.pos.ChunkSectionTilePos;
 import net.minecraft.core.world.pos.ChunkPos;
 import net.minecraft.core.world.pos.ChunkPosc;
-import net.minecraft.core.world.pos.ChunkSectionTilePos;
 import net.minecraft.core.world.pos.ChunkTilePos;
 import net.minecraft.core.world.pos.ChunkTilePosc;
 import net.minecraft.core.world.pos.TilePos;
@@ -683,6 +684,42 @@ public class Chunk {
         for (ChunkSection section : this.sections) {
             section.onLoad(this.world);
         }
+
+        if (!this.world.isClientSide) {
+            this.registerParticleEmittersFromLoadedBlocks();
+        }
+    }
+
+    /**
+     * Registers particle emitter blocks in this chunk on the server.
+     */
+    private void registerParticleEmittersFromLoadedBlocks() {
+        final ChunkSectionTilePos sectionTile = new ChunkSectionTilePos();
+        for (final ChunkSection section : this.sections) {
+            if (section.blocks == null) {
+                continue;
+            }
+            final int baseY = section.yPosition * ChunkSection.SECTION_SIZE_Y;
+            for (sectionTile.y = 0; sectionTile.y < ChunkSection.SECTION_SIZE_Y; sectionTile.y++) {
+                for (sectionTile.z = 0; sectionTile.z < CHUNK_SIZE_Z; sectionTile.z++) {
+                    for (sectionTile.x = 0; sectionTile.x < CHUNK_SIZE_X; sectionTile.x++) {
+                        final int id = section.getBlockId(sectionTile);
+                        if (id == 0) {
+                            continue;
+                        }
+                        final Block<?> block = Blocks.getBlock(id);
+                        final ParticleEmitterRange range = block.getLogic().getParticleEmitterRange();
+                        if (range == null) {
+                            continue;
+                        }
+                        final int wx = this.pos.x * CHUNK_SIZE_X + sectionTile.x;
+                        final int wy = baseY + sectionTile.y;
+                        final int wz = this.pos.z * CHUNK_SIZE_Z + sectionTile.z;
+                        this.world.particleEmitters.add(new TilePos(wx, wy, wz), range);
+                    }
+                }
+            }
+        }
     }
 
     public void onUnload() {
diff --git a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
index 7e7d6b532..14016cc2d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
+++ b/game/core/src/main/java/net/minecraft/core/world/particle/ParticleEmitterRegistry.java
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
 
-public class ParticleEmitterRegistry {
+public final class ParticleEmitterRegistry {
 
     private record EmitterNode(TilePos pos, ParticleEmitterRange type) { }
 
@@ -51,7 +51,7 @@ public class ParticleEmitterRegistry {
         this.pendingRemoves.add(getPosHash(pos.x(), pos.y(), pos.z()));
     }
 
-    public void tick(World world, Player player, int renderDistanceChunks) {
+    private void drainPendingQueues() {
         while (!this.pendingRemoves.isEmpty()) {
             this.emitters.remove(this.pendingRemoves.poll().longValue());
         }
@@ -59,6 +59,10 @@ public class ParticleEmitterRegistry {
             EmitterNode node = this.pendingAdds.poll();
             this.emitters.put(getPosHash(node.pos.x(), node.pos.y(), node.pos.z()), node);
         }
+    }
+
+    public void tick(World world, Player player, int renderDistanceChunks) {
+        this.drainPendingQueues();
 
         if (this.emitters.isEmpty()) return;
 
@@ -100,4 +104,58 @@ public class ParticleEmitterRegistry {
             }
         }
     }
+
+    /**
+     * runs/ticks emitters if any player is within range.
+     */
+    public void tickServer(World world, Iterable<? extends Player> players, int renderDistanceChunks) {
+        this.drainPendingQueues();
+
+        if (this.emitters.isEmpty()) return;
+
+        var iterator = this.emitters.long2ObjectEntrySet().fastIterator();
+
+        while (iterator.hasNext()) {
+            Long2ObjectMap.Entry<EmitterNode> entry = iterator.next();
+            EmitterNode node = entry.getValue();
+            TilePos pos = node.pos;
+            ParticleEmitterRange type = node.type;
+
+            if (!world.isBlockLoaded(pos)) {
+                iterator.remove();
+                continue;
+            }
+
+            double maxDistSq = type.getMaxDistanceSq(renderDistanceChunks);
+            double distSq = Double.POSITIVE_INFINITY;
+            for (Player player : players) {
+                double dx = pos.x() - player.x;
+                double dy = pos.y() - player.y;
+                double dz = pos.z() - player.z;
+                double d = dx * dx + dy * dy + dz * dz;
+                if (d <= maxDistSq && d < distSq) {
+                    distSq = d;
+                }
+            }
+
+            if (distSq == Double.POSITIVE_INFINITY) continue;
+
+            int throttle = 4;
+            if (distSq > 4096) {
+                throttle = 64;
+            } else if (distSq > 1024) {
+                throttle = 16;
+            }
+
+            if (world.rand.nextInt(throttle) == 0) {
+                Block<?> block = world.getBlockType(pos);
+
+                if (block.getLogic().getParticleEmitterRange() != null) {
+                    block.animationTick(world, pos, world.rand);
+                } else {
+                    iterator.remove();
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/game/server/src/main/java/net/minecraft/server/MinecraftServer.java b/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
index 54b837927..c073953af 100644
--- a/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -135,6 +135,7 @@ public class MinecraftServer
     public int sleepPercentage = 100;
     public int maxPlayers = 20;
     public int viewDistance = 10;
+    public int particleRenderDistance = 10;
     public Gamemode defaultGamemode;
     public static String statsToken;
     public static boolean statsStatus;
@@ -243,6 +244,7 @@ public class MinecraftServer
         NetworkManager.TIMEOUT_TIME = propertyManager.getIntProperty("login-timeout-time", 1800 * 50);
         maxPlayers = propertyManager.getIntProperty("max-players", 20);
         viewDistance = propertyManager.getIntProperty("view-distance", 10);
+        particleRenderDistance = Math.max(1, Math.min(24, propertyManager.getIntProperty("particle-render-distance", 10)));
         language = propertyManager.getStringProperty("language", "en_US");
         disablePhotoMode = propertyManager.getBooleanProperty("disable-photomode", false);
         name = propertyManager.getStringProperty("name", "");
diff --git a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
index 1cbab820d..8be6bfd6a 100644
--- a/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
+++ b/game/server/src/main/java/net/minecraft/server/world/WorldServer.java
@@ -279,6 +279,10 @@ public class WorldServer extends World {
     public void tick() {
         super.tick();
 
+        if (!this.players.isEmpty()) {
+            this.particleEmitters.tickServer(this, this.players, this.mcServer.particleRenderDistance);
+        }
+
         if (System.currentTimeMillis() - lastWeatherSend > 1000) {
             mcServer.playerList.sendPacketToAllPlayers(new PacketWeatherStatus(dimension.id, getCurrentWeather() != null ? getCurrentWeather().getId() : -1, getWeatherManager().getNextWeather() != null ? getWeatherManager().getNextWeather().getId() : -1, getWeatherManager().getWeatherDuration(), getWeatherManager().getWeatherIntensity(), getWeatherManager().getWeatherPower()));
             lastWeatherSend = System.currentTimeMillis();
diff --git a/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
index 96b7562d4..ef1a9abbe 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/SmeltingGenerator.java
@@ -43,24 +43,12 @@ public class SmeltingGenerator {
         furnaceRecipe("cobble_gloomstone_to_gloomstone", Blocks.COBBLE_GLOOMSTONE.getDefaultStack(), Blocks.GLOOMSTONE.getDefaultStack());
 
         blastRecipe("cobble_stone_to_slate", Blocks.COBBLE_STONE.getDefaultStack(), Blocks.SLATE.getDefaultStack());
-        blastRecipe("cobble_stone_to_stone", Blocks.COBBLE_STONE.getDefaultStack(), Blocks.COBBLE_STONE.getDefaultStack(), Blocks.STONE.getDefaultStack());
-
         blastRecipe("cobble_basalt_to_olivine", Blocks.COBBLE_BASALT.getDefaultStack(), Items.OLIVINE.getDefaultStack());
-        blastRecipe("cobble_basalt_to_basalt", Blocks.COBBLE_BASALT.getDefaultStack(), Blocks.COBBLE_BASALT.getDefaultStack(), Blocks.BASALT.getDefaultStack());
-
         blastRecipe("cobble_granite_to_quartz", Blocks.COBBLE_GRANITE.getDefaultStack(), Items.QUARTZ.getDefaultStack());
-        blastRecipe("cobble_granite_to_granite", Blocks.COBBLE_GRANITE.getDefaultStack(), Blocks.COBBLE_GRANITE.getDefaultStack(), Blocks.GRANITE.getDefaultStack());
-
         blastRecipe("cobble_limestone_to_marble", Blocks.COBBLE_LIMESTONE.getDefaultStack(), Blocks.MARBLE.getDefaultStack());
-        blastRecipe("cobble_limestone_to_limestone", Blocks.COBBLE_LIMESTONE.getDefaultStack(), Blocks.COBBLE_LIMESTONE.getDefaultStack(), Blocks.LIMESTONE.getDefaultStack());
-
         blastRecipe("cobble_netherrack_to_magma", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.MAGMA.getDefaultStack());
-        blastRecipe("cobble_netherrack_to_netherrack", Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.COBBLE_NETHERRACK.getDefaultStack(), Blocks.NETHERRACK.getDefaultStack());
-
         blastRecipe("brimstone_to_brimthaw", Blocks.BRIMSTONE.getDefaultStack(), Blocks.BRIMTHAW.getDefaultStack());
-
         blastRecipe("cobble_permafrost_to_permafrost", Blocks.COBBLE_PERMAFROST.getDefaultStack(), Blocks.PERMAFROST.getDefaultStack());
-        blastRecipe("cobble_gloomstone_to_gloomstone", Blocks.COBBLE_GLOOMSTONE.getDefaultStack(), Blocks.GLOOMSTONE.getDefaultStack());
 
         blastRecipe("iron_ingot_and_coal_to_crude_steel", Items.INGOT_IRON.getDefaultStack(), Items.COAL.getDefaultStack(), Items.INGOT_STEEL_CRUDE.getDefaultStack());
         blastRecipe("iron_ingot_and_charcoal_to_crude_steel", Items.INGOT_IRON.getDefaultStack(), new ItemStack(Items.COAL, 1, 1), Items.INGOT_STEEL_CRUDE.getDefaultStack());
