diff --git a/Cargo.lock b/Cargo.lock
index cbcf274a7251610986854b5185d1474c0dd9fba1..6b553a728cd5f9247dde3213b097a4195c83f013 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -132,12 +132,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "anyhow"
-version = "1.0.97"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
-
 [[package]]
 name = "approx"
 version = "0.5.1"
@@ -2235,17 +2229,16 @@ dependencies = [
 
 [[package]]
 name = "micro_ldtk"
-version = "0.14.0"
+version = "0.14.1"
 dependencies = [
- "anyhow",
  "bevy",
  "glam",
- "log",
  "micro_autotile",
  "num-traits",
  "quadtree_rs",
  "serde",
  "serde_json",
+ "test-case",
  "thiserror 2.0.12",
 ]
 
@@ -3379,6 +3372,39 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "test-case"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
+dependencies = [
+ "test-case-macros",
+]
+
+[[package]]
+name = "test-case-core"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
+dependencies = [
+ "cfg-if",
+ "proc-macro2 1.0.94",
+ "quote 1.0.40",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "test-case-macros"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
+dependencies = [
+ "proc-macro2 1.0.94",
+ "quote 1.0.40",
+ "syn 2.0.100",
+ "test-case-core",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.69"
diff --git a/Cargo.toml b/Cargo.toml
index f667095a1606f8aa8dddaa08b8303bd3044d30a7..7ab6a1b629b576a4d14a7213b41c5ce20c891441 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "micro_ldtk"
-version = "0.14.0"
+version = "0.14.1"
 edition = "2021"
 
 authors = [
@@ -12,10 +12,10 @@ license = "Apache-2.0"
 
 [features]
 default = ["ldtk_1_5_3", "autotile"]
-ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list"]
-ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup"]
-ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup"]
-ldtk_1_3_0 = ["_supports_ldtk"]
+ldtk_1_5_3 = ["_supports_ldtk", "_supports_intgridgroup", "_optional_tile_list", "_supports_ui_tags", "_supports_worlds"]
+ldtk_1_4_1 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"]
+ldtk_1_4_0 = ["_supports_ldtk", "_supports_intgridgroup", "_supports_worlds"]
+ldtk_1_3_0 = ["_supports_ldtk", "_supports_worlds"]
 ldtk_1_2_5 = ["_supports_ldtk"]
 ldtk_1_2_4 = ["_supports_ldtk"]
 ldtk_1_2_3 = ["_supports_ldtk"]
@@ -29,17 +29,17 @@ ldtk_1_1_0 = ["_supports_ldtk"]
 ldtk_1_0_0 = ["_supports_ldtk"]
 autotile = ["dep:micro_autotile"]
 bevy = ["dep:bevy"]
+_supports_ui_tags = []
 _supports_intgridgroup = []
 _supports_ldtk = []
+_supports_worlds = []
 _optional_tile_list = []
 no_panic = []
 
 [dependencies]
 bevy = { optional = true, version = "0.15", default-features = false, features = ["bevy_render", "bevy_sprite", "bevy_asset", "serialize"] }
 
-anyhow = "1.0"
 thiserror = "2.0"
-log = "0.4"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 num-traits = "0.2"
@@ -47,3 +47,5 @@ quadtree_rs = "0.1"
 micro_autotile = { version = "0.2", optional = true }
 glam = { version = "0.29", features = ["serde"] }
 
+[dev-dependencies]
+test-case = "3.3.1"
\ No newline at end of file
diff --git a/src/ldtk/mod.rs b/src/ldtk/mod.rs
index 09f4471204cc46e67bde6fe7c9c382bd51414466..53aa94e01d45273d1ba04bccfe0aed340928c492 100644
--- a/src/ldtk/mod.rs
+++ b/src/ldtk/mod.rs
@@ -224,24 +224,12 @@ impl Project {
 		}
 	}
 
-	#[cfg(any(
-		feature = "ldtk_1_2_5",
-		feature = "ldtk_1_2_4",
-		feature = "ldtk_1_2_3",
-		feature = "ldtk_1_2_2",
-		feature = "ldtk_1_2_1",
-		feature = "ldtk_1_2_0",
-		feature = "ldtk_1_1_3",
-		feature = "ldtk_1_1_2",
-		feature = "ldtk_1_1_1",
-		feature = "ldtk_1_1_0",
-		feature = "ldtk_1_0_0"
-	))]
+	#[cfg(not(feature = "_supports_worlds"))]
 	pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
 		vec![]
 	}
 
