diff --git a/game_core/src/assets/startup.rs b/game_core/src/assets/startup.rs
index e848c5fff0f0bd6adce82aada13ccc0799812bde..50b235cbebbe25b17a141fd8c5075dbffb027e64 100644
--- a/game_core/src/assets/startup.rs
+++ b/game_core/src/assets/startup.rs
@@ -11,11 +11,16 @@ pub fn start_preload_resources(mut commands: Commands) {
 	commands.insert_resource(NextState(AppState::Setup))
 }
 
+/// All sheets have been adjusted to be 64 x 64 sprites
+pub const SHEET_WIDTH: usize = 64;
+/// All sheets have been adjusted to be 64 x 64 sprites
+pub const SHEET_HEIGHT: usize = 64;
+
 pub fn start_load_resources(mut loader: AssetTypeLoader) {
 	loader.load_images(&[("splash.png", "splash")]);
 	loader.load_audio(&[("splash_sting.mp3", "splash_sting")]);
 
-	let sheet_config = SpriteSheetConfig::squares(16, 64, 64);
+	let sheet_config = SpriteSheetConfig::squares(16, SHEET_WIDTH, SHEET_HEIGHT);
 	loader.load_spritesheet(
 		&sheet_config,
 		&[
diff --git a/game_core/src/control/mod.rs b/game_core/src/control/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f28d7c205ea9a9b4c0a1c6bf1404f8a0f7421669
--- /dev/null
+++ b/game_core/src/control/mod.rs
@@ -0,0 +1 @@
+pub mod player;
diff --git a/game_core/src/debug.rs b/game_core/src/debug.rs
index 0b87c5f02f21ca9524bccaf402d200c86a5b2291..928add3d7a5eacb15566f4c082ac034ae662e8cb 100644
--- a/game_core/src/debug.rs
+++ b/game_core/src/debug.rs
@@ -5,10 +5,15 @@ use iyes_loopless::state::NextState;
 
 use crate::entities::spawner::EntitySpawner;
 use crate::system::flow::AppState;
+use crate::world::generation::generate_map;
+use crate::world::level_map::LevelMapBundle;
 
 pub fn spawn_player(mut spawner: EntitySpawner) {
 	log::info!("Spawning player");
-	spawner.spawn_player(uvec2(2, 2));
+	spawner.spawn_player(uvec2(25, 25));
+	spawner
+		.commands
+		.spawn_bundle(LevelMapBundle::generate(50, 50));
 }
 
 pub fn skip_menu(mut commands: Commands) {
diff --git a/game_core/src/entities/lifecycle.rs b/game_core/src/entities/lifecycle.rs
index 4d105b24723296807d15e7946b9c19f9827031e9..8007a1dbfe41240fc35766f76eacb0646b92bf90 100644
--- a/game_core/src/entities/lifecycle.rs
+++ b/game_core/src/entities/lifecycle.rs
@@ -1,4 +1,7 @@
 use bevy::prelude::*;
+use iyes_loopless::prelude::AppLooplessStateExt;
+
+use crate::system::flow::AppState;
 
 #[derive(Debug, Clone, Copy, Component)]
 pub struct GameEntity;
@@ -8,3 +11,10 @@ pub fn remove_game_entities(mut commands: Commands, query: Query<Entity, With<Ga
 		commands.entity(entity).despawn_recursive();
 	}
 }
+
+pub struct EntityLifecyclePlugin;
+impl Plugin for EntityLifecyclePlugin {
+	fn build(&self, app: &mut App) {
+		app.add_exit_system(AppState::InGame, remove_game_entities);
+	}
+}
diff --git a/game_core/src/entities/spawner.rs b/game_core/src/entities/spawner.rs
index ab539eaeff6c77f6fa5ec52ab0a03095d35b4cde..7d78cbd43f5cae102ad802866f953aa051128e1d 100644
--- a/game_core/src/entities/spawner.rs
+++ b/game_core/src/entities/spawner.rs
@@ -5,6 +5,7 @@ use crate::assets::AssetHandles;
 use crate::entities::lifecycle::GameEntity;
 use crate::system::camera::ChaseCam;
 use crate::system::graphics::LAYER_CREATURE;
+use crate::world::level_map::GridPosition;
 
 const PLAYER_SPRITE: usize = 2;
 
@@ -18,7 +19,10 @@ impl<'w, 's> EntitySpawner<'w, 's> {
 	pub fn spawn_player(&mut self, grid_position: UVec2) {
 		let mut entity = self.commands.spawn();
 
-		entity.insert(ChaseCam).insert(GameEntity);
+		entity
+			.insert(ChaseCam)
+			.insert(GameEntity)
+			.insert(GridPosition(grid_position));
 
 		entity.insert_bundle(SpriteSheetBundle {
 			texture_atlas: self.handles.atlas("creatures"),
diff --git a/game_core/src/lib.rs b/game_core/src/lib.rs
index aff889092a604f6530bdc59a3d05060465c8e162..9bf52a28ce185a0c746a0e95195eb59eeffd45ba 100644
--- a/game_core/src/lib.rs
+++ b/game_core/src/lib.rs
@@ -1,4 +1,5 @@
 pub mod assets;
+pub mod control;
 pub mod debug;
 pub mod entities;
 pub mod multiplayer;
diff --git a/game_core/src/main.rs b/game_core/src/main.rs
index 879c0037d6db3da1d399500c0de73ac2016df47f..bbe51c5948b1f5cb9158439cb9fecbce79d7c115 100644
--- a/game_core/src/main.rs
+++ b/game_core/src/main.rs
@@ -20,5 +20,6 @@ fn main() {
 			game_core::multiplayer::IncomingEvent,
 		>::new())
 		.add_plugin(game_core::debug::DebugPlugin)
+		.add_plugin(game_core::world::WorldPlugin)
 		.run();
 }
diff --git a/game_core/src/world/adjacency.rs b/game_core/src/world/adjacency.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f663ac7e25c20198f03a4f8a1693e6e72254f2a9
--- /dev/null
+++ b/game_core/src/world/adjacency.rs
@@ -0,0 +1,57 @@
+pub const NONE: u8 = 0b0000;
+
+pub const TOP: u8 = 0b0001;
+pub const BOTTOM: u8 = 0b0010;
+pub const LEFT: u8 = 0b0100;
+pub const RIGHT: u8 = 0b1000;
+
+pub const BOTTOM_RIGHT: u8 = BOTTOM | RIGHT;
+pub const BOTTOM_LEFT_RIGHT: u8 = BOTTOM | LEFT | RIGHT;
+pub const BOTTOM_LEFT: u8 = BOTTOM | LEFT;
+pub const TOP_BOTTOM_RIGHT: u8 = TOP | BOTTOM | RIGHT;
+pub const TOP_BOTTOM_LEFT_RIGHT: u8 = TOP | BOTTOM | LEFT | RIGHT;
+pub const TOP_BOTTOM_LEFT: u8 = TOP | BOTTOM | LEFT;
+pub const TOP_RIGHT: u8 = TOP | RIGHT;
+pub const TOP_LEFT_RIGHT: u8 = TOP | LEFT | RIGHT;
+pub const TOP_LEFT: u8 = TOP | LEFT;
+pub const TOP_BOTTOM: u8 = TOP | BOTTOM;
+pub const LEFT_RIGHT: u8 = LEFT | RIGHT;
+
+pub fn get_floor_sprite_offset(adjacency: u8) -> usize {
+	match adjacency {
+		BOTTOM_RIGHT => 0,
+		BOTTOM_LEFT_RIGHT => 1,
+		BOTTOM_LEFT => 2,
+		BOTTOM => 3,
+		TOP_BOTTOM_RIGHT => 64,
+		TOP_BOTTOM_LEFT_RIGHT => 64 + 1,
+		TOP_BOTTOM_LEFT => 64 + 2,
+		TOP_BOTTOM => 64 + 3,
+		RIGHT => 64 + 4,
+		LEFT_RIGHT => 64 + 5,
+		LEFT => 64 + 6,
+		TOP_RIGHT => 128,
+		TOP_LEFT_RIGHT => 128 + 1,
+		TOP_LEFT => 128 + 2,
+		TOP => 128 + 3,
+		NONE | _ => 5,
+	}
+}
+
+pub fn get_wall_sprite_offset(adjacency: u8) -> usize {
+	match adjacency {
+		BOTTOM_RIGHT => 0,
+		LEFT_RIGHT => 1,
+		BOTTOM_LEFT => 2,
+		BOTTOM_LEFT_RIGHT => 4,
+		TOP_BOTTOM | BOTTOM => 64,
+		NONE | TOP => 64 + 1,
+		TOP_BOTTOM_RIGHT => 64 + 3,
+		TOP_BOTTOM_LEFT_RIGHT => 64 + 4,
+		TOP_BOTTOM_LEFT => 64 + 5,
+		TOP_RIGHT | RIGHT => 128,
+		TOP_LEFT | LEFT => 128 + 2,
+		TOP_LEFT_RIGHT => 128 + 4,
+		_ => 3,
+	}
+}
diff --git a/game_core/src/world/generation.rs b/game_core/src/world/generation.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4f39d6b74238c9d647ebc1c9554132504f24445b
--- /dev/null
+++ b/game_core/src/world/generation.rs
@@ -0,0 +1,262 @@
+use fastrand::Rng;
+
+use crate::world::level_map::{Indexer, LevelMap, MapLayer, MapTile};
+
+const TMP_FLOOR_GROUP: usize = 6 * 64;
+const TMP_WALL_GROUP: usize = 3 * 64 + 28;
+
+#[derive(Copy, Clone, Debug, Default)]
+enum GenerationDirection {
+	Top,
+	Bottom,
+	Left,
+	Right,
+	#[default]
+	None,
+}
+
+pub fn generate_map(width: usize, height: usize) -> LevelMap {
+	generate_map_with_seed(fastrand::u64(0..u64::MAX), width, height)
+}
+
+pub fn generate_map_with_seed(seed: u64, width: usize, height: usize) -> LevelMap {
+	let rng = fastrand::Rng::with_seed(seed);
+	generate_map_with_rng(&rng, width, height)
+}
+
+pub fn generate_map_with_rng(rng: &Rng, width: usize, height: usize) -> LevelMap {
+	let indexer = Indexer::new(width, height);
+
+	let mut floor_layer = vec![0; width * height];
+	let mut wall_layer = vec![0; width * height];
+	let mut decoration_layer = vec![0; width * height];
+
+	let initial_x = width / 2;
+	let initial_y = width / 2;
+	let initial_width = rng.usize(3..=5);
+	let initial_height = rng.usize(3..=5);
+
+	draw_box(
+		initial_x - initial_width / 2,
+		initial_y - initial_height / 2,
+		initial_width,
+		initial_height,
+		TMP_FLOOR_GROUP,
+		&mut floor_layer,
+		&indexer,
+	);
+
+	for _ in 0..1000 {
+		let initial = find_next_door(1000, &floor_layer, &rng, &indexer);
+		if let Some((next_x, next_y, direction)) = initial {
+			let size_selection = rng.usize(1..=10);
+			let (room_width, room_height) = if size_selection < 8 {
+				(rng.usize(3..=6), rng.usize(3..=6))
+			} else {
+				(rng.usize(7..=12), rng.usize(7..=12))
+			};
+
+			// Align room drawing to top left corner of new box
+			match direction {
+				GenerationDirection::Top => {
+					let start_x = next_x - (room_width / 4);
+					let start_y = next_y + room_height;
+					draw_box(
+						start_x,
+						start_y,
+						room_width,
+						room_height,
+						TMP_FLOOR_GROUP,
+						&mut floor_layer,
+						&indexer,
+					);
+				}
+				GenerationDirection::Bottom => {
+					let start_x = next_x - (room_width / 4);
+					let start_y = next_y;
+					draw_box(
+						start_x,
+						start_y,
+						room_width,
+						room_height,
+						TMP_FLOOR_GROUP,
+						&mut floor_layer,
+						&indexer,
+					);
+				}
+				GenerationDirection::Left => {
+					let start_x = next_x - room_width;
+					let start_y = next_y;
+					draw_box(
+						start_x,
+						start_y,
+						room_width,
+						room_height,
+						TMP_FLOOR_GROUP,
+						&mut floor_layer,
+						&indexer,
+					);
+				}
+				GenerationDirection::Right => {
+					let start_x = next_x;
+					let start_y = next_y;
+					draw_box(
+						start_x,
+						start_y,
+						room_width,
+						room_height,
+						TMP_FLOOR_GROUP,
+						&mut floor_layer,
+						&indexer,
+					);
+				}
+				GenerationDirection::None => {}
+			}
+		}
+	}
+
+	LevelMap {
+		width,
+		height,
+		layers: vec![
+			MapLayer::from_sized_list(
+				width,
+				height,
+				floor_layer
+					.into_iter()
+					.map(|tile| match tile {
+						0 => None,
+						rest => Some(MapTile::new_floor(rest)),
+					})
+					.collect(),
+			),
+			MapLayer::from_sized_list(
+				width,
+				height,
+				wall_layer
+					.into_iter()
+					.map(|tile| match tile {
+						0 => None,
+						rest => Some(MapTile::new_wall(rest)),
+					})
+					.collect(),
+			),
+			MapLayer::from_sized_list(
+				width,
+				height,
+				decoration_layer
+					.into_iter()
+					.map(|tile| match tile {
+						0 => None,
+						rest => Some(MapTile::new_obstacle(rest)),
+					})
+					.collect(),
+			),
+		],
+	}
+}
+
+fn find_next_door(
+	max_attempts: usize,
+	floor: &Vec<usize>,
+	rng: &Rng,
+	idx: &Indexer,
+) -> Option<(usize, usize, GenerationDirection)> {
+	let mut remaining = max_attempts;
+	let mut found = None;
+	let mut last_direction = GenerationDirection::None;
+
+	while remaining > 0 && found.is_none() {
+		remaining = remaining.saturating_sub(1);
+
+		let target_index = rng.usize(0..floor.len());
+		let (x, y) = idx.reverse(target_index);
+
+		// Edges of the map are unsuitable for new rooms
+		if x == 0 || x == idx.width() - 1 || y == 0 || y == idx.height() - 1 {
+			continue;
+		}
+
+		let mut adjacents = 0;
+
+		// Check Left
+		if floor[idx.index(x - 1, y)] != 0 {
+			adjacents += 1;
+			last_direction = GenerationDirection::Right;
+		}
+		// Check Right
+		if floor[idx.index(x + 1, y)] != 0 {
+			adjacents += 1;
+			last_direction = GenerationDirection::Left;
+		}
+		// Check Top
+		if floor[idx.index(x, y + 1)] != 0 {
+			adjacents += 1;
+			last_direction = GenerationDirection::Bottom;
+		}
+		// Check Bottom
+		if floor[idx.index(x, y - 1)] != 0 {
+			adjacents += 1;
+			last_direction = GenerationDirection::Top;
+		}
+
+		if adjacents == 1 {
+			found = Some((x, y, last_direction));
+			break;
+		}
+	}
+
+	found
+}
+
+fn draw_box(
+	start_x: usize,
+	start_y: usize,
+	width: usize,
+	height: usize,
+	value: usize,
+	layer: &mut Vec<usize>,
+	indexer: &Indexer,
+) {
+	if !validate_box(start_x, start_y, width, height, layer, indexer) {
+		return;
+	}
+
+	for x in start_x..(start_x + width) {
+		for y in start_y..(start_y + height) {
+			let idx = indexer.index(x, y);
+			layer[idx] = value;
+		}
+	}
+}
+
+fn validate_box(
+	start_x: usize,
+	start_y: usize,
+	width: usize,
+	height: usize,
+	layer: &Vec<usize>,
+	indexer: &Indexer,
+) -> bool {
+	let map_width = indexer.width();
+	let map_height = indexer.height();
+
+	if start_y + height >= map_height {
+		return false;
+	}
+
+	if start_x + width >= map_width {
+		return false;
+	}
+
+	for x in start_x..(start_x + width) {
+		for y in start_y..(start_y + height) {
+			let idx = indexer.index(x, y);
+			if layer[idx] != 0 {
+				return false;
+			}
+		}
+	}
+
+	true
+}
diff --git a/game_core/src/world/handlers.rs b/game_core/src/world/handlers.rs
new file mode 100644
index 0000000000000000000000000000000000000000..14d0d582c86cd923e3f635a2b3b49c6685623149
--- /dev/null
+++ b/game_core/src/world/handlers.rs
@@ -0,0 +1,45 @@
+use bevy::math::uvec2;
+use bevy::prelude::*;
+
+use crate::assets::AssetHandles;
+use crate::entities::lifecycle::GameEntity;
+use crate::system::graphics::LAYER_TILE;
+use crate::world::adjacency::get_floor_sprite_offset;
+use crate::world::level_map::{GridPosition, Indexer, LevelMap, Tile, WORLD_TILE_SIZE};
+
+pub fn spawn_new_world(
+	mut commands: Commands,
+	assets: Res<AssetHandles>,
+	query: Query<(Entity, &LevelMap), Added<LevelMap>>,
+) {
+	for (entity, map) in &query {
+		let mut cmds = commands.entity(entity);
+		for (idx, layer) in map.layers.iter().enumerate() {
+			cmds.with_children(|layer_cmds| {
+				let indexer = Indexer::new(layer.width, layer.height);
+				for (idx, tile) in layer.tiles.iter().enumerate() {
+					if let Some(data) = tile {
+						let (x, y) = indexer.reverse(idx);
+						layer_cmds
+							.spawn_bundle(SpriteSheetBundle {
+								texture_atlas: assets.atlas("environs"),
+								sprite: TextureAtlasSprite::new(
+									data.tile_group
+										+ get_floor_sprite_offset(layer.get_tile_adjacency(x, y)),
+								),
+								transform: Transform::from_xyz(
+									x as f32 * WORLD_TILE_SIZE,
+									y as f32 * WORLD_TILE_SIZE,
+									LAYER_TILE + (idx as f32 / 100.0),
+								),
+								..Default::default()
+							})
+							.insert(GameEntity)
+							.insert(Tile)
+							.insert(GridPosition(uvec2(x as u32, y as u32)));
+					}
+				}
+			});
+		}
+	}
+}
diff --git a/game_core/src/world/level_map.rs b/game_core/src/world/level_map.rs
index b6e2ed7be7c06a9cd0cddae1967c7097f11f93ba..b674c1dd3496a4f722f7622256dda706a9259f8d 100644
--- a/game_core/src/world/level_map.rs
+++ b/game_core/src/world/level_map.rs
@@ -1,10 +1,19 @@
+use std::fmt::{Debug, Formatter};
 use std::ops::Deref;
 
 use bevy::math::UVec2;
 use bevy::prelude::*;
+use fastrand::Rng;
+
+use crate::world::adjacency::{BOTTOM, LEFT, NONE, RIGHT, TOP};
+use crate::world::generation::{generate_map, generate_map_with_rng, generate_map_with_seed};
 
 pub const WORLD_TILE_SIZE: f32 = 16.0;
 
+/// Marker component for any spawned tile entity
+#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Component)]
+pub struct Tile;
+
 /// Track the location of an entity within a grid
 #[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Component)]
 #[repr(transparent)]
@@ -24,3 +33,171 @@ pub fn sync_grid_to_transform(mut query: Query<(&GridPosition, &mut Transform)>)
 			.extend(transform.translation.z);
 	}
 }
+
+/// Store accessibility of a tile and the sprite group that the tile belongs to.
+/// Used for pathfinding, and for selecting which sprites should be considered as
+/// a group
+#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
+pub struct MapTile {
+	pub can_walk: bool,
+	pub can_fly: bool,
+	pub can_see: bool,
+	pub tile_group: usize,
+}
+
+impl MapTile {
+	/// Create a tile that can be passed by all entities
+	pub fn new_floor(tile_group: usize) -> MapTile {
+		MapTile {
+			can_walk: true,
+			can_fly: true,
+			can_see: true,
+			tile_group,
+		}
+	}
+
+	/// Create a tile that can be passed by no entities
+	pub fn new_wall(tile_group: usize) -> MapTile {
+		MapTile {
+			can_walk: false,
+			can_fly: false,
+			can_see: false,
+			tile_group,
+		}
+	}
+	/// Create a tile that can be passed by flying entities
+	/// and seen through by any entity
+	pub fn new_obstacle(tile_group: usize) -> MapTile {
+		MapTile {
+			can_walk: false,
+			can_fly: true,
+			can_see: true,
+			tile_group,
+		}
+	}
+}
+
+#[derive(Clone, Eq, PartialEq)]
+pub struct MapLayer {
+	pub width: usize,
+	pub height: usize,
+	pub offset_horizontal: usize,
+	pub offset_vertical: usize,
+	pub tiles: Vec<Option<MapTile>>,
+}
+
+impl MapLayer {
+	pub fn from_sized_list(width: usize, height: usize, tiles: Vec<Option<MapTile>>) -> Self {
+		MapLayer {
+			width,
+			height,
+			offset_horizontal: 0,
+			offset_vertical: 0,
+			tiles,
+		}
+	}
+
+	pub fn get_tile_adjacency(&self, x: usize, y: usize) -> u8 {
+		let indexer = Indexer::new(self.width, self.height);
+		let mut adjacency = NONE;
+
+		if x > 0 && self.tiles[indexer.index(x - 1, y)].is_some() {
+			adjacency = adjacency | LEFT;
+		}
+
+		if x < self.width - 1 && self.tiles[indexer.index(x + 1, y)].is_some() {
+			adjacency = adjacency | RIGHT;
+		}
+
+		if y > 0 && self.tiles[indexer.index(x, y - 1)].is_some() {
+			adjacency = adjacency | BOTTOM;
+		}
+
+		if y < self.height - 1 && self.tiles[indexer.index(x, y + 1)].is_some() {
+			adjacency = adjacency | TOP;
+		}
+
+		adjacency
+	}
+}
+
+impl Debug for MapLayer {
+	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+		writeln!(f, "---\nMap Layer: {}x{}", self.width, self.height)?;
+		let idx = Indexer::new(self.width, self.height);
+		for y in (0..self.height).rev() {
+			for x in 0..self.width {
+				write!(
+					f,
+					"{}",
+					self.tiles[idx.index(x, y)]
+						.map(|t| t.tile_group.to_string())
+						.unwrap_or(" ".to_string())
+				)?;
+			}
+			write!(f, "\n")?;
+		}
+
+		writeln!(f, "---")
+	}
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Component)]
+pub struct LevelMap {
+	pub width: usize,
+	pub height: usize,
+	pub layers: Vec<MapLayer>,
+}
+
+#[derive(Bundle)]
+pub struct LevelMapBundle {
+	level_map: LevelMap,
+	#[bundle]
+	transform: TransformBundle,
+	#[bundle]
+	visibility: VisibilityBundle,
+}
+
+impl LevelMapBundle {
+	pub fn new(level_map: LevelMap) -> Self {
+		Self {
+			level_map,
+			transform: TransformBundle::default(),
+			visibility: VisibilityBundle::default(),
+		}
+	}
+
+	pub fn generate(width: usize, height: usize) -> Self {
+		Self::new(generate_map(width, height))
+	}
+	pub fn generate_with_seed(seed: u64, width: usize, height: usize) -> Self {
+		Self::new(generate_map_with_seed(seed, width, height))
+	}
+	pub fn generate_with_rng(rng: &Rng, width: usize, height: usize) -> Self {
+		Self::new(generate_map_with_rng(rng, width, height))
+	}
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct Indexer {
+	width: usize,
+	height: usize,
+}
+
+impl Indexer {
+	pub fn new(width: usize, height: usize) -> Self {
+		Indexer { width, height }
+	}
+	pub fn index(&self, x: usize, y: usize) -> usize {
+		(y * self.width) + x
+	}
+	pub fn reverse(&self, index: usize) -> (usize, usize) {
+		(index / self.width, index % self.width)
+	}
+	pub fn width(&self) -> usize {
+		self.width
+	}
+	pub fn height(&self) -> usize {
+		self.height
+	}
+}
diff --git a/game_core/src/world/mod.rs b/game_core/src/world/mod.rs
index c379ed7d24b0e81501eff93d8ae2a4db3351a1ec..0997770ae74867bef99c1e8ee6cb3457d44ea0a5 100644
--- a/game_core/src/world/mod.rs
+++ b/game_core/src/world/mod.rs
@@ -1 +1,27 @@
+pub mod adjacency;
+pub mod generation;
+pub mod handlers;
 pub mod level_map;
+
+mod __internal {
+	use bevy::app::{App, CoreStage, Plugin};
+	use iyes_loopless::condition::ConditionSet;
+
+	use crate::system::flow::AppState;
+
+	pub struct WorldPlugin;
+	impl Plugin for WorldPlugin {
+		fn build(&self, app: &mut App) {
+			app.add_system_set_to_stage(
+				CoreStage::PostUpdate,
+				ConditionSet::new()
+					.run_in_state(AppState::InGame)
+					.with_system(super::handlers::spawn_new_world)
+					.with_system(super::level_map::sync_grid_to_transform)
+					.into(),
+			);
+		}
+	}
+}
+
+pub use __internal::WorldPlugin;