diff --git a/src/assets.rs b/src/assets.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2aaea5ad022e8800fcf66efa7e64b603e82764cf
--- /dev/null
+++ b/src/assets.rs
@@ -0,0 +1,113 @@
+use bevy::{prelude::*, sprite::TextureAtlas};
+
+use crate::{
+    audio::AudioAssets, grid::GridTextures, gridcursor::GridCursorTexture, machine::MachineTextures,
+};
+
+// textures
+pub const GRIDCURSOR_TEXTURE: TextureInfo =
+    TextureInfo::new("cursor_select_64x64.png", Vec2::new(64., 64.));
+pub const GRID_TEXTURE: TextureInfo = TextureInfo::new("ferris.png", Vec2::new(1024., 1024.));
+
+// atlases
+pub const WALL_ATLAS: TextureAtlasInfo = TextureAtlasInfo::new("wall.png", Vec2::new(8., 8.), 1, 1);
+pub const TURBINE_ATLAS: TextureAtlasInfo =
+    TextureAtlasInfo::new("turbine.png", Vec2::new(8., 8.), 1, 1);
+pub const CABLE_JOINT_ATLAS: TextureAtlasInfo =
+    TextureAtlasInfo::new("cable_joint.png", Vec2::new(8., 8.), 1, 10);
+pub const CABLE_SIDE_ATLAS: TextureAtlasInfo =
+    TextureAtlasInfo::new("cable_side.png", Vec2::new(8., 8.), 1, 2);
+
+// audio
+pub const THEME_AUDIO: AudioInfo = AudioInfo::new("theme.ogg");
+
+pub struct AssetLoadingPlugin;
+
+impl Plugin for AssetLoadingPlugin {
+    fn build(&self, app: &mut bevy::prelude::App) {
+        app.add_startup_system(asset_loading_system);
+    }
+}
+
+// loading logic
+fn asset_loading_system(
+    mut commands: Commands,
+    asset_server: Res<AssetServer>,
+    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
+) {
+    // machine texture
+    let machine_textures = MachineTextures {
+        wall: texture_atlases.add(WALL_ATLAS.to_texture_atlas(asset_server.as_ref())),
+        turbine: texture_atlases.add(TURBINE_ATLAS.to_texture_atlas(asset_server.as_ref())),
+        cable_joint: texture_atlases.add(CABLE_JOINT_ATLAS.to_texture_atlas(asset_server.as_ref())),
+        cable_side: texture_atlases.add(CABLE_SIDE_ATLAS.to_texture_atlas(asset_server.as_ref())),
+    };
+    commands.insert_resource(machine_textures);
+
+    // grid textures
+    let grid_stack_textures = GridTextures {
+        handles: Vec::from([asset_server.load(GRID_TEXTURE.path)]),
+    };
+    commands.insert_resource(grid_stack_textures);
+
+    // marker texture
+    let marker_texture = GridCursorTexture {
+        handle: asset_server.load(GRIDCURSOR_TEXTURE.path),
+    };
+    commands.insert_resource(marker_texture);
+
+    // audio assets
+    let audio_assets = AudioAssets {
+        theme_handle: asset_server.load(THEME_AUDIO.path),
+    };
+    commands.insert_resource(audio_assets);
+}
+
+// asset structs
+pub struct TextureInfo<'a> {
+    pub path: &'a str,
+    pub size: Vec2,
+}
+
+impl<'a> TextureInfo<'a> {
+    pub const fn new(path: &'a str, size: Vec2) -> Self {
+        Self { path, size }
+    }
+}
+
+pub struct TextureAtlasInfo<'a> {
+    pub path: &'a str,
+    pub size: Vec2,
+    pub columns: usize,
+    pub rows: usize,
+}
+
+impl<'a> TextureAtlasInfo<'a> {
+    pub const fn new(path: &'a str, size: Vec2, columns: usize, rows: usize) -> Self {
+        Self {
+            size,
+            path,
+            columns,
+            rows,
+        }
+    }
+
+    pub fn to_texture_atlas(self, asset_server: &AssetServer) -> TextureAtlas {
+        TextureAtlas::from_grid(
+            asset_server.load(self.path),
+            self.size,
+            self.columns,
+            self.rows,
+        )
+    }
+}
+
+pub struct AudioInfo<'a> {
+    pub path: &'a str,
+}
+
+impl<'a> AudioInfo<'a> {
+    pub const fn new(path: &'a str) -> Self {
+        Self { path }
+    }
+}
diff --git a/src/grid.rs b/src/grid.rs
index 2a8840e3dde680d020ca27a457330bf844077677..ee0cb23ea92fc391a22b482dd2506135996004a9 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -1,7 +1,8 @@
 use crate::{
+    assets::GRID_TEXTURE,
     components::{GridLayer, GridPos, Side},
     error::Error,
-    GRID_TEXTURE, RENDER_ORDER,
+    RENDER_ORDER,
 };
 
 use bevy::prelude::*;