-	#[cfg(any(feature = "ldtk_1_3_0", feature = "ldtk_1_4_0", feature = "ldtk_1_4_1"))]
+	#[cfg(feature = "_supports_worlds")]
 	pub fn get_world_levels(&self, identifier: impl ToString) -> Vec<&Level> {
 		let id = identifier.to_string();
 		self.worlds
@@ -322,9 +310,12 @@ mod autotile_support {
 		fn from(value: &Project) -> Self {
 			let mut base_set = AutoRuleSet::default();
 
-			for layers in value.defs.layers.iter() {
-				for rule_group in layers.auto_rule_groups.iter() {
-					base_set = base_set + rule_group.into();
+			#[cfg(feature = "_supports_ui_tags")]
+			{
+				for layers in value.defs.layers.iter() {
+					for rule_group in layers.auto_rule_groups.iter() {
+						base_set = base_set + rule_group.into();
+					}
 				}
 			}
 
diff --git a/src/system/types.rs b/src/system/types.rs
index 5bd656887a193d6c312eb7a8a4f67fd443ba7ad5..b7497b8ed29bbc22584a28c225d57fc934fadc38 100644
--- a/src/system/types.rs
+++ b/src/system/types.rs
@@ -119,13 +119,16 @@ pub struct LdtkLevel {
 impl LdtkLevel {
 	pub fn from_project(project: &Project, level: Level) -> Self {
 		let mut level_data = LdtkLevel::from(level);
-		level_data.layers_mut().for_each(|layer| {
-			if let Some(def) = project.defs.layers.iter().find(|inner| {
-				inner.uid == layer.layer.layer_def_uid
-			}) {
-				layer.tags = def.ui_filter_tags.clone();
-			}
-		});
+		#[cfg(feature = "_supports_ui_tags")]
+		{
+			level_data.layers_mut().for_each(|layer| {
+				if let Some(def) = project.defs.layers.iter().find(|inner| {
+					inner.uid == layer.layer.layer_def_uid
+				}) {
+					layer.tags = def.ui_filter_tags.clone();
+				}
+			});
+		}
 		level_data
 	}
 	pub fn width(&self) -> f32 {
diff --git a/src/system/utils.rs b/src/system/utils.rs
index be47c5e2a84b6e62363713e43015222cbbc6700b..0b03580a841275a25c0489f9d02d34c3a43c5577 100644
--- a/src/system/utils.rs
+++ b/src/system/utils.rs
@@ -1,5 +1,6 @@
 #[cfg(feature = "bevy")]
-use bevy::prelude::{Component, IVec2, Resource};
+use bevy::prelude::{Component, Resource};
+use glam::IVec2;
 use num_traits::AsPrimitive;
 
 use crate::get_ldtk_tile_scale;
@@ -19,11 +20,11 @@ pub fn entity_centre(level_height: i64, entity: &EntityInstance) -> (f32, f32) {
 	(x as f32, y as f32)
 }
 
-#[cfg_attr(feature="bevy", derive(Component))]
+#[cfg_attr(feature = "bevy", derive(Component))]
 pub struct WorldLinked;
 
 #[derive(Default, Clone, Debug)]
-#[cfg_attr(feature="bevy", derive(Resource))]
+#[cfg_attr(feature = "bevy", derive(Resource))]
 pub struct ActiveLevel {
 	pub map: String,
 	pub dirty: bool,
@@ -53,7 +54,10 @@ impl Indexer {
 	}
 
 	pub fn index(&self, x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize {
-		((y.as_() * self.width) + x.as_()).as_()
+		match (self.width(), self.height()) {
+			(0, _) | (_, 0) => 0,
+            (w, _) => ((y.as_() * w) + x.as_()).as_(),
+		}
 	}
 
 	pub fn index_checked(
@@ -69,10 +73,14 @@ impl Indexer {
 	}
 
 	pub fn reverse(&self, index: impl AsPrimitive<i64>) -> (usize, usize) {
-		(
-			(index.as_() % self.width).max(0) as usize,
-			(index.as_() / self.width).max(0) as usize,
-		)
+		let index = index.as_();
+		match index {
+			0 => (0, 0),
+            _ => (
+				index.checked_rem(self.width).unwrap_or(0) as usize,
+				index.checked_div(self.width).unwrap_or(0) as usize,
+			)
+		}
 	}
 
 	pub fn width(&self) -> i64 {
@@ -89,7 +97,6 @@ impl Indexer {
 		x >= 0 && x < self.width && y >= 0 && y < self.height
 	}
 
-	#[cfg(feature = "bevy")]
 	/// Perform a transformation to flip a grid point (top down coordinates) into a render
 	/// point (bottom up coordinates)
 	pub fn flip_y(&self, point: IVec2) -> IVec2 {
diff --git a/tests/indexer.rs b/tests/indexer.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c20979ef41500c10fe5346e5955e25b1e03e8d39
--- /dev/null
+++ b/tests/indexer.rs
@@ -0,0 +1,88 @@
+use micro_ldtk::Indexer;
+use glam::IVec2;
+use num_traits::AsPrimitive;
+use test_case::test_case;
+
+#[test_case(0, 0 => 0)]
+#[test_case(4, 0 => 4)]
+#[test_case(0, 3 => 15)]
+#[test_case(4, 3 => 19)]
+#[test_case(2, 2 => 12)]
+#[test_case(1i32, 1i32 => 6)]
+#[test_case(2.5f32, 1.2f32 => 7)] // Floating point coordinates are truncated
+fn test_indexer_calculates_correct_index(x: impl AsPrimitive<i64>, y: impl AsPrimitive<i64>) -> usize {
+    let indexer = Indexer::new(5, 4);
+    indexer.index(x, y)
+}
+
+#[test_case(0 => (0, 0))]
+#[test_case(4 => (4, 0))]
+#[test_case(5 => (0, 1))]
+#[test_case(9 => (4, 1))]
+#[test_case(10 => (0, 2))]
+#[test_case(14 => (4, 2))]
+fn test_indexer_reverse(index: usize) -> (usize, usize) {
+    let indexer = Indexer::new(5, 3);
+    indexer.reverse(index)
+}
+
+#[test_case(0, 0)]
+#[test_case(4, 0)]
+#[test_case(0, 1)]
+#[test_case(2, 2)]
+#[test_case(4, 2)]
+fn test_index_is_reflexive(x: usize, y: usize) {
+    let indexer = Indexer::new(5, 3);
+    let idx = indexer.index(x, y);
+    let (x_rev, y_rev) = indexer.reverse(idx);
+
+    assert_eq!((x, y), (x_rev, y_rev));
+}
+
+#[test_case(5, -1)]
+#[test_case(5, 10)]
+#[test_case(10, 10)]
+#[test_case(10, 3)]
+#[test_case(-12, 3)]
+#[test_case(-00, 123_000_000)]
+fn test_indexer_index_checked_returns_none_for_out_of_bounds(x: i64, y: i64) {
+    let indexer = Indexer::new(10, 10);
+    assert_eq!(indexer.index_checked(x, y), None);
+}
+
+
+#[test]
+fn test_indexer_with_zero_dimensions() {
+
+    // Create an indexer with zero width
+    let zero_width_indexer = Indexer::new(0, 10);
+
+    assert_eq!(zero_width_indexer.width(), 0);
+    assert_eq!(zero_width_indexer.height(), 10);
+
+    // Zero width should make all x coordinates invalid
+    assert!(!zero_width_indexer.is_valid(0, 5));
+    assert_eq!(zero_width_indexer.index_checked(0, 5), None);
+
+    // Index calculation with zero width
+    assert_eq!(zero_width_indexer.index(2, 3), 0); // 3 * 0 + 2 = 0
+
+    // Reverse calculation with zero width
+    let (x, y) = zero_width_indexer.reverse(5);
+    assert_eq!(x, 0); // Any index % 0 is treated as 0 due to .max(0)
+    assert_eq!(y, 0); // Any index / 0 is treated as 0 due to .max(0)
+
+    // Create an indexer with zero height
+    let zero_height_indexer = Indexer::new(10, 0);
+    assert_eq!(zero_height_indexer.width(), 10);
+    assert_eq!(zero_height_indexer.height(), 0);
+
+    // Zero height should make all y coordinates invalid
+    assert!(!zero_height_indexer.is_valid(5, 0));
+    assert_eq!(zero_height_indexer.index_checked(5, 0), None);
+
+    // Test flip_y with zero height
+    let flipped = zero_height_indexer.flip_y(IVec2::new(5, 3));
+    assert_eq!(flipped, IVec2::new(5, -3)); // 0 - 3 = -3
+}
+