diff --git a/common/src/event.rs b/common/src/event.rs
index c9eded34a1..4c7d1bc896 100644
--- a/common/src/event.rs
+++ b/common/src/event.rs
@@ -148,7 +148,6 @@ pub enum ServerEvent {
     CreateWaypoint(Vec3<f32>),
     ClientDisconnect(EcsEntity, DisconnectReason),
     ClientDisconnectWithoutPersistence(EcsEntity),
-    ChunkRequest(EcsEntity, Vec2<i32>),
     Command(EcsEntity, String, Vec<String>),
     /// Send a chat message to the player from an npc or other player
     Chat(comp::UnresolvedChatMsg),
diff --git a/server/src/chunk_generator.rs b/server/src/chunk_generator.rs
index 0559b4a460..87124c0cf4 100644
--- a/server/src/chunk_generator.rs
+++ b/server/src/chunk_generator.rs
@@ -65,14 +65,17 @@ impl ChunkGenerator {
     }
 
     pub fn recv_new_chunk(&mut self) -> Option<ChunkGenResult> {
-        if let Ok((key, res)) = self.chunk_rx.try_recv() {
-            self.pending_chunks.remove(&key);
-            self.metrics.chunks_served.inc();
-            // TODO: do anything else if res is an Err?
-            Some((key, res))
-        } else {
-            None
+        // Make sure chunk wasn't cancelled and if it was check to see if there are more
+        // chunks to receive
+        while let Ok((key, res)) = self.chunk_rx.try_recv() {
+            if self.pending_chunks.remove(&key).is_some() {
+                self.metrics.chunks_served.inc();
+                // TODO: do anything else if res is an Err?
+                return Some((key, res));
+            }
         }
+
+        None
     }
 
     pub fn pending_chunks(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs
index 201d9a1258..35911547e4 100644
--- a/server/src/events/mod.rs
+++ b/server/src/events/mod.rs
@@ -51,7 +51,6 @@ impl Server {
         span!(_guard, "handle_events", "Server::handle_events");
         let mut frontend_events = Vec::new();
 
-        let mut requested_chunks = Vec::new();
         let mut commands = Vec::new();
         let mut chat_messages = Vec::new();
 
@@ -191,10 +190,6 @@ impl Server {
                         true,
                     ))
                 },
-
-                ServerEvent::ChunkRequest(entity, key) => {
-                    requested_chunks.push((entity, key));
-                },
                 ServerEvent::Command(entity, name, args) => {
                     commands.push((entity, name, args));
                 },
@@ -245,11 +240,6 @@ impl Server {
             }
         }
 
-        // Generate requested chunks
-        for (entity, key) in requested_chunks {
-            self.generate_chunk(entity, key);
-        }
-
         for (entity, name, args) in commands {
             self.process_command(entity, name, args);
         }
diff --git a/server/src/lib.rs b/server/src/lib.rs
index f73147b480..be13805968 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -180,6 +180,11 @@ impl BattleModeBuffer {
     }
 }
 
+pub struct ChunkRequest {
+    entity: EcsEntity,
+    key: Vec2<i32>,
+}
+
 pub struct Server {
     state: State,
     world: Arc<World>,
@@ -234,6 +239,7 @@ impl Server {
             path: data_dir.to_owned(),
         });
         state.ecs_mut().insert(EventBus::<ServerEvent>::default());