diff --git a/src/gridcursor.rs b/src/gridcursor.rs
index 6a04e66fbaa842168ff986bc7fcce12e0b1e2ce8..8a5e569653c6302c8eba487d0dfe0bd138514de4 100644
--- a/src/gridcursor.rs
+++ b/src/gridcursor.rs
@@ -1,7 +1,8 @@
 use crate::{
+    assets::GRIDCURSOR_TEXTURE,
     components::{GridCursor, GridPos},
     machine::{Machine, MACHINE_SIZE},
-    GRIDCURSOR_TEXTURE, RENDER_ORDER,
+    RENDER_ORDER,
 };
 
 use bevy::prelude::*;
diff --git a/src/machine/cable.rs b/src/machine/cable.rs
index ceb01f3111ec7a735078fae51a9c8f803c51289a..b8b122d49f967d8a399f1160e94fe735d48867d5 100644
--- a/src/machine/cable.rs
+++ b/src/machine/cable.rs
@@ -1,8 +1,9 @@
 use crate::{
+    assets::CABLE_JOINT_ATLAS,
     components::{Cable, GridPos, Side},
     grid::Grid,
     power::{Drain, Resistance, Source},
-    CABLE_JOINT_ATLAS, RENDER_ORDER,
+    RENDER_ORDER,
 };
 
 use bevy::{ecs::query::WorldQuery, prelude::*};
@@ -126,7 +127,8 @@ fn populate_cable_sides(
         let to_side_index: usize;
 
         if cable.2.output.contains(&side)
-            && matches!(drains_query.get(neigh), Ok(v) if v.1.input.contains(&side.opposite())) // is_ok_and() would be so much prettier, "let's be stable mimimi"
+            && matches!(drains_query.get(neigh), Ok(v) if v.1.input.contains(&side.opposite()))
+        // is_ok_and() would be so much prettier, "let's be stable mimimi"
         {
             from_side_index = 1;
             to_side_index = 0;
diff --git a/src/machine/mod.rs b/src/machine/mod.rs
index 613a5d8435027f27902aa3fc480752aae281edc8..963fc849f4a0497c27c5db9dead5682f6f7e1bc7 100644
--- a/src/machine/mod.rs
+++ b/src/machine/mod.rs
@@ -1,7 +1,8 @@
 use crate::{
-    components::{Ghost, GridPos, Cable},
+    assets::{TURBINE_ATLAS, WALL_ATLAS},
+    components::{Cable, Ghost, GridPos},
     grid::{Grid, GRID_SIZE},
-    {RENDER_ORDER, TURBINE_ATLAS, WALL_ATLAS},
+    RENDER_ORDER,
 };
 
 use bevy::prelude::*;
@@ -133,11 +134,14 @@ fn add_gridpos_system(
                 });
             }
             Machine::Cable => {
-                commands.entity(entity).insert_bundle(MachineBundle {
-                    position: *grid_pos,
-                    machine: *machine,
-                    ..default()
-                }).insert(Cable);
+                commands
+                    .entity(entity)
+                    .insert_bundle(MachineBundle {
+                        position: *grid_pos,
+                        machine: *machine,
+                        ..default()
+                    })
+                    .insert(Cable);
             }
         }
     }
diff --git a/src/main.rs b/src/main.rs
index be6ef42ef6cf079f32ef1c30eda9eb7122cbaebd..7833625f26dc5465bb1a3c3895bcea4550eab445 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+mod assets;
 mod audio;
 mod components;
 mod error;
@@ -8,79 +9,18 @@ mod machine;
 mod power;
 
 use crate::{
-    audio::{AudioAssets, AudioPlugin},
+    audio::AudioPlugin,
     components::GridPos,
-    grid::{GridPlugin, GridTextures},
-    gridcursor::{GridCursorPlugin, GridCursorTexture},
+    grid::GridPlugin,
+    gridcursor::GridCursorPlugin,
     lighting::{LightEmitter, LightingPlugin},
-    machine::{MachinePlugin, MachineTextures},
+    machine::MachinePlugin,
     power::PowerPlugin,
 };
 
+use assets::AssetLoadingPlugin;
 use bevy::{prelude::*, render::texture::ImageSettings};
 
