diff --git a/resources/critte.png b/resources/critte.png
new file mode 100644
index 0000000..ab1a661
Binary files /dev/null and b/resources/critte.png differ
diff --git a/resources/player.png b/resources/player.png
new file mode 100644
index 0000000..408275f
Binary files /dev/null and b/resources/player.png differ
diff --git a/resources/wall.png b/resources/wall.png
index 2187d61..b1d0ddd 100644
Binary files a/resources/wall.png and b/resources/wall.png differ
diff --git a/resources/wizard.png b/resources/wizard.png
index 4083189..75b923a 100644
Binary files a/resources/wizard.png and b/resources/wizard.png differ
diff --git a/src/core/character.h b/src/core/character.h
index ef3b955..e60c7db 100644
--- a/src/core/character.h
+++ b/src/core/character.h
@@ -26,17 +26,19 @@ struct CharacterData {
 };
 
 struct Character {
+	Character() = default;
 	Character(Tile location, CharacterLogic *logic, CharacterData stats);
 
 	void act();
 	void draw();
 	bool deal_damage(int damage);
 
-	CharacterData const data;
-	std::shared_ptr<CharacterLogic> logic{nullptr};
+	CharacterData data{};
 	int health{1};
 	Tile location{0, 0};
 	World *world{nullptr};
+private:
+	std::shared_ptr<CharacterLogic> logic{nullptr};
 };
 
 struct NullCharacterLogic : public CharacterLogic {
diff --git a/src/core/renderer.cpp b/src/core/renderer.cpp
index d8c800d..e8a92f8 100644
--- a/src/core/renderer.cpp
+++ b/src/core/renderer.cpp
@@ -77,14 +77,15 @@ void Render::provide_render_data(RenderData &data) {
 	Render::data = &data;
 }
 
-void Render::clear(Tile camera_center) {
+void Render::clear(Tile camera_center, unsigned fov) {
 	assert_render_initialized();
+	Render::camera_width = fov;
 	SDL_SetRenderDrawColor(Render::data->renderer, 0u, 0u, 0u, 255u);
 	SDL_RenderClear(Render::data->renderer);
 
 	int width, height;
 	SDL_GetWindowSize(Render::data->window, &width, &height);
-	Render::tile_screen_width = int(width / camera_width);
+	Render::tile_screen_width = int(width / Render::camera_width);
 	Render::half_tile_screen_width = Render::tile_screen_width >> 1;
 
 	Render::camera_center = camera_center;
@@ -94,6 +95,10 @@ void Render::clear(Tile camera_center) {
 
 void Render::draw(Tile world_space_tile, Sprite sprite) {
 	assert_render_initialized();
+	if(abs(world_space_tile.x - Render::camera_center.x) > Render::camera_width / 2
+	|| abs(world_space_tile.y - Render::camera_center.y) > Render::camera_width / 2) {
+		return;
+	}
 	SDL_Rect const rect{
 		.x = (world_space_tile.x * Render::tile_screen_width) - Render::half_tile_screen_width + Render::camera_offset.x,
 		.y = (world_space_tile.y * Render::tile_screen_width) - Render::half_tile_screen_width + Render::camera_offset.y,
diff --git a/src/core/renderer.h b/src/core/renderer.h
index e2e287b..0c7a1e8 100644
--- a/src/core/renderer.h
+++ b/src/core/renderer.h
@@ -29,7 +29,7 @@ private:
 public:
 	static void provide_render_data(RenderData &data);
 
-	static void clear(Tile camera_center);
+	static void clear(Tile camera_center, unsigned fov = 10);
 	static void draw(Tile world_space_tile, Sprite sprite);
 	static void present();
 private:
@@ -51,6 +51,7 @@ public:
 	RenderDataSetup &with_sprite(std::filesystem::path sprite_path);
 	RenderData build();
 private:
+	unsigned fov{10};
 	std::optional<std::filesystem::path> resource_base_path;
 	SDL_Window *window{nullptr};
 	SDL_Renderer *renderer{nullptr};
diff --git a/src/core/roguedefs.h b/src/core/roguedefs.h
index 257798a..336df9e 100644
--- a/src/core/roguedefs.h
+++ b/src/core/roguedefs.h
@@ -3,12 +3,14 @@
 
 #include "SDL2/SDL_rect.h"
 
-enum Directions : unsigned short {
-	NORTH = 0x1,
-	EAST = 0x2,
-	SOUTH = 0x4,
-	WEST = 0x8,
-};
+
+typedef unsigned short Directions;
+#define NORTH 0x1u
+#define EAST (0x1u << 1)
+#define SOUTH (0x1u << 2)
+#define WEST (0x1u << 3)
+
+#define WRAP(M_x, M_min, M_max) (((M_x - M_min) % (M_max - M_min)) + M_min)
 
 #define casel(M_switch, M_case)\
 case M_case:\
diff --git a/src/core/world.cpp b/src/core/world.cpp
index c9e598f..0d26d46 100644
--- a/src/core/world.cpp
+++ b/src/core/world.cpp
@@ -3,6 +3,7 @@
 #include "core/renderer.h"
 #include "core/roguedefs.h"
 #include <SDL2/SDL_log.h>
+#include <algorithm>
 #include <cassert>
 #include <cstdlib>
 #include <memory>
@@ -30,10 +31,10 @@ bool Room::tile_is_wall(Tile local_tile) const {
 		|| local_tile.y >= this->rect.y + this->rect.h-1};
 	int chunk_size2 = chunk_size/2;
 	bool const is_outside_hallways{
-		((this->hallway_paths & Directions::NORTH) == 0 || local_tile.x != chunk_size / 2 || local_tile.y > chunk_size2)
-		&& ((this->hallway_paths & Directions::SOUTH) == 0 || local_tile.x != chunk_size / 2 || local_tile.y < chunk_size2)
-		&& ((this->hallway_paths & Directions::WEST) == 0 || local_tile.y != chunk_size / 2 || local_tile.x < chunk_size2)
-		&& ((this->hallway_paths & Directions::EAST) == 0 || local_tile.y != chunk_size / 2 || local_tile.x > chunk_size2)
+		((this->hallway_paths & NORTH) == 0 || local_tile.x != chunk_size / 2 || local_tile.y > chunk_size2)
+		&& ((this->hallway_paths & SOUTH) == 0 || local_tile.x != chunk_size / 2 || local_tile.y < chunk_size2)
+		&& ((this->hallway_paths & WEST) == 0 || local_tile.y != chunk_size / 2 || local_tile.x > chunk_size2)
+		&& ((this->hallway_paths & EAST) == 0 || local_tile.y != chunk_size / 2 || local_tile.x < chunk_size2)
 	};
 	return is_outside_room && is_outside_hallways;
 }
@@ -47,13 +48,14 @@ World::World()
 } {}
 
 void World::act() {
+	player.act();
 	for(Character &character : this->characters) {
 		character.act();
 	}
 }
 
 void World::render() {
-	Render::clear(this->player->location);
+	Render::clear(this->player.location, 15);
 	for(size_t i{0}; i < this->rooms.size(); ++i) {
 		this->rooms[i].draw({
 			.x = int(i % this->shear * this->chunk_size),
@@ -63,20 +65,33 @@ void World::render() {
 	for(Character &character : this->characters) {
 		character.draw();
 	}
+	player.draw();
 }
 
 Room &World::get_room(Chunk chunk) {
 	return rooms[chunk.x + chunk.y * this->shear];
 }
 
+Character *World::get_player() {
+	return &this->player;
+}
+
+Chunk World::get_chunk(Tile tile) {
+	return {tile.x / this->chunk_size, tile.y / this->chunk_size};
+}
+
 TileData World::query_tile(Tile tile) {
 	Room &room{this->get_room({tile.x / this->chunk_size, tile.y / this->chunk_size})};
 	TileData out{
 		.character = nullptr,
 		.is_wall = room.tile_is_wall({tile.x % this->chunk_size, tile.y % this->chunk_size})
 	};
+	if(player.location == tile) {
+		out.character = &player;
+		return out;
+	}
 	for(Character &character : this->characters) {
-		if(character.location == tile) {
+		if(character.location == tile && character.health > 0) {
 			out.character = &character;
 			return out;
 		}
@@ -118,6 +133,16 @@ WorldGenerator &WorldGenerator::with_player(Character character) {
 	return *this;
 }
 
+WorldGenerator &WorldGenerator::with_enemy(std::function<Character(Tile)> spawner, int inv_frequency) {
+	this->enemy_spawners.push_back({spawner, inv_frequency});
+	return *this;
+}
+
+WorldGenerator &WorldGenerator::with_connecting_chance(unsigned num) {
+	this->connecting_chance = num;
+	return *this;
+}
+
 std::unique_ptr<World> WorldGenerator::generate() {
 	assert(this->chunk_side_length > 0 && "Chunk size is required to generate world");
 	assert(this->min_room_size > 0 && "Room size is required to generate world");
@@ -126,28 +151,12 @@ std::unique_ptr<World> WorldGenerator::generate() {
 	assert(this->player.has_value() && "World cannot be generated without a player"); 
 
 	std::unique_ptr<World> world{new World()};
-	Chunk start_room{this->select_start()};
+	Chunk start_room{this->world_side_length / 2, this->world_side_length / 2};
 	this->setup_player(world, start_room);
+	this->generate_world(world, start_room);
 	return world;
 }
 
-Chunk WorldGenerator::select_start() const {
-	Chunk start{0, 0};
-	switch(std::rand() % 4) {
-		case 0:
-			start.y = this->world_side_length - 1;
-		case 1:
-			start.x = std::rand() % this->world_side_length;
-			break;
-		case 2: 
-			start.x = this->world_side_length - 1;
-		case 3:
-			start.y = std::rand() % this->world_side_length;
-			break;
-	}
-	return start;
-}
-
 void WorldGenerator::generate_world(std::unique_ptr<World> &world, Chunk start) {
 	world->chunk_size = this->chunk_side_length;
 	world->shear = this->world_side_length;
@@ -157,48 +166,85 @@ void WorldGenerator::generate_world(std::unique_ptr<World> &world, Chunk start)
 			.hallway_paths = Directions(0x0),
 			.chunk_size = this->chunk_side_length,
 			.rect = {
-				(this->chunk_side_length - this->max_room_size) / 2,
-				(this->chunk_side_length - this->max_room_size) / 2,
-				this->max_room_size,
-				this->max_room_size
+				0,
+				0,
+				this->chunk_side_length,
+				this->chunk_side_length
 			}
 		};
 	}
+	world->get_room(start).rect = SDL_Rect {
+		.x = this->chunk_side_length/2-2,
+		.y = this->chunk_side_length/2-2,
+		.w = 5,
+		.h = 5
+	};
 	world->get_room(start).initialized = true;
 	this->generate_room(world, start);
 }
 
-void WorldGenerator::generate_room(std::unique_ptr<World> &world, Chunk last) {
-	static const Chunk options[]{
+void WorldGenerator::generate_room(std::unique_ptr<World> &world, Chunk const &last) {
+	Room &last_room{world->get_room(last)};
+	last_room.initialized = true;
+	// generate a room that encloses center tile of this chunk
+	last_room.rect.w = WRAP(std::rand(), this->min_room_size, this->max_room_size);
+	last_room.rect.x = WRAP(std::rand(),
+		std::max(this->chunk_side_length/2 - last_room.rect.w + 2, 0),
+		1 + std::min(this->chunk_side_length - last_room.rect.w - 1, this->chunk_side_length/2));
+	last_room.rect.h = WRAP(std::rand(), this->min_room_size, this->max_room_size);
+	last_room.rect.y = WRAP(std::rand(),
+		std::max(this->chunk_side_length/2 - last_room.rect.h + 2, 0),
+		1 + std::min(this->chunk_side_length - last_room.rect.h - 1, this->chunk_side_length/2));
+	const Chunk options[]{
 		{last.x + 1, last.y},
 		{last.x - 1, last.y},
 		{last.x, last.y + 1},
 		{last.x, last.y - 1}
 	};
-	static const Directions directions[]{
-		EAST, WEST, SOUTH, NORTH
-	};
+	this->add_enemies(world, last, last_room);
+	static const Directions directions[]{EAST, WEST, SOUTH, NORTH};
+	static const Directions inv_directions[] {WEST, EAST, NORTH, SOUTH};
+	int const rand_offset{std::rand() % 4};
 	for(size_t i{0}; i < 4; ++i) {
-		Chunk next{options[i]};
-		Room &option{world->get_room(next)};
-		if(options[i].x < 0 || options[i].x >= this->world_side_length
-		|| options[i].y < 0 || options[i].y >= this->world_side_length) {
-			continue;
-		} else if(option.initialized) {
+		size_t const true_offset{(i + rand_offset) % 4};
+		Chunk const &next{options[true_offset]};
+		if(next.x < 0 || next.x >= this->world_side_length
+		|| next.y < 0 || next.y >= this->world_side_length) {
 			continue;
 		}
-		// register hallway with the last chunk
-		option.hallway_paths = Directions(option.hallway_paths | directions[i]);
-		// generate random rect that encloses center of chunk
-		// add hallway to the new chunk
+		Room &option{world->get_room(next)};
+		if(this->connecting_chance > 0 && !option.initialized || WRAP(std::rand(), 0, this->connecting_chance) == 0) {
+			// register hallway with the last chunk
+			last_room.hallway_paths |= directions[true_offset];
+			// add returning hallway from the new chunk
+			option.hallway_paths |= inv_directions[true_offset];
+		}
+		if(option.initialized) {
+			continue;
+		}
+		// generate surrounding rooms
+		this->generate_room(world, next);
+	}
+}
+
+void WorldGenerator::add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room) {
+	for(std::pair<std::function<Character(Tile)>, int> const &pair : this->enemy_spawners) {
+		if(WRAP(std::rand(), 0, pair.second) == 0) {
+			Tile offset{
+				WRAP(std::rand(), 1, room.rect.w - 2) + (chunk.x * this->chunk_side_length),
+				WRAP(std::rand(), 1, room.rect.h - 2) + (chunk.y * this->chunk_side_length)
+			};
+			Character character{pair.first(offset)};
+			character.world = world.get();
+			world->characters.push_back(character);
+		}
 	}
 }
 
 void WorldGenerator::setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk) {
-	world->characters.push_back(player.value());
-	world->player = &world->characters[0];
-	world->player->world = world.get();
-	world->player->location = {
+	world->player = this->player.value();
+	world->player.world = world.get();
+	world->player.location = {
 		(spawn_chunk.x * this->chunk_side_length) + (this->chunk_side_length / 2),
 		(spawn_chunk.y * this->chunk_side_length) + (this->chunk_side_length / 2),
 	};
diff --git a/src/core/world.h b/src/core/world.h
index 1245131..a213722 100644
--- a/src/core/world.h
+++ b/src/core/world.h
@@ -2,6 +2,7 @@
 #define ROGUE_WORLD_H
 
 #include <optional>
+#include <utility>
 #include <vector>
 #include <SDL2/SDL_rect.h>
 #include "character.h"
@@ -34,16 +35,17 @@ struct World {
 	void act();
 	void render();
 	Room &get_room(Chunk chunk);
+	Character *get_player();
+	Chunk get_chunk(Tile tile);
 	TileData query_tile(Tile tile);
 	void on_input(bool);
 private:
 	int chunk_size{1};
 	unsigned shear{0};
-	Character *player{nullptr};
+	Character player{};
 	std::vector<Room> rooms{};
 	std::vector<Character> characters{};
 	KeyEventHandler direction_pressed;
-private:
 };
 
 class WorldGenerator {
@@ -53,16 +55,20 @@ public:
 	WorldGenerator &with_world_size(unsigned side_length);
 	WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
 	WorldGenerator &with_player(Character character);
+	WorldGenerator &with_enemy(std::function<Character(Tile)> spawner, int inv_frequency);
+	WorldGenerator &with_connecting_chance(unsigned num);
 	std::unique_ptr<World> generate();
 private:
-	Chunk select_start() const;
 	void generate_world(std::unique_ptr<World> &world, Chunk start);
-	void generate_room(std::unique_ptr<World> &world, Chunk last);
+	void generate_room(std::unique_ptr<World> &world, Chunk const &last);
+	void add_enemies(std::unique_ptr<World> &world, Chunk const &chunk, Room &room);
 	void setup_player(std::unique_ptr<World> &world, Chunk spawn_chunk);
 private:
 	std::optional<Character> player;
+	std::vector<std::pair<std::function<Character(Tile)>, int>> enemy_spawners{};
 	int chunk_side_length{0}, world_side_length{0};
 	int max_room_size{0}, min_room_size{0};
+	unsigned connecting_chance{1};
 };
 }
 
diff --git a/src/enemies.cpp b/src/enemies.cpp
new file mode 100644
index 0000000..1339e4e
--- /dev/null
+++ b/src/enemies.cpp
@@ -0,0 +1,48 @@
+#include "enemies.h"
+#include "core/roguedefs.h"
+#include "core/world.h"
+#include <functional>
+
+namespace rogue {
+Tile CritteLogic::move() {
+	Tile location{this->character->location};
+	Character *player{this->character->world->get_player()};
+	if(this->is_aware_of_player) {
+		// cache query_tile call for repeated use
+		std::function<TileData(Tile)> query_tile{std::bind(&World::query_tile, this->character->world, std::placeholders::_1)};
+		Tile difference{
+			player->location.x - location.x,
+			player->location.y - location.y
+		};
+		Tile abs{std::abs(difference.x), std::abs(difference.y)};
+		Tile direction{
+			difference.x == 0 ? 0 : difference.x / std::abs(difference.x),
+			difference.y == 0 ? 0 : difference.y / std::abs(difference.y)
+		};
+		Tile x_motion{location.x + direction.x, location.y};
+		Tile y_motion{location.x, location.y + direction.y};
+		if(abs.x > abs.y) {
+			if(!query_tile(x_motion).is_wall)
+				return x_motion;
+			else if(!query_tile(y_motion).is_wall) {
+				return y_motion;
+			}
+		} else {
+			if(!query_tile(y_motion).is_wall) {
+				return y_motion;
+			} else if(!query_tile(x_motion).is_wall) {
+				return x_motion;
+			}
+		}
+	} else {
+		// cache get_chunk call for easier reading
+		std::function<Chunk(Tile)> get_chunk{std::bind(&World::get_chunk, this->character->world, std::placeholders::_1)};
+		this->is_aware_of_player |= get_chunk(player->location) != get_chunk(location);
+	}
+	// fallback return value, for if there is no desirable tile to move to
+	return {
+		.x = location.x,
+		.y = location.y
+	};
+}
+}
diff --git a/src/enemies.h b/src/enemies.h
new file mode 100644
index 0000000..7e97d96
--- /dev/null
+++ b/src/enemies.h
@@ -0,0 +1,13 @@
+#ifndef ENEMIES_H
+#define ENEMIES_H
+
+#include "core/character.h"
+
+namespace rogue {
+class CritteLogic : public CharacterLogic {
+	virtual Tile move() override;
+	bool is_aware_of_player{false};
+};
+}
+
+#endif // !ENEMIES_H
diff --git a/src/main.cpp b/src/main.cpp
index dd5a620..c6137aa 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,23 +4,36 @@
 #include "core/input.h"
 #include "core/renderer.h"
 #include "core/world.h"
+#include "enemies.h"
 #include "player_logic.h"
 
 using namespace rogue;
 
+CharacterData const player_data{
+	.health = 20,
+	.damage = 2,
+	.sprite = 3
+};
+
+CharacterData const critte_data{
+	.health = 5,
+	.damage = 1,
+	.sprite = 2
+};
+
+template <typename TLogicType>
+Character create_character(CharacterData const &stats, Tile tile) {
+	return Character(tile, new TLogicType, stats);
+}
+
 int main(int argc, char *argv[]) {
 	std::unique_ptr<World> world{WorldGenerator()
-		.with_world_size(5)
-		.with_chunk_size(9)
-		.with_room_size(2, 5)
-		.with_player(Character({9/2, 9/2},
-			new PlayerCharacterLogic(),
-			CharacterData {
-				.health = 1,
-				.damage = 1,
-				.sprite = 0
-			}
-		))
+		.with_world_size(20)
+		.with_chunk_size(10)
+		.with_room_size(6, 10)
+		.with_connecting_chance(5)
+		.with_player(Character({0,0}, new PlayerCharacterLogic(), player_data))
+		.with_enemy(std::bind(&create_character<CritteLogic>, critte_data, std::placeholders::_1), 2)
 		.generate()
 	};
 	RenderData data{RenderDataSetup()
@@ -29,6 +42,8 @@ int main(int argc, char *argv[]) {
 		.with_resource_path("resources/")
 		.with_sprite("wizard.png")
 		.with_sprite("wall.png")
+		.with_sprite("critte.png")
+		.with_sprite("player.png")
 		.build()
 	};
 	Render::provide_render_data(data);