+        state.ecs_mut().insert(Vec::<ChunkRequest>::new());
         state.ecs_mut().insert(LoginProvider::new(
             settings.auth_server_address.clone(),
             Arc::clone(&runtime),
@@ -1071,7 +1077,7 @@ impl Server {
 
     pub fn generate_chunk(&mut self, entity: EcsEntity, key: Vec2<i32>) {
         let ecs = self.state.ecs();
-        let slow_jobs = ecs.write_resource::<SlowJobPool>();
+        let slow_jobs = ecs.read_resource::<SlowJobPool>();
         ecs.write_resource::<ChunkGenerator>().generate_chunk(
             Some(entity),
             key,
diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs
index 64ea2d60b8..d0f76023c6 100644
--- a/server/src/sys/mod.rs
+++ b/server/src/sys/mod.rs
@@ -27,7 +27,7 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
     dispatch::<melee::Sys>(dispatch_builder, &[&projectile::Sys::sys_name()]);
     //Note: server should not depend on interpolation system
     dispatch::<agent::Sys>(dispatch_builder, &[]);
-    dispatch::<terrain::Sys>(dispatch_builder, &[]);
+    dispatch::<terrain::Sys>(dispatch_builder, &[&msg::terrain::Sys::sys_name()]);
     dispatch::<waypoint::Sys>(dispatch_builder, &[]);
     dispatch::<invite_timeout::Sys>(dispatch_builder, &[]);
     dispatch::<persistence::Sys>(dispatch_builder, &[]);
diff --git a/server/src/sys/msg/terrain.rs b/server/src/sys/msg/terrain.rs
index a68cd5f85b..bf6c81b715 100644
--- a/server/src/sys/msg/terrain.rs
+++ b/server/src/sys/msg/terrain.rs
@@ -1,7 +1,6 @@
-use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence};
+use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, ChunkRequest};
 use common::{
     comp::Pos,
-    event::{EventBus, ServerEvent},
     spiral::Spiral2d,
     terrain::{TerrainChunkSize, TerrainGrid},
     vol::RectVolSize,
@@ -9,7 +8,7 @@ use common::{
 use common_ecs::{Job, Origin, ParMode, Phase, System};
 use common_net::msg::{ClientGeneral, SerializedTerrainChunk, ServerGeneral};
 use rayon::iter::ParallelIterator;
-use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
+use specs::{Entities, Join, ParJoin, ReadExpect, ReadStorage, Write};
 use tracing::{debug, trace};
 
 /// This system will handle new messages from clients
@@ -19,9 +18,9 @@ impl<'a> System<'a> for Sys {
     #[allow(clippy::type_complexity)]
     type SystemData = (
         Entities<'a>,
-        Read<'a, EventBus<ServerEvent>>,
         ReadExpect<'a, TerrainGrid>,
         ReadExpect<'a, NetworkRequestMetrics>,
+        Write<'a, Vec<ChunkRequest>>,
         ReadStorage<'a, Pos>,
         ReadStorage<'a, Presence>,
         ReadStorage<'a, Client>,
@@ -35,21 +34,19 @@ impl<'a> System<'a> for Sys {
         job: &mut Job<Self>,
         (
             entities,
-            server_event_bus,
             terrain,
             network_metrics,
+            mut chunk_requests,
             positions,
             presences,
             clients,
         ): Self::SystemData,
     ) {
-        let mut server_emitter = server_event_bus.emitter();
-
         job.cpu_stats.measure(ParMode::Rayon);
-        let mut events = (&entities, &clients, (&presences).maybe())
+        let mut new_chunk_requests = (&entities, &clients, (&presences).maybe())
             .par_join()
             .map(|(entity, client, maybe_presence)| {
-                let mut events = Vec::new();
+                let mut chunk_requests = Vec::new();
                 let _ = super::try_recv_all(client, 5, |client, msg| {
                     let presence = match maybe_presence {
                         Some(g) => g,
@@ -93,7 +90,7 @@ impl<'a> System<'a> for Sys {
                                     },
                                     None => {
                                         network_metrics.chunks_generation_triggered.inc();
-                                        events.push(ServerEvent::ChunkRequest(entity, key));
+                                        chunk_requests.push(ChunkRequest { entity, key });
                                     },
                                 }
                             } else {
@@ -117,19 +114,21 @@ impl<'a> System<'a> for Sys {
                     for rpos in Spiral2d::new().take((crate::MIN_VD as usize + 1).pow(2)) {
                         let key = player_chunk + rpos;
                         if terrain.get_key(key).is_none() {
-                            events.push(ServerEvent::ChunkRequest(entity, key));
+                            // TODO: @zesterer do we want to be sending these chunk to the client
+                            // even if they aren't requested? If we don't we could replace the
+                            // entity here with Option<Entity> and pass in None.
+                            chunk_requests.push(ChunkRequest { entity, key });
                         }
                     }
                 }
 
-                events
+                chunk_requests
             })
             .flatten()
             .collect::<Vec<_>>();
 
         job.cpu_stats.measure(ParMode::Single);
-        for event in events.drain(..) {
-            server_emitter.emit(event);
-        }
+
+        chunk_requests.append(&mut new_chunk_requests);
     }
 }
diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs
index 932f98a27d..91a7741d42 100644
--- a/server/src/sys/terrain.rs
+++ b/server/src/sys/terrain.rs
@@ -1,5 +1,10 @@
 #[cfg(feature = "persistent_world")]
 use crate::TerrainPersistence;
+#[cfg(not(feature = "worldgen"))]
+use test_world::{IndexOwned, World};
+#[cfg(feature = "worldgen")]
+use world::{IndexOwned, World};
+
 use crate::{
     chunk_generator::ChunkGenerator,
     client::Client,
@@ -7,14 +12,15 @@ use crate::{
     presence::{Presence, RepositionOnChunkLoad},
     rtsim::RtSim,
     settings::Settings,
-    SpawnPoint, Tick,
+    ChunkRequest, SpawnPoint, Tick,
 };
 use common::{
     comp::{self, agent, bird_medium, BehaviorCapability, ForceUpdate, Pos, Waypoint},
     event::{EventBus, ServerEvent},
     generation::EntityInfo,
     lottery::LootSpec,
-    resources::Time,
+    resources::{Time, TimeOfDay},
+    slowjob::SlowJobPool,
     terrain::TerrainGrid,
     LoadoutBuilder, SkillSetBuilder,
 };
@@ -104,10 +110,15 @@ impl<'a> System<'a> for Sys {
         Read<'a, Tick>,
         Read<'a, SpawnPoint>,
         Read<'a, Settings>,
+        Read<'a, TimeOfDay>,
+        ReadExpect<'a, SlowJobPool>,
+        ReadExpect<'a, IndexOwned>,
+        ReadExpect<'a, Arc<World>>,
         ReadExpect<'a, NetworkRequestMetrics>,
         WriteExpect<'a, ChunkGenerator>,
         WriteExpect<'a, TerrainGrid>,
         Write<'a, TerrainChanges>,
+        Write<'a, Vec<ChunkRequest>>,
         WriteExpect<'a, RtSim>,
         TerrainPersistenceData<'a>,
         WriteStorage<'a, Pos>,
@@ -131,10 +142,15 @@ impl<'a> System<'a> for Sys {
             tick,
             spawn_point,
             server_settings,
+            time_of_day,
+            slow_jobs,
+            index,
+            world,
             network_metrics,
             mut chunk_generator,
             mut terrain,
             mut terrain_changes,
+            mut chunk_requests,
             mut rtsim,
             mut _terrain_persistence,
             mut positions,
@@ -149,6 +165,22 @@ impl<'a> System<'a> for Sys {
     ) {
         let mut server_emitter = server_event_bus.emitter();
 
+        // Generate requested chunks
+        //
+        // Submit requests for chunks right before receiving finished chunks so that we
+        // don't create duplicate work for chunks that just finished but are not
+        // yet added to the terrain.
+        chunk_requests.drain(..).for_each(|request| {
+            chunk_generator.generate_chunk(
+                Some(request.entity),
+                request.key,
+                &slow_jobs,
+                Arc::clone(&world),
+                index.clone(),
+                *time_of_day,
+            )
+        });
+
         // Fetch any generated `TerrainChunk`s and insert them into the terrain.
         // Also, send the chunk data to anybody that is close by.
         let mut new_chunks = Vec::new();