-pub const GRIDCURSOR_TEXTURE: TextureInfo =
-    TextureInfo::new("cursor_select_64x64.png", Vec2::new(64., 64.));
-pub const GRID_TEXTURE: TextureInfo = TextureInfo::new("ferris.png", Vec2::new(1024., 1024.));
-
-pub const WALL_ATLAS: TextureAtlasInfo = TextureAtlasInfo::new("wall.png", Vec2::new(8., 8.), 1, 1);
-pub const TURBINE_ATLAS: TextureAtlasInfo =
-    TextureAtlasInfo::new("turbine.png", Vec2::new(8., 8.), 1, 1);
-pub const CABLE_JOINT_ATLAS: TextureAtlasInfo =
-    TextureAtlasInfo::new("cable_joint.png", Vec2::new(8., 8.), 1, 10);
-pub const CABLE_SIDE_ATLAS: TextureAtlasInfo =
-    TextureAtlasInfo::new("cable_side.png", Vec2::new(8., 8.), 1, 2);
-
-pub struct TextureInfo<'a> {
-    pub path: &'a str,
-    pub size: Vec2,
-}
-
-impl<'a> TextureInfo<'a> {
-    pub const fn new(path: &'a str, size: Vec2) -> Self {
-        Self { path, size }
-    }
-}
-
-pub struct TextureAtlasInfo<'a> {
-    pub path: &'a str,
-    pub size: Vec2,
-    pub columns: usize,
-    pub rows: usize,
-}
-
-impl<'a> TextureAtlasInfo<'a> {
-    pub const fn new(path: &'a str, size: Vec2, columns: usize, rows: usize) -> Self {
-        Self {
-            size,
-            path,
-            columns,
-            rows,
-        }
-    }
-
-    pub fn to_texture_atlas(self, asset_server: &AssetServer) -> TextureAtlas {
-        TextureAtlas::from_grid(
-            asset_server.load(self.path),
-            self.size,
-            self.columns,
-            self.rows,
-        )
-    }
-}
-
-pub const THEME_AUDIO: AudioInfo = AudioInfo::new("theme.ogg");
-
-pub struct AudioInfo<'a> {
-    pub path: &'a str,
-}
-
-impl<'a> AudioInfo<'a> {
-    pub const fn new(path: &'a str) -> Self {
-        Self { path }
-    }
-}
-
 pub const RENDER_ORDER: RenderOrder = RenderOrder {
     grid: 0.0,
     machine: 1.0,
@@ -106,6 +46,7 @@ fn main() {
         .insert_resource(ImageSettings::default_nearest())
         .insert_resource(Msaa { samples: 1 })
         .add_plugins(DefaultPlugins)
+        .add_plugin(AssetLoadingPlugin)
         .add_startup_system(main_setup_system)
         .add_plugin(MachinePlugin)
         .add_plugin(GridPlugin)
@@ -116,42 +57,11 @@ fn main() {
         .run();
 }
 
-fn main_setup_system(
-    mut commands: Commands,
-    asset_server: Res<AssetServer>,
-    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
-) {
+fn main_setup_system(mut commands: Commands) {
     // camera
     commands.spawn_bundle(Camera2dBundle::default());
 
-    // machine texture
-    //asset_server.load(WALL_TEXTURE.path),
-    let machine_textures = MachineTextures {
-        wall: texture_atlases.add(WALL_ATLAS.to_texture_atlas(asset_server.as_ref())),
-        turbine: texture_atlases.add(TURBINE_ATLAS.to_texture_atlas(asset_server.as_ref())),
-        cable_joint: texture_atlases.add(CABLE_JOINT_ATLAS.to_texture_atlas(asset_server.as_ref())),
-        cable_side: texture_atlases.add(CABLE_SIDE_ATLAS.to_texture_atlas(asset_server.as_ref())),
-    };
-    commands.insert_resource(machine_textures);
-
-    // grid textures
-    let grid_stack_textures = GridTextures {
-        handles: Vec::from([asset_server.load(GRID_TEXTURE.path)]),
-    };
-    commands.insert_resource(grid_stack_textures);
-
-    // marker texture
-    let marker_texture = GridCursorTexture {
-        handle: asset_server.load(GRIDCURSOR_TEXTURE.path),
-    };
-    commands.insert_resource(marker_texture);
-
-    // audio assets
-    let audio_assets = AudioAssets {
-        theme_handle: asset_server.load("theme.ogg"),
-    };
-    commands.insert_resource(audio_assets);
-
+    // light
     commands
         .spawn()
         .insert(LightEmitter {