Browse Source

introduce clang format and run it on all the files

djeada 2 months ago
parent
commit
12b6f21791
100 changed files with 5823 additions and 5228 deletions
  1. 61 0
      .clang_format
  2. 34 0
      CMakeLists.txt
  3. 40 23
      Makefile
  4. 668 612
      app/game_engine.cpp
  5. 155 132
      app/game_engine.h
  6. 67 49
      app/selected_units_model.cpp
  7. 17 10
      app/selected_units_model.h
  8. 1 5
      game/core/component.cpp
  9. 70 65
      game/core/component.h
  10. 0 3
      game/core/entity.cpp
  11. 39 43
      game/core/entity.h
  12. 1 5
      game/core/event_manager.cpp
  13. 31 32
      game/core/event_manager.h
  14. 85 83
      game/core/serialization.cpp
  15. 10 10
      game/core/serialization.h
  16. 1 5
      game/core/system.cpp
  17. 2 2
      game/core/system.h
  18. 17 20
      game/core/world.cpp
  19. 25 27
      game/core/world.h
  20. 24 17
      game/map/environment.cpp
  21. 14 6
      game/map/environment.h
  22. 94 70
      game/map/level_loader.cpp
  23. 30 19
      game/map/level_loader.h
  24. 20 23
      game/map/map_definition.h
  25. 89 73
      game/map/map_loader.cpp
  26. 2 2
      game/map/map_loader.h
  27. 104 78
      game/map/map_transformer.cpp
  28. 19 9
      game/map/map_transformer.h
  29. 398 407
      game/systems/ai_system.cpp
  30. 109 101
      game/systems/ai_system.h
  31. 28 23
      game/systems/arrow_system.cpp
  32. 15 13
      game/systems/arrow_system.h
  33. 29 19
      game/systems/camera_controller.cpp
  34. 15 9
      game/systems/camera_controller.h
  35. 47 41
      game/systems/camera_follow_system.cpp
  36. 23 11
      game/systems/camera_follow_system.h
  37. 176 176
      game/systems/combat_system.cpp
  38. 10 5
      game/systems/combat_system.h
  39. 47 40
      game/systems/command_service.cpp
  40. 17 15
      game/systems/command_service.h
  41. 20 16
      game/systems/formation_planner.h
  42. 81 84
      game/systems/movement_system.cpp
  43. 10 6
      game/systems/movement_system.h
  44. 105 108
      game/systems/pathfinding.cpp
  45. 27 25
      game/systems/pathfinding.h
  46. 85 84
      game/systems/patrol_system.cpp
  47. 4 4
      game/systems/patrol_system.h
  48. 289 208
      game/systems/picking_service.cpp
  49. 40 36
      game/systems/picking_service.h
  50. 72 53
      game/systems/production_service.cpp
  51. 28 24
      game/systems/production_service.h
  52. 49 41
      game/systems/production_system.cpp
  53. 5 3
      game/systems/production_system.h
  54. 25 32
      game/systems/selection_system.cpp
  55. 19 13
      game/systems/selection_system.h
  56. 49 36
      game/units/archer.cpp
  57. 8 6
      game/units/archer.h
  58. 49 46
      game/units/barracks.cpp
  59. 8 5
      game/units/barracks.h
  60. 11 7
      game/units/factory.cpp
  61. 21 13
      game/units/factory.h
  62. 43 30
      game/units/unit.cpp
  63. 42 35
      game/units/unit.h
  64. 13 8
      game/visuals/team_colors.h
  65. 80 59
      game/visuals/visual_catalog.cpp
  66. 18 14
      game/visuals/visual_catalog.h
  67. 60 55
      main.cpp
  68. 2 6
      render/draw_queue.cpp
  69. 55 45
      render/draw_queue.h
  70. 443 427
      render/entity/archer_renderer.cpp
  71. 2 3
      render/entity/archer_renderer.h
  72. 35 28
      render/entity/arrow_vfx_renderer.cpp
  73. 14 5
      render/entity/arrow_vfx_renderer.h
  74. 188 198
      render/entity/barracks_renderer.cpp
  75. 7 3
      render/entity/barracks_renderer.h
  76. 14 12
      render/entity/registry.cpp
  77. 26 16
      render/entity/registry.h
  78. 108 101
      render/geom/arrow.cpp
  79. 5 6
      render/geom/arrow.h
  80. 24 29
      render/geom/flag.cpp
  81. 17 32
      render/geom/flag.h
  82. 64 73
      render/geom/patrol_flags.cpp
  83. 9 16
      render/geom/patrol_flags.h
  84. 25 23
      render/geom/selection_disc.cpp
  85. 3 2
      render/geom/selection_disc.h
  86. 32 27
      render/geom/selection_ring.cpp
  87. 3 3
      render/geom/selection_ring.h
  88. 148 132
      render/gl/backend.cpp
  89. 58 45
      render/gl/backend.h
  90. 17 16
      render/gl/bootstrap.cpp
  91. 5 5
      render/gl/bootstrap.h
  92. 60 62
      render/gl/buffer.cpp
  93. 29 37
      render/gl/buffer.h
  94. 200 185
      render/gl/camera.cpp
  95. 81 86
      render/gl/camera.h
  96. 102 119
      render/gl/mesh.cpp
  97. 22 22
      render/gl/mesh.h
  98. 12 12
      render/gl/resources.cpp
  99. 15 16
      render/gl/resources.h
  100. 103 102
      render/gl/shader.cpp

+ 61 - 0
.clang_format

@@ -0,0 +1,61 @@
+# .clang-format
+BasedOnStyle: LLVM
+Language: Cpp
+
+# Black-like "one obvious way"
+IndentWidth: 4
+TabWidth: 4
+UseTab: Never
+ColumnLimit: 88
+
+# Keep things broken over lines instead of packed
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+BinPackArguments: false
+BinPackParameters: false
+
+# Braces & breaking
+BreakBeforeBraces: Attach
+BreakBeforeBinaryOperators: NonAssignment
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+BreakConstructorInitializers: BeforeComma
+
+# No pretty alignment games
+AlignOperands: false
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+DerivePointerAlignment: false
+PointerAlignment: Left
+
+# Namespaces & comments
+FixNamespaceComments: true
+NamespaceIndentation: All
+ReflowComments: true
+BreakStringLiterals: true
+
+# Deterministic include sorting & grouping (project first, then libs)
+SortIncludes: true
+IncludeBlocks: Regroup
+IncludeIsMainRegex: '(^|.*/)([^/]+)\.(h|hpp)'
+IncludeCategories:
+  - Regex: '^"(app|game|render|tools|ui|engine_core|game_systems|render_gl)/'
+    Priority: 1
+  - Regex: '^<Qt/'
+    Priority: 2
+  - Regex: '^<GL/|^<OpenGL/'
+    Priority: 3
+  - Regex: '^<.*>'
+    Priority: 4
+  - Regex: '^".*"'
+    Priority: 5
+
+# Parens & spacing
+SpaceBeforeParens: ControlStatements
+SpacesBeforeTrailingComments: 1
+
+# Templates
+AlwaysBreakTemplateDeclarations: Yes
+

+ 34 - 0
CMakeLists.txt

@@ -122,3 +122,37 @@ endif()
 
 # Copy assets next to the binary for dev runs
 file(COPY assets/ DESTINATION ${CMAKE_BINARY_DIR}/assets/)
+
+# ---- clang-format helpers (optional but convenient) ----
+# Provides:
+#   - clang-format        : formats all C/C++ sources using .clang-format
+#   - clang-format-check  : CI-style check (no changes), fails on violations
+find_program(CLANG_FORMAT_EXE NAMES clang-format)
+if (CLANG_FORMAT_EXE)
+  message(STATUS "Found clang-format: ${CLANG_FORMAT_EXE}")
+
+  file(GLOB_RECURSE ALL_CXX CONFIGURE_DEPENDS
+       "${CMAKE_SOURCE_DIR}/*.c"   "${CMAKE_SOURCE_DIR}/*.cpp"
+       "${CMAKE_SOURCE_DIR}/*.h"   "${CMAKE_SOURCE_DIR}/*.hpp")
+
+  # Exclude the current build tree (generated sources, moc_*.cpp, etc.)
+  list(FILTER ALL_CXX EXCLUDE REGEX "${CMAKE_BINARY_DIR}/.*")
+
+  if (ALL_CXX)
+    add_custom_target(clang-format
+      COMMAND "${CLANG_FORMAT_EXE}" -i --style=file ${ALL_CXX}
+      WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+      COMMENT "Running clang-format with .clang-format (strict)")
+
+    add_custom_target(clang-format-check
+      COMMAND "${CLANG_FORMAT_EXE}" --dry-run -Werror --style=file ${ALL_CXX}
+      WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+      COMMENT "Checking clang-format compliance")
+  else()
+    add_custom_target(clang-format
+      COMMAND "${CMAKE_COMMAND}" -E echo "No C/C++ files found to format.")
+    add_custom_target(clang-format-check
+      COMMAND "${CMAKE_COMMAND}" -E echo "No C/C++ files found to check.")
+  endif()
+endif()
+

+ 40 - 23
Makefile

@@ -9,6 +9,10 @@ BUILD_DIR := build
 BINARY_NAME := standard_of_iron
 MAP_EDITOR_BINARY := map_editor
 
+# Formatting config
+CLANG_FORMAT ?= clang-format
+FMT_GLOBS := -name "*.cpp" -o -name "*.c" -o -name "*.h" -o -name "*.hpp"
+
 # Colors for output
 BOLD := \033[1m
 GREEN := \033[32m
@@ -23,18 +27,19 @@ help:
 	@echo "$(BOLD)Standard of Iron - Build System$(RESET)"
 	@echo ""
 	@echo "$(BOLD)Available targets:$(RESET)"
-	@echo "  $(GREEN)install$(RESET)     - Install all dependencies"
-	@echo "  $(GREEN)configure$(RESET)   - Configure build with CMake"
-	@echo "  $(GREEN)build$(RESET)       - Build the project"
-	@echo "  $(GREEN)run$(RESET)         - Run the main application"
-	@echo "  $(GREEN)editor$(RESET)      - Run the map editor"
-	@echo "  $(GREEN)clean$(RESET)       - Clean build directory"
-	@echo "  $(GREEN)rebuild$(RESET)     - Clean and build"
-	@echo "  $(GREEN)test$(RESET)        - Run tests (if any)"
-	@echo "  $(GREEN)format$(RESET)      - Format code and strip comments in all dirs"
-	@echo "  $(GREEN)check-deps$(RESET)  - Check if dependencies are installed"
-	@echo "  $(GREEN)dev$(RESET)         - Set up development environment (install + configure + build)"
-	@echo "  $(GREEN)all$(RESET)         - Full build (configure + build)"
+	@echo "  $(GREEN)install$(RESET)       - Install all dependencies"
+	@echo "  $(GREEN)configure$(RESET)     - Configure build with CMake"
+	@echo "  $(GREEN)build$(RESET)         - Build the project"
+	@echo "  $(GREEN)run$(RESET)           - Run the main application"
+	@echo "  $(GREEN)editor$(RESET)        - Run the map editor"
+	@echo "  $(GREEN)clean$(RESET)         - Clean build directory"
+	@echo "  $(GREEN)rebuild$(RESET)       - Clean and build"
+	@echo "  $(GREEN)test$(RESET)          - Run tests (if any)"
+	@echo "  $(GREEN)format$(RESET)        - Strip comments then clang-format (strict)"
+	@echo "  $(GREEN)format-check$(RESET)  - Verify formatting (CI-friendly, no changes)"
+	@echo "  $(GREEN)check-deps$(RESET)    - Check if dependencies are installed"
+	@echo "  $(GREEN)dev$(RESET)           - Set up development environment (install + configure + build)"
+	@echo "  $(GREEN)all$(RESET)           - Full build (configure + build)"
 	@echo ""
 	@echo "$(BOLD)Examples:$(RESET)"
 	@echo "  make install    # Install dependencies"
@@ -133,17 +138,10 @@ test: build
 		echo "$(YELLOW)No tests found. Test suite not yet implemented.$(RESET)"; \
 	fi
 
-# Format code (clang-format) THEN strip comments in ui/ via your script
-.PHONY: format
+# ---- Formatting: strip comments first, then clang-format (strict) ----
+.PHONY: format format-check
 format:
-	@echo "$(BOLD)$(BLUE)Formatting code...$(RESET)"
-	@if command -v clang-format >/dev/null 2>&1; then \
-		find . -type f \( -name "*.cpp" -o -name "*.c" -o -name "*.h" -o -name "*.hpp" \) -not -path "./$(BUILD_DIR)/*" -exec clang-format -i {} +; \
-		echo "$(GREEN)✓ Code formatted$(RESET)"; \
-	else \
-		echo "$(YELLOW)clang-format not found. Skipping code formatting.$(RESET)"; \
-	fi
-	@echo "$(BOLD)$(BLUE)Stripping comments in app/... game/... render/... tools/... ui/... $(RESET)"
+	@echo "$(BOLD)$(BLUE)Stripping comments in app/... game/... render/... tools/... ui/...$(RESET)"
 	@if [ -x scripts/remove-comments.sh ]; then \
 		./scripts/remove-comments.sh app/ game/ render/ tools/ ui/; \
 	elif [ -f scripts/remove-comments.sh ]; then \
@@ -151,7 +149,25 @@ format:
 	else \
 		echo "$(RED)scripts/remove-comments.sh not found$(RESET)"; exit 1; \
 	fi
-	@echo "$(GREEN)✓ Format + comment strip complete$(RESET)"
+	@echo "$(BOLD)$(BLUE)Formatting with clang-format (strict)...$(RESET)"
+	@if command -v $(CLANG_FORMAT) >/dev/null 2>&1; then \
+		find . -type f \( $(FMT_GLOBS) \) -not -path "./$(BUILD_DIR)/*" -print0 \
+		| xargs -0 -r $(CLANG_FORMAT) -i --style=file; \
+		echo "$(GREEN)✓ Format + comment strip complete$(RESET)"; \
+	else \
+		echo "$(RED)clang-format not found. Please install it.$(RESET)"; exit 1; \
+	fi
+
+# CI/verification: fail if anything would be reformatted
+format-check:
+	@echo "$(BOLD)$(BLUE)Checking clang-format compliance...$(RESET)"
+	@if command -v $(CLANG_FORMAT) >/dev/null 2>&1; then \
+		find . -type f \( $(FMT_GLOBS) \) -not -path "./$(BUILD_DIR)/*" -print0 \
+		| xargs -0 -r $(CLANG_FORMAT) --dry-run -Werror --style=file; \
+		echo "$(GREEN)✓ Formatting OK$(RESET)"; \
+	else \
+		echo "$(RED)clang-format not found. Please install it.$(RESET)"; exit 1; \
+	fi
 
 # Debug build
 .PHONY: debug
@@ -193,3 +209,4 @@ quickstart:
 	@echo "3. Run the game: $(BLUE)make run$(RESET)"
 	@echo ""
 	@echo "Or use the shortcut: $(BLUE)make dev$(RESET)"
+

+ 668 - 612
app/game_engine.cpp

@@ -1,746 +1,802 @@
 #include "game_engine.h"
 
-#include <QQuickWindow>
-#include <QOpenGLContext>
+#include <QCoreApplication>
+#include <QCursor>
 #include <QDebug>
+#include <QOpenGLContext>
+#include <QQuickWindow>
 #include <QVariant>
-#include <QCursor>
-#include <QCoreApplication>
 
-#include "game/core/world.h"
 #include "game/core/component.h"
-#include "render/scene_renderer.h"
-#include "render/gl/camera.h"
-#include "render/gl/resources.h"
-#include "render/ground/ground_renderer.h"
-#include "render/geom/arrow.h"
-#include "render/geom/patrol_flags.h"
-#include "render/gl/bootstrap.h"
+#include "game/core/world.h"
 #include "game/map/level_loader.h"
-#include "game/systems/movement_system.h"
-#include "game/systems/combat_system.h"
 #include "game/systems/ai_system.h"
-#include "game/systems/patrol_system.h"
-#include "game/systems/selection_system.h"
 #include "game/systems/arrow_system.h"
-#include "game/systems/production_system.h"
-#include "game/systems/picking_service.h"
-#include "game/systems/formation_planner.h"
+#include "game/systems/camera_controller.h"
+#include "game/systems/camera_follow_system.h"
+#include "game/systems/combat_system.h"
 #include "game/systems/command_service.h"
+#include "game/systems/formation_planner.h"
+#include "game/systems/movement_system.h"
+#include "game/systems/patrol_system.h"
+#include "game/systems/picking_service.h"
 #include "game/systems/production_service.h"
-#include "game/systems/camera_follow_system.h"
-#include "game/systems/camera_controller.h"
+#include "game/systems/production_system.h"
+#include "game/systems/selection_system.h"
+#include "render/geom/arrow.h"
+#include "render/geom/patrol_flags.h"
+#include "render/gl/bootstrap.h"
+#include "render/gl/camera.h"
+#include "render/gl/resources.h"
+#include "render/ground/ground_renderer.h"
+#include "render/scene_renderer.h"
 
 #include "selected_units_model.h"
-#include <cmath>
-#include <limits>
 #include <QDir>
 #include <QFile>
 #include <QJsonDocument>
 #include <QJsonObject>
+#include <cmath>
+#include <limits>
 
 GameEngine::GameEngine() {
-    m_world    = std::make_unique<Engine::Core::World>();
-    m_renderer = std::make_unique<Render::GL::Renderer>();
-    m_camera   = std::make_unique<Render::GL::Camera>();
-    m_ground   = std::make_unique<Render::GL::GroundRenderer>();
+  m_world = std::make_unique<Engine::Core::World>();
+  m_renderer = std::make_unique<Render::GL::Renderer>();
+  m_camera = std::make_unique<Render::GL::Camera>();
+  m_ground = std::make_unique<Render::GL::GroundRenderer>();
 
-    std::unique_ptr<Engine::Core::System> arrowSys = std::make_unique<Game::Systems::ArrowSystem>();
-    m_arrowSystem = static_cast<Game::Systems::ArrowSystem*>(arrowSys.get());
-    m_world->addSystem(std::move(arrowSys));
+  std::unique_ptr<Engine::Core::System> arrowSys =
+      std::make_unique<Game::Systems::ArrowSystem>();
+  m_arrowSystem = static_cast<Game::Systems::ArrowSystem *>(arrowSys.get());
+  m_world->addSystem(std::move(arrowSys));
 
-    m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
-    m_world->addSystem(std::make_unique<Game::Systems::PatrolSystem>());
-    m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
-    m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
-    m_world->addSystem(std::make_unique<Game::Systems::ProductionSystem>());
+  m_world->addSystem(std::make_unique<Game::Systems::MovementSystem>());
+  m_world->addSystem(std::make_unique<Game::Systems::PatrolSystem>());
+  m_world->addSystem(std::make_unique<Game::Systems::CombatSystem>());
+  m_world->addSystem(std::make_unique<Game::Systems::AISystem>());
+  m_world->addSystem(std::make_unique<Game::Systems::ProductionSystem>());
 
-    m_selectionSystem = std::make_unique<Game::Systems::SelectionSystem>();
-    m_world->addSystem(std::make_unique<Game::Systems::SelectionSystem>());
+  m_selectionSystem = std::make_unique<Game::Systems::SelectionSystem>();
+  m_world->addSystem(std::make_unique<Game::Systems::SelectionSystem>());
 
-    m_selectedUnitsModel = new SelectedUnitsModel(this, this);
-    QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
-    m_pickingService = std::make_unique<Game::Systems::PickingService>();
+  m_selectedUnitsModel = new SelectedUnitsModel(this, this);
+  QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
+  m_pickingService = std::make_unique<Game::Systems::PickingService>();
 }
 
 GameEngine::~GameEngine() = default;
 
 void GameEngine::onMapClicked(qreal sx, qreal sy) {
-    if (!m_window) return;
-    ensureInitialized();
-    QVector3D hit;
-    if (!screenToGround(QPointF(sx, sy), hit)) return;
-    onClickSelect(sx, sy, false);
+  if (!m_window)
+    return;
+  ensureInitialized();
+  QVector3D hit;
+  if (!screenToGround(QPointF(sx, sy), hit))
+    return;
+  onClickSelect(sx, sy, false);
 }
 
 void GameEngine::onRightClick(qreal sx, qreal sy) {
-    if (!m_window) return;
-    ensureInitialized();
-    if (!m_selectionSystem) return;
-    
-    // Right-click always clears selection and resets to normal mode (standard RTS behavior)
-    m_selectionSystem->clearSelection();
-    syncSelectionFlags();
-    emit selectedUnitsChanged();
-    if (m_selectedUnitsModel) QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
-    
-    // Reset cursor mode to normal (cancel attack/patrol/guard modes)
-    setCursorMode("normal");
+  if (!m_window)
+    return;
+  ensureInitialized();
+  if (!m_selectionSystem)
+    return;
+
+  m_selectionSystem->clearSelection();
+  syncSelectionFlags();
+  emit selectedUnitsChanged();
+  if (m_selectedUnitsModel)
+    QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
+
+  setCursorMode("normal");
 }
 
 void GameEngine::onAttackClick(qreal sx, qreal sy) {
-    if (!m_window) return;
-    ensureInitialized();
-    if (!m_selectionSystem || !m_pickingService || !m_camera || !m_world) return;
-    
-    const auto& selected = m_selectionSystem->getSelectedUnits();
-    if (selected.empty()) {
-        setCursorMode("normal");
-        return;
-    }
-    
-    // Pick an enemy unit or building (don't filter by owner - we want enemies)
-    Engine::Core::EntityID targetId = m_pickingService->pickSingle(
-        float(sx), float(sy), *m_world, *m_camera, 
-        m_viewport.width, m_viewport.height, 
-        0, // owner filter = 0 means no owner filter
-        true // prefer buildings first (to make barracks easier to click)
-    );
-    
-    if (targetId == 0) {
-        // No target found - keep attack mode active for retry
-        return;
-    }
-    
-    // Verify target is an enemy
-    auto* targetEntity = m_world->getEntity(targetId);
-    if (!targetEntity) {
-        return;
-    }
-    
-    auto* targetUnit = targetEntity->getComponent<Engine::Core::UnitComponent>();
-    if (!targetUnit) {
-        return;
-    }
-    
-    // Only attack enemies (not our own units)
-    if (targetUnit->ownerId == m_runtime.localOwnerId) {
-        return;
-    }
-    
-    // SUCCESS: Issue attack command with chase enabled
-    Game::Systems::CommandService::attackTarget(*m_world, selected, targetId, true);
-    
-    // Visual feedback: Spawn a red marker arrow at the target
-    if (m_arrowSystem) {
-        // Spawn a quick visual indicator arrow or marker at the target
-        auto* targetTrans = targetEntity->getComponent<Engine::Core::TransformComponent>();
-        if (targetTrans) {
-            QVector3D targetPos(targetTrans->position.x, targetTrans->position.y + 1.0f, targetTrans->position.z);
-            QVector3D aboveTarget = targetPos + QVector3D(0, 2.0f, 0);
-            // Spawn a red marker arrow pointing down at the target
-            m_arrowSystem->spawnArrow(aboveTarget, targetPos, QVector3D(1.0f, 0.2f, 0.2f), 6.0f);
-        }
-    }
-    
-    // Reset cursor mode to normal after successful attack command
+  if (!m_window)
+    return;
+  ensureInitialized();
+  if (!m_selectionSystem || !m_pickingService || !m_camera || !m_world)
+    return;
+
+  const auto &selected = m_selectionSystem->getSelectedUnits();
+  if (selected.empty()) {
     setCursorMode("normal");
+    return;
+  }
+
+  Engine::Core::EntityID targetId = m_pickingService->pickSingle(
+      float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
+      m_viewport.height, 0, true);
+
+  if (targetId == 0) {
+
+    return;
+  }
+
+  auto *targetEntity = m_world->getEntity(targetId);
+  if (!targetEntity) {
+    return;
+  }
+
+  auto *targetUnit = targetEntity->getComponent<Engine::Core::UnitComponent>();
+  if (!targetUnit) {
+    return;
+  }
+
+  if (targetUnit->ownerId == m_runtime.localOwnerId) {
+    return;
+  }
+
+  Game::Systems::CommandService::attackTarget(*m_world, selected, targetId,
+                                              true);
+
+  if (m_arrowSystem) {
+
+    auto *targetTrans =
+        targetEntity->getComponent<Engine::Core::TransformComponent>();
+    if (targetTrans) {
+      QVector3D targetPos(targetTrans->position.x,
+                          targetTrans->position.y + 1.0f,
+                          targetTrans->position.z);
+      QVector3D aboveTarget = targetPos + QVector3D(0, 2.0f, 0);
+
+      m_arrowSystem->spawnArrow(aboveTarget, targetPos,
+                                QVector3D(1.0f, 0.2f, 0.2f), 6.0f);
+    }
+  }
+
+  setCursorMode("normal");
 }
 
 void GameEngine::onStopCommand() {
-    if (!m_selectionSystem || !m_world) return;
-    ensureInitialized();
-    
-    const auto& selected = m_selectionSystem->getSelectedUnits();
-    if (selected.empty()) return;
-    
-    // Clear movement and attack targets for all selected units
-    for (auto id : selected) {
-        auto* entity = m_world->getEntity(id);
-        if (!entity) continue;
-        
-        // Clear movement target
-        if (auto* movement = entity->getComponent<Engine::Core::MovementComponent>()) {
-            movement->hasTarget = false;
-            movement->targetX = 0.0f;
-            movement->targetY = 0.0f;
-            movement->path.clear();
-        }
-        
-        // Clear attack target
-        if (auto* attack = entity->getComponent<Engine::Core::AttackTargetComponent>()) {
-            attack->targetId = 0;
-        }
-        
-        // Clear patrol route
-        if (auto* patrol = entity->getComponent<Engine::Core::PatrolComponent>()) {
-            patrol->patrolling = false;
-            patrol->waypoints.clear();
-        }
+  if (!m_selectionSystem || !m_world)
+    return;
+  ensureInitialized();
+
+  const auto &selected = m_selectionSystem->getSelectedUnits();
+  if (selected.empty())
+    return;
+
+  for (auto id : selected) {
+    auto *entity = m_world->getEntity(id);
+    if (!entity)
+      continue;
+
+    if (auto *movement =
+            entity->getComponent<Engine::Core::MovementComponent>()) {
+      movement->hasTarget = false;
+      movement->targetX = 0.0f;
+      movement->targetY = 0.0f;
+      movement->path.clear();
     }
-    
-    // Reset cursor mode to normal
-    setCursorMode("normal");
+
+    if (auto *attack =
+            entity->getComponent<Engine::Core::AttackTargetComponent>()) {
+      attack->targetId = 0;
+    }
+
+    if (auto *patrol = entity->getComponent<Engine::Core::PatrolComponent>()) {
+      patrol->patrolling = false;
+      patrol->waypoints.clear();
+    }
+  }
+
+  setCursorMode("normal");
 }
 
 void GameEngine::onPatrolClick(qreal sx, qreal sy) {
-    if (!m_selectionSystem || !m_world) return;
-    ensureInitialized();
-    
-    const auto& selected = m_selectionSystem->getSelectedUnits();
-    if (selected.empty()) return;
-    
-    // Convert screen to world position
-    QVector3D hit;
-    if (!screenToGround(QPointF(sx, sy), hit)) return;
-    
-    // First waypoint placement
-    if (!m_patrol.hasFirstWaypoint) {
-        m_patrol.firstWaypoint = hit;
-        m_patrol.hasFirstWaypoint = true;
-        
-        // TODO: Render first waypoint flag visually
-        return;
+  if (!m_selectionSystem || !m_world)
+    return;
+  ensureInitialized();
+
+  const auto &selected = m_selectionSystem->getSelectedUnits();
+  if (selected.empty())
+    return;
+
+  QVector3D hit;
+  if (!screenToGround(QPointF(sx, sy), hit))
+    return;
+
+  if (!m_patrol.hasFirstWaypoint) {
+    m_patrol.firstWaypoint = hit;
+    m_patrol.hasFirstWaypoint = true;
+
+    return;
+  }
+
+  QVector3D secondWaypoint = hit;
+
+  for (auto id : selected) {
+    auto *entity = m_world->getEntity(id);
+    if (!entity)
+      continue;
+
+    auto *building = entity->getComponent<Engine::Core::BuildingComponent>();
+    if (building)
+      continue;
+
+    auto *patrol = entity->getComponent<Engine::Core::PatrolComponent>();
+    if (!patrol) {
+      patrol = entity->addComponent<Engine::Core::PatrolComponent>();
     }
-    
-    // Second waypoint placement - complete patrol setup
-    QVector3D secondWaypoint = hit;
-    
-    // Set up patrol component on all selected units
-    for (auto id : selected) {
-        auto* entity = m_world->getEntity(id);
-        if (!entity) continue;
-        
-        // Only patrol units, not buildings
-        auto* building = entity->getComponent<Engine::Core::BuildingComponent>();
-        if (building) continue;
-        
-        // Get or add patrol component
-        auto* patrol = entity->getComponent<Engine::Core::PatrolComponent>();
-        if (!patrol) {
-            patrol = entity->addComponent<Engine::Core::PatrolComponent>();
-        }
-        
-        if (patrol) {
-            patrol->waypoints.clear();
-            patrol->waypoints.push_back({m_patrol.firstWaypoint.x(), m_patrol.firstWaypoint.z()});
-            patrol->waypoints.push_back({secondWaypoint.x(), secondWaypoint.z()});
-            patrol->currentWaypoint = 0;
-            patrol->patrolling = true;
-        }
-        
-        // Clear any existing movement/attack commands
-        if (auto* movement = entity->getComponent<Engine::Core::MovementComponent>()) {
-            movement->hasTarget = false;
-            movement->path.clear();
-        }
-        if (auto* attack = entity->getComponent<Engine::Core::AttackTargetComponent>()) {
-            attack->targetId = 0;
-        }
+
+    if (patrol) {
+      patrol->waypoints.clear();
+      patrol->waypoints.push_back(
+          {m_patrol.firstWaypoint.x(), m_patrol.firstWaypoint.z()});
+      patrol->waypoints.push_back({secondWaypoint.x(), secondWaypoint.z()});
+      patrol->currentWaypoint = 0;
+      patrol->patrolling = true;
     }
-    
-    // TODO: Render both waypoint flags visually
-    
-    // Reset patrol state and cursor mode
-    m_patrol.hasFirstWaypoint = false;
-    setCursorMode("normal");
-}
 
-void GameEngine::setCursorMode(const QString& mode) {
-    if (m_runtime.cursorMode == mode) return;
-    
-    // Clear patrol preview state when leaving patrol mode
-    if (m_runtime.cursorMode == "patrol" && mode != "patrol") {
-        m_patrol.hasFirstWaypoint = false;
+    if (auto *movement =
+            entity->getComponent<Engine::Core::MovementComponent>()) {
+      movement->hasTarget = false;
+      movement->path.clear();
+    }
+    if (auto *attack =
+            entity->getComponent<Engine::Core::AttackTargetComponent>()) {
+      attack->targetId = 0;
     }
-    
-    m_runtime.cursorMode = mode;
-    
-    // Immediately update cursor appearance when mode changes
-    if (m_window) {
-        if (mode == "normal") {
-            m_window->setCursor(Qt::ArrowCursor);
-        } else {
-            // Check if we're currently over the game view
-            // If not sure, set blank cursor anyway (it will be corrected on next hover event)
-            m_window->setCursor(Qt::BlankCursor);
-        }
+  }
+
+  m_patrol.hasFirstWaypoint = false;
+  setCursorMode("normal");
+}
+
+void GameEngine::setCursorMode(const QString &mode) {
+  if (m_runtime.cursorMode == mode)
+    return;
+
+  if (m_runtime.cursorMode == "patrol" && mode != "patrol") {
+    m_patrol.hasFirstWaypoint = false;
+  }
+
+  m_runtime.cursorMode = mode;
+
+  if (m_window) {
+    if (mode == "normal") {
+      m_window->setCursor(Qt::ArrowCursor);
+    } else {
+
+      m_window->setCursor(Qt::BlankCursor);
     }
-    
-    emit cursorModeChanged();
-    // Force custom cursor position update
-    emit globalCursorChanged();
+  }
+
+  emit cursorModeChanged();
+
+  emit globalCursorChanged();
 }
 
 qreal GameEngine::globalCursorX() const {
-    if (!m_window) return 0;
-    QPoint globalPos = QCursor::pos();
-    QPoint localPos = m_window->mapFromGlobal(globalPos);
-    return localPos.x();
+  if (!m_window)
+    return 0;
+  QPoint globalPos = QCursor::pos();
+  QPoint localPos = m_window->mapFromGlobal(globalPos);
+  return localPos.x();
 }
 
 qreal GameEngine::globalCursorY() const {
-    if (!m_window) return 0;
-    QPoint globalPos = QCursor::pos();
-    QPoint localPos = m_window->mapFromGlobal(globalPos);
-    return localPos.y();
+  if (!m_window)
+    return 0;
+  QPoint globalPos = QCursor::pos();
+  QPoint localPos = m_window->mapFromGlobal(globalPos);
+  return localPos.y();
 }
 
 void GameEngine::setHoverAtScreen(qreal sx, qreal sy) {
-    if (!m_window) return;
-    ensureInitialized();
-    if (!m_pickingService || !m_camera || !m_world) return;
-    
-    // If hovering outside the game view (sx < 0 or sy < 0), restore normal cursor
-    if (sx < 0 || sy < 0) {
-        if (m_runtime.cursorMode != "normal") {
-            // Temporarily restore normal cursor when not over game view
-            m_window->setCursor(Qt::ArrowCursor);
-        }
-        m_hover.buildingId = 0;
-        return;
-    }
-    
-    // Over the game view - apply cursor mode
-    if (m_runtime.cursorMode == "normal") {
-        m_window->setCursor(Qt::ArrowCursor);
-    } else {
-        m_window->setCursor(Qt::BlankCursor);
+  if (!m_window)
+    return;
+  ensureInitialized();
+  if (!m_pickingService || !m_camera || !m_world)
+    return;
+
+  if (sx < 0 || sy < 0) {
+    if (m_runtime.cursorMode != "normal") {
+
+      m_window->setCursor(Qt::ArrowCursor);
     }
-    
-    m_hover.buildingId = m_pickingService->updateHover(float(sx), float(sy), *m_world, *m_camera, m_viewport.width, m_viewport.height);
+    m_hover.buildingId = 0;
+    return;
+  }
+
+  if (m_runtime.cursorMode == "normal") {
+    m_window->setCursor(Qt::ArrowCursor);
+  } else {
+    m_window->setCursor(Qt::BlankCursor);
+  }
+
+  m_hover.buildingId =
+      m_pickingService->updateHover(float(sx), float(sy), *m_world, *m_camera,
+                                    m_viewport.width, m_viewport.height);
 }
 
 void GameEngine::onClickSelect(qreal sx, qreal sy, bool additive) {
-    if (!m_window || !m_selectionSystem) return;
-    ensureInitialized();
-    if (!m_pickingService || !m_camera || !m_world) return;
-    Engine::Core::EntityID picked = m_pickingService->pickSingle(float(sx), float(sy), *m_world, *m_camera, m_viewport.width, m_viewport.height, m_runtime.localOwnerId, true);
-    if (picked) {
-        if (!additive) m_selectionSystem->clearSelection();
-        m_selectionSystem->selectUnit(picked);
-        syncSelectionFlags();
-        emit selectedUnitsChanged();
-        if (m_selectedUnitsModel) QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
-        return;
-    }
+  if (!m_window || !m_selectionSystem)
+    return;
+  ensureInitialized();
+  if (!m_pickingService || !m_camera || !m_world)
+    return;
+  Engine::Core::EntityID picked = m_pickingService->pickSingle(
+      float(sx), float(sy), *m_world, *m_camera, m_viewport.width,
+      m_viewport.height, m_runtime.localOwnerId, true);
+  if (picked) {
+    if (!additive)
+      m_selectionSystem->clearSelection();
+    m_selectionSystem->selectUnit(picked);
+    syncSelectionFlags();
+    emit selectedUnitsChanged();
+    if (m_selectedUnitsModel)
+      QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
+    return;
+  }
 
-    const auto& selected = m_selectionSystem->getSelectedUnits();
-    if (!selected.empty()) {
-        QVector3D hit;
-        if (!screenToGround(QPointF(sx, sy), hit)) {
-            return;
-        }
-        auto targets = Game::Systems::FormationPlanner::spreadFormation(int(selected.size()), hit, 1.0f);
-        Game::Systems::CommandService::moveUnits(*m_world, selected, targets);
-        syncSelectionFlags();
-        return;
+  const auto &selected = m_selectionSystem->getSelectedUnits();
+  if (!selected.empty()) {
+    QVector3D hit;
+    if (!screenToGround(QPointF(sx, sy), hit)) {
+      return;
     }
+    auto targets = Game::Systems::FormationPlanner::spreadFormation(
+        int(selected.size()), hit, 1.0f);
+    Game::Systems::CommandService::moveUnits(*m_world, selected, targets);
+    syncSelectionFlags();
+    return;
+  }
 }
 
-void GameEngine::onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2, bool additive) {
-    if (!m_window || !m_selectionSystem) return;
-    ensureInitialized();
-    if (!additive) m_selectionSystem->clearSelection();
-    if (!m_pickingService || !m_camera || !m_world) return;
-    auto picked = m_pickingService->pickInRect(float(x1), float(y1), float(x2), float(y2), *m_world, *m_camera, m_viewport.width, m_viewport.height, m_runtime.localOwnerId);
-    for (auto id : picked) m_selectionSystem->selectUnit(id);
-    syncSelectionFlags();
-    emit selectedUnitsChanged();
-    if (m_selectedUnitsModel) QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
+void GameEngine::onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2,
+                                bool additive) {
+  if (!m_window || !m_selectionSystem)
+    return;
+  ensureInitialized();
+  if (!additive)
+    m_selectionSystem->clearSelection();
+  if (!m_pickingService || !m_camera || !m_world)
+    return;
+  auto picked = m_pickingService->pickInRect(
+      float(x1), float(y1), float(x2), float(y2), *m_world, *m_camera,
+      m_viewport.width, m_viewport.height, m_runtime.localOwnerId);
+  for (auto id : picked)
+    m_selectionSystem->selectUnit(id);
+  syncSelectionFlags();
+  emit selectedUnitsChanged();
+  if (m_selectedUnitsModel)
+    QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh");
 }
 
 void GameEngine::initialize() {
-    if (!Render::GL::RenderBootstrap::initialize(*m_renderer, *m_camera)) {
-        return;
-    }
-    // Initialize rendering only - don't load map until user selects one
-    if (m_ground) {
-        m_ground->configureExtent(50.0f);
-    }
-    m_runtime.initialized = true;
+  if (!Render::GL::RenderBootstrap::initialize(*m_renderer, *m_camera)) {
+    return;
+  }
+
+  if (m_ground) {
+    m_ground->configureExtent(50.0f);
+  }
+  m_runtime.initialized = true;
 }
 
-void GameEngine::ensureInitialized() { if (!m_runtime.initialized) initialize(); }
+void GameEngine::ensureInitialized() {
+  if (!m_runtime.initialized)
+    initialize();
+}
 
 void GameEngine::update(float dt) {
-    // Skip updates while loading a new map to avoid race conditions
-    if (m_runtime.loading) {
-        return;
-    }
-    
-    if (m_runtime.paused) {
-        dt = 0.0f;
-    } else {
-        dt *= m_runtime.timeScale;
-    }
-    if (m_world) m_world->update(dt);
-    syncSelectionFlags();
-    checkVictoryCondition();
-    
-    // Check for troop count changes
-    int currentTroopCount = playerTroopCount();
-    if (currentTroopCount != m_runtime.lastTroopCount) {
-        m_runtime.lastTroopCount = currentTroopCount;
-        emit troopCountChanged();
-    }
-    
-    if (m_followSelectionEnabled && m_camera && m_selectionSystem && m_world) {
-        Game::Systems::CameraFollowSystem cfs;
-        cfs.update(*m_world, *m_selectionSystem, *m_camera);
-    }
-    if (m_selectedUnitsModel) QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh", Qt::QueuedConnection);
-}
 
-void GameEngine::render(int pixelWidth, int pixelHeight) {
-    if (!m_renderer || !m_world || !m_runtime.initialized) return;
-    if (pixelWidth > 0 && pixelHeight > 0) {
-        m_viewport.width = pixelWidth; m_viewport.height = pixelHeight;
-        m_renderer->setViewport(pixelWidth, pixelHeight);
-    }
-    if (m_selectionSystem) {
-        const auto& sel = m_selectionSystem->getSelectedUnits();
-        std::vector<unsigned int> ids(sel.begin(), sel.end());
-        m_renderer->setSelectedEntities(ids);
-    }
-    m_renderer->beginFrame();
-    if (m_ground && m_renderer) {
-        if (auto* res = m_renderer->resources()) m_ground->submit(*m_renderer, *res);
-    }
-    if (m_renderer) m_renderer->setHoveredBuildingId(m_hover.buildingId);
-    m_renderer->renderWorld(m_world.get());
-    if (m_arrowSystem) {
-        if (auto* res = m_renderer->resources()) Render::GL::renderArrows(m_renderer.get(), res, *m_arrowSystem);
-    }
-    // Render patrol waypoint flags (including preview during placement)
-    if (auto* res = m_renderer->resources()) {
-        std::optional<QVector3D> previewWaypoint;
-        if (m_patrol.hasFirstWaypoint) {
-            previewWaypoint = m_patrol.firstWaypoint;
-        }
-        Render::GL::renderPatrolFlags(m_renderer.get(), res, *m_world, previewWaypoint);
-    }
-    m_renderer->endFrame();
-    
-    // Emit cursor position change every frame so QML cursor tracking updates
-    emit globalCursorChanged();
+  if (m_runtime.loading) {
+    return;
+  }
+
+  if (m_runtime.paused) {
+    dt = 0.0f;
+  } else {
+    dt *= m_runtime.timeScale;
+  }
+  if (m_world)
+    m_world->update(dt);
+  syncSelectionFlags();
+  checkVictoryCondition();
+
+  int currentTroopCount = playerTroopCount();
+  if (currentTroopCount != m_runtime.lastTroopCount) {
+    m_runtime.lastTroopCount = currentTroopCount;
+    emit troopCountChanged();
+  }
+
+  if (m_followSelectionEnabled && m_camera && m_selectionSystem && m_world) {
+    Game::Systems::CameraFollowSystem cfs;
+    cfs.update(*m_world, *m_selectionSystem, *m_camera);
+  }
+  if (m_selectedUnitsModel)
+    QMetaObject::invokeMethod(m_selectedUnitsModel, "refresh",
+                              Qt::QueuedConnection);
 }
 
-bool GameEngine::screenToGround(const QPointF& screenPt, QVector3D& outWorld) {
-    if (!m_window || !m_camera || !m_pickingService) return false;
-    int w = (m_viewport.width > 0 ? m_viewport.width : m_window->width());
-    int h = (m_viewport.height > 0 ? m_viewport.height : m_window->height());
-    return m_pickingService->screenToGround(*m_camera, w, h, screenPt, outWorld);
+void GameEngine::render(int pixelWidth, int pixelHeight) {
+  if (!m_renderer || !m_world || !m_runtime.initialized)
+    return;
+  if (pixelWidth > 0 && pixelHeight > 0) {
+    m_viewport.width = pixelWidth;
+    m_viewport.height = pixelHeight;
+    m_renderer->setViewport(pixelWidth, pixelHeight);
+  }
+  if (m_selectionSystem) {
+    const auto &sel = m_selectionSystem->getSelectedUnits();
+    std::vector<unsigned int> ids(sel.begin(), sel.end());
+    m_renderer->setSelectedEntities(ids);
+  }
+  m_renderer->beginFrame();
+  if (m_ground && m_renderer) {
+    if (auto *res = m_renderer->resources())
+      m_ground->submit(*m_renderer, *res);
+  }
+  if (m_renderer)
+    m_renderer->setHoveredBuildingId(m_hover.buildingId);
+  m_renderer->renderWorld(m_world.get());
+  if (m_arrowSystem) {
+    if (auto *res = m_renderer->resources())
+      Render::GL::renderArrows(m_renderer.get(), res, *m_arrowSystem);
+  }
+
+  if (auto *res = m_renderer->resources()) {
+    std::optional<QVector3D> previewWaypoint;
+    if (m_patrol.hasFirstWaypoint) {
+      previewWaypoint = m_patrol.firstWaypoint;
+    }
+    Render::GL::renderPatrolFlags(m_renderer.get(), res, *m_world,
+                                  previewWaypoint);
+  }
+  m_renderer->endFrame();
+
+  emit globalCursorChanged();
+}
+
+bool GameEngine::screenToGround(const QPointF &screenPt, QVector3D &outWorld) {
+  if (!m_window || !m_camera || !m_pickingService)
+    return false;
+  int w = (m_viewport.width > 0 ? m_viewport.width : m_window->width());
+  int h = (m_viewport.height > 0 ? m_viewport.height : m_window->height());
+  return m_pickingService->screenToGround(*m_camera, w, h, screenPt, outWorld);
 }
 
-bool GameEngine::worldToScreen(const QVector3D& world, QPointF& outScreen) const {
-    if (!m_camera || m_viewport.width <= 0 || m_viewport.height <= 0 || !m_pickingService) return false;
-    return m_pickingService->worldToScreen(*m_camera, m_viewport.width, m_viewport.height, world, outScreen);
+bool GameEngine::worldToScreen(const QVector3D &world,
+                               QPointF &outScreen) const {
+  if (!m_camera || m_viewport.width <= 0 || m_viewport.height <= 0 ||
+      !m_pickingService)
+    return false;
+  return m_pickingService->worldToScreen(*m_camera, m_viewport.width,
+                                         m_viewport.height, world, outScreen);
 }
 
 void GameEngine::syncSelectionFlags() {
-    if (!m_world || !m_selectionSystem) return;
-    const auto& sel = m_selectionSystem->getSelectedUnits();
-    std::vector<Engine::Core::EntityID> toKeep;
-    toKeep.reserve(sel.size());
-    for (auto id : sel) {
-        if (auto* e = m_world->getEntity(id)) {
-            if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
-                if (u->health > 0) toKeep.push_back(id);
-            }
-        }
-    }
-    if (toKeep.size() != sel.size()) {
-        m_selectionSystem->clearSelection();
-        for (auto id : toKeep) m_selectionSystem->selectUnit(id);
-    }
+  if (!m_world || !m_selectionSystem)
+    return;
+  const auto &sel = m_selectionSystem->getSelectedUnits();
+  std::vector<Engine::Core::EntityID> toKeep;
+  toKeep.reserve(sel.size());
+  for (auto id : sel) {
+    if (auto *e = m_world->getEntity(id)) {
+      if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
+        if (u->health > 0)
+          toKeep.push_back(id);
+      }
+    }
+  }
+  if (toKeep.size() != sel.size()) {
+    m_selectionSystem->clearSelection();
+    for (auto id : toKeep)
+      m_selectionSystem->selectUnit(id);
+  }
 }
 
 void GameEngine::cameraMove(float dx, float dz) {
-    ensureInitialized();
-    if (!m_camera) return;
-    Game::Systems::CameraController ctrl;
-    ctrl.move(*m_camera, dx, dz);
+  ensureInitialized();
+  if (!m_camera)
+    return;
+  Game::Systems::CameraController ctrl;
+  ctrl.move(*m_camera, dx, dz);
 }
 
 void GameEngine::cameraElevate(float dy) {
-    ensureInitialized();
-    if (!m_camera) return;
-    Game::Systems::CameraController ctrl;
-    ctrl.elevate(*m_camera, dy);
+  ensureInitialized();
+  if (!m_camera)
+    return;
+  Game::Systems::CameraController ctrl;
+  ctrl.elevate(*m_camera, dy);
 }
 
 void GameEngine::cameraYaw(float degrees) {
-    ensureInitialized();
-    if (!m_camera) return;
-    Game::Systems::CameraController ctrl;
-    ctrl.yaw(*m_camera, degrees);
+  ensureInitialized();
+  if (!m_camera)
+    return;
+  Game::Systems::CameraController ctrl;
+  ctrl.yaw(*m_camera, degrees);
 }
 
 void GameEngine::cameraOrbit(float yawDeg, float pitchDeg) {
-    ensureInitialized();
-    if (!m_camera) return;
-    Game::Systems::CameraController ctrl;
-    ctrl.orbit(*m_camera, yawDeg, pitchDeg);
+  ensureInitialized();
+  if (!m_camera)
+    return;
+  Game::Systems::CameraController ctrl;
+  ctrl.orbit(*m_camera, yawDeg, pitchDeg);
 }
 
 void GameEngine::cameraFollowSelection(bool enable) {
-    ensureInitialized();
-    m_followSelectionEnabled = enable;
-    if (m_camera) {
-        Game::Systems::CameraController ctrl;
-        ctrl.setFollowEnabled(*m_camera, enable);
-    }
-    if (enable && m_camera && m_selectionSystem && m_world) {
-        Game::Systems::CameraFollowSystem cfs;
-        cfs.snapToSelection(*m_world, *m_selectionSystem, *m_camera);
-    } else if (m_camera) {
-        auto pos = m_camera->getPosition();
-        auto tgt = m_camera->getTarget();
-        QVector3D front = (tgt - pos).normalized();
-        m_camera->lookAt(pos, tgt, QVector3D(0,1,0));
-    }
+  ensureInitialized();
+  m_followSelectionEnabled = enable;
+  if (m_camera) {
+    Game::Systems::CameraController ctrl;
+    ctrl.setFollowEnabled(*m_camera, enable);
+  }
+  if (enable && m_camera && m_selectionSystem && m_world) {
+    Game::Systems::CameraFollowSystem cfs;
+    cfs.snapToSelection(*m_world, *m_selectionSystem, *m_camera);
+  } else if (m_camera) {
+    auto pos = m_camera->getPosition();
+    auto tgt = m_camera->getTarget();
+    QVector3D front = (tgt - pos).normalized();
+    m_camera->lookAt(pos, tgt, QVector3D(0, 1, 0));
+  }
 }
 
 void GameEngine::cameraSetFollowLerp(float alpha) {
-    ensureInitialized();
-    if (!m_camera) return;
-    float a = std::clamp(alpha, 0.0f, 1.0f);
-    Game::Systems::CameraController ctrl;
-    ctrl.setFollowLerp(*m_camera, a);
+  ensureInitialized();
+  if (!m_camera)
+    return;
+  float a = std::clamp(alpha, 0.0f, 1.0f);
+  Game::Systems::CameraController ctrl;
+  ctrl.setFollowLerp(*m_camera, a);
 }
 
-QObject* GameEngine::selectedUnitsModel() { return m_selectedUnitsModel; }
+QObject *GameEngine::selectedUnitsModel() { return m_selectedUnitsModel; }
 
 bool GameEngine::hasUnitsSelected() const {
-    if (!m_selectionSystem) return false;
-    const auto& sel = m_selectionSystem->getSelectedUnits();
-    return !sel.empty();
+  if (!m_selectionSystem)
+    return false;
+  const auto &sel = m_selectionSystem->getSelectedUnits();
+  return !sel.empty();
 }
 
 int GameEngine::playerTroopCount() const {
-    if (!m_world) return 0;
-    
-    int count = 0;
-    auto entities = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* entity : entities) {
-        auto* unit = entity->getComponent<Engine::Core::UnitComponent>();
-        if (!unit) continue;
-        
-        // Count alive units (not buildings) owned by local player
-        if (unit->ownerId == m_runtime.localOwnerId && 
-            unit->health > 0 && 
-            unit->unitType != "barracks") {
-            count++;
-        }
-    }
-    return count;
-}
-
-bool GameEngine::hasSelectedType(const QString& type) const {
-    if (!m_selectionSystem || !m_world) return false;
-    const auto& sel = m_selectionSystem->getSelectedUnits();
-    for (auto id : sel) {
-        if (auto* e = m_world->getEntity(id)) {
-            if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
-                if (QString::fromStdString(u->unitType) == type) return true;
-            }
-        }
+  if (!m_world)
+    return 0;
+
+  int count = 0;
+  auto entities = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *entity : entities) {
+    auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
+    if (!unit)
+      continue;
+
+    if (unit->ownerId == m_runtime.localOwnerId && unit->health > 0 &&
+        unit->unitType != "barracks") {
+      count++;
     }
-    return false;
+  }
+  return count;
 }
 
-void GameEngine::recruitNearSelected(const QString& unitType) {
-    ensureInitialized();
-    if (!m_world) return;
-    if (!m_selectionSystem) return;
-    const auto& sel = m_selectionSystem->getSelectedUnits();
-    if (sel.empty()) return;
-    Game::Systems::ProductionService::startProductionForFirstSelectedBarracks(*m_world, sel, m_runtime.localOwnerId, unitType.toStdString());
+bool GameEngine::hasSelectedType(const QString &type) const {
+  if (!m_selectionSystem || !m_world)
+    return false;
+  const auto &sel = m_selectionSystem->getSelectedUnits();
+  for (auto id : sel) {
+    if (auto *e = m_world->getEntity(id)) {
+      if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
+        if (QString::fromStdString(u->unitType) == type)
+          return true;
+      }
+    }
+  }
+  return false;
+}
+
+void GameEngine::recruitNearSelected(const QString &unitType) {
+  ensureInitialized();
+  if (!m_world)
+    return;
+  if (!m_selectionSystem)
+    return;
+  const auto &sel = m_selectionSystem->getSelectedUnits();
+  if (sel.empty())
+    return;
+  Game::Systems::ProductionService::startProductionForFirstSelectedBarracks(
+      *m_world, sel, m_runtime.localOwnerId, unitType.toStdString());
 }
 
 QVariantMap GameEngine::getSelectedProductionState() const {
-    QVariantMap m;
-    m["hasBarracks"] = false;
-    m["inProgress"] = false;
-    m["timeRemaining"] = 0.0;
-    m["buildTime"] = 0.0;
-    m["producedCount"] = 0;
-    m["maxUnits"] = 0;
-    if (!m_selectionSystem || !m_world) return m;
-    Game::Systems::ProductionState st;
-    Game::Systems::ProductionService::getSelectedBarracksState(*m_world, m_selectionSystem->getSelectedUnits(), m_runtime.localOwnerId, st);
-    m["hasBarracks"] = st.hasBarracks;
-    m["inProgress"] = st.inProgress;
-    m["timeRemaining"] = st.timeRemaining;
-    m["buildTime"] = st.buildTime;
-    m["producedCount"] = st.producedCount;
-    m["maxUnits"] = st.maxUnits;
+  QVariantMap m;
+  m["hasBarracks"] = false;
+  m["inProgress"] = false;
+  m["timeRemaining"] = 0.0;
+  m["buildTime"] = 0.0;
+  m["producedCount"] = 0;
+  m["maxUnits"] = 0;
+  if (!m_selectionSystem || !m_world)
     return m;
+  Game::Systems::ProductionState st;
+  Game::Systems::ProductionService::getSelectedBarracksState(
+      *m_world, m_selectionSystem->getSelectedUnits(), m_runtime.localOwnerId,
+      st);
+  m["hasBarracks"] = st.hasBarracks;
+  m["inProgress"] = st.inProgress;
+  m["timeRemaining"] = st.timeRemaining;
+  m["buildTime"] = st.buildTime;
+  m["producedCount"] = st.producedCount;
+  m["maxUnits"] = st.maxUnits;
+  return m;
 }
 
 void GameEngine::setRallyAtScreen(qreal sx, qreal sy) {
-    ensureInitialized();
-    if (!m_world || !m_selectionSystem) return;
-    QVector3D hit;
-    if (!screenToGround(QPointF(sx, sy), hit)) return;
-    Game::Systems::ProductionService::setRallyForFirstSelectedBarracks(*m_world, m_selectionSystem->getSelectedUnits(), m_runtime.localOwnerId, hit.x(), hit.z());
+  ensureInitialized();
+  if (!m_world || !m_selectionSystem)
+    return;
+  QVector3D hit;
+  if (!screenToGround(QPointF(sx, sy), hit))
+    return;
+  Game::Systems::ProductionService::setRallyForFirstSelectedBarracks(
+      *m_world, m_selectionSystem->getSelectedUnits(), m_runtime.localOwnerId,
+      hit.x(), hit.z());
 }
 
 QVariantList GameEngine::availableMaps() const {
-    QVariantList list;
-    QDir mapsDir(QStringLiteral("assets/maps"));
-    if (!mapsDir.exists()) return list;
-
-    QStringList files = mapsDir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name);
-    for (const QString& f : files) {
-        QString path = mapsDir.filePath(f);
-        QFile file(path);
-        QString name = f;
-        QString desc;
-        if (file.open(QIODevice::ReadOnly)) {
-            QByteArray data = file.readAll();
-            file.close();
-            QJsonParseError err;
-            QJsonDocument doc = QJsonDocument::fromJson(data, &err);
-            if (err.error == QJsonParseError::NoError && doc.isObject()) {
-                QJsonObject obj = doc.object();
-                if (obj.contains("name") && obj["name"].isString()) name = obj["name"].toString();
-                if (obj.contains("description") && obj["description"].isString()) desc = obj["description"].toString();
-            }
-        }
-        QVariantMap entry;
-        entry["name"] = name;
-        entry["description"] = desc;
-        entry["path"] = path;
-        list.append(entry);
-    }
+  QVariantList list;
+  QDir mapsDir(QStringLiteral("assets/maps"));
+  if (!mapsDir.exists())
     return list;
-}
 
-void GameEngine::startSkirmish(const QString& mapPath) {
-    // Set the requested map and load it into the current world.
-    // DO NOT replace m_world here: other subsystems keep pointers into the World
-    m_level.mapName = mapPath;
-    // If engine isn't initialized yet, initialize normally (will load the map)
-    if (!m_runtime.initialized) {
-        initialize();
-        return;
+  QStringList files =
+      mapsDir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name);
+  for (const QString &f : files) {
+    QString path = mapsDir.filePath(f);
+    QFile file(path);
+    QString name = f;
+    QString desc;
+    if (file.open(QIODevice::ReadOnly)) {
+      QByteArray data = file.readAll();
+      file.close();
+      QJsonParseError err;
+      QJsonDocument doc = QJsonDocument::fromJson(data, &err);
+      if (err.error == QJsonParseError::NoError && doc.isObject()) {
+        QJsonObject obj = doc.object();
+        if (obj.contains("name") && obj["name"].isString())
+          name = obj["name"].toString();
+        if (obj.contains("description") && obj["description"].isString())
+          desc = obj["description"].toString();
+      }
+    }
+    QVariantMap entry;
+    entry["name"] = name;
+    entry["description"] = desc;
+    entry["path"] = path;
+    list.append(entry);
+  }
+  return list;
+}
+
+void GameEngine::startSkirmish(const QString &mapPath) {
+
+  m_level.mapName = mapPath;
+
+  if (!m_runtime.initialized) {
+    initialize();
+    return;
+  }
+
+  if (m_world && m_renderer && m_camera) {
+
+    m_runtime.loading = true;
+
+    if (m_selectionSystem) {
+      m_selectionSystem->clearSelection();
     }
 
-    // If already initialized, reload the level into the existing world
-    if (m_world && m_renderer && m_camera) {
-        // CRITICAL: Prevent updates during reload to avoid race conditions
-        m_runtime.loading = true;
-        
-        // 1. Clear selection to avoid invalid entity IDs
-        if (m_selectionSystem) {
-            m_selectionSystem->clearSelection();
-        }
-        
-        // 2. Clear renderer's entity ID references
-        if (m_renderer) {
-            m_renderer->setSelectedEntities({});  // Clear selected entities
-            m_renderer->setHoveredBuildingId(0);  // Clear hovered building
-        }
-        
-        // 3. Clear hover state
-        m_hover.buildingId = 0;
-        
-        // 4. Clear all entities from the world
-        m_world->clear();
-        
-        auto lr = Game::Map::LevelLoader::loadFromAssets(m_level.mapName, *m_world, *m_renderer, *m_camera);
-        if (m_ground) {
-            if (lr.ok) m_ground->configure(lr.tileSize, lr.gridWidth, lr.gridHeight);
-            else m_ground->configureExtent(50.0f);
-        }
-        m_level.mapName = lr.mapName;
-        m_level.playerUnitId = lr.playerUnitId;
-        m_level.camFov = lr.camFov; m_level.camNear = lr.camNear; m_level.camFar = lr.camFar;
-        m_level.maxTroopsPerPlayer = lr.maxTroopsPerPlayer;
-        
-        // Re-enable updates now that loading is complete
-        m_runtime.loading = false;
+    if (m_renderer) {
+      m_renderer->setSelectedEntities({});
+      m_renderer->setHoveredBuildingId(0);
     }
-}
 
-void GameEngine::openSettings() {
-    // Placeholder: settings are not implemented. Emit a log entry for now.
-    qInfo() << "Open settings requested";
+    m_hover.buildingId = 0;
+
+    m_world->clear();
+
+    auto lr = Game::Map::LevelLoader::loadFromAssets(m_level.mapName, *m_world,
+                                                     *m_renderer, *m_camera);
+    if (m_ground) {
+      if (lr.ok)
+        m_ground->configure(lr.tileSize, lr.gridWidth, lr.gridHeight);
+      else
+        m_ground->configureExtent(50.0f);
+    }
+    m_level.mapName = lr.mapName;
+    m_level.playerUnitId = lr.playerUnitId;
+    m_level.camFov = lr.camFov;
+    m_level.camNear = lr.camNear;
+    m_level.camFar = lr.camFar;
+    m_level.maxTroopsPerPlayer = lr.maxTroopsPerPlayer;
+
+    m_runtime.loading = false;
+  }
 }
 
+void GameEngine::openSettings() { qInfo() << "Open settings requested"; }
+
 void GameEngine::loadSave() {
-    // Placeholder: saved games not implemented yet.
-    qInfo() << "Load save requested (not implemented)";
+
+  qInfo() << "Load save requested (not implemented)";
 }
 
 void GameEngine::exitGame() {
-    qInfo() << "Exit requested";
-    QCoreApplication::quit();
-}
-
-void GameEngine::getSelectedUnitIds(std::vector<Engine::Core::EntityID>& out) const {
-    out.clear();
-    if (!m_selectionSystem) return;
-    const auto& ids = m_selectionSystem->getSelectedUnits();
-    out.assign(ids.begin(), ids.end());
-}
-
-bool GameEngine::getUnitInfo(Engine::Core::EntityID id, QString& name, int& health, int& maxHealth,
-                             bool& isBuilding, bool& alive) const {
-    if (!m_world) return false;
-    auto* e = m_world->getEntity(id);
-    if (!e) return false;
-    isBuilding = e->hasComponent<Engine::Core::BuildingComponent>();
-    if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
-        name = QString::fromStdString(u->unitType);
-        health = u->health;
-        maxHealth = u->maxHealth;
-        alive = (u->health > 0);
-        return true;
-    }
-    name = QStringLiteral("Entity");
-    health = maxHealth = 0;
-    alive = true;
+  qInfo() << "Exit requested";
+  QCoreApplication::quit();
+}
+
+void GameEngine::getSelectedUnitIds(
+    std::vector<Engine::Core::EntityID> &out) const {
+  out.clear();
+  if (!m_selectionSystem)
+    return;
+  const auto &ids = m_selectionSystem->getSelectedUnits();
+  out.assign(ids.begin(), ids.end());
+}
+
+bool GameEngine::getUnitInfo(Engine::Core::EntityID id, QString &name,
+                             int &health, int &maxHealth, bool &isBuilding,
+                             bool &alive) const {
+  if (!m_world)
+    return false;
+  auto *e = m_world->getEntity(id);
+  if (!e)
+    return false;
+  isBuilding = e->hasComponent<Engine::Core::BuildingComponent>();
+  if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
+    name = QString::fromStdString(u->unitType);
+    health = u->health;
+    maxHealth = u->maxHealth;
+    alive = (u->health > 0);
     return true;
+  }
+  name = QStringLiteral("Entity");
+  health = maxHealth = 0;
+  alive = true;
+  return true;
 }
 
 void GameEngine::checkVictoryCondition() {
-    if (!m_world || m_runtime.victoryState != "") return; // Already won/lost
-    
-    // Don't check victory until map is loaded (level name is set)
-    if (m_level.mapName.isEmpty()) return;
-    
-    // Check if enemy barracks (playerId 2) still exists and is alive
-    bool enemyBarracksAlive = false;
-    bool playerBarracksAlive = false;
-    
-    auto entities = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* unit = e->getComponent<Engine::Core::UnitComponent>();
-        if (!unit || unit->health <= 0) continue;
-        
-        if (unit->unitType == "barracks") {
-            if (unit->ownerId == 2) {
-                enemyBarracksAlive = true;
-            } else if (unit->ownerId == m_runtime.localOwnerId) {
-                playerBarracksAlive = true;
-            }
-        }
-    }
-    
-    // Victory if enemy barracks destroyed
-    if (!enemyBarracksAlive) {
-        m_runtime.victoryState = "victory";
-        emit victoryStateChanged();
-        qInfo() << "VICTORY! Enemy barracks destroyed!";
-    }
-    // Defeat if player barracks destroyed
-    else if (!playerBarracksAlive) {
-        m_runtime.victoryState = "defeat";
-        emit victoryStateChanged();
-        qInfo() << "DEFEAT! Your barracks was destroyed!";
-    }
+  if (!m_world || m_runtime.victoryState != "")
+    return;
+
+  if (m_level.mapName.isEmpty())
+    return;
+
+  bool enemyBarracksAlive = false;
+  bool playerBarracksAlive = false;
+
+  auto entities = m_world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *unit = e->getComponent<Engine::Core::UnitComponent>();
+    if (!unit || unit->health <= 0)
+      continue;
+
+    if (unit->unitType == "barracks") {
+      if (unit->ownerId == 2) {
+        enemyBarracksAlive = true;
+      } else if (unit->ownerId == m_runtime.localOwnerId) {
+        playerBarracksAlive = true;
+      }
+    }
+  }
+
+  if (!enemyBarracksAlive) {
+    m_runtime.victoryState = "victory";
+    emit victoryStateChanged();
+    qInfo() << "VICTORY! Enemy barracks destroyed!";
+  }
+
+  else if (!playerBarracksAlive) {
+    m_runtime.victoryState = "defeat";
+    emit victoryStateChanged();
+    qInfo() << "DEFEAT! Your barracks was destroyed!";
+  }
 }
-

+ 155 - 132
app/game_engine.h

@@ -1,159 +1,182 @@
 #pragma once
 
-#include <QObject>
-#include <QVector3D>
 #include <QMatrix4x4>
+#include <QObject>
 #include <QPointF>
-#include <memory>
-#include <algorithm>
-#include <QVariant>
 #include <QStringList>
+#include <QVariant>
+#include <QVector3D>
+#include <algorithm>
+#include <memory>
 #include <vector>
 
-namespace Engine { namespace Core {
+namespace Engine {
+namespace Core {
 class World;
 using EntityID = unsigned int;
 struct MovementComponent;
 struct TransformComponent;
 struct RenderableComponent;
-} }
+} // namespace Core
+} // namespace Engine
 
-namespace Render { namespace GL {
+namespace Render {
+namespace GL {
 class Renderer;
 class Camera;
 class ResourceManager;
 class GroundRenderer;
-} }
+} // namespace GL
+} // namespace Render
 
-namespace Game { namespace Systems { class SelectionSystem; class ArrowSystem; class PickingService; } }
+namespace Game {
+namespace Systems {
+class SelectionSystem;
+class ArrowSystem;
+class PickingService;
+} // namespace Systems
+} // namespace Game
 
 class QQuickWindow;
 
 class GameEngine : public QObject {
-    Q_OBJECT
+  Q_OBJECT
 public:
-    GameEngine();
-    ~GameEngine();
-
-    Q_PROPERTY(QObject* selectedUnitsModel READ selectedUnitsModel NOTIFY selectedUnitsChanged)
-    Q_PROPERTY(bool paused READ paused WRITE setPaused)
-    Q_PROPERTY(float timeScale READ timeScale WRITE setGameSpeed)
-    Q_PROPERTY(QString victoryState READ victoryState NOTIFY victoryStateChanged)
-    Q_PROPERTY(QString cursorMode READ cursorMode WRITE setCursorMode NOTIFY cursorModeChanged)
-    Q_PROPERTY(qreal globalCursorX READ globalCursorX NOTIFY globalCursorChanged)
-    Q_PROPERTY(qreal globalCursorY READ globalCursorY NOTIFY globalCursorChanged)
-    Q_PROPERTY(bool hasUnitsSelected READ hasUnitsSelected NOTIFY selectedUnitsChanged)
-    Q_PROPERTY(int playerTroopCount READ playerTroopCount NOTIFY troopCountChanged)
-    Q_PROPERTY(int maxTroopsPerPlayer READ maxTroopsPerPlayer NOTIFY troopCountChanged)
-    Q_PROPERTY(QVariantList availableMaps READ availableMaps NOTIFY availableMapsChanged)
-
-    Q_INVOKABLE void onMapClicked(qreal sx, qreal sy);
-    Q_INVOKABLE void onRightClick(qreal sx, qreal sy);
-    Q_INVOKABLE void onClickSelect(qreal sx, qreal sy, bool additive = false);
-    Q_INVOKABLE void onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2, bool additive = false);
-    Q_INVOKABLE void setHoverAtScreen(qreal sx, qreal sy);
-    Q_INVOKABLE void onAttackClick(qreal sx, qreal sy);
-    Q_INVOKABLE void onStopCommand();
-    Q_INVOKABLE void onPatrolClick(qreal sx, qreal sy);
-
-    Q_INVOKABLE void cameraMove(float dx, float dz);
-    Q_INVOKABLE void cameraElevate(float dy);
-    Q_INVOKABLE void cameraYaw(float degrees);
-    Q_INVOKABLE void cameraOrbit(float yawDeg, float pitchDeg);
-    Q_INVOKABLE void cameraFollowSelection(bool enable);
-    Q_INVOKABLE void cameraSetFollowLerp(float alpha);
-
-    Q_INVOKABLE void setPaused(bool paused) { m_runtime.paused = paused; }
-    Q_INVOKABLE void setGameSpeed(float speed) { m_runtime.timeScale = std::max(0.0f, speed); }
-    bool paused() const { return m_runtime.paused; }
-    float timeScale() const { return m_runtime.timeScale; }
-    QString victoryState() const { return m_runtime.victoryState; }
-    QString cursorMode() const { return m_runtime.cursorMode; }
-    void setCursorMode(const QString& mode);
-    qreal globalCursorX() const;
-    qreal globalCursorY() const;
-    bool hasUnitsSelected() const;
-    int playerTroopCount() const;
-    int maxTroopsPerPlayer() const { return m_level.maxTroopsPerPlayer; }
-
-    Q_INVOKABLE bool hasSelectedType(const QString& type) const;
-    Q_INVOKABLE void recruitNearSelected(const QString& unitType);
-    Q_INVOKABLE QVariantMap getSelectedProductionState() const;
-    Q_INVOKABLE void setRallyAtScreen(qreal sx, qreal sy);
-    Q_INVOKABLE QVariantList availableMaps() const;
-    Q_INVOKABLE void startSkirmish(const QString& mapPath);
-    Q_INVOKABLE void openSettings();
-    Q_INVOKABLE void loadSave();
-    Q_INVOKABLE void exitGame();
-
-    void setWindow(QQuickWindow* w) { m_window = w; }
-
-    void ensureInitialized();
-    void update(float dt);
-    void render(int pixelWidth, int pixelHeight);
-
-    void getSelectedUnitIds(std::vector<Engine::Core::EntityID>& out) const;
-    bool getUnitInfo(Engine::Core::EntityID id, QString& name, int& health, int& maxHealth,
-                     bool& isBuilding, bool& alive) const;
-    
-    // Patrol preview state for rendering
-    bool hasPatrolPreviewWaypoint() const { return m_patrol.hasFirstWaypoint; }
-    QVector3D getPatrolPreviewWaypoint() const { return m_patrol.firstWaypoint; }
+  GameEngine();
+  ~GameEngine();
+
+  Q_PROPERTY(QObject *selectedUnitsModel READ selectedUnitsModel NOTIFY
+                 selectedUnitsChanged)
+  Q_PROPERTY(bool paused READ paused WRITE setPaused)
+  Q_PROPERTY(float timeScale READ timeScale WRITE setGameSpeed)
+  Q_PROPERTY(QString victoryState READ victoryState NOTIFY victoryStateChanged)
+  Q_PROPERTY(QString cursorMode READ cursorMode WRITE setCursorMode NOTIFY
+                 cursorModeChanged)
+  Q_PROPERTY(qreal globalCursorX READ globalCursorX NOTIFY globalCursorChanged)
+  Q_PROPERTY(qreal globalCursorY READ globalCursorY NOTIFY globalCursorChanged)
+  Q_PROPERTY(
+      bool hasUnitsSelected READ hasUnitsSelected NOTIFY selectedUnitsChanged)
+  Q_PROPERTY(
+      int playerTroopCount READ playerTroopCount NOTIFY troopCountChanged)
+  Q_PROPERTY(
+      int maxTroopsPerPlayer READ maxTroopsPerPlayer NOTIFY troopCountChanged)
+  Q_PROPERTY(
+      QVariantList availableMaps READ availableMaps NOTIFY availableMapsChanged)
+
+  Q_INVOKABLE void onMapClicked(qreal sx, qreal sy);
+  Q_INVOKABLE void onRightClick(qreal sx, qreal sy);
+  Q_INVOKABLE void onClickSelect(qreal sx, qreal sy, bool additive = false);
+  Q_INVOKABLE void onAreaSelected(qreal x1, qreal y1, qreal x2, qreal y2,
+                                  bool additive = false);
+  Q_INVOKABLE void setHoverAtScreen(qreal sx, qreal sy);
+  Q_INVOKABLE void onAttackClick(qreal sx, qreal sy);
+  Q_INVOKABLE void onStopCommand();
+  Q_INVOKABLE void onPatrolClick(qreal sx, qreal sy);
+
+  Q_INVOKABLE void cameraMove(float dx, float dz);
+  Q_INVOKABLE void cameraElevate(float dy);
+  Q_INVOKABLE void cameraYaw(float degrees);
+  Q_INVOKABLE void cameraOrbit(float yawDeg, float pitchDeg);
+  Q_INVOKABLE void cameraFollowSelection(bool enable);
+  Q_INVOKABLE void cameraSetFollowLerp(float alpha);
+
+  Q_INVOKABLE void setPaused(bool paused) { m_runtime.paused = paused; }
+  Q_INVOKABLE void setGameSpeed(float speed) {
+    m_runtime.timeScale = std::max(0.0f, speed);
+  }
+  bool paused() const { return m_runtime.paused; }
+  float timeScale() const { return m_runtime.timeScale; }
+  QString victoryState() const { return m_runtime.victoryState; }
+  QString cursorMode() const { return m_runtime.cursorMode; }
+  void setCursorMode(const QString &mode);
+  qreal globalCursorX() const;
+  qreal globalCursorY() const;
+  bool hasUnitsSelected() const;
+  int playerTroopCount() const;
+  int maxTroopsPerPlayer() const { return m_level.maxTroopsPerPlayer; }
+
+  Q_INVOKABLE bool hasSelectedType(const QString &type) const;
+  Q_INVOKABLE void recruitNearSelected(const QString &unitType);
+  Q_INVOKABLE QVariantMap getSelectedProductionState() const;
+  Q_INVOKABLE void setRallyAtScreen(qreal sx, qreal sy);
+  Q_INVOKABLE QVariantList availableMaps() const;
+  Q_INVOKABLE void startSkirmish(const QString &mapPath);
+  Q_INVOKABLE void openSettings();
+  Q_INVOKABLE void loadSave();
+  Q_INVOKABLE void exitGame();
+
+  void setWindow(QQuickWindow *w) { m_window = w; }
+
+  void ensureInitialized();
+  void update(float dt);
+  void render(int pixelWidth, int pixelHeight);
+
+  void getSelectedUnitIds(std::vector<Engine::Core::EntityID> &out) const;
+  bool getUnitInfo(Engine::Core::EntityID id, QString &name, int &health,
+                   int &maxHealth, bool &isBuilding, bool &alive) const;
+
+  bool hasPatrolPreviewWaypoint() const { return m_patrol.hasFirstWaypoint; }
+  QVector3D getPatrolPreviewWaypoint() const { return m_patrol.firstWaypoint; }
 
 private:
-    struct RuntimeState { 
-        bool initialized = false; 
-        bool paused = false;
-        bool loading = false;  // Prevents updates during map reload
-        float timeScale = 1.0f; 
-        int localOwnerId = 1; 
-        QString victoryState = ""; // "", "victory", or "defeat"
-        QString cursorMode = "normal"; // "normal", "attack", "guard", "patrol"
-        int lastTroopCount = 0; // For change detection
-    };
-    struct ViewportState { int width = 0; int height = 0; };
-    struct LevelState { 
-        QString mapName; 
-        Engine::Core::EntityID playerUnitId = 0; 
-        float camFov = 45.0f; 
-        float camNear = 0.1f; 
-        float camFar = 1000.0f;
-        int maxTroopsPerPlayer = 50;
-    };
-    struct HoverState { Engine::Core::EntityID buildingId = 0; };
-    struct PatrolState {
-        QVector3D firstWaypoint;
-        bool hasFirstWaypoint = false;
-    };
-
-    Game::Systems::ArrowSystem* m_arrowSystem = nullptr;
-    void initialize();
-    void checkVictoryCondition();
-    bool screenToGround(const QPointF& screenPt, QVector3D& outWorld);
-    bool worldToScreen(const QVector3D& world, QPointF& outScreen) const;
-    void syncSelectionFlags();
-    QObject* selectedUnitsModel();
-
-    std::unique_ptr<Engine::Core::World> m_world;
-    std::unique_ptr<Render::GL::Renderer> m_renderer;
-    std::unique_ptr<Render::GL::Camera>   m_camera;
-    std::shared_ptr<Render::GL::ResourceManager> m_resources; // deprecated; will be unused with backend-owned resources
-    std::unique_ptr<Render::GL::GroundRenderer> m_ground;
-    std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
-    std::unique_ptr<Game::Systems::PickingService> m_pickingService;
-    QQuickWindow* m_window = nullptr;
-    RuntimeState m_runtime;
-    ViewportState m_viewport;
-    bool m_followSelectionEnabled = false;
-    LevelState m_level;
-    QObject* m_selectedUnitsModel = nullptr;
-    HoverState m_hover;
-    PatrolState m_patrol;
+  struct RuntimeState {
+    bool initialized = false;
+    bool paused = false;
+    bool loading = false;
+    float timeScale = 1.0f;
+    int localOwnerId = 1;
+    QString victoryState = "";
+    QString cursorMode = "normal";
+    int lastTroopCount = 0;
+  };
+  struct ViewportState {
+    int width = 0;
+    int height = 0;
+  };
+  struct LevelState {
+    QString mapName;
+    Engine::Core::EntityID playerUnitId = 0;
+    float camFov = 45.0f;
+    float camNear = 0.1f;
+    float camFar = 1000.0f;
+    int maxTroopsPerPlayer = 50;
+  };
+  struct HoverState {
+    Engine::Core::EntityID buildingId = 0;
+  };
+  struct PatrolState {
+    QVector3D firstWaypoint;
+    bool hasFirstWaypoint = false;
+  };
+
+  Game::Systems::ArrowSystem *m_arrowSystem = nullptr;
+  void initialize();
+  void checkVictoryCondition();
+  bool screenToGround(const QPointF &screenPt, QVector3D &outWorld);
+  bool worldToScreen(const QVector3D &world, QPointF &outScreen) const;
+  void syncSelectionFlags();
+  QObject *selectedUnitsModel();
+
+  std::unique_ptr<Engine::Core::World> m_world;
+  std::unique_ptr<Render::GL::Renderer> m_renderer;
+  std::unique_ptr<Render::GL::Camera> m_camera;
+  std::shared_ptr<Render::GL::ResourceManager> m_resources;
+  std::unique_ptr<Render::GL::GroundRenderer> m_ground;
+  std::unique_ptr<Game::Systems::SelectionSystem> m_selectionSystem;
+  std::unique_ptr<Game::Systems::PickingService> m_pickingService;
+  QQuickWindow *m_window = nullptr;
+  RuntimeState m_runtime;
+  ViewportState m_viewport;
+  bool m_followSelectionEnabled = false;
+  LevelState m_level;
+  QObject *m_selectedUnitsModel = nullptr;
+  HoverState m_hover;
+  PatrolState m_patrol;
 signals:
-    void selectedUnitsChanged();
-    void victoryStateChanged();
-    void cursorModeChanged();
-    void globalCursorChanged();
-    void troopCountChanged();
-    void availableMapsChanged();
+  void selectedUnitsChanged();
+  void victoryStateChanged();
+  void cursorModeChanged();
+  void globalCursorChanged();
+  void troopCountChanged();
+  void availableMapsChanged();
 };

+ 67 - 49
app/selected_units_model.cpp

@@ -1,67 +1,85 @@
 #include "selected_units_model.h"
-#include "game_engine.h"
-#include "../game/systems/selection_system.h"
-#include "../game/core/world.h"
 #include "../game/core/component.h"
+#include "../game/core/world.h"
+#include "../game/systems/selection_system.h"
+#include "game_engine.h"
 #include <algorithm>
 
-SelectedUnitsModel::SelectedUnitsModel(GameEngine* engine, QObject* parent)
-    : QAbstractListModel(parent), m_engine(engine) {
-}
+SelectedUnitsModel::SelectedUnitsModel(GameEngine *engine, QObject *parent)
+    : QAbstractListModel(parent), m_engine(engine) {}
 
-int SelectedUnitsModel::rowCount(const QModelIndex& parent) const {
-    if (parent.isValid()) return 0;
-    return static_cast<int>(m_ids.size());
+int SelectedUnitsModel::rowCount(const QModelIndex &parent) const {
+  if (parent.isValid())
+    return 0;
+  return static_cast<int>(m_ids.size());
 }
 
-QVariant SelectedUnitsModel::data(const QModelIndex& index, int role) const {
-    if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_ids.size())) return {};
-    auto id = m_ids[index.row()];
-    if (!m_engine) return {};
-    QString name; int hp=0, maxHp=0; bool isB=false, alive=false;
-    if (role == UnitIdRole) return QVariant::fromValue<int>(static_cast<int>(id));
-    if (!m_engine->getUnitInfo(id, name, hp, maxHp, isB, alive)) return {};
-    if (role == NameRole) return name;
-    if (role == HealthRole) return hp;
-    if (role == MaxHealthRole) return maxHp;
-    if (role == HealthRatioRole) return (maxHp > 0 ? static_cast<double>(std::clamp(hp, 0, maxHp)) / static_cast<double>(maxHp) : 0.0);
+QVariant SelectedUnitsModel::data(const QModelIndex &index, int role) const {
+  if (!index.isValid() || index.row() < 0 ||
+      index.row() >= static_cast<int>(m_ids.size()))
+    return {};
+  auto id = m_ids[index.row()];
+  if (!m_engine)
+    return {};
+  QString name;
+  int hp = 0, maxHp = 0;
+  bool isB = false, alive = false;
+  if (role == UnitIdRole)
+    return QVariant::fromValue<int>(static_cast<int>(id));
+  if (!m_engine->getUnitInfo(id, name, hp, maxHp, isB, alive))
     return {};
+  if (role == NameRole)
+    return name;
+  if (role == HealthRole)
+    return hp;
+  if (role == MaxHealthRole)
+    return maxHp;
+  if (role == HealthRatioRole)
+    return (maxHp > 0 ? static_cast<double>(std::clamp(hp, 0, maxHp)) /
+                            static_cast<double>(maxHp)
+                      : 0.0);
+  return {};
 }
 
 QHash<int, QByteArray> SelectedUnitsModel::roleNames() const {
-    return {
-        { UnitIdRole, "unitId" },
-        { NameRole, "name" },
-        { HealthRole, "health" },
-        { MaxHealthRole, "maxHealth" },
-        { HealthRatioRole, "healthRatio" }
-    };
+  return {{UnitIdRole, "unitId"},
+          {NameRole, "name"},
+          {HealthRole, "health"},
+          {MaxHealthRole, "maxHealth"},
+          {HealthRatioRole, "healthRatio"}};
 }
 
 void SelectedUnitsModel::refresh() {
-    if (!m_engine) return;
-    std::vector<Engine::Core::EntityID> ids;
-    m_engine->getSelectedUnitIds(ids);
+  if (!m_engine)
+    return;
+  std::vector<Engine::Core::EntityID> ids;
+  m_engine->getSelectedUnitIds(ids);
 
-    // If the selected IDs are unchanged, emit dataChanged to refresh health ratios without resetting the list
-    if (ids.size() == m_ids.size() && std::equal(ids.begin(), ids.end(), m_ids.begin())) {
-        if (!m_ids.empty()) {
-            QModelIndex first = index(0, 0);
-            QModelIndex last = index(static_cast<int>(m_ids.size()) - 1, 0);
-            emit dataChanged(first, last, { HealthRole, MaxHealthRole, HealthRatioRole });
-        }
-        return;
+  if (ids.size() == m_ids.size() &&
+      std::equal(ids.begin(), ids.end(), m_ids.begin())) {
+    if (!m_ids.empty()) {
+      QModelIndex first = index(0, 0);
+      QModelIndex last = index(static_cast<int>(m_ids.size()) - 1, 0);
+      emit dataChanged(first, last,
+                       {HealthRole, MaxHealthRole, HealthRatioRole});
     }
+    return;
+  }
 
-    beginResetModel();
-    // Filter out entities that are dead (health <= 0) if we can access the world
-    m_ids.clear();
-    for (auto id : ids) {
-        QString nm; int hp=0, maxHp=0; bool isB=false, alive=false;
-        if (!m_engine->getUnitInfo(id, nm, hp, maxHp, isB, alive)) continue;
-        if (isB) continue;
-        if (!alive) continue;
-        m_ids.push_back(id);
-    }
-    endResetModel();
+  beginResetModel();
+
+  m_ids.clear();
+  for (auto id : ids) {
+    QString nm;
+    int hp = 0, maxHp = 0;
+    bool isB = false, alive = false;
+    if (!m_engine->getUnitInfo(id, nm, hp, maxHp, isB, alive))
+      continue;
+    if (isB)
+      continue;
+    if (!alive)
+      continue;
+    m_ids.push_back(id);
+  }
+  endResetModel();
 }

+ 17 - 10
app/selected_units_model.h

@@ -1,26 +1,33 @@
 #pragma once
 
+#include "../game/core/entity.h"
 #include <QAbstractListModel>
 #include <vector>
-#include "../game/core/entity.h"
 
 class GameEngine;
 
 class SelectedUnitsModel : public QAbstractListModel {
-    Q_OBJECT
+  Q_OBJECT
 public:
-    enum Roles { UnitIdRole = Qt::UserRole + 1, NameRole, HealthRole, MaxHealthRole, HealthRatioRole };
+  enum Roles {
+    UnitIdRole = Qt::UserRole + 1,
+    NameRole,
+    HealthRole,
+    MaxHealthRole,
+    HealthRatioRole
+  };
 
-    explicit SelectedUnitsModel(GameEngine* engine, QObject* parent = nullptr);
+  explicit SelectedUnitsModel(GameEngine *engine, QObject *parent = nullptr);
 
-    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
-    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
-    QHash<int, QByteArray> roleNames() const override;
+  int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+  QVariant data(const QModelIndex &index,
+                int role = Qt::DisplayRole) const override;
+  QHash<int, QByteArray> roleNames() const override;
 
 public slots:
-    void refresh();
+  void refresh();
 
 private:
-    GameEngine* m_engine = nullptr;
-    std::vector<Engine::Core::EntityID> m_ids;
+  GameEngine *m_engine = nullptr;
+  std::vector<Engine::Core::EntityID> m_ids;
 };

+ 1 - 5
game/core/component.cpp

@@ -1,7 +1,3 @@
 #include "component.h"
 
-namespace Engine::Core {
-
-// Component implementations are mostly in header due to simplicity
-
-} // namespace Engine::Core
+namespace Engine::Core {}

+ 70 - 65
game/core/component.h

@@ -4,87 +4,92 @@
 #include <string>
 #include <vector>
 
-
 namespace Engine::Core {
 
-
 class TransformComponent : public Component {
 public:
-    TransformComponent(float x = 0.0f, float y = 0.0f, float z = 0.0f, 
-                      float rotX = 0.0f, float rotY = 0.0f, float rotZ = 0.0f,
-                      float scaleX = 1.0f, float scaleY = 1.0f, float scaleZ = 1.0f)
-        : position{x, y, z}, rotation{rotX, rotY, rotZ}, scale{scaleX, scaleY, scaleZ} {}
-
-    struct Vec3 { float x, y, z; };
-    Vec3 position;
-    Vec3 rotation;
-    Vec3 scale;
+  TransformComponent(float x = 0.0f, float y = 0.0f, float z = 0.0f,
+                     float rotX = 0.0f, float rotY = 0.0f, float rotZ = 0.0f,
+                     float scaleX = 1.0f, float scaleY = 1.0f,
+                     float scaleZ = 1.0f)
+      : position{x, y, z}, rotation{rotX, rotY, rotZ},
+        scale{scaleX, scaleY, scaleZ} {}
+
+  struct Vec3 {
+    float x, y, z;
+  };
+  Vec3 position;
+  Vec3 rotation;
+  Vec3 scale;
 };
 
 class RenderableComponent : public Component {
 public:
-    enum class MeshKind { None, Quad, Plane, Cube, Capsule, Ring };
-
-    RenderableComponent(const std::string& meshPath, const std::string& texturePath)
-        : meshPath(meshPath), texturePath(texturePath), visible(true), mesh(MeshKind::Cube) {
-        color[0] = color[1] = color[2] = 1.0f;
-    }
-
-    std::string meshPath;
-    std::string texturePath;
-    bool visible;
-    MeshKind mesh;
-    float color[3];
+  enum class MeshKind { None, Quad, Plane, Cube, Capsule, Ring };
+
+  RenderableComponent(const std::string &meshPath,
+                      const std::string &texturePath)
+      : meshPath(meshPath), texturePath(texturePath), visible(true),
+        mesh(MeshKind::Cube) {
+    color[0] = color[1] = color[2] = 1.0f;
+  }
+
+  std::string meshPath;
+  std::string texturePath;
+  bool visible;
+  MeshKind mesh;
+  float color[3];
 };
 
 class UnitComponent : public Component {
 public:
-    UnitComponent(int health = 100, int maxHealth = 100, float speed = 1.0f)
-        : health(health), maxHealth(maxHealth), speed(speed), ownerId(0) {}
-
-    int health;
-    int maxHealth;
-    float speed;
-    std::string unitType;
-    int ownerId; // faction/player ownership
+  UnitComponent(int health = 100, int maxHealth = 100, float speed = 1.0f)
+      : health(health), maxHealth(maxHealth), speed(speed), ownerId(0) {}
+
+  int health;
+  int maxHealth;
+  float speed;
+  std::string unitType;
+  int ownerId;
 };
 
 class MovementComponent : public Component {
 public:
-    MovementComponent() : hasTarget(false), targetX(0.0f), targetY(0.0f), vx(0.0f), vz(0.0f) {}
+  MovementComponent()
+      : hasTarget(false), targetX(0.0f), targetY(0.0f), vx(0.0f), vz(0.0f) {}
 
-    bool hasTarget;
-    float targetX, targetY;
-    float vx, vz;
-    std::vector<std::pair<float, float>> path;
+  bool hasTarget;
+  float targetX, targetY;
+  float vx, vz;
+  std::vector<std::pair<float, float>> path;
 };
 
 class AttackComponent : public Component {
 public:
-    AttackComponent(float range = 2.0f, int damage = 10, float cooldown = 1.0f)
-        : range(range), damage(damage), cooldown(cooldown), timeSinceLast(0.0f) {}
+  AttackComponent(float range = 2.0f, int damage = 10, float cooldown = 1.0f)
+      : range(range), damage(damage), cooldown(cooldown), timeSinceLast(0.0f) {}
 
-    float range;
-    int damage;
-    float cooldown;
-    float timeSinceLast;
+  float range;
+  int damage;
+  float cooldown;
+  float timeSinceLast;
 };
 
 class AttackTargetComponent : public Component {
 public:
-    AttackTargetComponent() : targetId(0), shouldChase(false) {}
-    
-    EntityID targetId;     // Explicit target to attack
-    bool shouldChase;      // If true, unit will chase fleeing targets
+  AttackTargetComponent() : targetId(0), shouldChase(false) {}
+
+  EntityID targetId;
+  bool shouldChase;
 };
 
 class PatrolComponent : public Component {
 public:
-    PatrolComponent() : currentWaypoint(0), patrolling(false) {}
-    
-    std::vector<std::pair<float, float>> waypoints; // List of patrol points (x, z)
-    size_t currentWaypoint; // Index of current target waypoint
-    bool patrolling;        // Active patrol state
+  PatrolComponent() : currentWaypoint(0), patrolling(false) {}
+
+  std::vector<std::pair<float, float>> waypoints;
+  size_t currentWaypoint;
+  bool patrolling;
 };
 
 } // namespace Engine::Core
@@ -93,24 +98,24 @@ namespace Engine::Core {
 
 class BuildingComponent : public Component {
 public:
-    BuildingComponent() = default; // marker component for buildings (non-squad units)
+  BuildingComponent() = default;
 };
 
 class ProductionComponent : public Component {
 public:
-    ProductionComponent()
-                : inProgress(false), buildTime(4.0f), timeRemaining(0.0f),
-                    producedCount(0), maxUnits(5), productType("archer"),
-                    rallyX(0.0f), rallyZ(0.0f), rallySet(false) {}
-
-    bool inProgress;
-    float buildTime;      // seconds for one unit
-    float timeRemaining;  // seconds left for current production
-    int producedCount;    // how many produced by this building
-    int maxUnits;         // cap after which building can't produce more
-    std::string productType;
-    float rallyX, rallyZ; // rally point on ground plane (XZ)
-    bool rallySet;        // true if rally point explicitly set
+  ProductionComponent()
+      : inProgress(false), buildTime(4.0f), timeRemaining(0.0f),
+        producedCount(0), maxUnits(5), productType("archer"), rallyX(0.0f),
+        rallyZ(0.0f), rallySet(false) {}
+
+  bool inProgress;
+  float buildTime;
+  float timeRemaining;
+  int producedCount;
+  int maxUnits;
+  std::string productType;
+  float rallyX, rallyZ;
+  bool rallySet;
 };
 
 } // namespace Engine::Core

+ 0 - 3
game/core/entity.cpp

@@ -7,7 +7,4 @@ Entity::Entity(EntityID id) : m_id(id) {}
 
 EntityID Entity::getId() const { return m_id; }
 
-// Template methods remain header-only in previous layout.
-// For simplicity, we keep non-template parts here.
-
 } // namespace Engine::Core

+ 39 - 43
game/core/entity.h

@@ -1,11 +1,11 @@
 #pragma once
 
 #include <cstdint>
-#include <vector>
 #include <memory>
+#include <type_traits>
 #include <typeindex>
 #include <unordered_map>
-#include <type_traits>
+#include <vector>
 
 namespace Engine::Core {
 
@@ -14,55 +14,51 @@ constexpr EntityID NULL_ENTITY = 0;
 
 class Component {
 public:
-    virtual ~Component() = default;
+  virtual ~Component() = default;
 };
 
 class Entity {
 public:
-    Entity(EntityID id);
-    
-    EntityID getId() const;
-    
-    template<typename T, typename... Args>
-    T* addComponent(Args&&... args) {
-        static_assert(std::is_base_of_v<Component, T>, "T must inherit from Component");
-        auto component = std::make_unique<T>(std::forward<Args>(args)...);
-        auto ptr = component.get();
-        m_components[std::type_index(typeid(T))] = std::move(component);
-        return ptr;
-    }
-    
-    template<typename T>
-    T* getComponent() {
-        auto it = m_components.find(std::type_index(typeid(T)));
-        if (it != m_components.end()) {
-            return static_cast<T*>(it->second.get());
-        }
-        return nullptr;
-    }
-    
-    template<typename T>
-    const T* getComponent() const {
-        auto it = m_components.find(std::type_index(typeid(T)));
-        if (it != m_components.end()) {
-            return static_cast<const T*>(it->second.get());
-        }
-        return nullptr;
-    }
-    
-    template<typename T>
-    void removeComponent() {
-        m_components.erase(std::type_index(typeid(T)));
+  Entity(EntityID id);
+
+  EntityID getId() const;
+
+  template <typename T, typename... Args> T *addComponent(Args &&...args) {
+    static_assert(std::is_base_of_v<Component, T>,
+                  "T must inherit from Component");
+    auto component = std::make_unique<T>(std::forward<Args>(args)...);
+    auto ptr = component.get();
+    m_components[std::type_index(typeid(T))] = std::move(component);
+    return ptr;
+  }
+
+  template <typename T> T *getComponent() {
+    auto it = m_components.find(std::type_index(typeid(T)));
+    if (it != m_components.end()) {
+      return static_cast<T *>(it->second.get());
     }
-    
-    template<typename T>
-    bool hasComponent() const {
-        return m_components.find(std::type_index(typeid(T))) != m_components.end();
+    return nullptr;
+  }
+
+  template <typename T> const T *getComponent() const {
+    auto it = m_components.find(std::type_index(typeid(T)));
+    if (it != m_components.end()) {
+      return static_cast<const T *>(it->second.get());
     }
+    return nullptr;
+  }
+
+  template <typename T> void removeComponent() {
+    m_components.erase(std::type_index(typeid(T)));
+  }
+
+  template <typename T> bool hasComponent() const {
+    return m_components.find(std::type_index(typeid(T))) != m_components.end();
+  }
 
 private:
-    EntityID m_id;
-    std::unordered_map<std::type_index, std::unique_ptr<Component>> m_components;
+  EntityID m_id;
+  std::unordered_map<std::type_index, std::unique_ptr<Component>> m_components;
 };
 
 } // namespace Engine::Core

+ 1 - 5
game/core/event_manager.cpp

@@ -1,7 +1,3 @@
 #include "event_manager.h"
 
-namespace Engine::Core {
-
-// Templates in header; nothing here.
-
-} // namespace Engine::Core
+namespace Engine::Core {}

+ 31 - 32
game/core/event_manager.h

@@ -1,60 +1,59 @@
 #pragma once
 
+#include "entity.h"
 #include <functional>
+#include <memory>
+#include <typeindex>
 #include <unordered_map>
 #include <vector>
-#include <typeindex>
-#include <memory>
-#include "entity.h"
 
 namespace Engine::Core {
 
 class Event {
 public:
-    virtual ~Event() = default;
+  virtual ~Event() = default;
 };
 
-template<typename T>
-using EventHandler = std::function<void(const T&)>;
+template <typename T> using EventHandler = std::function<void(const T &)>;
 
 class EventManager {
 public:
-    template<typename T>
-    void subscribe(EventHandler<T> handler) {
-        static_assert(std::is_base_of_v<Event, T>, "T must inherit from Event");
-        auto wrapper = [handler](const void* event) {
-            handler(*static_cast<const T*>(event));
-        };
-        m_handlers[std::type_index(typeid(T))].push_back(wrapper);
+  template <typename T> void subscribe(EventHandler<T> handler) {
+    static_assert(std::is_base_of_v<Event, T>, "T must inherit from Event");
+    auto wrapper = [handler](const void *event) {
+      handler(*static_cast<const T *>(event));
+    };
+    m_handlers[std::type_index(typeid(T))].push_back(wrapper);
+  }
+
+  template <typename T> void publish(const T &event) {
+    static_assert(std::is_base_of_v<Event, T>, "T must inherit from Event");
+    auto it = m_handlers.find(std::type_index(typeid(T)));
+    if (it != m_handlers.end()) {
+      for (const auto &handler : it->second) {
+        handler(&event);
+      }
     }
-    
-    template<typename T>
-    void publish(const T& event) {
-        static_assert(std::is_base_of_v<Event, T>, "T must inherit from Event");
-        auto it = m_handlers.find(std::type_index(typeid(T)));
-        if (it != m_handlers.end()) {
-            for (const auto& handler : it->second) {
-                handler(&event);
-            }
-        }
-    }
-    
+  }
+
 private:
-    std::unordered_map<std::type_index, std::vector<std::function<void(const void*)>>> m_handlers;
+  std::unordered_map<std::type_index,
+                     std::vector<std::function<void(const void *)>>>
+      m_handlers;
 };
 
-// Common game events
 class UnitSelectedEvent : public Event {
 public:
-    UnitSelectedEvent(EntityID unitId) : unitId(unitId) {}
-    EntityID unitId;
+  UnitSelectedEvent(EntityID unitId) : unitId(unitId) {}
+  EntityID unitId;
 };
 
 class UnitMovedEvent : public Event {
 public:
-    UnitMovedEvent(EntityID unitId, float x, float y) : unitId(unitId), x(x), y(y) {}
-    EntityID unitId;
-    float x, y;
+  UnitMovedEvent(EntityID unitId, float x, float y)
+      : unitId(unitId), x(x), y(y) {}
+  EntityID unitId;
+  float x, y;
 };
 
 } // namespace Engine::Core

+ 85 - 83
game/core/serialization.cpp

@@ -1,106 +1,108 @@
 #include "serialization.h"
+#include "component.h"
 #include "entity.h"
 #include "world.h"
-#include "component.h"
+#include <QDebug>
+#include <QFile>
 #include <QJsonArray>
 #include <QJsonObject>
-#include <QFile>
-#include <QDebug>
 
 namespace Engine::Core {
 
-QJsonObject Serialization::serializeEntity(const Entity* entity) {
-    QJsonObject entityObj;
-    entityObj["id"] = static_cast<qint64>(entity->getId());
-    
-    if (auto transform = const_cast<Entity*>(entity)->getComponent<TransformComponent>()) {
-        QJsonObject transformObj;
-        transformObj["posX"] = transform->position.x;
-        transformObj["posY"] = transform->position.y;
-        transformObj["posZ"] = transform->position.z;
-        transformObj["rotX"] = transform->rotation.x;
-        transformObj["rotY"] = transform->rotation.y;
-        transformObj["rotZ"] = transform->rotation.z;
-        transformObj["scaleX"] = transform->scale.x;
-        transformObj["scaleY"] = transform->scale.y;
-        transformObj["scaleZ"] = transform->scale.z;
-        entityObj["transform"] = transformObj;
-    }
-    
-    if (auto unit = const_cast<Entity*>(entity)->getComponent<UnitComponent>()) {
-        QJsonObject unitObj;
-        unitObj["health"] = unit->health;
-        unitObj["maxHealth"] = unit->maxHealth;
-        unitObj["speed"] = unit->speed;
-        unitObj["unitType"] = QString::fromStdString(unit->unitType);
-        unitObj["ownerId"] = unit->ownerId;
-        entityObj["unit"] = unitObj;
-    }
-    
-    return entityObj;
+QJsonObject Serialization::serializeEntity(const Entity *entity) {
+  QJsonObject entityObj;
+  entityObj["id"] = static_cast<qint64>(entity->getId());
+
+  if (auto transform =
+          const_cast<Entity *>(entity)->getComponent<TransformComponent>()) {
+    QJsonObject transformObj;
+    transformObj["posX"] = transform->position.x;
+    transformObj["posY"] = transform->position.y;
+    transformObj["posZ"] = transform->position.z;
+    transformObj["rotX"] = transform->rotation.x;
+    transformObj["rotY"] = transform->rotation.y;
+    transformObj["rotZ"] = transform->rotation.z;
+    transformObj["scaleX"] = transform->scale.x;
+    transformObj["scaleY"] = transform->scale.y;
+    transformObj["scaleZ"] = transform->scale.z;
+    entityObj["transform"] = transformObj;
+  }
+
+  if (auto unit = const_cast<Entity *>(entity)->getComponent<UnitComponent>()) {
+    QJsonObject unitObj;
+    unitObj["health"] = unit->health;
+    unitObj["maxHealth"] = unit->maxHealth;
+    unitObj["speed"] = unit->speed;
+    unitObj["unitType"] = QString::fromStdString(unit->unitType);
+    unitObj["ownerId"] = unit->ownerId;
+    entityObj["unit"] = unitObj;
+  }
+
+  return entityObj;
 }
 
-void Serialization::deserializeEntity(Entity* entity, const QJsonObject& json) {
-    if (json.contains("transform")) {
-        auto transformObj = json["transform"].toObject();
-        auto transform = entity->addComponent<TransformComponent>();
-        transform->position.x = transformObj["posX"].toDouble();
-        transform->position.y = transformObj["posY"].toDouble();
-        transform->position.z = transformObj["posZ"].toDouble();
-        transform->rotation.x = transformObj["rotX"].toDouble();
-        transform->rotation.y = transformObj["rotY"].toDouble();
-        transform->rotation.z = transformObj["rotZ"].toDouble();
-        transform->scale.x = transformObj["scaleX"].toDouble();
-        transform->scale.y = transformObj["scaleY"].toDouble();
-        transform->scale.z = transformObj["scaleZ"].toDouble();
-    }
-    
-    if (json.contains("unit")) {
-        auto unitObj = json["unit"].toObject();
-        auto unit = entity->addComponent<UnitComponent>();
-        unit->health = unitObj["health"].toInt();
-        unit->maxHealth = unitObj["maxHealth"].toInt();
-        unit->speed = unitObj["speed"].toDouble();
+void Serialization::deserializeEntity(Entity *entity, const QJsonObject &json) {
+  if (json.contains("transform")) {
+    auto transformObj = json["transform"].toObject();
+    auto transform = entity->addComponent<TransformComponent>();
+    transform->position.x = transformObj["posX"].toDouble();
+    transform->position.y = transformObj["posY"].toDouble();
+    transform->position.z = transformObj["posZ"].toDouble();
+    transform->rotation.x = transformObj["rotX"].toDouble();
+    transform->rotation.y = transformObj["rotY"].toDouble();
+    transform->rotation.z = transformObj["rotZ"].toDouble();
+    transform->scale.x = transformObj["scaleX"].toDouble();
+    transform->scale.y = transformObj["scaleY"].toDouble();
+    transform->scale.z = transformObj["scaleZ"].toDouble();
+  }
+
+  if (json.contains("unit")) {
+    auto unitObj = json["unit"].toObject();
+    auto unit = entity->addComponent<UnitComponent>();
+    unit->health = unitObj["health"].toInt();
+    unit->maxHealth = unitObj["maxHealth"].toInt();
+    unit->speed = unitObj["speed"].toDouble();
     unit->unitType = unitObj["unitType"].toString().toStdString();
     unit->ownerId = unitObj["ownerId"].toInt(0);
-    }
+  }
 }
 
-QJsonDocument Serialization::serializeWorld(const World* world) {
-    QJsonObject worldObj;
-    QJsonArray entitiesArray;
-    worldObj["entities"] = entitiesArray;
-    return QJsonDocument(worldObj);
+QJsonDocument Serialization::serializeWorld(const World *world) {
+  QJsonObject worldObj;
+  QJsonArray entitiesArray;
+  worldObj["entities"] = entitiesArray;
+  return QJsonDocument(worldObj);
 }
 
-void Serialization::deserializeWorld(World* world, const QJsonDocument& doc) {
-    auto worldObj = doc.object();
-    auto entitiesArray = worldObj["entities"].toArray();
-    for (const auto& value : entitiesArray) {
-        auto entityObj = value.toObject();
-        auto entity = world->createEntity();
-        deserializeEntity(entity, entityObj);
-    }
+void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
+  auto worldObj = doc.object();
+  auto entitiesArray = worldObj["entities"].toArray();
+  for (const auto &value : entitiesArray) {
+    auto entityObj = value.toObject();
+    auto entity = world->createEntity();
+    deserializeEntity(entity, entityObj);
+  }
 }
 
-bool Serialization::saveToFile(const QString& filename, const QJsonDocument& doc) {
-    QFile file(filename);
-    if (!file.open(QIODevice::WriteOnly)) {
-        qWarning() << "Could not open file for writing:" << filename;
-        return false;
-    }
-    file.write(doc.toJson());
-    return true;
+bool Serialization::saveToFile(const QString &filename,
+                               const QJsonDocument &doc) {
+  QFile file(filename);
+  if (!file.open(QIODevice::WriteOnly)) {
+    qWarning() << "Could not open file for writing:" << filename;
+    return false;
+  }
+  file.write(doc.toJson());
+  return true;
 }
 
-QJsonDocument Serialization::loadFromFile(const QString& filename) {
-    QFile file(filename);
-    if (!file.open(QIODevice::ReadOnly)) {
-        qWarning() << "Could not open file for reading:" << filename;
-        return QJsonDocument();
-    }
-    QByteArray data = file.readAll();
-    return QJsonDocument::fromJson(data);
+QJsonDocument Serialization::loadFromFile(const QString &filename) {
+  QFile file(filename);
+  if (!file.open(QIODevice::ReadOnly)) {
+    qWarning() << "Could not open file for reading:" << filename;
+    return QJsonDocument();
+  }
+  QByteArray data = file.readAll();
+  return QJsonDocument::fromJson(data);
 }
 
 } // namespace Engine::Core

+ 10 - 10
game/core/serialization.h

@@ -1,21 +1,21 @@
 #pragma once
 
-#include <QString>
-#include <QJsonObject>
 #include <QJsonDocument>
+#include <QJsonObject>
+#include <QString>
 
 namespace Engine::Core {
 
 class Serialization {
 public:
-    static QJsonObject serializeEntity(const class Entity* entity);
-    static void deserializeEntity(class Entity* entity, const QJsonObject& json);
-    
-    static QJsonDocument serializeWorld(const class World* world);
-    static void deserializeWorld(class World* world, const QJsonDocument& doc);
-    
-    static bool saveToFile(const QString& filename, const QJsonDocument& doc);
-    static QJsonDocument loadFromFile(const QString& filename);
+  static QJsonObject serializeEntity(const class Entity *entity);
+  static void deserializeEntity(class Entity *entity, const QJsonObject &json);
+
+  static QJsonDocument serializeWorld(const class World *world);
+  static void deserializeWorld(class World *world, const QJsonDocument &doc);
+
+  static bool saveToFile(const QString &filename, const QJsonDocument &doc);
+  static QJsonDocument loadFromFile(const QString &filename);
 };
 
 } // namespace Engine::Core

+ 1 - 5
game/core/system.cpp

@@ -1,7 +1,3 @@
 #include "system.h"
 
-namespace Engine::Core {
-
-// Base system, no concrete logic.
-
-} // namespace Engine::Core
+namespace Engine::Core {}

+ 2 - 2
game/core/system.h

@@ -8,8 +8,8 @@ class World;
 
 class System {
 public:
-    virtual ~System() = default;
-    virtual void update(World* world, float deltaTime) = 0;
+  virtual ~System() = default;
+  virtual void update(World *world, float deltaTime) = 0;
 };
 
 } // namespace Engine::Core

+ 17 - 20
game/core/world.cpp

@@ -5,38 +5,35 @@ namespace Engine::Core {
 World::World() = default;
 World::~World() = default;
 
-Entity* World::createEntity() {
-    EntityID id = m_nextEntityId++;
-    auto entity = std::make_unique<Entity>(id);
-    auto ptr = entity.get();
-    m_entities[id] = std::move(entity);
-    return ptr;
+Entity *World::createEntity() {
+  EntityID id = m_nextEntityId++;
+  auto entity = std::make_unique<Entity>(id);
+  auto ptr = entity.get();
+  m_entities[id] = std::move(entity);
+  return ptr;
 }
 
-void World::destroyEntity(EntityID id) {
-    m_entities.erase(id);
-}
+void World::destroyEntity(EntityID id) { m_entities.erase(id); }
 
 void World::clear() {
-    // Remove all entities and reset ID counter
-    // Used when reloading a map to avoid entity ID conflicts and duplicates
-    m_entities.clear();
-    m_nextEntityId = 1;
+
+  m_entities.clear();
+  m_nextEntityId = 1;
 }
 
-Entity* World::getEntity(EntityID id) {
-    auto it = m_entities.find(id);
-    return it != m_entities.end() ? it->second.get() : nullptr;
+Entity *World::getEntity(EntityID id) {
+  auto it = m_entities.find(id);
+  return it != m_entities.end() ? it->second.get() : nullptr;
 }
 
 void World::addSystem(std::unique_ptr<System> system) {
-    m_systems.push_back(std::move(system));
+  m_systems.push_back(std::move(system));
 }
 
 void World::update(float deltaTime) {
-    for (auto& system : m_systems) {
-        system->update(this, deltaTime);
-    }
+  for (auto &system : m_systems) {
+    system->update(this, deltaTime);
+  }
 }
 
 } // namespace Engine::Core

+ 25 - 27
game/core/world.h

@@ -2,43 +2,41 @@
 
 #include "entity.h"
 #include "system.h"
-#include <unordered_map>
 #include <memory>
+#include <unordered_map>
 #include <vector>
 
 namespace Engine::Core {
 
 class World {
 public:
-    World();
-    ~World();
-
-    Entity* createEntity();
-    void destroyEntity(EntityID id);
-    Entity* getEntity(EntityID id);
-    void clear(); // Clear all entities (for map reload)
-    
-    void addSystem(std::unique_ptr<System> system);
-    void update(float deltaTime);
-
-    // Expose systems for iteration (needed for cross-system events)
-    std::vector<std::unique_ptr<System>>& systems() { return m_systems; }
-    
-    template<typename T>
-    std::vector<Entity*> getEntitiesWith() {
-        std::vector<Entity*> result;
-        for (auto& [id, entity] : m_entities) {
-            if (entity->hasComponent<T>()) {
-                result.push_back(entity.get());
-            }
-        }
-        return result;
+  World();
+  ~World();
+
+  Entity *createEntity();
+  void destroyEntity(EntityID id);
+  Entity *getEntity(EntityID id);
+  void clear();
+
+  void addSystem(std::unique_ptr<System> system);
+  void update(float deltaTime);
+
+  std::vector<std::unique_ptr<System>> &systems() { return m_systems; }
+
+  template <typename T> std::vector<Entity *> getEntitiesWith() {
+    std::vector<Entity *> result;
+    for (auto &[id, entity] : m_entities) {
+      if (entity->hasComponent<T>()) {
+        result.push_back(entity.get());
+      }
     }
+    return result;
+  }
 
 private:
-    EntityID m_nextEntityId = 1;
-    std::unordered_map<EntityID, std::unique_ptr<Entity>> m_entities;
-    std::vector<std::unique_ptr<System>> m_systems;
+  EntityID m_nextEntityId = 1;
+  std::unordered_map<EntityID, std::unique_ptr<Entity>> m_entities;
+  std::vector<std::unique_ptr<System>> m_systems;
 };
 
 } // namespace Engine::Core

+ 24 - 17
game/map/environment.cpp

@@ -1,26 +1,33 @@
 #include "environment.h"
-#include "../../render/scene_renderer.h"
 #include "../../render/gl/camera.h"
+#include "../../render/scene_renderer.h"
 #include <algorithm>
 
-namespace Game { namespace Map {
+namespace Game {
+namespace Map {
 
-void Environment::apply(const MapDefinition& def, Render::GL::Renderer& renderer, Render::GL::Camera& camera) {
-    camera.setRTSView(def.camera.center, def.camera.distance, def.camera.tiltDeg);
-    camera.setPerspective(def.camera.fovY, 16.0f/9.0f, def.camera.nearPlane, def.camera.farPlane);
-    Render::GL::Renderer::GridParams gp;
-    gp.cellSize = def.grid.tileSize;
-    gp.extent = std::max(def.grid.width, def.grid.height) * def.grid.tileSize * 0.5f;
-    renderer.setGridParams(gp);
+void Environment::apply(const MapDefinition &def,
+                        Render::GL::Renderer &renderer,
+                        Render::GL::Camera &camera) {
+  camera.setRTSView(def.camera.center, def.camera.distance, def.camera.tiltDeg);
+  camera.setPerspective(def.camera.fovY, 16.0f / 9.0f, def.camera.nearPlane,
+                        def.camera.farPlane);
+  Render::GL::Renderer::GridParams gp;
+  gp.cellSize = def.grid.tileSize;
+  gp.extent =
+      std::max(def.grid.width, def.grid.height) * def.grid.tileSize * 0.5f;
+  renderer.setGridParams(gp);
 }
 
-void Environment::applyDefault(Render::GL::Renderer& renderer, Render::GL::Camera& camera) {
-    camera.setRTSView(QVector3D(0,0,0), 15.0f, 45.0f);
-    camera.setPerspective(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
-    Render::GL::Renderer::GridParams gp;
-    gp.cellSize = 1.0f;
-    gp.extent = 50.0f;
-    renderer.setGridParams(gp);
+void Environment::applyDefault(Render::GL::Renderer &renderer,
+                               Render::GL::Camera &camera) {
+  camera.setRTSView(QVector3D(0, 0, 0), 15.0f, 45.0f);
+  camera.setPerspective(45.0f, 16.0f / 9.0f, 0.1f, 1000.0f);
+  Render::GL::Renderer::GridParams gp;
+  gp.cellSize = 1.0f;
+  gp.extent = 50.0f;
+  renderer.setGridParams(gp);
 }
 
-} } // namespace Game::Map
+} // namespace Map
+} // namespace Game

+ 14 - 6
game/map/environment.h

@@ -2,14 +2,22 @@
 
 #include "map_definition.h"
 
-namespace Render { namespace GL { class Renderer; class Camera; } }
+namespace Render {
+namespace GL {
+class Renderer;
+class Camera;
+} // namespace GL
+} // namespace Render
 
-namespace Game { namespace Map {
+namespace Game {
+namespace Map {
 
-// Applies camera and ground/grid environment based on map definition
 struct Environment {
-    static void apply(const MapDefinition& def, Render::GL::Renderer& renderer, Render::GL::Camera& camera);
-    static void applyDefault(Render::GL::Renderer& renderer, Render::GL::Camera& camera);
+  static void apply(const MapDefinition &def, Render::GL::Renderer &renderer,
+                    Render::GL::Camera &camera);
+  static void applyDefault(Render::GL::Renderer &renderer,
+                           Render::GL::Camera &camera);
 };
 
-} } // namespace Game::Map
+} // namespace Map
+} // namespace Game

+ 94 - 70
game/map/level_loader.cpp

@@ -1,89 +1,113 @@
 #include "level_loader.h"
+#include "../../render/gl/camera.h"
+#include "../../render/scene_renderer.h"
+#include "../core/component.h"
+#include "../core/world.h"
+#include "../units/factory.h"
+#include "../visuals/visual_catalog.h"
+#include "environment.h"
 #include "map_loader.h"
 #include "map_transformer.h"
-#include "environment.h"
-#include "../visuals/visual_catalog.h"
-#include "../units/factory.h"
-#include "../core/world.h"
-#include "../core/component.h"
-#include "../../render/scene_renderer.h"
-#include "../../render/gl/camera.h"
 #include <QDebug>
 
-namespace Game { namespace Map {
+namespace Game {
+namespace Map {
 
-LevelLoadResult LevelLoader::loadFromAssets(const QString& mapPath,
-                                           Engine::Core::World& world,
-                                           Render::GL::Renderer& renderer,
-                                           Render::GL::Camera& camera) {
-    LevelLoadResult res;
+LevelLoadResult LevelLoader::loadFromAssets(const QString &mapPath,
+                                            Engine::Core::World &world,
+                                            Render::GL::Renderer &renderer,
+                                            Render::GL::Camera &camera) {
+  LevelLoadResult res;
 
-    // Load visuals JSON
-    Game::Visuals::VisualCatalog visualCatalog;
-    QString visualsErr;
-    visualCatalog.loadFromJsonFile("assets/visuals/unit_visuals.json", &visualsErr);
+  Game::Visuals::VisualCatalog visualCatalog;
+  QString visualsErr;
+  visualCatalog.loadFromJsonFile("assets/visuals/unit_visuals.json",
+                                 &visualsErr);
 
-    // Install unit factories
-    auto unitReg = std::make_shared<Game::Units::UnitFactoryRegistry>();
-    Game::Units::registerBuiltInUnits(*unitReg);
-    Game::Map::MapTransformer::setFactoryRegistry(unitReg);
+  auto unitReg = std::make_shared<Game::Units::UnitFactoryRegistry>();
+  Game::Units::registerBuiltInUnits(*unitReg);
+  Game::Map::MapTransformer::setFactoryRegistry(unitReg);
 
-    // Try load map JSON
-    Game::Map::MapDefinition def;
-    QString err;
-    if (Game::Map::MapLoader::loadFromJsonFile(mapPath, def, &err)) {
-        res.ok = true;
+  Game::Map::MapDefinition def;
+  QString err;
+  if (Game::Map::MapLoader::loadFromJsonFile(mapPath, def, &err)) {
+    res.ok = true;
     res.mapName = def.name;
-        // Apply environment
-        Game::Map::Environment::apply(def, renderer, camera);
-    res.camFov = def.camera.fovY; res.camNear = def.camera.nearPlane; res.camFar = def.camera.farPlane;
-    res.gridWidth = def.grid.width; res.gridHeight = def.grid.height; res.tileSize = def.grid.tileSize;
+
+    Game::Map::Environment::apply(def, renderer, camera);
+    res.camFov = def.camera.fovY;
+    res.camNear = def.camera.nearPlane;
+    res.camFar = def.camera.farPlane;
+    res.gridWidth = def.grid.width;
+    res.gridHeight = def.grid.height;
+    res.tileSize = def.grid.tileSize;
     res.maxTroopsPerPlayer = def.maxTroopsPerPlayer;
-        // Populate world
-        auto rt = Game::Map::MapTransformer::applyToWorld(def, world, &visualCatalog);
-        if (!rt.unitIds.empty()) {
-            res.playerUnitId = rt.unitIds.front();
+
+    auto rt =
+        Game::Map::MapTransformer::applyToWorld(def, world, &visualCatalog);
+    if (!rt.unitIds.empty()) {
+      res.playerUnitId = rt.unitIds.front();
+    } else {
+
+      auto reg = Game::Map::MapTransformer::getFactoryRegistry();
+      if (reg) {
+        Game::Units::SpawnParams sp;
+        sp.position = QVector3D(0.0f, 0.0f, 0.0f);
+        sp.playerId = 0;
+        sp.unitType = "archer";
+        if (auto unit = reg->create("archer", world, sp)) {
+          res.playerUnitId = unit->id();
         } else {
-            // Fallback: try to spawn archer at origin using registry
-            auto reg = Game::Map::MapTransformer::getFactoryRegistry();
-            if (reg) {
-                Game::Units::SpawnParams sp; sp.position = QVector3D(0.0f, 0.0f, 0.0f); sp.playerId = 0; sp.unitType = "archer";
-                if (auto unit = reg->create("archer", world, sp)) {
-                    res.playerUnitId = unit->id();
-                } else {
-                    qWarning() << "LevelLoader: Fallback archer spawn failed";
-                }
-            }
-        }
-        // Ensure there's at least one barracks for local player near origin
-        bool hasBarracks = false;
-        for (auto* e : world.getEntitiesWith<Engine::Core::UnitComponent>()) {
-            if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) {
-                if (u->unitType == "barracks" && u->ownerId == 1) { hasBarracks = true; break; }
-            }
+          qWarning() << "LevelLoader: Fallback archer spawn failed";
         }
-        if (!hasBarracks) {
-            auto reg2 = Game::Map::MapTransformer::getFactoryRegistry();
-            if (reg2) {
-                Game::Units::SpawnParams sp; sp.position = QVector3D(-4.0f, 0.0f, -3.0f); sp.playerId = 1; sp.unitType = "barracks";
-                reg2->create("barracks", world, sp);
-            }
+      }
+    }
+
+    bool hasBarracks = false;
+    for (auto *e : world.getEntitiesWith<Engine::Core::UnitComponent>()) {
+      if (auto *u = e->getComponent<Engine::Core::UnitComponent>()) {
+        if (u->unitType == "barracks" && u->ownerId == 1) {
+          hasBarracks = true;
+          break;
         }
-    } else {
-        qWarning() << "LevelLoader: Map load failed:" << err << " - applying default environment";
-        Game::Map::Environment::applyDefault(renderer, camera);
+      }
+    }
+    if (!hasBarracks) {
+      auto reg2 = Game::Map::MapTransformer::getFactoryRegistry();
+      if (reg2) {
+        Game::Units::SpawnParams sp;
+        sp.position = QVector3D(-4.0f, 0.0f, -3.0f);
+        sp.playerId = 1;
+        sp.unitType = "barracks";
+        reg2->create("barracks", world, sp);
+      }
+    }
+  } else {
+    qWarning() << "LevelLoader: Map load failed:" << err
+               << " - applying default environment";
+    Game::Map::Environment::applyDefault(renderer, camera);
     res.ok = false;
-        res.camFov = camera.getFOV(); res.camNear = camera.getNear(); res.camFar = camera.getFar();
-    res.gridWidth = 50; res.gridHeight = 50; res.tileSize = 1.0f;
-        // Fallback archer spawn as last resort
-        auto reg = Game::Map::MapTransformer::getFactoryRegistry();
-        if (reg) {
-            Game::Units::SpawnParams sp; sp.position = QVector3D(0.0f, 0.0f, 0.0f); sp.playerId = 0; sp.unitType = "archer";
-            if (auto unit = reg->create("archer", world, sp)) { res.playerUnitId = unit->id(); }
-        }
+    res.camFov = camera.getFOV();
+    res.camNear = camera.getNear();
+    res.camFar = camera.getFar();
+    res.gridWidth = 50;
+    res.gridHeight = 50;
+    res.tileSize = 1.0f;
+
+    auto reg = Game::Map::MapTransformer::getFactoryRegistry();
+    if (reg) {
+      Game::Units::SpawnParams sp;
+      sp.position = QVector3D(0.0f, 0.0f, 0.0f);
+      sp.playerId = 0;
+      sp.unitType = "archer";
+      if (auto unit = reg->create("archer", world, sp)) {
+        res.playerUnitId = unit->id();
+      }
     }
+  }
 
-    return res;
+  return res;
 }
 
-} } // namespace Game::Map
+} // namespace Map
+} // namespace Game

+ 30 - 19
game/map/level_loader.h

@@ -3,31 +3,42 @@
 #include <QString>
 #include <memory>
 
-namespace Engine { namespace Core { class World; using EntityID = unsigned int; } }
-namespace Render { namespace GL { class Renderer; class Camera; } }
+namespace Engine {
+namespace Core {
+class World;
+using EntityID = unsigned int;
+} // namespace Core
+} // namespace Engine
+namespace Render {
+namespace GL {
+class Renderer;
+class Camera;
+} // namespace GL
+} // namespace Render
 
-namespace Game { namespace Map {
+namespace Game {
+namespace Map {
 
 struct LevelLoadResult {
-    bool ok = false;
-    QString mapName;
-    Engine::Core::EntityID playerUnitId = 0;
-    float camFov = 45.0f;
-    float camNear = 0.1f;
-    float camFar = 1000.0f;
-    int gridWidth = 50;
-    int gridHeight = 50;
-    float tileSize = 1.0f;
-    int maxTroopsPerPlayer = 50;
+  bool ok = false;
+  QString mapName;
+  Engine::Core::EntityID playerUnitId = 0;
+  float camFov = 45.0f;
+  float camNear = 0.1f;
+  float camFar = 1000.0f;
+  int gridWidth = 50;
+  int gridHeight = 50;
+  float tileSize = 1.0f;
+  int maxTroopsPerPlayer = 50;
 };
 
 class LevelLoader {
 public:
-    // Loads visuals, installs unit factories, loads map, applies environment, populates world. Falls back to default env if needed.
-    static LevelLoadResult loadFromAssets(const QString& mapPath,
-                                          Engine::Core::World& world,
-                                          Render::GL::Renderer& renderer,
-                                          Render::GL::Camera& camera);
+  static LevelLoadResult loadFromAssets(const QString &mapPath,
+                                        Engine::Core::World &world,
+                                        Render::GL::Renderer &renderer,
+                                        Render::GL::Camera &camera);
 };
 
-} } // namespace Game::Map
+} // namespace Map
+} // namespace Game

+ 20 - 23
game/map/map_definition.h

@@ -7,39 +7,36 @@
 namespace Game::Map {
 
 struct GridDefinition {
-    int width = 50;      // number of cells in X
-    int height = 50;     // number of cells in Z
-    float tileSize = 1.0f;
+  int width = 50;
+  int height = 50;
+  float tileSize = 1.0f;
 };
 
 struct CameraDefinition {
-    QVector3D center{0.0f, 0.0f, 0.0f};
-    float distance = 15.0f; // RTS orbit distance
-    float tiltDeg = 45.0f;  // RTS tilt angle
-    float fovY = 45.0f;     // degrees
-    float nearPlane = 0.1f;
-    float farPlane = 1000.0f;
+  QVector3D center{0.0f, 0.0f, 0.0f};
+  float distance = 15.0f;
+  float tiltDeg = 45.0f;
+  float fovY = 45.0f;
+  float nearPlane = 0.1f;
+  float farPlane = 1000.0f;
 };
 
 struct UnitSpawn {
-    QString type; // e.g., "archer"
-    float x = 0.0f; // world X (or grid x * tileSize)
-    float z = 0.0f; // world Z
-    int playerId = 0;
+  QString type;
+  float x = 0.0f;
+  float z = 0.0f;
+  int playerId = 0;
 };
 
-enum class CoordSystem {
-    Grid,   // x,z are grid indices [0..width-1], centered to world
-    World   // x,z are raw world coordinates
-};
+enum class CoordSystem { Grid, World };
 
 struct MapDefinition {
-    QString name;
-    GridDefinition grid;
-    CameraDefinition camera;
-    std::vector<UnitSpawn> spawns;
-    CoordSystem coordSystem = CoordSystem::Grid;
-    int maxTroopsPerPlayer = 50;  // Maximum number of units per player
+  QString name;
+  GridDefinition grid;
+  CameraDefinition camera;
+  std::vector<UnitSpawn> spawns;
+  CoordSystem coordSystem = CoordSystem::Grid;
+  int maxTroopsPerPlayer = 50;
 };
 
 } // namespace Game::Map

+ 89 - 73
game/map/map_loader.cpp

@@ -1,100 +1,116 @@
 #include "map_loader.h"
 
 #include <QFile>
+#include <QJsonArray>
 #include <QJsonDocument>
 #include <QJsonObject>
-#include <QJsonArray>
 #include <QString>
 
 namespace Game::Map {
 
-static bool readGrid(const QJsonObject& obj, GridDefinition& grid) {
-    if (obj.contains("width")) grid.width = obj.value("width").toInt(grid.width);
-    if (obj.contains("height")) grid.height = obj.value("height").toInt(grid.height);
-    if (obj.contains("tileSize")) grid.tileSize = float(obj.value("tileSize").toDouble(grid.tileSize));
-    return grid.width > 0 && grid.height > 0 && grid.tileSize > 0.0f;
+static bool readGrid(const QJsonObject &obj, GridDefinition &grid) {
+  if (obj.contains("width"))
+    grid.width = obj.value("width").toInt(grid.width);
+  if (obj.contains("height"))
+    grid.height = obj.value("height").toInt(grid.height);
+  if (obj.contains("tileSize"))
+    grid.tileSize = float(obj.value("tileSize").toDouble(grid.tileSize));
+  return grid.width > 0 && grid.height > 0 && grid.tileSize > 0.0f;
 }
 
-static bool readCamera(const QJsonObject& obj, CameraDefinition& cam) {
-    if (obj.contains("center")) {
-        auto arr = obj.value("center").toArray();
-        if (arr.size() == 3) {
-            cam.center = {float(arr[0].toDouble(0.0)), float(arr[1].toDouble(0.0)), float(arr[2].toDouble(0.0))};
-        }
+static bool readCamera(const QJsonObject &obj, CameraDefinition &cam) {
+  if (obj.contains("center")) {
+    auto arr = obj.value("center").toArray();
+    if (arr.size() == 3) {
+      cam.center = {float(arr[0].toDouble(0.0)), float(arr[1].toDouble(0.0)),
+                    float(arr[2].toDouble(0.0))};
     }
-    if (obj.contains("distance")) cam.distance = float(obj.value("distance").toDouble(cam.distance));
-    if (obj.contains("tiltDeg")) cam.tiltDeg = float(obj.value("tiltDeg").toDouble(cam.tiltDeg));
-    if (obj.contains("fovY")) cam.fovY = float(obj.value("fovY").toDouble(cam.fovY));
-    if (obj.contains("near")) cam.nearPlane = float(obj.value("near").toDouble(cam.nearPlane));
-    if (obj.contains("far")) cam.farPlane = float(obj.value("far").toDouble(cam.farPlane));
-    return true;
+  }
+  if (obj.contains("distance"))
+    cam.distance = float(obj.value("distance").toDouble(cam.distance));
+  if (obj.contains("tiltDeg"))
+    cam.tiltDeg = float(obj.value("tiltDeg").toDouble(cam.tiltDeg));
+  if (obj.contains("fovY"))
+    cam.fovY = float(obj.value("fovY").toDouble(cam.fovY));
+  if (obj.contains("near"))
+    cam.nearPlane = float(obj.value("near").toDouble(cam.nearPlane));
+  if (obj.contains("far"))
+    cam.farPlane = float(obj.value("far").toDouble(cam.farPlane));
+  return true;
 }
 
-static void readSpawns(const QJsonArray& arr, std::vector<UnitSpawn>& out) {
-    out.clear();
-    out.reserve(arr.size());
-    for (const auto& v : arr) {
-        auto o = v.toObject();
-        UnitSpawn s;
-        s.type = o.value("type").toString();
-        s.x = float(o.value("x").toDouble(0.0));
-        s.z = float(o.value("z").toDouble(0.0));
-        s.playerId = o.value("playerId").toInt(0);
-        out.push_back(s);
-    }
+static void readSpawns(const QJsonArray &arr, std::vector<UnitSpawn> &out) {
+  out.clear();
+  out.reserve(arr.size());
+  for (const auto &v : arr) {
+    auto o = v.toObject();
+    UnitSpawn s;
+    s.type = o.value("type").toString();
+    s.x = float(o.value("x").toDouble(0.0));
+    s.z = float(o.value("z").toDouble(0.0));
+    s.playerId = o.value("playerId").toInt(0);
+    out.push_back(s);
+  }
 }
 
-bool MapLoader::loadFromJsonFile(const QString& path, MapDefinition& outMap, QString* outError) {
-    QFile f(path);
-    if (!f.open(QIODevice::ReadOnly)) {
-        if (outError) *outError = QString("Failed to open map file: %1").arg(path);
-        return false;
-    }
-    auto data = f.readAll();
-    f.close();
+bool MapLoader::loadFromJsonFile(const QString &path, MapDefinition &outMap,
+                                 QString *outError) {
+  QFile f(path);
+  if (!f.open(QIODevice::ReadOnly)) {
+    if (outError)
+      *outError = QString("Failed to open map file: %1").arg(path);
+    return false;
+  }
+  auto data = f.readAll();
+  f.close();
 
-    QJsonParseError perr;
-    auto doc = QJsonDocument::fromJson(data, &perr);
-    if (perr.error != QJsonParseError::NoError) {
-        if (outError) *outError = QString("JSON parse error at %1: %2").arg(perr.offset).arg(perr.errorString());
-        return false;
-    }
-    if (!doc.isObject()) {
-        if (outError) *outError = "Map JSON root must be an object";
-        return false;
-    }
-    auto root = doc.object();
+  QJsonParseError perr;
+  auto doc = QJsonDocument::fromJson(data, &perr);
+  if (perr.error != QJsonParseError::NoError) {
+    if (outError)
+      *outError = QString("JSON parse error at %1: %2")
+                      .arg(perr.offset)
+                      .arg(perr.errorString());
+    return false;
+  }
+  if (!doc.isObject()) {
+    if (outError)
+      *outError = "Map JSON root must be an object";
+    return false;
+  }
+  auto root = doc.object();
 
-    outMap.name = root.value("name").toString("Unnamed Map");
+  outMap.name = root.value("name").toString("Unnamed Map");
 
-    // Optional: coordinate system for spawns
-    if (root.contains("coordSystem")) {
-        const QString cs = root.value("coordSystem").toString().trimmed().toLower();
-        if (cs == "world") outMap.coordSystem = CoordSystem::World;
-        else outMap.coordSystem = CoordSystem::Grid;
-    }
-    
-    // Optional: max troops per player
-    if (root.contains("maxTroopsPerPlayer")) {
-        outMap.maxTroopsPerPlayer = root.value("maxTroopsPerPlayer").toInt(50);
-    }
+  if (root.contains("coordSystem")) {
+    const QString cs = root.value("coordSystem").toString().trimmed().toLower();
+    if (cs == "world")
+      outMap.coordSystem = CoordSystem::World;
+    else
+      outMap.coordSystem = CoordSystem::Grid;
+  }
 
-    if (root.contains("grid") && root.value("grid").isObject()) {
-        if (!readGrid(root.value("grid").toObject(), outMap.grid)) {
-            if (outError) *outError = "Invalid grid definition";
-            return false;
-        }
-    }
+  if (root.contains("maxTroopsPerPlayer")) {
+    outMap.maxTroopsPerPlayer = root.value("maxTroopsPerPlayer").toInt(50);
+  }
 
-    if (root.contains("camera") && root.value("camera").isObject()) {
-        readCamera(root.value("camera").toObject(), outMap.camera);
+  if (root.contains("grid") && root.value("grid").isObject()) {
+    if (!readGrid(root.value("grid").toObject(), outMap.grid)) {
+      if (outError)
+        *outError = "Invalid grid definition";
+      return false;
     }
+  }
 
-    if (root.contains("spawns") && root.value("spawns").isArray()) {
-        readSpawns(root.value("spawns").toArray(), outMap.spawns);
-    }
+  if (root.contains("camera") && root.value("camera").isObject()) {
+    readCamera(root.value("camera").toObject(), outMap.camera);
+  }
+
+  if (root.contains("spawns") && root.value("spawns").isArray()) {
+    readSpawns(root.value("spawns").toArray(), outMap.spawns);
+  }
 
-    return true;
+  return true;
 }
 
 } // namespace Game::Map

+ 2 - 2
game/map/map_loader.h

@@ -7,8 +7,8 @@ namespace Game::Map {
 
 class MapLoader {
 public:
-    // Load MapDefinition from a JSON file. Returns true on success.
-    static bool loadFromJsonFile(const QString& path, MapDefinition& outMap, QString* outError = nullptr);
+  static bool loadFromJsonFile(const QString &path, MapDefinition &outMap,
+                               QString *outError = nullptr);
 };
 
 } // namespace Game::Map

+ 104 - 78
game/map/map_transformer.cpp

@@ -1,99 +1,125 @@
 #include "map_transformer.h"
 
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include "../units/factory.h"
 #include <QDebug>
 #include <QVector3D>
 
 namespace Game::Map {
 
-namespace { std::shared_ptr<Game::Units::UnitFactoryRegistry> s_registry; }
+namespace {
+std::shared_ptr<Game::Units::UnitFactoryRegistry> s_registry;
+}
 
-void MapTransformer::setFactoryRegistry(std::shared_ptr<Game::Units::UnitFactoryRegistry> reg) { s_registry = std::move(reg); }
-std::shared_ptr<Game::Units::UnitFactoryRegistry> MapTransformer::getFactoryRegistry() { return s_registry; }
+void MapTransformer::setFactoryRegistry(
+    std::shared_ptr<Game::Units::UnitFactoryRegistry> reg) {
+  s_registry = std::move(reg);
+}
+std::shared_ptr<Game::Units::UnitFactoryRegistry>
+MapTransformer::getFactoryRegistry() {
+  return s_registry;
+}
 
-MapRuntime MapTransformer::applyToWorld(const MapDefinition& def, Engine::Core::World& world, const Game::Visuals::VisualCatalog* visuals) {
-    MapRuntime rt;
-    rt.unitIds.reserve(def.spawns.size());
+MapRuntime
+MapTransformer::applyToWorld(const MapDefinition &def,
+                             Engine::Core::World &world,
+                             const Game::Visuals::VisualCatalog *visuals) {
+  MapRuntime rt;
+  rt.unitIds.reserve(def.spawns.size());
 
-    for (const auto& s : def.spawns) {
-        // Compute world-space position
-        float worldX = s.x;
-        float worldZ = s.z;
-        if (def.coordSystem == CoordSystem::Grid) {
-            const float tile = std::max(0.0001f, def.grid.tileSize);
-            worldX = (s.x - (def.grid.width  * 0.5f - 0.5f)) * tile;
-            worldZ = (s.z - (def.grid.height * 0.5f - 0.5f)) * tile;
-        }
+  for (const auto &s : def.spawns) {
 
-        Engine::Core::Entity* e = nullptr;
-        if (s_registry) {
-            Game::Units::SpawnParams sp;
-            sp.position = QVector3D(worldX, 0.0f, worldZ);
-            sp.playerId = s.playerId;
-            sp.unitType = s.type.toStdString();
-            auto obj = s_registry->create(s.type.toStdString(), world, sp);
-            if (obj) {
-                e = world.getEntity(obj->id());
-                rt.unitIds.push_back(obj->id());
-                // destroy temporary wrapper; ECS holds state
-            }
-        }
-        if (!e) {
-            // Fallback manual path
-            e = world.createEntity();
-            if (!e) continue;
-            auto* t = e->addComponent<Engine::Core::TransformComponent>();
-            t->position = {worldX, 0.0f, worldZ};
-            t->scale = {0.5f, 0.5f, 0.5f};
-            auto* r = e->addComponent<Engine::Core::RenderableComponent>("", "");
-            r->visible = true;
-            auto* u = e->addComponent<Engine::Core::UnitComponent>();
-            u->unitType = s.type.toStdString();
-            u->ownerId = s.playerId;
-            // Team tint
-            QVector3D tc;
-            switch (u->ownerId) {
-                case 1: tc = QVector3D(0.20f, 0.55f, 1.00f); break; // blue
-                case 2: tc = QVector3D(1.00f, 0.30f, 0.30f); break; // red
-                case 3: tc = QVector3D(0.20f, 0.80f, 0.40f); break; // green
-                case 4: tc = QVector3D(1.00f, 0.80f, 0.20f); break; // yellow
-                default: tc = QVector3D(0.8f, 0.9f, 1.0f); break;
-            }
-            r->color[0] = tc.x(); r->color[1] = tc.y(); r->color[2] = tc.z();
-            if (s.type == "archer") {
-                u->health = 80; u->maxHealth = 80; u->speed = 3.0f;
-                auto* atk = e->addComponent<Engine::Core::AttackComponent>();
-                atk->range = 6.0f; atk->damage = 12; atk->cooldown = 1.2f;
-            }
-            e->addComponent<Engine::Core::MovementComponent>();
-            rt.unitIds.push_back(e->getId());
-        }
+    float worldX = s.x;
+    float worldZ = s.z;
+    if (def.coordSystem == CoordSystem::Grid) {
+      const float tile = std::max(0.0001f, def.grid.tileSize);
+      worldX = (s.x - (def.grid.width * 0.5f - 0.5f)) * tile;
+      worldZ = (s.z - (def.grid.height * 0.5f - 0.5f)) * tile;
+    }
 
-        // Apply visuals if available
-        if (auto* r = e->getComponent<Engine::Core::RenderableComponent>()) {
-            if (visuals) {
-                Game::Visuals::VisualDef defv;
-                if (visuals->lookup(s.type.toStdString(), defv)) {
-                    Game::Visuals::applyToRenderable(defv, *r);
-                }
-            }
-            if (r->color[0] == 0.0f && r->color[1] == 0.0f && r->color[2] == 0.0f) {
-                r->color[0] = r->color[1] = r->color[2] = 1.0f;
-            }
-        }
+    Engine::Core::Entity *e = nullptr;
+    if (s_registry) {
+      Game::Units::SpawnParams sp;
+      sp.position = QVector3D(worldX, 0.0f, worldZ);
+      sp.playerId = s.playerId;
+      sp.unitType = s.type.toStdString();
+      auto obj = s_registry->create(s.type.toStdString(), world, sp);
+      if (obj) {
+        e = world.getEntity(obj->id());
+        rt.unitIds.push_back(obj->id());
+      }
+    }
+    if (!e) {
+
+      e = world.createEntity();
+      if (!e)
+        continue;
+      auto *t = e->addComponent<Engine::Core::TransformComponent>();
+      t->position = {worldX, 0.0f, worldZ};
+      t->scale = {0.5f, 0.5f, 0.5f};
+      auto *r = e->addComponent<Engine::Core::RenderableComponent>("", "");
+      r->visible = true;
+      auto *u = e->addComponent<Engine::Core::UnitComponent>();
+      u->unitType = s.type.toStdString();
+      u->ownerId = s.playerId;
 
-        // Log spawn
-        if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-            qInfo() << "Spawned" << s.type
-                << "id=" << e->getId()
-                << "at" << QVector3D(t->position.x, t->position.y, t->position.z)
-                << "(coordSystem=" << (def.coordSystem == CoordSystem::Grid ? "Grid" : "World") << ")";
+      QVector3D tc;
+      switch (u->ownerId) {
+      case 1:
+        tc = QVector3D(0.20f, 0.55f, 1.00f);
+        break;
+      case 2:
+        tc = QVector3D(1.00f, 0.30f, 0.30f);
+        break;
+      case 3:
+        tc = QVector3D(0.20f, 0.80f, 0.40f);
+        break;
+      case 4:
+        tc = QVector3D(1.00f, 0.80f, 0.20f);
+        break;
+      default:
+        tc = QVector3D(0.8f, 0.9f, 1.0f);
+        break;
+      }
+      r->color[0] = tc.x();
+      r->color[1] = tc.y();
+      r->color[2] = tc.z();
+      if (s.type == "archer") {
+        u->health = 80;
+        u->maxHealth = 80;
+        u->speed = 3.0f;
+        auto *atk = e->addComponent<Engine::Core::AttackComponent>();
+        atk->range = 6.0f;
+        atk->damage = 12;
+        atk->cooldown = 1.2f;
+      }
+      e->addComponent<Engine::Core::MovementComponent>();
+      rt.unitIds.push_back(e->getId());
+    }
+
+    if (auto *r = e->getComponent<Engine::Core::RenderableComponent>()) {
+      if (visuals) {
+        Game::Visuals::VisualDef defv;
+        if (visuals->lookup(s.type.toStdString(), defv)) {
+          Game::Visuals::applyToRenderable(defv, *r);
         }
+      }
+      if (r->color[0] == 0.0f && r->color[1] == 0.0f && r->color[2] == 0.0f) {
+        r->color[0] = r->color[1] = r->color[2] = 1.0f;
+      }
+    }
+
+    if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+      qInfo() << "Spawned" << s.type << "id=" << e->getId() << "at"
+              << QVector3D(t->position.x, t->position.y, t->position.z)
+              << "(coordSystem="
+              << (def.coordSystem == CoordSystem::Grid ? "Grid" : "World")
+              << ")";
     }
+  }
 
-    return rt;
+  return rt;
 }
 
 } // namespace Game::Map

+ 19 - 9
game/map/map_transformer.h

@@ -1,26 +1,36 @@
 #pragma once
 
-#include "map_definition.h"
 #include "../visuals/visual_catalog.h"
+#include "map_definition.h"
 #include <memory>
 
-namespace Engine { namespace Core { class World; using EntityID = unsigned int; } }
-namespace Game { namespace Units { class UnitFactoryRegistry; } }
+namespace Engine {
+namespace Core {
+class World;
+using EntityID = unsigned int;
+} // namespace Core
+} // namespace Engine
+namespace Game {
+namespace Units {
+class UnitFactoryRegistry;
+}
+} // namespace Game
 
 namespace Game::Map {
 
 struct MapRuntime {
-    std::vector<Engine::Core::EntityID> unitIds;
+  std::vector<Engine::Core::EntityID> unitIds;
 };
 
 class MapTransformer {
 public:
-    // Populates the world from a MapDefinition. Returns runtime info like created entity IDs.
-    static MapRuntime applyToWorld(const MapDefinition& def, Engine::Core::World& world, const Game::Visuals::VisualCatalog* visuals = nullptr);
+  static MapRuntime
+  applyToWorld(const MapDefinition &def, Engine::Core::World &world,
+               const Game::Visuals::VisualCatalog *visuals = nullptr);
 
-    // Factory registry access (singleton-like for simplicity here)
-    static void setFactoryRegistry(std::shared_ptr<Game::Units::UnitFactoryRegistry> reg);
-    static std::shared_ptr<Game::Units::UnitFactoryRegistry> getFactoryRegistry();
+  static void
+  setFactoryRegistry(std::shared_ptr<Game::Units::UnitFactoryRegistry> reg);
+  static std::shared_ptr<Game::Units::UnitFactoryRegistry> getFactoryRegistry();
 };
 
 } // namespace Game::Map

+ 398 - 407
game/systems/ai_system.cpp

@@ -1,6 +1,6 @@
 #include "ai_system.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include "command_service.h"
 #include "formation_planner.h"
 #include <QVector3D>
@@ -9,457 +9,448 @@
 
 namespace Game::Systems {
 
-// ============================================================================
-// AISystem Implementation
-// ============================================================================
-
 AISystem::AISystem() {
-    // Initialize enemy AI context
-    m_enemyAI.playerId = 2;
-    m_enemyAI.state = AIState::Idle;
-    
-    // Register default behaviors (order matters for prioritization)
-    // Critical behaviors first
-    registerBehavior(std::make_unique<DefendBehavior>());
-    
-    // High priority
-    registerBehavior(std::make_unique<ProductionBehavior>());
-    
-    // Normal priority
-    registerBehavior(std::make_unique<AttackBehavior>());
-    
-    // Low priority
-    registerBehavior(std::make_unique<GatherBehavior>());
+
+  m_enemyAI.playerId = 2;
+  m_enemyAI.state = AIState::Idle;
+
+  registerBehavior(std::make_unique<DefendBehavior>());
+
+  registerBehavior(std::make_unique<ProductionBehavior>());
+
+  registerBehavior(std::make_unique<AttackBehavior>());
+
+  registerBehavior(std::make_unique<GatherBehavior>());
 }
 
 AISystem::~AISystem() {
-    // Stop AI thread if running
-    if (m_aiThread && m_aiThread->joinable()) {
-        m_shouldStop = true;
-        m_aiCondition.notify_all();
-        m_aiThread->join();
-    }
+
+  if (m_aiThread && m_aiThread->joinable()) {
+    m_shouldStop = true;
+    m_aiCondition.notify_all();
+    m_aiThread->join();
+  }
 }
 
 void AISystem::registerBehavior(std::unique_ptr<AIBehavior> behavior) {
-    m_behaviors.push_back(std::move(behavior));
-    
-    // Sort behaviors by priority (highest first)
-    std::sort(m_behaviors.begin(), m_behaviors.end(),
-        [](const std::unique_ptr<AIBehavior>& a, const std::unique_ptr<AIBehavior>& b) {
-            return a->getPriority() > b->getPriority();
-        });
+  m_behaviors.push_back(std::move(behavior));
+
+  std::sort(m_behaviors.begin(), m_behaviors.end(),
+            [](const std::unique_ptr<AIBehavior> &a,
+               const std::unique_ptr<AIBehavior> &b) {
+              return a->getPriority() > b->getPriority();
+            });
 }
 
-void AISystem::update(Engine::Core::World* world, float deltaTime) {
-    if (!world) return;
-    
-    // Global update rate limiting (update AI decisions at reasonable intervals)
-    m_globalUpdateTimer += deltaTime;
-    if (m_globalUpdateTimer < 0.5f) return; // Update twice per second
-    deltaTime = m_globalUpdateTimer;
-    m_globalUpdateTimer = 0.0f;
-    
-    // Update AI context with current world state
-    updateContext(world, m_enemyAI);
-    
-    // Run state machine
-    updateStateMachine(world, m_enemyAI, deltaTime);
-    
-    // Execute behaviors (threaded or sequential)
-    if (m_useThreading) {
-        executeBehaviorsThreaded(world, deltaTime);
-    } else {
-        executeBehaviors(world, deltaTime);
-    }
+void AISystem::update(Engine::Core::World *world, float deltaTime) {
+  if (!world)
+    return;
+
+  m_globalUpdateTimer += deltaTime;
+  if (m_globalUpdateTimer < 0.5f)
+    return;
+  deltaTime = m_globalUpdateTimer;
+  m_globalUpdateTimer = 0.0f;
+
+  updateContext(world, m_enemyAI);
+
+  updateStateMachine(world, m_enemyAI, deltaTime);
+
+  if (m_useThreading) {
+    executeBehaviorsThreaded(world, deltaTime);
+  } else {
+    executeBehaviors(world, deltaTime);
+  }
 }
 
-void AISystem::executeBehaviors(Engine::Core::World* world, float deltaTime) {
-    // Execute behaviors with priority and mutual exclusion
-    bool exclusiveBehaviorExecuted = false;
-    
-    for (auto& behavior : m_behaviors) {
-        if (!behavior) continue;
-        
-        // Skip if an exclusive behavior already ran and this one can't run concurrently
-        if (exclusiveBehaviorExecuted && !behavior->canRunConcurrently()) {
-            continue;
-        }
-        
-        // Check if behavior should execute
-        if (behavior->shouldExecute(world, m_enemyAI.playerId)) {
-            behavior->execute(world, m_enemyAI.playerId, deltaTime);
-            
-            // Mark that an exclusive behavior has run
-            if (!behavior->canRunConcurrently()) {
-                exclusiveBehaviorExecuted = true;
-            }
-        }
+void AISystem::executeBehaviors(Engine::Core::World *world, float deltaTime) {
+
+  bool exclusiveBehaviorExecuted = false;
+
+  for (auto &behavior : m_behaviors) {
+    if (!behavior)
+      continue;
+
+    if (exclusiveBehaviorExecuted && !behavior->canRunConcurrently()) {
+      continue;
+    }
+
+    if (behavior->shouldExecute(world, m_enemyAI.playerId)) {
+      behavior->execute(world, m_enemyAI.playerId, deltaTime);
+
+      if (!behavior->canRunConcurrently()) {
+        exclusiveBehaviorExecuted = true;
+      }
     }
+  }
 }
 
-void AISystem::executeBehaviorsThreaded(Engine::Core::World* world, float deltaTime) {
-    // For now, keep it simple - execute on main thread
-    // Threading can cause race conditions with ECS, so we'd need thread-safe World access
-    // This is a placeholder for future async AI processing
-    executeBehaviors(world, deltaTime);
-    
-    // TODO: Implement proper threaded execution with:
-    // 1. Snapshot world state
-    // 2. Process AI decisions in background thread
-    // 3. Queue commands to be applied on main thread
+void AISystem::executeBehaviorsThreaded(Engine::Core::World *world,
+                                        float deltaTime) {
+
+  executeBehaviors(world, deltaTime);
 }
 
-void AISystem::updateContext(Engine::Core::World* world, AIContext& ctx) {
-    // Clear previous frame data
-    ctx.militaryUnits.clear();
-    ctx.buildings.clear();
-    ctx.totalUnits = 0;
-    ctx.idleUnits = 0;
-    ctx.combatUnits = 0;
-    float totalHealthRatio = 0.0f;
-    
-    // Scan all entities and categorize
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != ctx.playerId || u->health <= 0) continue;
-        
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) {
-            ctx.buildings.push_back(e->getId());
-            if (u->unitType == "barracks" && ctx.primaryBarracks == 0) {
-                ctx.primaryBarracks = e->getId();
-                // Set rally point near barracks
-                if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-                    ctx.rallyX = t->position.x - 5.0f;
-                    ctx.rallyZ = t->position.z;
-                }
-            }
-        } else {
-            ctx.militaryUnits.push_back(e->getId());
-            ctx.totalUnits++;
-            
-            // Check if unit is idle or in combat
-            auto* m = e->getComponent<Engine::Core::MovementComponent>();
-            if (!m || !m->hasTarget) {
-                ctx.idleUnits++;
-            } else {
-                ctx.combatUnits++;
-            }
-            
-            // Track health
-            if (u->maxHealth > 0) {
-                totalHealthRatio += float(u->health) / float(u->maxHealth);
-            }
+void AISystem::updateContext(Engine::Core::World *world, AIContext &ctx) {
+
+  ctx.militaryUnits.clear();
+  ctx.buildings.clear();
+  ctx.totalUnits = 0;
+  ctx.idleUnits = 0;
+  ctx.combatUnits = 0;
+  float totalHealthRatio = 0.0f;
+
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != ctx.playerId || u->health <= 0)
+      continue;
+
+    if (e->hasComponent<Engine::Core::BuildingComponent>()) {
+      ctx.buildings.push_back(e->getId());
+      if (u->unitType == "barracks" && ctx.primaryBarracks == 0) {
+        ctx.primaryBarracks = e->getId();
+
+        if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+          ctx.rallyX = t->position.x - 5.0f;
+          ctx.rallyZ = t->position.z;
         }
-    }
-    
-    // Calculate average health
-    if (ctx.totalUnits > 0) {
-        ctx.averageHealth = totalHealthRatio / float(ctx.totalUnits);
+      }
     } else {
-        ctx.averageHealth = 1.0f;
+      ctx.militaryUnits.push_back(e->getId());
+      ctx.totalUnits++;
+
+      auto *m = e->getComponent<Engine::Core::MovementComponent>();
+      if (!m || !m->hasTarget) {
+        ctx.idleUnits++;
+      } else {
+        ctx.combatUnits++;
+      }
+
+      if (u->maxHealth > 0) {
+        totalHealthRatio += float(u->health) / float(u->maxHealth);
+      }
     }
+  }
+
+  if (ctx.totalUnits > 0) {
+    ctx.averageHealth = totalHealthRatio / float(ctx.totalUnits);
+  } else {
+    ctx.averageHealth = 1.0f;
+  }
 }
 
-void AISystem::updateStateMachine(Engine::Core::World* world, AIContext& ctx, float deltaTime) {
-    ctx.stateTimer += deltaTime;
-    ctx.decisionTimer += deltaTime;
-    
-    // Make state decisions every 5 seconds
-    if (ctx.decisionTimer < 5.0f) return;
-    ctx.decisionTimer = 0.0f;
-    
-    AIState previousState = ctx.state;
-    
-    // State transition logic
-    switch (ctx.state) {
-        case AIState::Idle:
-            // Gather units if we have idle units
-            if (ctx.idleUnits >= 3) {
-                ctx.state = AIState::Gathering;
-            }
-            // Defend if under attack (low health)
-            else if (ctx.averageHealth < 0.5f && ctx.totalUnits > 0) {
-                ctx.state = AIState::Defending;
-            }
-            break;
-            
-        case AIState::Gathering:
-            // Once we have enough units, attack
-            if (ctx.totalUnits >= 5 && ctx.idleUnits < 2) {
-                ctx.state = AIState::Attacking;
-            }
-            // Go idle if not enough units
-            else if (ctx.totalUnits < 2) {
-                ctx.state = AIState::Idle;
-            }
-            break;
-            
-        case AIState::Attacking:
-            // Retreat if heavily damaged
-            if (ctx.averageHealth < 0.3f) {
-                ctx.state = AIState::Retreating;
-            }
-            // Return to gathering if we lost too many units
-            else if (ctx.totalUnits < 3) {
-                ctx.state = AIState::Gathering;
-            }
-            break;
-            
-        case AIState::Defending:
-            // Return to normal operations if healthy again
-            if (ctx.averageHealth > 0.7f) {
-                ctx.state = AIState::Idle;
-            }
-            // Attack if we have enough healthy units
-            else if (ctx.totalUnits >= 5 && ctx.averageHealth > 0.5f) {
-                ctx.state = AIState::Attacking;
-            }
-            break;
-            
-        case AIState::Retreating:
-            // Defend base when we arrive
-            if (ctx.stateTimer > 8.0f) {
-                ctx.state = AIState::Defending;
-            }
-            break;
-            
-        case AIState::Expanding:
-            // Not implemented yet, return to idle
-            ctx.state = AIState::Idle;
-            break;
+void AISystem::updateStateMachine(Engine::Core::World *world, AIContext &ctx,
+                                  float deltaTime) {
+  ctx.stateTimer += deltaTime;
+  ctx.decisionTimer += deltaTime;
+
+  if (ctx.decisionTimer < 5.0f)
+    return;
+  ctx.decisionTimer = 0.0f;
+
+  AIState previousState = ctx.state;
+
+  switch (ctx.state) {
+  case AIState::Idle:
+
+    if (ctx.idleUnits >= 3) {
+      ctx.state = AIState::Gathering;
     }
-    
-    // Reset state timer on transition
-    if (ctx.state != previousState) {
-        ctx.stateTimer = 0.0f;
+
+    else if (ctx.averageHealth < 0.5f && ctx.totalUnits > 0) {
+      ctx.state = AIState::Defending;
     }
-}
+    break;
 
-// ============================================================================
-// ProductionBehavior Implementation
-// ============================================================================
-
-void ProductionBehavior::execute(Engine::Core::World* world, int playerId, float deltaTime) {
-    m_productionTimer += deltaTime;
-    if (m_productionTimer < 2.0f) return;
-    m_productionTimer = 0.0f;
-    
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (u->unitType != "barracks") continue;
-        
-        auto* prod = e->getComponent<Engine::Core::ProductionComponent>();
-        if (!prod) continue;
-        
-        // Start production if not already producing and under cap
-        if (!prod->inProgress && prod->producedCount < prod->maxUnits) {
-            prod->productType = "archer";
-            prod->timeRemaining = prod->buildTime;
-            prod->inProgress = true;
-        }
+  case AIState::Gathering:
+
+    if (ctx.totalUnits >= 5 && ctx.idleUnits < 2) {
+      ctx.state = AIState::Attacking;
     }
-}
 
-bool ProductionBehavior::shouldExecute(Engine::Core::World* world, int playerId) const {
-    // Always try to produce units
-    return true;
-}
+    else if (ctx.totalUnits < 2) {
+      ctx.state = AIState::Idle;
+    }
+    break;
 
-// ============================================================================
-// GatherBehavior Implementation
-// ============================================================================
-
-void GatherBehavior::execute(Engine::Core::World* world, int playerId, float deltaTime) {
-    m_gatherTimer += deltaTime;
-    if (m_gatherTimer < 4.0f) return;
-    m_gatherTimer = 0.0f;
-    
-    // Find rally point (near barracks)
-    QVector3D rallyPoint(0.0f, 0.0f, 0.0f);
-    bool foundBarracks = false;
-    
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (u->unitType == "barracks") {
-            if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-                rallyPoint = QVector3D(t->position.x - 5.0f, 0.0f, t->position.z);
-                foundBarracks = true;
-                break;
-            }
-        }
+  case AIState::Attacking:
+
+    if (ctx.averageHealth < 0.3f) {
+      ctx.state = AIState::Retreating;
     }
-    
-    if (!foundBarracks) return;
-    
-    // Gather idle units to rally point
-    std::vector<Engine::Core::EntityID> idleUnits;
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        
-        auto* m = e->getComponent<Engine::Core::MovementComponent>();
-        if (!m || m->hasTarget) continue;
-        
-        idleUnits.push_back(e->getId());
+
+    else if (ctx.totalUnits < 3) {
+      ctx.state = AIState::Gathering;
     }
-    
-    if (idleUnits.empty()) return;
-    
-    // Move units to rally point in formation
-    auto targets = FormationPlanner::spreadFormation(int(idleUnits.size()), rallyPoint, 1.2f);
-    CommandService::moveUnits(*world, idleUnits, targets);
-}
+    break;
 
-bool GatherBehavior::shouldExecute(Engine::Core::World* world, int playerId) const {
-    // Execute when we have idle units
-    int idleCount = 0;
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        
-        auto* m = e->getComponent<Engine::Core::MovementComponent>();
-        if (!m || !m->hasTarget) idleCount++;
+  case AIState::Defending:
+
+    if (ctx.averageHealth > 0.7f) {
+      ctx.state = AIState::Idle;
     }
-    return idleCount >= 2;
-}
 
-// ============================================================================
-// AttackBehavior Implementation
-// ============================================================================
-
-void AttackBehavior::execute(Engine::Core::World* world, int playerId, float deltaTime) {
-    m_attackTimer += deltaTime;
-    if (m_attackTimer < 3.0f) return;
-    m_attackTimer = 0.0f;
-    
-    // Find enemy targets (prioritize buildings, then units)
-    std::vector<Engine::Core::Entity*> enemyBuildings;
-    std::vector<Engine::Core::Entity*> enemyUnits;
-    
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId == playerId || u->health <= 0) continue;
-        
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) {
-            enemyBuildings.push_back(e);
-        } else {
-            enemyUnits.push_back(e);
-        }
+    else if (ctx.totalUnits >= 5 && ctx.averageHealth > 0.5f) {
+      ctx.state = AIState::Attacking;
     }
-    
-    // Choose target (prefer buildings for strategic victory)
-    Engine::Core::Entity* mainTarget = nullptr;
-    if (!enemyBuildings.empty()) {
-        mainTarget = enemyBuildings[0]; // Target first building (usually barracks)
-    } else if (!enemyUnits.empty()) {
-        mainTarget = enemyUnits[0];
+    break;
+
+  case AIState::Retreating:
+
+    if (ctx.stateTimer > 8.0f) {
+      ctx.state = AIState::Defending;
     }
-    
-    if (!mainTarget) return;
-    
-    // Gather our units
-    std::vector<Engine::Core::EntityID> attackers;
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        
-        attackers.push_back(e->getId());
+    break;
+
+  case AIState::Expanding:
+
+    ctx.state = AIState::Idle;
+    break;
+  }
+
+  if (ctx.state != previousState) {
+    ctx.stateTimer = 0.0f;
+  }
+}
+
+void ProductionBehavior::execute(Engine::Core::World *world, int playerId,
+                                 float deltaTime) {
+  m_productionTimer += deltaTime;
+  if (m_productionTimer < 2.0f)
+    return;
+  m_productionTimer = 0.0f;
+
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (u->unitType != "barracks")
+      continue;
+
+    auto *prod = e->getComponent<Engine::Core::ProductionComponent>();
+    if (!prod)
+      continue;
+
+    if (!prod->inProgress && prod->producedCount < prod->maxUnits) {
+      prod->productType = "archer";
+      prod->timeRemaining = prod->buildTime;
+      prod->inProgress = true;
     }
-    
-    if (attackers.empty()) return;
-    
-    // Issue attack command on the target building
-    CommandService::attackTarget(*world, attackers, mainTarget->getId(), true);
+  }
 }
 
-bool AttackBehavior::shouldExecute(Engine::Core::World* world, int playerId) const {
-    // Attack when we have sufficient forces
-    int unitCount = 0;
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (!e->hasComponent<Engine::Core::BuildingComponent>()) unitCount++;
+bool ProductionBehavior::shouldExecute(Engine::Core::World *world,
+                                       int playerId) const {
+
+  return true;
+}
+
+void GatherBehavior::execute(Engine::Core::World *world, int playerId,
+                             float deltaTime) {
+  m_gatherTimer += deltaTime;
+  if (m_gatherTimer < 4.0f)
+    return;
+  m_gatherTimer = 0.0f;
+
+  QVector3D rallyPoint(0.0f, 0.0f, 0.0f);
+  bool foundBarracks = false;
+
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (u->unitType == "barracks") {
+      if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+        rallyPoint = QVector3D(t->position.x - 5.0f, 0.0f, t->position.z);
+        foundBarracks = true;
+        break;
+      }
     }
-    return unitCount >= 4;
+  }
+
+  if (!foundBarracks)
+    return;
+
+  std::vector<Engine::Core::EntityID> idleUnits;
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+
+    auto *m = e->getComponent<Engine::Core::MovementComponent>();
+    if (!m || m->hasTarget)
+      continue;
+
+    idleUnits.push_back(e->getId());
+  }
+
+  if (idleUnits.empty())
+    return;
+
+  auto targets = FormationPlanner::spreadFormation(int(idleUnits.size()),
+                                                   rallyPoint, 1.2f);
+  CommandService::moveUnits(*world, idleUnits, targets);
 }
 
-// ============================================================================
-// DefendBehavior Implementation
-// ============================================================================
-
-void DefendBehavior::execute(Engine::Core::World* world, int playerId, float deltaTime) {
-    m_defendTimer += deltaTime;
-    if (m_defendTimer < 3.0f) return;
-    m_defendTimer = 0.0f;
-    
-    // Find our barracks to defend
-    QVector3D defendPos(0.0f, 0.0f, 0.0f);
-    bool foundBarracks = false;
-    
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (u->unitType == "barracks") {
-            if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-                defendPos = QVector3D(t->position.x, 0.0f, t->position.z);
-                foundBarracks = true;
-                break;
-            }
-        }
+bool GatherBehavior::shouldExecute(Engine::Core::World *world,
+                                   int playerId) const {
+
+  int idleCount = 0;
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+
+    auto *m = e->getComponent<Engine::Core::MovementComponent>();
+    if (!m || !m->hasTarget)
+      idleCount++;
+  }
+  return idleCount >= 2;
+}
+
+void AttackBehavior::execute(Engine::Core::World *world, int playerId,
+                             float deltaTime) {
+  m_attackTimer += deltaTime;
+  if (m_attackTimer < 3.0f)
+    return;
+  m_attackTimer = 0.0f;
+
+  std::vector<Engine::Core::Entity *> enemyBuildings;
+  std::vector<Engine::Core::Entity *> enemyUnits;
+
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId == playerId || u->health <= 0)
+      continue;
+
+    if (e->hasComponent<Engine::Core::BuildingComponent>()) {
+      enemyBuildings.push_back(e);
+    } else {
+      enemyUnits.push_back(e);
     }
-    
-    if (!foundBarracks) return;
-    
-    // Gather defenders
-    std::vector<Engine::Core::EntityID> defenders;
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        
-        defenders.push_back(e->getId());
+  }
+
+  Engine::Core::Entity *mainTarget = nullptr;
+  if (!enemyBuildings.empty()) {
+    mainTarget = enemyBuildings[0];
+  } else if (!enemyUnits.empty()) {
+    mainTarget = enemyUnits[0];
+  }
+
+  if (!mainTarget)
+    return;
+
+  std::vector<Engine::Core::EntityID> attackers;
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+
+    attackers.push_back(e->getId());
+  }
+
+  if (attackers.empty())
+    return;
+
+  CommandService::attackTarget(*world, attackers, mainTarget->getId(), true);
+}
+
+bool AttackBehavior::shouldExecute(Engine::Core::World *world,
+                                   int playerId) const {
+
+  int unitCount = 0;
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (!e->hasComponent<Engine::Core::BuildingComponent>())
+      unitCount++;
+  }
+  return unitCount >= 4;
+}
+
+void DefendBehavior::execute(Engine::Core::World *world, int playerId,
+                             float deltaTime) {
+  m_defendTimer += deltaTime;
+  if (m_defendTimer < 3.0f)
+    return;
+  m_defendTimer = 0.0f;
+
+  QVector3D defendPos(0.0f, 0.0f, 0.0f);
+  bool foundBarracks = false;
+
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (u->unitType == "barracks") {
+      if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+        defendPos = QVector3D(t->position.x, 0.0f, t->position.z);
+        foundBarracks = true;
+        break;
+      }
     }
-    
-    if (defenders.empty()) return;
-    
-    // Position defenders around barracks in defensive formation
-    auto targets = FormationPlanner::spreadFormation(int(defenders.size()), defendPos, 3.0f);
-    CommandService::moveUnits(*world, defenders, targets);
+  }
+
+  if (!foundBarracks)
+    return;
+
+  std::vector<Engine::Core::EntityID> defenders;
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+
+    defenders.push_back(e->getId());
+  }
+
+  if (defenders.empty())
+    return;
+
+  auto targets =
+      FormationPlanner::spreadFormation(int(defenders.size()), defendPos, 3.0f);
+  CommandService::moveUnits(*world, defenders, targets);
 }
 
-bool DefendBehavior::shouldExecute(Engine::Core::World* world, int playerId) const {
-    // Defend when units are damaged or under threat
-    float totalHealth = 0.0f;
-    int count = 0;
-    
-    auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-    for (auto* e : entities) {
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != playerId || u->health <= 0) continue;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        
-        if (u->maxHealth > 0) {
-            totalHealth += float(u->health) / float(u->maxHealth);
-            count++;
-        }
+bool DefendBehavior::shouldExecute(Engine::Core::World *world,
+                                   int playerId) const {
+
+  float totalHealth = 0.0f;
+  int count = 0;
+
+  auto entities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+  for (auto *e : entities) {
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != playerId || u->health <= 0)
+      continue;
+    if (e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+
+    if (u->maxHealth > 0) {
+      totalHealth += float(u->health) / float(u->maxHealth);
+      count++;
     }
-    
-    if (count == 0) return false;
-    float avgHealth = totalHealth / float(count);
-    return avgHealth < 0.6f; // Defend when average health below 60%
+  }
+
+  if (count == 0)
+    return false;
+  float avgHealth = totalHealth / float(count);
+  return avgHealth < 0.6f;
 }
 
 } // namespace Game::Systems

+ 109 - 101
game/systems/ai_system.h

@@ -1,150 +1,158 @@
 #pragma once
 
 #include "../core/system.h"
-#include <vector>
-#include <memory>
 #include <atomic>
+#include <condition_variable>
+#include <memory>
 #include <mutex>
 #include <thread>
-#include <condition_variable>
+#include <vector>
 
-namespace Engine { namespace Core { 
-    class World; 
-    class Entity;
-    using EntityID = unsigned int;
-} }
+namespace Engine {
+namespace Core {
+class World;
+class Entity;
+using EntityID = unsigned int;
+} // namespace Core
+} // namespace Engine
 
 namespace Game::Systems {
 
-// AI state for a faction/player
 enum class AIState {
-    Idle,
-    Gathering,      // Collecting units at rally point
-    Attacking,      // Offensive push
-    Defending,      // Protecting base
-    Retreating,     // Pulling back damaged units
-    Expanding       // Building new structures
+  Idle,
+  Gathering,
+  Attacking,
+  Defending,
+  Retreating,
+  Expanding
 };
 
-// Behavior priority levels (higher = more important)
 enum class BehaviorPriority {
-    VeryLow = 0,
-    Low = 1,
-    Normal = 2,
-    High = 3,
-    Critical = 4
+  VeryLow = 0,
+  Low = 1,
+  Normal = 2,
+  High = 3,
+  Critical = 4
 };
 
-// AI behavior interface for modularity
 class AIBehavior {
 public:
-    virtual ~AIBehavior() = default;
-    virtual void execute(Engine::Core::World* world, int playerId, float deltaTime) = 0;
-    virtual bool shouldExecute(Engine::Core::World* world, int playerId) const = 0;
-    virtual BehaviorPriority getPriority() const = 0;
-    virtual bool canRunConcurrently() const { return false; } // Can this behavior run alongside others?
+  virtual ~AIBehavior() = default;
+  virtual void execute(Engine::Core::World *world, int playerId,
+                       float deltaTime) = 0;
+  virtual bool shouldExecute(Engine::Core::World *world,
+                             int playerId) const = 0;
+  virtual BehaviorPriority getPriority() const = 0;
+  virtual bool canRunConcurrently() const { return false; }
 };
 
-// AI context for a player/faction
 struct AIContext {
-    int playerId = 2;
-    AIState state = AIState::Idle;
-    float stateTimer = 0.0f;
-    float decisionTimer = 0.0f;
-    
-    // Strategic data
-    std::vector<Engine::Core::EntityID> militaryUnits;
-    std::vector<Engine::Core::EntityID> buildings;
-    Engine::Core::EntityID primaryBarracks = 0;
-    
-    // Tactical data
-    float rallyX = 0.0f;
-    float rallyZ = 0.0f;
-    int targetPriority = 0; // 0=units, 1=buildings
-    
-    // Statistics
-    int totalUnits = 0;
-    int idleUnits = 0;
-    int combatUnits = 0;
-    float averageHealth = 1.0f;
-    
-    // Concurrency control
-    std::mutex contextMutex;
-    std::atomic<bool> isProcessing{false};
+  int playerId = 2;
+  AIState state = AIState::Idle;
+  float stateTimer = 0.0f;
+  float decisionTimer = 0.0f;
+
+  std::vector<Engine::Core::EntityID> militaryUnits;
+  std::vector<Engine::Core::EntityID> buildings;
+  Engine::Core::EntityID primaryBarracks = 0;
+
+  float rallyX = 0.0f;
+  float rallyZ = 0.0f;
+  int targetPriority = 0;
+
+  int totalUnits = 0;
+  int idleUnits = 0;
+  int combatUnits = 0;
+  float averageHealth = 1.0f;
+
+  std::mutex contextMutex;
+  std::atomic<bool> isProcessing{false};
 };
 
-// Main AI system with modular architecture and threading
 class AISystem : public Engine::Core::System {
 public:
-    AISystem();
-    ~AISystem() override;
-    
-    void update(Engine::Core::World* world, float deltaTime) override;
-    
-    // Register AI behaviors
-    void registerBehavior(std::unique_ptr<AIBehavior> behavior);
-    
-    // Enable/disable threaded AI processing
-    void setThreadedMode(bool enabled) { m_useThreading = enabled; }
-    
+  AISystem();
+  ~AISystem() override;
+
+  void update(Engine::Core::World *world, float deltaTime) override;
+
+  void registerBehavior(std::unique_ptr<AIBehavior> behavior);
+
+  void setThreadedMode(bool enabled) { m_useThreading = enabled; }
+
 private:
-    void updateContext(Engine::Core::World* world, AIContext& ctx);
-    void updateStateMachine(Engine::Core::World* world, AIContext& ctx, float deltaTime);
-    void executeBehaviors(Engine::Core::World* world, float deltaTime);
-    void executeBehaviorsThreaded(Engine::Core::World* world, float deltaTime);
-    
-    AIContext m_enemyAI;
-    std::vector<std::unique_ptr<AIBehavior>> m_behaviors;
-    float m_globalUpdateTimer = 0.0f;
-    bool m_useThreading = false;
-    
-    // Threading support
-    std::unique_ptr<std::thread> m_aiThread;
-    std::condition_variable m_aiCondition;
-    std::mutex m_aiMutex;
-    std::atomic<bool> m_shouldStop{false};
-    std::atomic<bool> m_hasWork{false};
+  void updateContext(Engine::Core::World *world, AIContext &ctx);
+  void updateStateMachine(Engine::Core::World *world, AIContext &ctx,
+                          float deltaTime);
+  void executeBehaviors(Engine::Core::World *world, float deltaTime);
+  void executeBehaviorsThreaded(Engine::Core::World *world, float deltaTime);
+
+  AIContext m_enemyAI;
+  std::vector<std::unique_ptr<AIBehavior>> m_behaviors;
+  float m_globalUpdateTimer = 0.0f;
+  bool m_useThreading = false;
+
+  std::unique_ptr<std::thread> m_aiThread;
+  std::condition_variable m_aiCondition;
+  std::mutex m_aiMutex;
+  std::atomic<bool> m_shouldStop{false};
+  std::atomic<bool> m_hasWork{false};
 };
 
-// Concrete AI behaviors with priorities
 class ProductionBehavior : public AIBehavior {
 public:
-    void execute(Engine::Core::World* world, int playerId, float deltaTime) override;
-    bool shouldExecute(Engine::Core::World* world, int playerId) const override;
-    BehaviorPriority getPriority() const override { return BehaviorPriority::High; } // Keep producing
-    bool canRunConcurrently() const override { return true; } // Production is independent
+  void execute(Engine::Core::World *world, int playerId,
+               float deltaTime) override;
+  bool shouldExecute(Engine::Core::World *world, int playerId) const override;
+  BehaviorPriority getPriority() const override {
+    return BehaviorPriority::High;
+  }
+  bool canRunConcurrently() const override { return true; }
+
 private:
-    float m_productionTimer = 0.0f;
+  float m_productionTimer = 0.0f;
 };
 
 class GatherBehavior : public AIBehavior {
 public:
-    void execute(Engine::Core::World* world, int playerId, float deltaTime) override;
-    bool shouldExecute(Engine::Core::World* world, int playerId) const override;
-    BehaviorPriority getPriority() const override { return BehaviorPriority::Low; } // Low priority, passive
-    bool canRunConcurrently() const override { return false; } // Conflicts with attack/defend
+  void execute(Engine::Core::World *world, int playerId,
+               float deltaTime) override;
+  bool shouldExecute(Engine::Core::World *world, int playerId) const override;
+  BehaviorPriority getPriority() const override {
+    return BehaviorPriority::Low;
+  }
+  bool canRunConcurrently() const override { return false; }
+
 private:
-    float m_gatherTimer = 0.0f;
+  float m_gatherTimer = 0.0f;
 };
 
 class AttackBehavior : public AIBehavior {
 public:
-    void execute(Engine::Core::World* world, int playerId, float deltaTime) override;
-    bool shouldExecute(Engine::Core::World* world, int playerId) const override;
-    BehaviorPriority getPriority() const override { return BehaviorPriority::Normal; } // Offensive action
-    bool canRunConcurrently() const override { return false; } // Exclusive with defend/gather
+  void execute(Engine::Core::World *world, int playerId,
+               float deltaTime) override;
+  bool shouldExecute(Engine::Core::World *world, int playerId) const override;
+  BehaviorPriority getPriority() const override {
+    return BehaviorPriority::Normal;
+  }
+  bool canRunConcurrently() const override { return false; }
+
 private:
-    float m_attackTimer = 0.0f;
+  float m_attackTimer = 0.0f;
 };
 
 class DefendBehavior : public AIBehavior {
 public:
-    void execute(Engine::Core::World* world, int playerId, float deltaTime) override;
-    bool shouldExecute(Engine::Core::World* world, int playerId) const override;
-    BehaviorPriority getPriority() const override { return BehaviorPriority::Critical; } // Survival first
-    bool canRunConcurrently() const override { return false; } // Exclusive with attack/gather
+  void execute(Engine::Core::World *world, int playerId,
+               float deltaTime) override;
+  bool shouldExecute(Engine::Core::World *world, int playerId) const override;
+  BehaviorPriority getPriority() const override {
+    return BehaviorPriority::Critical;
+  }
+  bool canRunConcurrently() const override { return false; }
+
 private:
-    float m_defendTimer = 0.0f;
+  float m_defendTimer = 0.0f;
 };
 
 } // namespace Game::Systems

+ 28 - 23
game/systems/arrow_system.cpp

@@ -1,37 +1,42 @@
 #include "arrow_system.h"
-#include "../../render/scene_renderer.h"
-#include "../../render/gl/resources.h"
 #include "../../render/geom/arrow.h"
+#include "../../render/gl/resources.h"
+#include "../../render/scene_renderer.h"
 #include <algorithm>
 
 namespace Game::Systems {
 
 ArrowSystem::ArrowSystem() {}
 
-void ArrowSystem::spawnArrow(const QVector3D& start, const QVector3D& end, const QVector3D& color, float speed) {
-    ArrowInstance a;
-    a.start = start;
-    a.end = end;
-    a.color = color;
-    a.t = 0.0f;
-    a.speed = speed;
-    a.active = true;
-    float dist = (end - start).length();
-    a.arcHeight = std::clamp(0.15f * dist, 0.2f, 1.2f);
-    m_arrows.push_back(a);
+void ArrowSystem::spawnArrow(const QVector3D &start, const QVector3D &end,
+                             const QVector3D &color, float speed) {
+  ArrowInstance a;
+  a.start = start;
+  a.end = end;
+  a.color = color;
+  a.t = 0.0f;
+  a.speed = speed;
+  a.active = true;
+  float dist = (end - start).length();
+  a.arcHeight = std::clamp(0.15f * dist, 0.2f, 1.2f);
+  m_arrows.push_back(a);
 }
 
-void ArrowSystem::update(Engine::Core::World* world, float deltaTime) {
-    for (auto& arrow : m_arrows) {
-        if (!arrow.active) continue;
-        arrow.t += deltaTime * arrow.speed / (arrow.start - arrow.end).length();
-        if (arrow.t >= 1.0f) {
-            arrow.t = 1.0f;
-            arrow.active = false;
-        }
+void ArrowSystem::update(Engine::Core::World *world, float deltaTime) {
+  for (auto &arrow : m_arrows) {
+    if (!arrow.active)
+      continue;
+    arrow.t += deltaTime * arrow.speed / (arrow.start - arrow.end).length();
+    if (arrow.t >= 1.0f) {
+      arrow.t = 1.0f;
+      arrow.active = false;
     }
-    // Remove inactive arrows
-    m_arrows.erase(std::remove_if(m_arrows.begin(), m_arrows.end(), [](const ArrowInstance& a){ return !a.active; }), m_arrows.end());
+  }
+
+  m_arrows.erase(
+      std::remove_if(m_arrows.begin(), m_arrows.end(),
+                     [](const ArrowInstance &a) { return !a.active; }),
+      m_arrows.end());
 }
 
 } // namespace Game::Systems

+ 15 - 13
game/systems/arrow_system.h

@@ -1,29 +1,31 @@
 #pragma once
 #include "../core/system.h"
 #include "../core/world.h"
-#include <vector>
 #include <QVector3D>
+#include <vector>
 
 namespace Game::Systems {
 
 struct ArrowInstance {
-    QVector3D start;
-    QVector3D end;
-    QVector3D color;
-    float t; // 0=start, 1=end
-    float speed;
-    bool active;
-    float arcHeight; // peak height of arc
+  QVector3D start;
+  QVector3D end;
+  QVector3D color;
+  float t;
+  float speed;
+  bool active;
+  float arcHeight;
 };
 
 class ArrowSystem : public Engine::Core::System {
 public:
-    ArrowSystem();
-    void update(Engine::Core::World* world, float deltaTime) override;
-    void spawnArrow(const QVector3D& start, const QVector3D& end, const QVector3D& color, float speed = 8.0f);
-    const std::vector<ArrowInstance>& arrows() const { return m_arrows; }
+  ArrowSystem();
+  void update(Engine::Core::World *world, float deltaTime) override;
+  void spawnArrow(const QVector3D &start, const QVector3D &end,
+                  const QVector3D &color, float speed = 8.0f);
+  const std::vector<ArrowInstance> &arrows() const { return m_arrows; }
+
 private:
-    std::vector<ArrowInstance> m_arrows;
+  std::vector<ArrowInstance> m_arrows;
 };
 
 } // namespace Game::Systems

+ 29 - 19
game/systems/camera_controller.cpp

@@ -1,34 +1,44 @@
 #include "camera_controller.h"
 #include "../../render/gl/camera.h"
 
-namespace Game { namespace Systems {
-
-void CameraController::move(Render::GL::Camera& camera, float dx, float dz) const {
-    camera.pan(dx, dz);
-    if (camera.isFollowEnabled()) camera.captureFollowOffset();
+namespace Game {
+namespace Systems {
+
+void CameraController::move(Render::GL::Camera &camera, float dx,
+                            float dz) const {
+  camera.pan(dx, dz);
+  if (camera.isFollowEnabled())
+    camera.captureFollowOffset();
 }
 
-void CameraController::elevate(Render::GL::Camera& camera, float dy) const {
-    camera.elevate(dy);
-    if (camera.isFollowEnabled()) camera.captureFollowOffset();
+void CameraController::elevate(Render::GL::Camera &camera, float dy) const {
+  camera.elevate(dy);
+  if (camera.isFollowEnabled())
+    camera.captureFollowOffset();
 }
 
-void CameraController::yaw(Render::GL::Camera& camera, float degrees) const {
-    camera.yaw(degrees);
-    if (camera.isFollowEnabled()) camera.captureFollowOffset();
+void CameraController::yaw(Render::GL::Camera &camera, float degrees) const {
+  camera.yaw(degrees);
+  if (camera.isFollowEnabled())
+    camera.captureFollowOffset();
 }
 
-void CameraController::orbit(Render::GL::Camera& camera, float yawDeg, float pitchDeg) const {
-    camera.orbit(yawDeg, pitchDeg);
-    if (camera.isFollowEnabled()) camera.captureFollowOffset();
+void CameraController::orbit(Render::GL::Camera &camera, float yawDeg,
+                             float pitchDeg) const {
+  camera.orbit(yawDeg, pitchDeg);
+  if (camera.isFollowEnabled())
+    camera.captureFollowOffset();
 }
 
-void CameraController::setFollowEnabled(Render::GL::Camera& camera, bool enable) const {
-    camera.setFollowEnabled(enable);
+void CameraController::setFollowEnabled(Render::GL::Camera &camera,
+                                        bool enable) const {
+  camera.setFollowEnabled(enable);
 }
 
-void CameraController::setFollowLerp(Render::GL::Camera& camera, float alpha) const {
-    camera.setFollowLerp(alpha);
+void CameraController::setFollowLerp(Render::GL::Camera &camera,
+                                     float alpha) const {
+  camera.setFollowLerp(alpha);
 }
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 15 - 9
game/systems/camera_controller.h

@@ -1,17 +1,23 @@
 #pragma once
 
-namespace Render { namespace GL { class Camera; } }
+namespace Render {
+namespace GL {
+class Camera;
+}
+} // namespace Render
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 class CameraController {
 public:
-    void move(Render::GL::Camera& camera, float dx, float dz) const;
-    void elevate(Render::GL::Camera& camera, float dy) const;
-    void yaw(Render::GL::Camera& camera, float degrees) const;
-    void orbit(Render::GL::Camera& camera, float yawDeg, float pitchDeg) const;
-    void setFollowEnabled(Render::GL::Camera& camera, bool enable) const;
-    void setFollowLerp(Render::GL::Camera& camera, float alpha) const;
+  void move(Render::GL::Camera &camera, float dx, float dz) const;
+  void elevate(Render::GL::Camera &camera, float dy) const;
+  void yaw(Render::GL::Camera &camera, float degrees) const;
+  void orbit(Render::GL::Camera &camera, float yawDeg, float pitchDeg) const;
+  void setFollowEnabled(Render::GL::Camera &camera, bool enable) const;
+  void setFollowLerp(Render::GL::Camera &camera, float alpha) const;
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 47 - 41
game/systems/camera_follow_system.cpp

@@ -1,51 +1,57 @@
 #include "camera_follow_system.h"
-#include "selection_system.h"
-#include "../core/world.h"
-#include "../core/component.h"
 #include "../../render/gl/camera.h"
+#include "../core/component.h"
+#include "../core/world.h"
+#include "selection_system.h"
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
-void CameraFollowSystem::update(Engine::Core::World& world,
-                                SelectionSystem& selection,
-                                Render::GL::Camera& camera) {
-    const auto& sel = selection.getSelectedUnits();
-    if (sel.empty()) return;
-    QVector3D sum(0,0,0); int count = 0;
-    for (auto id : sel) {
-        if (auto* e = world.getEntity(id)) {
-            if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-                sum += QVector3D(t->position.x, t->position.y, t->position.z);
-                ++count;
-            }
-        }
-    }
-    if (count > 0) {
-        QVector3D center = sum / float(count);
-        camera.setTarget(center);
-        camera.updateFollow(center);
+void CameraFollowSystem::update(Engine::Core::World &world,
+                                SelectionSystem &selection,
+                                Render::GL::Camera &camera) {
+  const auto &sel = selection.getSelectedUnits();
+  if (sel.empty())
+    return;
+  QVector3D sum(0, 0, 0);
+  int count = 0;
+  for (auto id : sel) {
+    if (auto *e = world.getEntity(id)) {
+      if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+        sum += QVector3D(t->position.x, t->position.y, t->position.z);
+        ++count;
+      }
     }
+  }
+  if (count > 0) {
+    QVector3D center = sum / float(count);
+    camera.setTarget(center);
+    camera.updateFollow(center);
+  }
 }
 
-void CameraFollowSystem::snapToSelection(Engine::Core::World& world,
-                                         SelectionSystem& selection,
-                                         Render::GL::Camera& camera) {
-    const auto& sel = selection.getSelectedUnits();
-    if (sel.empty()) return;
-    QVector3D sum(0,0,0); int count = 0;
-    for (auto id : sel) {
-        if (auto* e = world.getEntity(id)) {
-            if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-                sum += QVector3D(t->position.x, t->position.y, t->position.z);
-                ++count;
-            }
-        }
-    }
-    if (count > 0) {
-        QVector3D target = sum / float(count);
-        camera.setTarget(target);
-        camera.captureFollowOffset();
+void CameraFollowSystem::snapToSelection(Engine::Core::World &world,
+                                         SelectionSystem &selection,
+                                         Render::GL::Camera &camera) {
+  const auto &sel = selection.getSelectedUnits();
+  if (sel.empty())
+    return;
+  QVector3D sum(0, 0, 0);
+  int count = 0;
+  for (auto id : sel) {
+    if (auto *e = world.getEntity(id)) {
+      if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+        sum += QVector3D(t->position.x, t->position.y, t->position.z);
+        ++count;
+      }
     }
+  }
+  if (count > 0) {
+    QVector3D target = sum / float(count);
+    camera.setTarget(target);
+    camera.captureFollowOffset();
+  }
 }
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 23 - 11
game/systems/camera_follow_system.h

@@ -2,21 +2,33 @@
 
 #include <QVector3D>
 
-namespace Engine { namespace Core { class World; } }
-namespace Render { namespace GL { class Camera; } }
-namespace Game { namespace Systems { class SelectionSystem; } }
+namespace Engine {
+namespace Core {
+class World;
+}
+} // namespace Engine
+namespace Render {
+namespace GL {
+class Camera;
+}
+} // namespace Render
+namespace Game {
+namespace Systems {
+class SelectionSystem;
+}
+} // namespace Game
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 class CameraFollowSystem {
 public:
-    void update(Engine::Core::World& world,
-                SelectionSystem& selection,
-                Render::GL::Camera& camera);
+  void update(Engine::Core::World &world, SelectionSystem &selection,
+              Render::GL::Camera &camera);
 
-    void snapToSelection(Engine::Core::World& world,
-                         SelectionSystem& selection,
-                         Render::GL::Camera& camera);
+  void snapToSelection(Engine::Core::World &world, SelectionSystem &selection,
+                       Render::GL::Camera &camera);
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 176 - 176
game/systems/combat_system.cpp

@@ -1,206 +1,206 @@
 #include "combat_system.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include "../visuals/team_colors.h"
 #include "arrow_system.h"
-#include "../core/world.h"
-#include <limits>
 #include <algorithm>
+#include <limits>
 
 namespace Game::Systems {
 
-void CombatSystem::update(Engine::Core::World* world, float deltaTime) {
-    processAttacks(world, deltaTime);
+void CombatSystem::update(Engine::Core::World *world, float deltaTime) {
+  processAttacks(world, deltaTime);
 }
 
-void CombatSystem::processAttacks(Engine::Core::World* world, float deltaTime) {
-    auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
+void CombatSystem::processAttacks(Engine::Core::World *world, float deltaTime) {
+  auto units = world->getEntitiesWith<Engine::Core::UnitComponent>();
 
-    // Find ArrowSystem if present
-    ArrowSystem* arrowSys = nullptr;
-    for (auto& sys : world->systems()) {
-        arrowSys = dynamic_cast<ArrowSystem*>(sys.get());
-        if (arrowSys) break;
+  ArrowSystem *arrowSys = nullptr;
+  for (auto &sys : world->systems()) {
+    arrowSys = dynamic_cast<ArrowSystem *>(sys.get());
+    if (arrowSys)
+      break;
+  }
+
+  for (auto attacker : units) {
+    auto attackerUnit = attacker->getComponent<Engine::Core::UnitComponent>();
+    auto attackerTransform =
+        attacker->getComponent<Engine::Core::TransformComponent>();
+    auto attackerAtk = attacker->getComponent<Engine::Core::AttackComponent>();
+
+    if (!attackerUnit || !attackerTransform) {
+      continue;
     }
 
-    for (auto attacker : units) {
-        auto attackerUnit = attacker->getComponent<Engine::Core::UnitComponent>();
-        auto attackerTransform = attacker->getComponent<Engine::Core::TransformComponent>();
-        auto attackerAtk = attacker->getComponent<Engine::Core::AttackComponent>();
+    if (attackerUnit->health <= 0)
+      continue;
 
-        if (!attackerUnit || !attackerTransform) {
-            continue;
-        }
-        // Skip dead attackers
-        if (attackerUnit->health <= 0) continue;
-        // If there's no attack component, use default melee-like behavior
-        float range = 2.0f;
-        int damage = 10;
-        float cooldown = 1.0f;
-        float* tAccum = nullptr;
-        float tmpAccum = 0.0f;
-        if (attackerAtk) {
-            range = std::max(0.1f, attackerAtk->range);
-            damage = std::max(0, attackerAtk->damage);
-            cooldown = std::max(0.05f, attackerAtk->cooldown);
-            attackerAtk->timeSinceLast += deltaTime;
-            tAccum = &attackerAtk->timeSinceLast;
+    float range = 2.0f;
+    int damage = 10;
+    float cooldown = 1.0f;
+    float *tAccum = nullptr;
+    float tmpAccum = 0.0f;
+    if (attackerAtk) {
+      range = std::max(0.1f, attackerAtk->range);
+      damage = std::max(0, attackerAtk->damage);
+      cooldown = std::max(0.05f, attackerAtk->cooldown);
+      attackerAtk->timeSinceLast += deltaTime;
+      tAccum = &attackerAtk->timeSinceLast;
+    } else {
+      tmpAccum += deltaTime;
+      tAccum = &tmpAccum;
+    }
+    if (*tAccum < cooldown) {
+      continue;
+    }
+
+    auto *attackTarget =
+        attacker->getComponent<Engine::Core::AttackTargetComponent>();
+    Engine::Core::Entity *bestTarget = nullptr;
+
+    if (attackTarget && attackTarget->targetId != 0) {
+
+      auto *target = world->getEntity(attackTarget->targetId);
+      if (target) {
+        auto *targetUnit = target->getComponent<Engine::Core::UnitComponent>();
+
+        if (targetUnit && targetUnit->health > 0 &&
+            targetUnit->ownerId != attackerUnit->ownerId) {
+
+          if (isInRange(attacker, target, range)) {
+            bestTarget = target;
+          } else if (attackTarget->shouldChase) {
+
+            auto *movement =
+                attacker->getComponent<Engine::Core::MovementComponent>();
+            if (!movement) {
+              movement =
+                  attacker->addComponent<Engine::Core::MovementComponent>();
+            }
+            if (movement) {
+              auto *targetTransform =
+                  target->getComponent<Engine::Core::TransformComponent>();
+              if (targetTransform) {
+                movement->targetX = targetTransform->position.x;
+                movement->targetY = targetTransform->position.z;
+                movement->hasTarget = true;
+              }
+            }
+
+            if (isInRange(attacker, target, range)) {
+              bestTarget = target;
+            }
+          } else {
+
+            attacker->removeComponent<Engine::Core::AttackTargetComponent>();
+          }
         } else {
-            tmpAccum += deltaTime;
-            tAccum = &tmpAccum;
+
+          attacker->removeComponent<Engine::Core::AttackTargetComponent>();
         }
-        if (*tAccum < cooldown) {
-            continue; // still cooling down
+      } else {
+
+        attacker->removeComponent<Engine::Core::AttackTargetComponent>();
+      }
+    }
+
+    if (!bestTarget && !attackTarget) {
+
+      for (auto target : units) {
+        if (target == attacker) {
+          continue;
         }
 
-        // Check for explicit attack target command first
-        auto* attackTarget = attacker->getComponent<Engine::Core::AttackTargetComponent>();
-        Engine::Core::Entity* bestTarget = nullptr;
-        
-        if (attackTarget && attackTarget->targetId != 0) {
-            // Explicit attack command - target specific entity
-            auto* target = world->getEntity(attackTarget->targetId);
-            if (target) {
-                auto* targetUnit = target->getComponent<Engine::Core::UnitComponent>();
-                
-                // Check if target is valid (alive and enemy)
-                if (targetUnit && targetUnit->health > 0 && 
-                    targetUnit->ownerId != attackerUnit->ownerId) {
-                    
-                    // Check if in range - if so, we can attack
-                    if (isInRange(attacker, target, range)) {
-                        bestTarget = target;
-                    } else if (attackTarget->shouldChase) {
-                        // Not in range but should chase - move toward target
-                        auto* movement = attacker->getComponent<Engine::Core::MovementComponent>();
-                        if (!movement) {
-                            movement = attacker->addComponent<Engine::Core::MovementComponent>();
-                        }
-                        if (movement) {
-                            auto* targetTransform = target->getComponent<Engine::Core::TransformComponent>();
-                            if (targetTransform) {
-                                movement->targetX = targetTransform->position.x;
-                                movement->targetY = targetTransform->position.z;
-                                movement->hasTarget = true;
-                            }
-                        }
-                        // Still try to attack if somehow we got close enough
-                        // (movement might not have updated position yet)
-                        if (isInRange(attacker, target, range)) {
-                            bestTarget = target;
-                        }
-                    } else {
-                        // Not chasing and not in range - clear attack target
-                        attacker->removeComponent<Engine::Core::AttackTargetComponent>();
-                    }
-                } else {
-                    // Target is dead, friendly, or invalid - clear attack target
-                    attacker->removeComponent<Engine::Core::AttackTargetComponent>();
-                }
-            } else {
-                // Target entity doesn't exist anymore
-                attacker->removeComponent<Engine::Core::AttackTargetComponent>();
-            }
+        auto targetUnit = target->getComponent<Engine::Core::UnitComponent>();
+        if (!targetUnit || targetUnit->health <= 0) {
+          continue;
         }
-        
-        // If no explicit target, use automatic targeting (units only, not buildings in auto-mode)
-        if (!bestTarget && !attackTarget) {
-            // Auto-targeting: only target enemy units (not buildings automatically)
-            for (auto target : units) {
-                if (target == attacker) {
-                    continue;
-                }
-
-                auto targetUnit = target->getComponent<Engine::Core::UnitComponent>();
-                if (!targetUnit || targetUnit->health <= 0) {
-                    continue;
-                }
-                // Friendly-fire check: only attack units with a different owner
-                if (targetUnit->ownerId == attackerUnit->ownerId) {
-                    continue;
-                }
-                
-                // Skip buildings in auto-targeting (but they CAN be targeted explicitly via AttackTargetComponent)
-                if (target->hasComponent<Engine::Core::BuildingComponent>()) {
-                    continue;
-                }
-
-                if (isInRange(attacker, target, range)) {
-                    bestTarget = target;
-                    break;
-                }
-            }
+
+        if (targetUnit->ownerId == attackerUnit->ownerId) {
+          continue;
         }
-        
-        // Attack the selected target
-        if (bestTarget) {
-            auto* bestTargetUnit = bestTarget->getComponent<Engine::Core::UnitComponent>();
-            
-            // Arrow visual: spawn arrow if ArrowSystem present
-            if (arrowSys) {
-                auto attT = attacker->getComponent<Engine::Core::TransformComponent>();
-                auto tgtT = bestTarget->getComponent<Engine::Core::TransformComponent>();
-                auto attU = attacker->getComponent<Engine::Core::UnitComponent>();
-                QVector3D aPos(attT->position.x, attT->position.y, attT->position.z);
-                QVector3D tPos(tgtT->position.x, tgtT->position.y, tgtT->position.z);
-                QVector3D dir = (tPos - aPos).normalized();
-                // Raise bow height and offset a bit forward to avoid intersecting the capsule body
-                QVector3D start = aPos + QVector3D(0.0f, 0.6f, 0.0f) + dir * 0.35f;
-                QVector3D end   = tPos + QVector3D(0.5f, 0.5f, 0.0f);
-                QVector3D color = attU ? Game::Visuals::teamColorForOwner(attU->ownerId) : QVector3D(0.8f, 0.9f, 1.0f);
-                arrowSys->spawnArrow(start, end, color, 14.0f);
-            }
-            dealDamage(bestTarget, damage);
-            *tAccum = 0.0f; // reset cooldown
+
+        if (target->hasComponent<Engine::Core::BuildingComponent>()) {
+          continue;
         }
-    }
-}
 
-bool CombatSystem::isInRange(Engine::Core::Entity* attacker, Engine::Core::Entity* target, float range) {
-    auto attackerTransform = attacker->getComponent<Engine::Core::TransformComponent>();
-    auto targetTransform = target->getComponent<Engine::Core::TransformComponent>();
-    
-    if (!attackerTransform || !targetTransform) {
-        return false;
+        if (isInRange(attacker, target, range)) {
+          bestTarget = target;
+          break;
+        }
+      }
     }
-    
-    float dx = targetTransform->position.x - attackerTransform->position.x;
-    float dz = targetTransform->position.z - attackerTransform->position.z;
-    float distanceSquared = dx * dx + dz * dz;
-    
-    // Account for target size (especially important for buildings)
-    // Add target's approximate radius to the attack range
-    float targetRadius = 0.0f;
-    if (target->hasComponent<Engine::Core::BuildingComponent>()) {
-        // Buildings are larger - use their scale to estimate radius
-        // Use the larger of X or Z scale as the radius estimate
-        float scaleX = targetTransform->scale.x;
-        float scaleZ = targetTransform->scale.z;
-        targetRadius = std::max(scaleX, scaleZ) * 0.5f; // half the scale is approx radius
-    } else {
-        // Regular units have smaller radius
-        float scaleX = targetTransform->scale.x;
-        float scaleZ = targetTransform->scale.z;
-        targetRadius = std::max(scaleX, scaleZ) * 0.5f;
+
+    if (bestTarget) {
+      auto *bestTargetUnit =
+          bestTarget->getComponent<Engine::Core::UnitComponent>();
+
+      if (arrowSys) {
+        auto attT = attacker->getComponent<Engine::Core::TransformComponent>();
+        auto tgtT =
+            bestTarget->getComponent<Engine::Core::TransformComponent>();
+        auto attU = attacker->getComponent<Engine::Core::UnitComponent>();
+        QVector3D aPos(attT->position.x, attT->position.y, attT->position.z);
+        QVector3D tPos(tgtT->position.x, tgtT->position.y, tgtT->position.z);
+        QVector3D dir = (tPos - aPos).normalized();
+
+        QVector3D start = aPos + QVector3D(0.0f, 0.6f, 0.0f) + dir * 0.35f;
+        QVector3D end = tPos + QVector3D(0.5f, 0.5f, 0.0f);
+        QVector3D color = attU ? Game::Visuals::teamColorForOwner(attU->ownerId)
+                               : QVector3D(0.8f, 0.9f, 1.0f);
+        arrowSys->spawnArrow(start, end, color, 14.0f);
+      }
+      dealDamage(bestTarget, damage);
+      *tAccum = 0.0f;
     }
-    
-    // Effective range includes target's size
-    float effectiveRange = range + targetRadius;
-    
-    return distanceSquared <= effectiveRange * effectiveRange;
+  }
 }
 
-void CombatSystem::dealDamage(Engine::Core::Entity* target, int damage) {
-    auto unit = target->getComponent<Engine::Core::UnitComponent>();
-    if (unit) {
-        unit->health = std::max(0, unit->health - damage);
-        
-        if (unit->health <= 0) {
-            // Hide the renderable so dead units disappear
-            if (auto* r = target->getComponent<Engine::Core::RenderableComponent>()) {
-                r->visible = false;
-            }
-        }
+bool CombatSystem::isInRange(Engine::Core::Entity *attacker,
+                             Engine::Core::Entity *target, float range) {
+  auto attackerTransform =
+      attacker->getComponent<Engine::Core::TransformComponent>();
+  auto targetTransform =
+      target->getComponent<Engine::Core::TransformComponent>();
+
+  if (!attackerTransform || !targetTransform) {
+    return false;
+  }
+
+  float dx = targetTransform->position.x - attackerTransform->position.x;
+  float dz = targetTransform->position.z - attackerTransform->position.z;
+  float distanceSquared = dx * dx + dz * dz;
+
+  float targetRadius = 0.0f;
+  if (target->hasComponent<Engine::Core::BuildingComponent>()) {
+
+    float scaleX = targetTransform->scale.x;
+    float scaleZ = targetTransform->scale.z;
+    targetRadius = std::max(scaleX, scaleZ) * 0.5f;
+  } else {
+
+    float scaleX = targetTransform->scale.x;
+    float scaleZ = targetTransform->scale.z;
+    targetRadius = std::max(scaleX, scaleZ) * 0.5f;
+  }
+
+  float effectiveRange = range + targetRadius;
+
+  return distanceSquared <= effectiveRange * effectiveRange;
+}
+
+void CombatSystem::dealDamage(Engine::Core::Entity *target, int damage) {
+  auto unit = target->getComponent<Engine::Core::UnitComponent>();
+  if (unit) {
+    unit->health = std::max(0, unit->health - damage);
+
+    if (unit->health <= 0) {
+
+      if (auto *r = target->getComponent<Engine::Core::RenderableComponent>()) {
+        r->visible = false;
+      }
     }
+  }
 }
 
 } // namespace Game::Systems

+ 10 - 5
game/systems/combat_system.h

@@ -2,18 +2,23 @@
 
 #include "../core/system.h"
 
-namespace Engine { namespace Core { class Entity; } }
+namespace Engine {
+namespace Core {
+class Entity;
+}
+} // namespace Engine
 
 namespace Game::Systems {
 
 class CombatSystem : public Engine::Core::System {
 public:
-    void update(Engine::Core::World* world, float deltaTime) override;
+  void update(Engine::Core::World *world, float deltaTime) override;
 
 private:
-    void processAttacks(Engine::Core::World* world, float deltaTime);
-    bool isInRange(Engine::Core::Entity* attacker, Engine::Core::Entity* target, float range);
-    void dealDamage(Engine::Core::Entity* target, int damage);
+  void processAttacks(Engine::Core::World *world, float deltaTime);
+  bool isInRange(Engine::Core::Entity *attacker, Engine::Core::Entity *target,
+                 float range);
+  void dealDamage(Engine::Core::Entity *target, int damage);
 };
 
 } // namespace Game::Systems

+ 47 - 40
game/systems/command_service.cpp

@@ -1,49 +1,56 @@
 #include "command_service.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include <QVector3D>
 
-namespace Game { namespace Systems {
-
-void CommandService::moveUnits(Engine::Core::World& world,
-                               const std::vector<Engine::Core::EntityID>& units,
-                               const std::vector<QVector3D>& targets) {
-    if (units.size() != targets.size()) return;
-    for (size_t i = 0; i < units.size(); ++i) {
-        auto* e = world.getEntity(units[i]);
-        if (!e) continue;
-        auto* mv = e->getComponent<Engine::Core::MovementComponent>();
-        if (!mv) mv = e->addComponent<Engine::Core::MovementComponent>();
-        if (!mv) continue;
-        mv->targetX = targets[i].x();
-        mv->targetY = targets[i].z();
-        mv->hasTarget = true;
-        
-        // Clear attack target when moving normally
-        e->removeComponent<Engine::Core::AttackTargetComponent>();
-    }
+namespace Game {
+namespace Systems {
+
+void CommandService::moveUnits(Engine::Core::World &world,
+                               const std::vector<Engine::Core::EntityID> &units,
+                               const std::vector<QVector3D> &targets) {
+  if (units.size() != targets.size())
+    return;
+  for (size_t i = 0; i < units.size(); ++i) {
+    auto *e = world.getEntity(units[i]);
+    if (!e)
+      continue;
+    auto *mv = e->getComponent<Engine::Core::MovementComponent>();
+    if (!mv)
+      mv = e->addComponent<Engine::Core::MovementComponent>();
+    if (!mv)
+      continue;
+    mv->targetX = targets[i].x();
+    mv->targetY = targets[i].z();
+    mv->hasTarget = true;
+
+    e->removeComponent<Engine::Core::AttackTargetComponent>();
+  }
 }
 
-void CommandService::attackTarget(Engine::Core::World& world,
-                                  const std::vector<Engine::Core::EntityID>& units,
-                                  Engine::Core::EntityID targetId,
-                                  bool shouldChase) {
-    if (targetId == 0) return;
-    
-    for (auto unitId : units) {
-        auto* e = world.getEntity(unitId);
-        if (!e) continue;
-        
-        // Add or update attack target component
-        auto* attackTarget = e->getComponent<Engine::Core::AttackTargetComponent>();
-        if (!attackTarget) {
-            attackTarget = e->addComponent<Engine::Core::AttackTargetComponent>();
-        }
-        if (!attackTarget) continue;
-        
-        attackTarget->targetId = targetId;
-        attackTarget->shouldChase = shouldChase;
+void CommandService::attackTarget(
+    Engine::Core::World &world,
+    const std::vector<Engine::Core::EntityID> &units,
+    Engine::Core::EntityID targetId, bool shouldChase) {
+  if (targetId == 0)
+    return;
+
+  for (auto unitId : units) {
+    auto *e = world.getEntity(unitId);
+    if (!e)
+      continue;
+
+    auto *attackTarget = e->getComponent<Engine::Core::AttackTargetComponent>();
+    if (!attackTarget) {
+      attackTarget = e->addComponent<Engine::Core::AttackTargetComponent>();
     }
+    if (!attackTarget)
+      continue;
+
+    attackTarget->targetId = targetId;
+    attackTarget->shouldChase = shouldChase;
+  }
 }
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 17 - 15
game/systems/command_service.h

@@ -1,28 +1,30 @@
 #pragma once
 
-#include <vector>
 #include <QVector3D>
+#include <vector>
 
-namespace Engine { namespace Core {
+namespace Engine {
+namespace Core {
 class World;
 using EntityID = unsigned int;
 struct MovementComponent;
-} }
+} // namespace Core
+} // namespace Engine
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 class CommandService {
 public:
-    // Ensure movement components exist and assign targets for each entity to corresponding positions (same size vectors expected).
-    static void moveUnits(Engine::Core::World& world,
-                          const std::vector<Engine::Core::EntityID>& units,
-                          const std::vector<QVector3D>& targets);
-    
-    // Issue attack command on specific target entity
-    static void attackTarget(Engine::Core::World& world,
-                            const std::vector<Engine::Core::EntityID>& units,
-                            Engine::Core::EntityID targetId,
-                            bool shouldChase = true);
+  static void moveUnits(Engine::Core::World &world,
+                        const std::vector<Engine::Core::EntityID> &units,
+                        const std::vector<QVector3D> &targets);
+
+  static void attackTarget(Engine::Core::World &world,
+                           const std::vector<Engine::Core::EntityID> &units,
+                           Engine::Core::EntityID targetId,
+                           bool shouldChase = true);
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 20 - 16
game/systems/formation_planner.h

@@ -1,27 +1,31 @@
 #pragma once
 
 #include <QVector3D>
-#include <vector>
 #include <cmath>
+#include <vector>
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 class FormationPlanner {
 public:
-    // Simple square-grid spread around center, row-major, centered, with given spacing.
-    static std::vector<QVector3D> spreadFormation(int n, const QVector3D& center, float spacing = 1.0f) {
-        std::vector<QVector3D> out; out.reserve(n);
-        if (n <= 0) return out;
-        int side = std::ceil(std::sqrt(float(n)));
-        for (int i = 0; i < n; ++i) {
-            int gx = i % side;
-            int gy = i / side;
-            float ox = (gx - (side - 1) * 0.5f) * spacing;
-            float oz = (gy - (side - 1) * 0.5f) * spacing;
-            out.emplace_back(center.x() + ox, center.y(), center.z() + oz);
-        }
-        return out;
+  static std::vector<QVector3D> spreadFormation(int n, const QVector3D &center,
+                                                float spacing = 1.0f) {
+    std::vector<QVector3D> out;
+    out.reserve(n);
+    if (n <= 0)
+      return out;
+    int side = std::ceil(std::sqrt(float(n)));
+    for (int i = 0; i < n; ++i) {
+      int gx = i % side;
+      int gy = i / side;
+      float ox = (gx - (side - 1) * 0.5f) * spacing;
+      float oz = (gy - (side - 1) * 0.5f) * spacing;
+      out.emplace_back(center.x() + ox, center.y(), center.z() + oz);
     }
+    return out;
+  }
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 81 - 84
game/systems/movement_system.cpp

@@ -1,100 +1,97 @@
 #include "movement_system.h"
-#include <cmath>
 #include <algorithm>
+#include <cmath>
 
 namespace Game::Systems {
 
-void MovementSystem::update(Engine::Core::World* world, float deltaTime) {
-    auto entities = world->getEntitiesWith<Engine::Core::MovementComponent>();
-    
-    for (auto entity : entities) {
-        moveUnit(entity, deltaTime);
-    }
+void MovementSystem::update(Engine::Core::World *world, float deltaTime) {
+  auto entities = world->getEntitiesWith<Engine::Core::MovementComponent>();
+
+  for (auto entity : entities) {
+    moveUnit(entity, deltaTime);
+  }
 }
 
-void MovementSystem::moveUnit(Engine::Core::Entity* entity, float deltaTime) {
-    auto transform = entity->getComponent<Engine::Core::TransformComponent>();
-    auto movement = entity->getComponent<Engine::Core::MovementComponent>();
-    auto unit = entity->getComponent<Engine::Core::UnitComponent>();
-    
-    if (!transform || !movement || !unit) {
-        return;
-    }
-    
-    // Smooth movement: integrate velocity toward desired direction with acceleration and damping
-    const float maxSpeed = std::max(0.1f, unit->speed);
-    const float accel = maxSpeed * 4.0f;   // time to accelerate to speed ~0.25s
-    const float damping = 6.0f;            // critically-damped-ish
-
-    // If there's no target, just damp velocity and keep position
-    if (!movement->hasTarget) {
-        movement->vx *= std::max(0.0f, 1.0f - damping * deltaTime);
-        movement->vz *= std::max(0.0f, 1.0f - damping * deltaTime);
+void MovementSystem::moveUnit(Engine::Core::Entity *entity, float deltaTime) {
+  auto transform = entity->getComponent<Engine::Core::TransformComponent>();
+  auto movement = entity->getComponent<Engine::Core::MovementComponent>();
+  auto unit = entity->getComponent<Engine::Core::UnitComponent>();
+
+  if (!transform || !movement || !unit) {
+    return;
+  }
+
+  const float maxSpeed = std::max(0.1f, unit->speed);
+  const float accel = maxSpeed * 4.0f;
+  const float damping = 6.0f;
+
+  if (!movement->hasTarget) {
+    movement->vx *= std::max(0.0f, 1.0f - damping * deltaTime);
+    movement->vz *= std::max(0.0f, 1.0f - damping * deltaTime);
+  } else {
+    float dx = movement->targetX - transform->position.x;
+    float dz = movement->targetY - transform->position.z;
+    float dist2 = dx * dx + dz * dz;
+    float distance = std::sqrt(dist2);
+
+    const float arriveRadius = 0.25f;
+    if (distance < arriveRadius) {
+
+      transform->position.x = movement->targetX;
+      transform->position.z = movement->targetY;
+      movement->hasTarget = false;
+      movement->vx = movement->vz = 0.0f;
     } else {
-        float dx = movement->targetX - transform->position.x;
-        float dz = movement->targetY - transform->position.z; // Using z for 2D movement
-        float dist2 = dx*dx + dz*dz;
-        float distance = std::sqrt(dist2);
-        // Arrival radius for smooth stop
-        const float arriveRadius = 0.25f;
-        if (distance < arriveRadius) {
-            // Arrived: snap to target and clear
-            transform->position.x = movement->targetX;
-            transform->position.z = movement->targetY;
-            movement->hasTarget = false;
-            movement->vx = movement->vz = 0.0f;
-        } else {
-        // Desired velocity toward target with slowing within arrive radius*4
-            float nx = dx / std::max(0.0001f, distance);
-            float nz = dz / std::max(0.0001f, distance);
-            float desiredSpeed = maxSpeed;
-            float slowRadius = arriveRadius * 4.0f;
-            if (distance < slowRadius) {
-                desiredSpeed = maxSpeed * (distance / slowRadius);
-            }
-            float desiredVx = nx * desiredSpeed;
-            float desiredVz = nz * desiredSpeed;
-
-            // accelerate toward desired velocity
-            float ax = (desiredVx - movement->vx) * accel;
-            float az = (desiredVz - movement->vz) * accel;
-            movement->vx += ax * deltaTime;
-            movement->vz += az * deltaTime;
-
-            // apply damping
-            movement->vx *= std::max(0.0f, 1.0f - 0.5f * damping * deltaTime);
-            movement->vz *= std::max(0.0f, 1.0f - 0.5f * damping * deltaTime);
-        }
+
+      float nx = dx / std::max(0.0001f, distance);
+      float nz = dz / std::max(0.0001f, distance);
+      float desiredSpeed = maxSpeed;
+      float slowRadius = arriveRadius * 4.0f;
+      if (distance < slowRadius) {
+        desiredSpeed = maxSpeed * (distance / slowRadius);
+      }
+      float desiredVx = nx * desiredSpeed;
+      float desiredVz = nz * desiredSpeed;
+
+      float ax = (desiredVx - movement->vx) * accel;
+      float az = (desiredVz - movement->vz) * accel;
+      movement->vx += ax * deltaTime;
+      movement->vz += az * deltaTime;
+
+      movement->vx *= std::max(0.0f, 1.0f - 0.5f * damping * deltaTime);
+      movement->vz *= std::max(0.0f, 1.0f - 0.5f * damping * deltaTime);
     }
+  }
+
+  transform->position.x += movement->vx * deltaTime;
+  transform->position.z += movement->vz * deltaTime;
 
-    // Integrate position
-    transform->position.x += movement->vx * deltaTime;
-    transform->position.z += movement->vz * deltaTime;
-
-    // Face movement direction smoothly (units only; buildings remain stationary orientation)
-    if (!entity->hasComponent<Engine::Core::BuildingComponent>()) {
-        float speed2 = movement->vx*movement->vx + movement->vz*movement->vz;
-        if (speed2 > 1e-5f) {
-            float targetYaw = std::atan2(movement->vx, movement->vz) * 180.0f / 3.14159265f; // yaw around Y
-            // Smoothly interpolate rotation.y toward targetYaw
-            float current = transform->rotation.y;
-            // shortest angle difference
-            float diff = std::fmod((targetYaw - current + 540.0f), 360.0f) - 180.0f;
-            float turnSpeed = 720.0f; // deg/sec
-            float step = std::clamp(diff, -turnSpeed * deltaTime, turnSpeed * deltaTime);
-            transform->rotation.y = current + step;
-        }
+  if (!entity->hasComponent<Engine::Core::BuildingComponent>()) {
+    float speed2 = movement->vx * movement->vx + movement->vz * movement->vz;
+    if (speed2 > 1e-5f) {
+      float targetYaw =
+          std::atan2(movement->vx, movement->vz) * 180.0f / 3.14159265f;
+
+      float current = transform->rotation.y;
+
+      float diff = std::fmod((targetYaw - current + 540.0f), 360.0f) - 180.0f;
+      float turnSpeed = 720.0f;
+      float step =
+          std::clamp(diff, -turnSpeed * deltaTime, turnSpeed * deltaTime);
+      transform->rotation.y = current + step;
     }
+  }
 }
 
-bool MovementSystem::hasReachedTarget(const Engine::Core::TransformComponent* transform,
-                                    const Engine::Core::MovementComponent* movement) {
-    float dx = movement->targetX - transform->position.x;
-    float dz = movement->targetY - transform->position.z;
-    float distanceSquared = dx * dx + dz * dz;
-    
-    const float threshold = 0.1f; // 0.1 unit tolerance
-    return distanceSquared < threshold * threshold;
+bool MovementSystem::hasReachedTarget(
+    const Engine::Core::TransformComponent *transform,
+    const Engine::Core::MovementComponent *movement) {
+  float dx = movement->targetX - transform->position.x;
+  float dz = movement->targetY - transform->position.z;
+  float distanceSquared = dx * dx + dz * dz;
+
+  const float threshold = 0.1f;
+  return distanceSquared < threshold * threshold;
 }
 
 } // namespace Game::Systems

+ 10 - 6
game/systems/movement_system.h

@@ -1,21 +1,25 @@
 #pragma once
 
+#include "../core/component.h"
 #include "../core/system.h"
 #include "../core/world.h"
-#include "../core/component.h"
 
-namespace Engine { namespace Core { class Entity; } }
+namespace Engine {
+namespace Core {
+class Entity;
+}
+} // namespace Engine
 
 namespace Game::Systems {
 
 class MovementSystem : public Engine::Core::System {
 public:
-    void update(Engine::Core::World* world, float deltaTime) override;
+  void update(Engine::Core::World *world, float deltaTime) override;
 
 private:
-    void moveUnit(Engine::Core::Entity* entity, float deltaTime);
-    bool hasReachedTarget(const Engine::Core::TransformComponent* transform,
-                         const Engine::Core::MovementComponent* movement);
+  void moveUnit(Engine::Core::Entity *entity, float deltaTime);
+  bool hasReachedTarget(const Engine::Core::TransformComponent *transform,
+                        const Engine::Core::MovementComponent *movement);
 };
 
 } // namespace Game::Systems

+ 105 - 108
game/systems/pathfinding.cpp

@@ -4,133 +4,130 @@
 
 namespace Game::Systems {
 
-Pathfinding::Pathfinding(int width, int height) 
+Pathfinding::Pathfinding(int width, int height)
     : m_width(width), m_height(height) {
-    m_obstacles.resize(height, std::vector<bool>(width, false));
+  m_obstacles.resize(height, std::vector<bool>(width, false));
 }
 
 void Pathfinding::setObstacle(int x, int y, bool isObstacle) {
-    if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
-        m_obstacles[y][x] = isObstacle;
-    }
+  if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
+    m_obstacles[y][x] = isObstacle;
+  }
 }
 
 bool Pathfinding::isWalkable(int x, int y) const {
-    if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
-        return false;
-    }
-    return !m_obstacles[y][x];
+  if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
+    return false;
+  }
+  return !m_obstacles[y][x];
 }
 
-std::vector<Point> Pathfinding::findPath(const Point& start, const Point& end) {
-    if (!isWalkable(start.x, start.y) || !isWalkable(end.x, end.y)) {
-        return {}; // No path if start or end is not walkable
-    }
-    
-    if (start == end) {
-        return {start}; // Already at destination
+std::vector<Point> Pathfinding::findPath(const Point &start, const Point &end) {
+  if (!isWalkable(start.x, start.y) || !isWalkable(end.x, end.y)) {
+    return {};
+  }
+
+  if (start == end) {
+    return {start};
+  }
+
+  std::vector<Node *> openList;
+  std::vector<std::vector<bool>> closedList(m_height,
+                                            std::vector<bool>(m_width, false));
+  std::vector<std::vector<Node *>> allNodes(
+      m_height, std::vector<Node *>(m_width, nullptr));
+
+  Node *startNode = new Node(start, 0, calculateHeuristic(start, end));
+  openList.push_back(startNode);
+  allNodes[start.y][start.x] = startNode;
+
+  std::vector<Point> path;
+
+  while (!openList.empty()) {
+
+    auto currentIt = std::min_element(openList.begin(), openList.end(),
+                                      [](const Node *a, const Node *b) {
+                                        return a->getFCost() < b->getFCost();
+                                      });
+
+    Node *current = *currentIt;
+    openList.erase(currentIt);
+
+    closedList[current->position.y][current->position.x] = true;
+
+    if (current->position == end) {
+      path = reconstructPath(current);
+      break;
     }
-    
-    std::vector<Node*> openList;
-    std::vector<std::vector<bool>> closedList(m_height, std::vector<bool>(m_width, false));
-    std::vector<std::vector<Node*>> allNodes(m_height, std::vector<Node*>(m_width, nullptr));
-    
-    // Create start node
-    Node* startNode = new Node(start, 0, calculateHeuristic(start, end));
-    openList.push_back(startNode);
-    allNodes[start.y][start.x] = startNode;
-    
-    std::vector<Point> path;
-    
-    while (!openList.empty()) {
-        // Find node with lowest f cost
-        auto currentIt = std::min_element(openList.begin(), openList.end(),
-            [](const Node* a, const Node* b) {
-                return a->getFCost() < b->getFCost();
-            });
-        
-        Node* current = *currentIt;
-        openList.erase(currentIt);
-        
-        // Mark as closed
-        closedList[current->position.y][current->position.x] = true;
-        
-        // Check if we reached the goal
-        if (current->position == end) {
-            path = reconstructPath(current);
-            break;
-        }
-        
-        // Check all neighbors
-        for (const auto& neighborPos : getNeighbors(current->position)) {
-            if (!isWalkable(neighborPos.x, neighborPos.y) ||
-                closedList[neighborPos.y][neighborPos.x]) {
-                continue;
-            }
-            
-            int tentativeGCost = current->gCost + 1; // Assuming uniform cost
-            Node* neighbor = allNodes[neighborPos.y][neighborPos.x];
-            
-            if (!neighbor) {
-                // Create new node
-                neighbor = new Node(neighborPos, tentativeGCost, 
-                                  calculateHeuristic(neighborPos, end), current);
-                allNodes[neighborPos.y][neighborPos.x] = neighbor;
-                openList.push_back(neighbor);
-            } else if (tentativeGCost < neighbor->gCost) {
-                // Update existing node
-                neighbor->gCost = tentativeGCost;
-                neighbor->parent = current;
-            }
-        }
+
+    for (const auto &neighborPos : getNeighbors(current->position)) {
+      if (!isWalkable(neighborPos.x, neighborPos.y) ||
+          closedList[neighborPos.y][neighborPos.x]) {
+        continue;
+      }
+
+      int tentativeGCost = current->gCost + 1;
+      Node *neighbor = allNodes[neighborPos.y][neighborPos.x];
+
+      if (!neighbor) {
+
+        neighbor = new Node(neighborPos, tentativeGCost,
+                            calculateHeuristic(neighborPos, end), current);
+        allNodes[neighborPos.y][neighborPos.x] = neighbor;
+        openList.push_back(neighbor);
+      } else if (tentativeGCost < neighbor->gCost) {
+
+        neighbor->gCost = tentativeGCost;
+        neighbor->parent = current;
+      }
     }
-    
-    // Clean up
-    for (int y = 0; y < m_height; ++y) {
-        for (int x = 0; x < m_width; ++x) {
-            delete allNodes[y][x];
-        }
+  }
+
+  for (int y = 0; y < m_height; ++y) {
+    for (int x = 0; x < m_width; ++x) {
+      delete allNodes[y][x];
     }
-    
-    return path;
+  }
+
+  return path;
 }
 
-int Pathfinding::calculateHeuristic(const Point& a, const Point& b) const {
-    // Manhattan distance
-    return std::abs(a.x - b.x) + std::abs(a.y - b.y);
+int Pathfinding::calculateHeuristic(const Point &a, const Point &b) const {
+
+  return std::abs(a.x - b.x) + std::abs(a.y - b.y);
 }
 
-std::vector<Point> Pathfinding::getNeighbors(const Point& point) const {
-    std::vector<Point> neighbors;
-    
-    // 8-directional movement
-    for (int dx = -1; dx <= 1; ++dx) {
-        for (int dy = -1; dy <= 1; ++dy) {
-            if (dx == 0 && dy == 0) continue;
-            
-            int x = point.x + dx;
-            int y = point.y + dy;
-            
-            if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
-                neighbors.emplace_back(x, y);
-            }
-        }
+std::vector<Point> Pathfinding::getNeighbors(const Point &point) const {
+  std::vector<Point> neighbors;
+
+  for (int dx = -1; dx <= 1; ++dx) {
+    for (int dy = -1; dy <= 1; ++dy) {
+      if (dx == 0 && dy == 0)
+        continue;
+
+      int x = point.x + dx;
+      int y = point.y + dy;
+
+      if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
+        neighbors.emplace_back(x, y);
+      }
     }
-    
-    return neighbors;
+  }
+
+  return neighbors;
 }
 
-std::vector<Point> Pathfinding::reconstructPath(Node* endNode) const {
-    std::vector<Point> path;
-    Node* current = endNode;
-    
-    while (current) {
-        path.push_back(current->position);
-        current = current->parent;
-    }
-    
-    std::reverse(path.begin(), path.end());
-    return path;
+std::vector<Point> Pathfinding::reconstructPath(Node *endNode) const {
+  std::vector<Point> path;
+  Node *current = endNode;
+
+  while (current) {
+    path.push_back(current->position);
+    current = current->parent;
+  }
+
+  std::reverse(path.begin(), path.end());
+  return path;
 }
 
 } // namespace Game::Systems

+ 27 - 25
game/systems/pathfinding.h

@@ -1,43 +1,45 @@
 #pragma once
 
-#include <vector>
 #include <queue>
+#include <vector>
 
 namespace Game::Systems {
 
 struct Point {
-    int x, y;
-    Point(int x = 0, int y = 0) : x(x), y(y) {}
-    bool operator==(const Point& other) const { return x == other.x && y == other.y; }
+  int x, y;
+  Point(int x = 0, int y = 0) : x(x), y(y) {}
+  bool operator==(const Point &other) const {
+    return x == other.x && y == other.y;
+  }
 };
 
 struct Node {
-    Point position;
-    int gCost, hCost;
-    Node* parent;
-    
-    Node(const Point& pos, int g = 0, int h = 0, Node* p = nullptr)
-        : position(pos), gCost(g), hCost(h), parent(p) {}
-    
-    int getFCost() const { return gCost + hCost; }
+  Point position;
+  int gCost, hCost;
+  Node *parent;
+
+  Node(const Point &pos, int g = 0, int h = 0, Node *p = nullptr)
+      : position(pos), gCost(g), hCost(h), parent(p) {}
+
+  int getFCost() const { return gCost + hCost; }
 };
 
 class Pathfinding {
 public:
-    Pathfinding(int width, int height);
-    
-    void setObstacle(int x, int y, bool isObstacle);
-    bool isWalkable(int x, int y) const;
-    
-    std::vector<Point> findPath(const Point& start, const Point& end);
-    
+  Pathfinding(int width, int height);
+
+  void setObstacle(int x, int y, bool isObstacle);
+  bool isWalkable(int x, int y) const;
+
+  std::vector<Point> findPath(const Point &start, const Point &end);
+
 private:
-    int m_width, m_height;
-    std::vector<std::vector<bool>> m_obstacles;
-    
-    int calculateHeuristic(const Point& a, const Point& b) const;
-    std::vector<Point> getNeighbors(const Point& point) const;
-    std::vector<Point> reconstructPath(Node* endNode) const;
+  int m_width, m_height;
+  std::vector<std::vector<bool>> m_obstacles;
+
+  int calculateHeuristic(const Point &a, const Point &b) const;
+  std::vector<Point> getNeighbors(const Point &point) const;
+  std::vector<Point> reconstructPath(Node *endNode) const;
 };
 
 } // namespace Game::Systems

+ 85 - 84
game/systems/patrol_system.cpp

@@ -1,97 +1,98 @@
 #include "patrol_system.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include <QVector3D>
 #include <cmath>
 
 namespace Game::Systems {
 
-void PatrolSystem::update(Engine::Core::World* world, float deltaTime) {
-    if (!world) return;
-    
-    auto entities = world->getEntitiesWith<Engine::Core::PatrolComponent>();
-    
-    for (auto* entity : entities) {
-        auto* patrol = entity->getComponent<Engine::Core::PatrolComponent>();
-        auto* movement = entity->getComponent<Engine::Core::MovementComponent>();
-        auto* transform = entity->getComponent<Engine::Core::TransformComponent>();
-        auto* unit = entity->getComponent<Engine::Core::UnitComponent>();
-        
-        if (!patrol || !movement || !transform || !unit) continue;
-        if (!patrol->patrolling || patrol->waypoints.size() < 2) continue;
-        
-        // Stop patrolling if unit is dead
-        if (unit->health <= 0) {
-            patrol->patrolling = false;
-            continue;
-        }
-        
-        // Check if unit is under attack or attacking - pause patrol
-        auto* attackTarget = entity->getComponent<Engine::Core::AttackTargetComponent>();
-        if (attackTarget && attackTarget->targetId != 0) {
-            // Unit is attacking something, pause patrol
-            continue;
-        }
-        
-        // Check if enemies are nearby - engage them
-        bool enemyNearby = false;
-        auto allEntities = world->getEntitiesWith<Engine::Core::UnitComponent>();
-        for (auto* other : allEntities) {
-            auto* otherUnit = other->getComponent<Engine::Core::UnitComponent>();
-            auto* otherTransform = other->getComponent<Engine::Core::TransformComponent>();
-            
-            if (!otherUnit || !otherTransform || otherUnit->health <= 0) continue;
-            if (otherUnit->ownerId == unit->ownerId) continue; // Skip allies
-            
-            // Calculate distance
-            float dx = otherTransform->position.x - transform->position.x;
-            float dz = otherTransform->position.z - transform->position.z;
-            float distSq = dx * dx + dz * dz;
-            
-            // If enemy within 5 units, engage
-            if (distSq < 25.0f) {
-                enemyNearby = true;
-                // Set attack target
-                if (!attackTarget) {
-                    entity->addComponent<Engine::Core::AttackTargetComponent>();
-                    attackTarget = entity->getComponent<Engine::Core::AttackTargetComponent>();
-                }
-                if (attackTarget) {
-                    attackTarget->targetId = other->getId();
-                    attackTarget->shouldChase = false; // Don't chase far, return to patrol
-                }
-                break;
-            }
-        }
-        
-        if (enemyNearby) {
-            // Pause patrol to engage enemy
-            continue;
+void PatrolSystem::update(Engine::Core::World *world, float deltaTime) {
+  if (!world)
+    return;
+
+  auto entities = world->getEntitiesWith<Engine::Core::PatrolComponent>();
+
+  for (auto *entity : entities) {
+    auto *patrol = entity->getComponent<Engine::Core::PatrolComponent>();
+    auto *movement = entity->getComponent<Engine::Core::MovementComponent>();
+    auto *transform = entity->getComponent<Engine::Core::TransformComponent>();
+    auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
+
+    if (!patrol || !movement || !transform || !unit)
+      continue;
+    if (!patrol->patrolling || patrol->waypoints.size() < 2)
+      continue;
+
+    if (unit->health <= 0) {
+      patrol->patrolling = false;
+      continue;
+    }
+
+    auto *attackTarget =
+        entity->getComponent<Engine::Core::AttackTargetComponent>();
+    if (attackTarget && attackTarget->targetId != 0) {
+
+      continue;
+    }
+
+    bool enemyNearby = false;
+    auto allEntities = world->getEntitiesWith<Engine::Core::UnitComponent>();
+    for (auto *other : allEntities) {
+      auto *otherUnit = other->getComponent<Engine::Core::UnitComponent>();
+      auto *otherTransform =
+          other->getComponent<Engine::Core::TransformComponent>();
+
+      if (!otherUnit || !otherTransform || otherUnit->health <= 0)
+        continue;
+      if (otherUnit->ownerId == unit->ownerId)
+        continue;
+
+      float dx = otherTransform->position.x - transform->position.x;
+      float dz = otherTransform->position.z - transform->position.z;
+      float distSq = dx * dx + dz * dz;
+
+      if (distSq < 25.0f) {
+        enemyNearby = true;
+
+        if (!attackTarget) {
+          entity->addComponent<Engine::Core::AttackTargetComponent>();
+          attackTarget =
+              entity->getComponent<Engine::Core::AttackTargetComponent>();
         }
-        
-        // Continue patrol - move to current waypoint
-        auto waypoint = patrol->waypoints[patrol->currentWaypoint];
-        float targetX = waypoint.first;
-        float targetZ = waypoint.second;
-        
-        // Check if reached current waypoint
-        float dx = targetX - transform->position.x;
-        float dz = targetZ - transform->position.z;
-        float distSq = dx * dx + dz * dz;
-        
-        if (distSq < 1.0f) { // Reached waypoint
-            // Move to next waypoint
-            patrol->currentWaypoint = (patrol->currentWaypoint + 1) % patrol->waypoints.size();
-            waypoint = patrol->waypoints[patrol->currentWaypoint];
-            targetX = waypoint.first;
-            targetZ = waypoint.second;
+        if (attackTarget) {
+          attackTarget->targetId = other->getId();
+          attackTarget->shouldChase = false;
         }
-        
-        // Set movement target to current waypoint
-        movement->hasTarget = true;
-        movement->targetX = targetX;
-        movement->targetY = targetZ;
+        break;
+      }
+    }
+
+    if (enemyNearby) {
+
+      continue;
     }
+
+    auto waypoint = patrol->waypoints[patrol->currentWaypoint];
+    float targetX = waypoint.first;
+    float targetZ = waypoint.second;
+
+    float dx = targetX - transform->position.x;
+    float dz = targetZ - transform->position.z;
+    float distSq = dx * dx + dz * dz;
+
+    if (distSq < 1.0f) {
+
+      patrol->currentWaypoint =
+          (patrol->currentWaypoint + 1) % patrol->waypoints.size();
+      waypoint = patrol->waypoints[patrol->currentWaypoint];
+      targetX = waypoint.first;
+      targetZ = waypoint.second;
+    }
+
+    movement->hasTarget = true;
+    movement->targetX = targetX;
+    movement->targetY = targetZ;
+  }
 }
 
 } // namespace Game::Systems

+ 4 - 4
game/systems/patrol_system.h

@@ -6,10 +6,10 @@ namespace Game::Systems {
 
 class PatrolSystem : public Engine::Core::System {
 public:
-    PatrolSystem() = default;
-    ~PatrolSystem() override = default;
-    
-    void update(Engine::Core::World* world, float deltaTime) override;
+  PatrolSystem() = default;
+  ~PatrolSystem() override = default;
+
+  void update(Engine::Core::World *world, float deltaTime) override;
 };
 
 } // namespace Game::Systems

+ 289 - 208
game/systems/picking_service.cpp

@@ -1,241 +1,322 @@
 #include "picking_service.h"
 #include "../../render/gl/camera.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include <algorithm>
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
-bool PickingService::worldToScreen(const Render::GL::Camera& cam, int viewW, int viewH, const QVector3D& world, QPointF& out) const {
-    return cam.worldToScreen(world, viewW, viewH, out);
+bool PickingService::worldToScreen(const Render::GL::Camera &cam, int viewW,
+                                   int viewH, const QVector3D &world,
+                                   QPointF &out) const {
+  return cam.worldToScreen(world, viewW, viewH, out);
 }
 
-bool PickingService::screenToGround(const Render::GL::Camera& cam, int viewW, int viewH, const QPointF& screenPt, QVector3D& outWorld) const {
-    if (viewW <= 0 || viewH <= 0) return false;
-    return cam.screenToGround(float(screenPt.x()), float(screenPt.y()), float(viewW), float(viewH), outWorld);
+bool PickingService::screenToGround(const Render::GL::Camera &cam, int viewW,
+                                    int viewH, const QPointF &screenPt,
+                                    QVector3D &outWorld) const {
+  if (viewW <= 0 || viewH <= 0)
+    return false;
+  return cam.screenToGround(float(screenPt.x()), float(screenPt.y()),
+                            float(viewW), float(viewH), outWorld);
 }
 
-bool PickingService::projectBounds(const Render::GL::Camera& cam, const QVector3D& center, float hx, float hz, int viewW, int viewH, QRectF& out) const {
-    QVector3D corners[4] = {
-        QVector3D(center.x() - hx, center.y(), center.z() - hz),
-        QVector3D(center.x() + hx, center.y(), center.z() - hz),
-        QVector3D(center.x() + hx, center.y(), center.z() + hz),
-        QVector3D(center.x() - hx, center.y(), center.z() + hz)
-    };
-    QPointF screenPts[4];
-    for (int i = 0; i < 4; ++i) {
-        if (!worldToScreen(cam, viewW, viewH, corners[i], screenPts[i])) return false;
-    }
-    float minX = screenPts[0].x(), maxX = screenPts[0].x();
-    float minY = screenPts[0].y(), maxY = screenPts[0].y();
-    for (int i = 1; i < 4; ++i) {
-        minX = std::min(minX, float(screenPts[i].x()));
-        maxX = std::max(maxX, float(screenPts[i].x()));
-        minY = std::min(minY, float(screenPts[i].y()));
-        maxY = std::max(maxY, float(screenPts[i].y()));
-    }
-    out = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
-    return true;
+bool PickingService::projectBounds(const Render::GL::Camera &cam,
+                                   const QVector3D &center, float hx, float hz,
+                                   int viewW, int viewH, QRectF &out) const {
+  QVector3D corners[4] = {
+      QVector3D(center.x() - hx, center.y(), center.z() - hz),
+      QVector3D(center.x() + hx, center.y(), center.z() - hz),
+      QVector3D(center.x() + hx, center.y(), center.z() + hz),
+      QVector3D(center.x() - hx, center.y(), center.z() + hz)};
+  QPointF screenPts[4];
+  for (int i = 0; i < 4; ++i) {
+    if (!worldToScreen(cam, viewW, viewH, corners[i], screenPts[i]))
+      return false;
+  }
+  float minX = screenPts[0].x(), maxX = screenPts[0].x();
+  float minY = screenPts[0].y(), maxY = screenPts[0].y();
+  for (int i = 1; i < 4; ++i) {
+    minX = std::min(minX, float(screenPts[i].x()));
+    maxX = std::max(maxX, float(screenPts[i].x()));
+    minY = std::min(minY, float(screenPts[i].y()));
+    maxY = std::max(maxY, float(screenPts[i].y()));
+  }
+  out = QRectF(QPointF(minX, minY), QPointF(maxX, maxY));
+  return true;
 }
 
-Engine::Core::EntityID PickingService::updateHover(float sx, float sy,
-                                                  Engine::Core::World& world,
-                                                  const Render::GL::Camera& camera,
-                                                  int viewW, int viewH) {
-    if (sx < 0 || sy < 0) {
-        m_prevHoverId = 0;
-        return 0;
-    }
-    auto prevHover = m_prevHoverId;
-    Engine::Core::EntityID currentHover = 0;
-
-    // Hysteresis check: keep previous hover if within expanded bounds
-    if (prevHover) {
-        if (auto* e = world.getEntity(prevHover)) {
-            if (e->hasComponent<Engine::Core::BuildingComponent>()) {
-                // Only allow hover for player's own buildings (ownerId == 1)
-                auto* u = e->getComponent<Engine::Core::UnitComponent>();
-                if (u && u->ownerId == 1) {
-                    if (auto* t = e->getComponent<Engine::Core::TransformComponent>()) {
-                        float pxPad = 12.0f;
-                        if (auto* prod = e->getComponent<Engine::Core::ProductionComponent>()) {
-                            if (prod->inProgress) pxPad = 24.0f;
-                        }
-                        const float marginXZ_keep = 1.10f;
-                        const float pad_keep  = 1.12f;
-                        float hxk = std::max(0.4f, t->scale.x * marginXZ_keep * pad_keep);
-                        float hzk = std::max(0.4f, t->scale.z * marginXZ_keep * pad_keep);
-                        QRectF bounds;
-                        if (projectBounds(camera, QVector3D(t->position.x, t->position.y, t->position.z), hxk, hzk, viewW, viewH, bounds)) {
-                            bounds.adjust(-pxPad, -pxPad, pxPad, pxPad);
-                            if (bounds.contains(QPointF(sx, sy))) {
-                                currentHover = prevHover;
-                                m_hoverGraceTicks = 6;
-                                m_prevHoverId = currentHover;
-                                return currentHover;
-                            }
-                        }
-                    }
-                }
+Engine::Core::EntityID
+PickingService::updateHover(float sx, float sy, Engine::Core::World &world,
+                            const Render::GL::Camera &camera, int viewW,
+                            int viewH) {
+  if (sx < 0 || sy < 0) {
+    m_prevHoverId = 0;
+    return 0;
+  }
+  auto prevHover = m_prevHoverId;
+  Engine::Core::EntityID currentHover = 0;
+
+  if (prevHover) {
+    if (auto *e = world.getEntity(prevHover)) {
+      if (e->hasComponent<Engine::Core::BuildingComponent>()) {
+
+        auto *u = e->getComponent<Engine::Core::UnitComponent>();
+        if (u && u->ownerId == 1) {
+          if (auto *t = e->getComponent<Engine::Core::TransformComponent>()) {
+            float pxPad = 12.0f;
+            if (auto *prod =
+                    e->getComponent<Engine::Core::ProductionComponent>()) {
+              if (prod->inProgress)
+                pxPad = 24.0f;
+            }
+            const float marginXZ_keep = 1.10f;
+            const float pad_keep = 1.12f;
+            float hxk = std::max(0.4f, t->scale.x * marginXZ_keep * pad_keep);
+            float hzk = std::max(0.4f, t->scale.z * marginXZ_keep * pad_keep);
+            QRectF bounds;
+            if (projectBounds(
+                    camera,
+                    QVector3D(t->position.x, t->position.y, t->position.z), hxk,
+                    hzk, viewW, viewH, bounds)) {
+              bounds.adjust(-pxPad, -pxPad, pxPad, pxPad);
+              if (bounds.contains(QPointF(sx, sy))) {
+                currentHover = prevHover;
+                m_hoverGraceTicks = 6;
+                m_prevHoverId = currentHover;
+                return currentHover;
+              }
             }
+          }
         }
+      }
     }
+  }
 
-    float bestD2 = std::numeric_limits<float>::max();
-    auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
-    for (auto* e : ents) {
-        if (!e->hasComponent<Engine::Core::UnitComponent>()) continue;
-        if (!e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        
-        // Only allow hover for player's own buildings (ownerId == 1)
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != 1) continue;
-        
-        auto* t = e->getComponent<Engine::Core::TransformComponent>();
-        const float marginXZ = 1.10f;
-        const float hoverPad  = 1.06f;
-        float hx = std::max(0.4f, t->scale.x * marginXZ * hoverPad);
-        float hz = std::max(0.4f, t->scale.z * marginXZ * hoverPad);
-        QRectF bounds;
-        if (!projectBounds(camera, QVector3D(t->position.x, t->position.y, t->position.z), hx, hz, viewW, viewH, bounds)) continue;
-        if (!bounds.contains(QPointF(sx, sy))) continue;
-        QPointF centerSp;
-    if (!worldToScreen(camera, viewW, viewH, QVector3D(t->position.x, t->position.y, t->position.z), centerSp)) centerSp = bounds.center();
-        float dx = float(sx) - float(centerSp.x());
-        float dy = float(sy) - float(centerSp.y());
-        float d2 = dx*dx + dy*dy;
-        if (d2 < bestD2) { bestD2 = d2; currentHover = e->getId(); }
-    }
+  float bestD2 = std::numeric_limits<float>::max();
+  auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
+  for (auto *e : ents) {
+    if (!e->hasComponent<Engine::Core::UnitComponent>())
+      continue;
+    if (!e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != 1)
+      continue;
 
-    if (currentHover != 0 && currentHover != prevHover) {
-        m_hoverGraceTicks = 6;
+    auto *t = e->getComponent<Engine::Core::TransformComponent>();
+    const float marginXZ = 1.10f;
+    const float hoverPad = 1.06f;
+    float hx = std::max(0.4f, t->scale.x * marginXZ * hoverPad);
+    float hz = std::max(0.4f, t->scale.z * marginXZ * hoverPad);
+    QRectF bounds;
+    if (!projectBounds(camera,
+                       QVector3D(t->position.x, t->position.y, t->position.z),
+                       hx, hz, viewW, viewH, bounds))
+      continue;
+    if (!bounds.contains(QPointF(sx, sy)))
+      continue;
+    QPointF centerSp;
+    if (!worldToScreen(camera, viewW, viewH,
+                       QVector3D(t->position.x, t->position.y, t->position.z),
+                       centerSp))
+      centerSp = bounds.center();
+    float dx = float(sx) - float(centerSp.x());
+    float dy = float(sy) - float(centerSp.y());
+    float d2 = dx * dx + dy * dy;
+    if (d2 < bestD2) {
+      bestD2 = d2;
+      currentHover = e->getId();
     }
+  }
 
-    if (currentHover == 0 && prevHover != 0) {
-        if (auto* e = world.getEntity(prevHover)) {
-            auto* t = e->getComponent<Engine::Core::TransformComponent>();
-            auto* u = e->getComponent<Engine::Core::UnitComponent>();
-            if (t && e->getComponent<Engine::Core::BuildingComponent>() && u && u->ownerId == 1) {
-                const float marginXZ = 1.12f;
-                const float keepPad  = (e->getComponent<Engine::Core::ProductionComponent>() && e->getComponent<Engine::Core::ProductionComponent>()->inProgress) ? 1.16f : 1.12f;
-                float hx = std::max(0.4f, t->scale.x * marginXZ * keepPad);
-                float hz = std::max(0.4f, t->scale.z * marginXZ * keepPad);
-                QRectF bounds;
-                if (projectBounds(camera, QVector3D(t->position.x, t->position.y, t->position.z), hx, hz, viewW, viewH, bounds)) {
-                    if (bounds.contains(QPointF(sx, sy))) {
-                        currentHover = prevHover;
-                    }
-                }
-            }
+  if (currentHover != 0 && currentHover != prevHover) {
+    m_hoverGraceTicks = 6;
+  }
+
+  if (currentHover == 0 && prevHover != 0) {
+    if (auto *e = world.getEntity(prevHover)) {
+      auto *t = e->getComponent<Engine::Core::TransformComponent>();
+      auto *u = e->getComponent<Engine::Core::UnitComponent>();
+      if (t && e->getComponent<Engine::Core::BuildingComponent>() && u &&
+          u->ownerId == 1) {
+        const float marginXZ = 1.12f;
+        const float keepPad =
+            (e->getComponent<Engine::Core::ProductionComponent>() &&
+             e->getComponent<Engine::Core::ProductionComponent>()->inProgress)
+                ? 1.16f
+                : 1.12f;
+        float hx = std::max(0.4f, t->scale.x * marginXZ * keepPad);
+        float hz = std::max(0.4f, t->scale.z * marginXZ * keepPad);
+        QRectF bounds;
+        if (projectBounds(
+                camera, QVector3D(t->position.x, t->position.y, t->position.z),
+                hx, hz, viewW, viewH, bounds)) {
+          if (bounds.contains(QPointF(sx, sy))) {
+            currentHover = prevHover;
+          }
         }
+      }
     }
+  }
 
-    if (currentHover == 0 && prevHover != 0 && m_hoverGraceTicks > 0) {
-        currentHover = prevHover;
-    }
+  if (currentHover == 0 && prevHover != 0 && m_hoverGraceTicks > 0) {
+    currentHover = prevHover;
+  }
 
-    if (m_hoverGraceTicks > 0) --m_hoverGraceTicks;
-    m_prevHoverId = currentHover;
-    return currentHover;
+  if (m_hoverGraceTicks > 0)
+    --m_hoverGraceTicks;
+  m_prevHoverId = currentHover;
+  return currentHover;
 }
 
-Engine::Core::EntityID PickingService::pickSingle(float sx, float sy,
-                                      Engine::Core::World& world,
-                                      const Render::GL::Camera& camera,
-                                      int viewW, int viewH,
-                                      int ownerFilter,
-                                      bool preferBuildingsFirst) const {
-    const float baseUnitPickRadius = 18.0f;
-    const float baseBuildingPickRadius = 28.0f;
-    float bestUnitDist2 = std::numeric_limits<float>::max();
-    float bestBuildingDist2 = std::numeric_limits<float>::max();
-    Engine::Core::EntityID bestUnitId = 0;
-    Engine::Core::EntityID bestBuildingId = 0;
-    auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
-    for (auto* e : ents) {
-        if (!e->hasComponent<Engine::Core::UnitComponent>()) continue;
-        auto* t = e->getComponent<Engine::Core::TransformComponent>();
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        
-        // Owner filter: 0 means no filter (pick any), otherwise pick only matching owner
-        if (ownerFilter != 0 && u->ownerId != ownerFilter) continue;
-        
-        QPointF sp;
-        if (!camera.worldToScreen(QVector3D(t->position.x, t->position.y, t->position.z), viewW, viewH, sp)) continue;
-        float dx = float(sx) - float(sp.x());
-        float dy = float(sy) - float(sp.y());
-        float d2 = dx*dx + dy*dy;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) {
-            bool hit = false; float pickDist2 = d2;
-            const float marginXZ = 1.6f; const float marginY = 1.2f;
-            float hx = std::max(0.6f, t->scale.x * marginXZ);
-            float hz = std::max(0.6f, t->scale.z * marginXZ);
-            float hy = std::max(0.5f, t->scale.y * marginY);
-            QPointF pts[8]; int okCount=0;
-            auto project = [&](const QVector3D& w, QPointF& out){ return camera.worldToScreen(w, viewW, viewH, out); };
-            okCount += project(QVector3D(t->position.x - hx, t->position.y + 0.0f, t->position.z - hz), pts[0]);
-            okCount += project(QVector3D(t->position.x + hx, t->position.y + 0.0f, t->position.z - hz), pts[1]);
-            okCount += project(QVector3D(t->position.x + hx, t->position.y + 0.0f, t->position.z + hz), pts[2]);
-            okCount += project(QVector3D(t->position.x - hx, t->position.y + 0.0f, t->position.z + hz), pts[3]);
-            okCount += project(QVector3D(t->position.x - hx, t->position.y + hy,   t->position.z - hz), pts[4]);
-            okCount += project(QVector3D(t->position.x + hx, t->position.y + hy,   t->position.z - hz), pts[5]);
-            okCount += project(QVector3D(t->position.x + hx, t->position.y + hy,   t->position.z + hz), pts[6]);
-            okCount += project(QVector3D(t->position.x - hx, t->position.y + hy,   t->position.z + hz), pts[7]);
-            if (okCount == 8) {
-                float minX = pts[0].x(), maxX = pts[0].x();
-                float minY = pts[0].y(), maxY = pts[0].y();
-                for (int i = 1; i < 8; ++i) {
-                    minX = std::min(minX, float(pts[i].x()));
-                    maxX = std::max(maxX, float(pts[i].x()));
-                    minY = std::min(minY, float(pts[i].y()));
-                    maxY = std::max(maxY, float(pts[i].y()));
-                }
-                if (float(sx) >= minX && float(sx) <= maxX && float(sy) >= minY && float(sy) <= maxY) {
-                    hit = true; pickDist2 = d2;
-                }
-            }
-            if (!hit) {
-                float scaleXZ = std::max(std::max(t->scale.x, t->scale.z), 1.0f);
-                float rp = baseBuildingPickRadius * scaleXZ; float r2 = rp*rp;
-                if (d2 <= r2) hit = true;
-            }
-            if (hit && pickDist2 < bestBuildingDist2) { bestBuildingDist2 = pickDist2; bestBuildingId = e->getId(); }
-        } else {
-            float r2 = baseUnitPickRadius * baseUnitPickRadius;
-            if (d2 <= r2 && d2 < bestUnitDist2) { bestUnitDist2 = d2; bestUnitId = e->getId(); }
+Engine::Core::EntityID
+PickingService::pickSingle(float sx, float sy, Engine::Core::World &world,
+                           const Render::GL::Camera &camera, int viewW,
+                           int viewH, int ownerFilter,
+                           bool preferBuildingsFirst) const {
+  const float baseUnitPickRadius = 18.0f;
+  const float baseBuildingPickRadius = 28.0f;
+  float bestUnitDist2 = std::numeric_limits<float>::max();
+  float bestBuildingDist2 = std::numeric_limits<float>::max();
+  Engine::Core::EntityID bestUnitId = 0;
+  Engine::Core::EntityID bestBuildingId = 0;
+  auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
+  for (auto *e : ents) {
+    if (!e->hasComponent<Engine::Core::UnitComponent>())
+      continue;
+    auto *t = e->getComponent<Engine::Core::TransformComponent>();
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+
+    if (ownerFilter != 0 && u->ownerId != ownerFilter)
+      continue;
+
+    QPointF sp;
+    if (!camera.worldToScreen(
+            QVector3D(t->position.x, t->position.y, t->position.z), viewW,
+            viewH, sp))
+      continue;
+    float dx = float(sx) - float(sp.x());
+    float dy = float(sy) - float(sp.y());
+    float d2 = dx * dx + dy * dy;
+    if (e->hasComponent<Engine::Core::BuildingComponent>()) {
+      bool hit = false;
+      float pickDist2 = d2;
+      const float marginXZ = 1.6f;
+      const float marginY = 1.2f;
+      float hx = std::max(0.6f, t->scale.x * marginXZ);
+      float hz = std::max(0.6f, t->scale.z * marginXZ);
+      float hy = std::max(0.5f, t->scale.y * marginY);
+      QPointF pts[8];
+      int okCount = 0;
+      auto project = [&](const QVector3D &w, QPointF &out) {
+        return camera.worldToScreen(w, viewW, viewH, out);
+      };
+      okCount += project(QVector3D(t->position.x - hx, t->position.y + 0.0f,
+                                   t->position.z - hz),
+                         pts[0]);
+      okCount += project(QVector3D(t->position.x + hx, t->position.y + 0.0f,
+                                   t->position.z - hz),
+                         pts[1]);
+      okCount += project(QVector3D(t->position.x + hx, t->position.y + 0.0f,
+                                   t->position.z + hz),
+                         pts[2]);
+      okCount += project(QVector3D(t->position.x - hx, t->position.y + 0.0f,
+                                   t->position.z + hz),
+                         pts[3]);
+      okCount += project(
+          QVector3D(t->position.x - hx, t->position.y + hy, t->position.z - hz),
+          pts[4]);
+      okCount += project(
+          QVector3D(t->position.x + hx, t->position.y + hy, t->position.z - hz),
+          pts[5]);
+      okCount += project(
+          QVector3D(t->position.x + hx, t->position.y + hy, t->position.z + hz),
+          pts[6]);
+      okCount += project(
+          QVector3D(t->position.x - hx, t->position.y + hy, t->position.z + hz),
+          pts[7]);
+      if (okCount == 8) {
+        float minX = pts[0].x(), maxX = pts[0].x();
+        float minY = pts[0].y(), maxY = pts[0].y();
+        for (int i = 1; i < 8; ++i) {
+          minX = std::min(minX, float(pts[i].x()));
+          maxX = std::max(maxX, float(pts[i].x()));
+          minY = std::min(minY, float(pts[i].y()));
+          maxY = std::max(maxY, float(pts[i].y()));
         }
-    }
-    if (preferBuildingsFirst) {
-        if (bestBuildingId && (!bestUnitId || bestBuildingDist2 <= bestUnitDist2)) return bestBuildingId;
-        if (bestUnitId) return bestUnitId;
+        if (float(sx) >= minX && float(sx) <= maxX && float(sy) >= minY &&
+            float(sy) <= maxY) {
+          hit = true;
+          pickDist2 = d2;
+        }
+      }
+      if (!hit) {
+        float scaleXZ = std::max(std::max(t->scale.x, t->scale.z), 1.0f);
+        float rp = baseBuildingPickRadius * scaleXZ;
+        float r2 = rp * rp;
+        if (d2 <= r2)
+          hit = true;
+      }
+      if (hit && pickDist2 < bestBuildingDist2) {
+        bestBuildingDist2 = pickDist2;
+        bestBuildingId = e->getId();
+      }
     } else {
-        if (bestUnitId) return bestUnitId;
-        if (bestBuildingId) return bestBuildingId;
+      float r2 = baseUnitPickRadius * baseUnitPickRadius;
+      if (d2 <= r2 && d2 < bestUnitDist2) {
+        bestUnitDist2 = d2;
+        bestUnitId = e->getId();
+      }
     }
-    return 0;
+  }
+  if (preferBuildingsFirst) {
+    if (bestBuildingId && (!bestUnitId || bestBuildingDist2 <= bestUnitDist2))
+      return bestBuildingId;
+    if (bestUnitId)
+      return bestUnitId;
+  } else {
+    if (bestUnitId)
+      return bestUnitId;
+    if (bestBuildingId)
+      return bestBuildingId;
+  }
+  return 0;
 }
 
-std::vector<Engine::Core::EntityID> PickingService::pickInRect(float x1, float y1, float x2, float y2,
-                                                   Engine::Core::World& world,
-                                                   const Render::GL::Camera& camera,
-                                                   int viewW, int viewH,
-                                                   int ownerFilter) const {
-    float minX = std::min(x1, x2); float maxX = std::max(x1, x2);
-    float minY = std::min(y1, y2); float maxY = std::max(y1, y2);
-    std::vector<Engine::Core::EntityID> picked;
-    auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
-    for (auto* e : ents) {
-        if (!e->hasComponent<Engine::Core::UnitComponent>()) continue;
-        if (e->hasComponent<Engine::Core::BuildingComponent>()) continue;
-        auto* u = e->getComponent<Engine::Core::UnitComponent>();
-        if (!u || u->ownerId != ownerFilter) continue;
-        auto* t = e->getComponent<Engine::Core::TransformComponent>();
-        QPointF sp; if (!camera.worldToScreen(QVector3D(t->position.x, t->position.y, t->position.z), viewW, viewH, sp)) continue;
-        if (sp.x() >= minX && sp.x() <= maxX && sp.y() >= minY && sp.y() <= maxY) {
-            picked.push_back(e->getId());
-        }
+std::vector<Engine::Core::EntityID>
+PickingService::pickInRect(float x1, float y1, float x2, float y2,
+                           Engine::Core::World &world,
+                           const Render::GL::Camera &camera, int viewW,
+                           int viewH, int ownerFilter) const {
+  float minX = std::min(x1, x2);
+  float maxX = std::max(x1, x2);
+  float minY = std::min(y1, y2);
+  float maxY = std::max(y1, y2);
+  std::vector<Engine::Core::EntityID> picked;
+  auto ents = world.getEntitiesWith<Engine::Core::TransformComponent>();
+  for (auto *e : ents) {
+    if (!e->hasComponent<Engine::Core::UnitComponent>())
+      continue;
+    if (e->hasComponent<Engine::Core::BuildingComponent>())
+      continue;
+    auto *u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!u || u->ownerId != ownerFilter)
+      continue;
+    auto *t = e->getComponent<Engine::Core::TransformComponent>();
+    QPointF sp;
+    if (!camera.worldToScreen(
+            QVector3D(t->position.x, t->position.y, t->position.z), viewW,
+            viewH, sp))
+      continue;
+    if (sp.x() >= minX && sp.x() <= maxX && sp.y() >= minY && sp.y() <= maxY) {
+      picked.push_back(e->getId());
     }
-    return picked;
+  }
+  return picked;
 }
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 40 - 36
game/systems/picking_service.h

@@ -6,50 +6,54 @@
 #include <limits>
 #include <vector>
 
-namespace Engine { namespace Core {
+namespace Engine {
+namespace Core {
 class World;
 using EntityID = unsigned int;
-} }
+} // namespace Core
+} // namespace Engine
 
-namespace Render { namespace GL { class Camera; } }
+namespace Render {
+namespace GL {
+class Camera;
+}
+} // namespace Render
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 class PickingService {
 public:
-    PickingService() = default;
-
-    // Update hover state given screen coordinates; returns current hovered building id (or 0).
-    Engine::Core::EntityID updateHover(float sx, float sy,
-                                       Engine::Core::World& world,
-                                       const Render::GL::Camera& camera,
-                                       int viewW, int viewH);
-
-    // Projection helpers bound to the active camera/viewport
-    bool screenToGround(const Render::GL::Camera& camera, int viewW, int viewH,
-                        const QPointF& screenPt, QVector3D& outWorld) const;
-    bool worldToScreen(const Render::GL::Camera& camera, int viewW, int viewH,
-                        const QVector3D& world, QPointF& outScreen) const;
-
-    // Single click pick (returns 0 if none). Prefer buildings first toggles tie-breaking.
-    Engine::Core::EntityID pickSingle(float sx, float sy,
-                                      Engine::Core::World& world,
-                                      const Render::GL::Camera& camera,
-                                      int viewW, int viewH,
-                                      int ownerFilter,
-                                      bool preferBuildingsFirst) const;
-
-    // Rectangle selection pick; returns friendlies inside screen rect (excludes buildings).
-    std::vector<Engine::Core::EntityID> pickInRect(float x1, float y1, float x2, float y2,
-                                                   Engine::Core::World& world,
-                                                   const Render::GL::Camera& camera,
-                                                   int viewW, int viewH,
-                                                   int ownerFilter) const;
+  PickingService() = default;
+
+  Engine::Core::EntityID updateHover(float sx, float sy,
+                                     Engine::Core::World &world,
+                                     const Render::GL::Camera &camera,
+                                     int viewW, int viewH);
+
+  bool screenToGround(const Render::GL::Camera &camera, int viewW, int viewH,
+                      const QPointF &screenPt, QVector3D &outWorld) const;
+  bool worldToScreen(const Render::GL::Camera &camera, int viewW, int viewH,
+                     const QVector3D &world, QPointF &outScreen) const;
+
+  Engine::Core::EntityID pickSingle(float sx, float sy,
+                                    Engine::Core::World &world,
+                                    const Render::GL::Camera &camera, int viewW,
+                                    int viewH, int ownerFilter,
+                                    bool preferBuildingsFirst) const;
+
+  std::vector<Engine::Core::EntityID>
+  pickInRect(float x1, float y1, float x2, float y2, Engine::Core::World &world,
+             const Render::GL::Camera &camera, int viewW, int viewH,
+             int ownerFilter) const;
 
 private:
-    Engine::Core::EntityID m_prevHoverId = 0;
-    int m_hoverGraceTicks = 0;
-    bool projectBounds(const Render::GL::Camera& cam, const QVector3D& center, float hx, float hz, int viewW, int viewH, QRectF& out) const;
+  Engine::Core::EntityID m_prevHoverId = 0;
+  int m_hoverGraceTicks = 0;
+  bool projectBounds(const Render::GL::Camera &cam, const QVector3D &center,
+                     float hx, float hz, int viewW, int viewH,
+                     QRectF &out) const;
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 72 - 53
game/systems/production_service.cpp

@@ -1,66 +1,85 @@
 #include "production_service.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
-static Engine::Core::Entity* findFirstSelectedBarracks(Engine::Core::World& world,
-                                                       const std::vector<Engine::Core::EntityID>& selected,
-                                                       int ownerId) {
-    for (auto id : selected) {
-        if (auto* e = world.getEntity(id)) {
-            auto* u = e->getComponent<Engine::Core::UnitComponent>();
-            if (!u || u->ownerId != ownerId) continue;
-            if (u->unitType == "barracks") return e;
-        }
+static Engine::Core::Entity *
+findFirstSelectedBarracks(Engine::Core::World &world,
+                          const std::vector<Engine::Core::EntityID> &selected,
+                          int ownerId) {
+  for (auto id : selected) {
+    if (auto *e = world.getEntity(id)) {
+      auto *u = e->getComponent<Engine::Core::UnitComponent>();
+      if (!u || u->ownerId != ownerId)
+        continue;
+      if (u->unitType == "barracks")
+        return e;
     }
-    return nullptr;
+  }
+  return nullptr;
 }
 
-bool ProductionService::startProductionForFirstSelectedBarracks(Engine::Core::World& world,
-                                                                const std::vector<Engine::Core::EntityID>& selected,
-                                                                int ownerId,
-                                                                const std::string& unitType) {
-    auto* e = findFirstSelectedBarracks(world, selected, ownerId);
-    if (!e) return false;
-    auto* p = e->getComponent<Engine::Core::ProductionComponent>();
-    if (!p) p = e->addComponent<Engine::Core::ProductionComponent>();
-    if (!p) return false;
-    if (p->producedCount >= p->maxUnits) return false;
-    if (p->inProgress) return false;
-    p->productType = unitType;
-    p->timeRemaining = p->buildTime;
-    p->inProgress = true;
-    return true;
+bool ProductionService::startProductionForFirstSelectedBarracks(
+    Engine::Core::World &world,
+    const std::vector<Engine::Core::EntityID> &selected, int ownerId,
+    const std::string &unitType) {
+  auto *e = findFirstSelectedBarracks(world, selected, ownerId);
+  if (!e)
+    return false;
+  auto *p = e->getComponent<Engine::Core::ProductionComponent>();
+  if (!p)
+    p = e->addComponent<Engine::Core::ProductionComponent>();
+  if (!p)
+    return false;
+  if (p->producedCount >= p->maxUnits)
+    return false;
+  if (p->inProgress)
+    return false;
+  p->productType = unitType;
+  p->timeRemaining = p->buildTime;
+  p->inProgress = true;
+  return true;
 }
 
-bool ProductionService::setRallyForFirstSelectedBarracks(Engine::Core::World& world,
-                                                         const std::vector<Engine::Core::EntityID>& selected,
-                                                         int ownerId,
-                                                         float x, float z) {
-    auto* e = findFirstSelectedBarracks(world, selected, ownerId);
-    if (!e) return false;
-    auto* p = e->getComponent<Engine::Core::ProductionComponent>();
-    if (!p) p = e->addComponent<Engine::Core::ProductionComponent>();
-    if (!p) return false;
-    p->rallyX = x; p->rallyZ = z; p->rallySet = true; return true;
+bool ProductionService::setRallyForFirstSelectedBarracks(
+    Engine::Core::World &world,
+    const std::vector<Engine::Core::EntityID> &selected, int ownerId, float x,
+    float z) {
+  auto *e = findFirstSelectedBarracks(world, selected, ownerId);
+  if (!e)
+    return false;
+  auto *p = e->getComponent<Engine::Core::ProductionComponent>();
+  if (!p)
+    p = e->addComponent<Engine::Core::ProductionComponent>();
+  if (!p)
+    return false;
+  p->rallyX = x;
+  p->rallyZ = z;
+  p->rallySet = true;
+  return true;
 }
 
-bool ProductionService::getSelectedBarracksState(Engine::Core::World& world,
-                                                 const std::vector<Engine::Core::EntityID>& selected,
-                                                 int ownerId,
-                                                 ProductionState& outState) {
-    auto* e = findFirstSelectedBarracks(world, selected, ownerId);
-    if (!e) { outState = {}; return false; }
-    outState.hasBarracks = true;
-    if (auto* p = e->getComponent<Engine::Core::ProductionComponent>()) {
-        outState.inProgress = p->inProgress;
-        outState.timeRemaining = p->timeRemaining;
-        outState.buildTime = p->buildTime;
-        outState.producedCount = p->producedCount;
-        outState.maxUnits = p->maxUnits;
-    }
-    return true;
+bool ProductionService::getSelectedBarracksState(
+    Engine::Core::World &world,
+    const std::vector<Engine::Core::EntityID> &selected, int ownerId,
+    ProductionState &outState) {
+  auto *e = findFirstSelectedBarracks(world, selected, ownerId);
+  if (!e) {
+    outState = {};
+    return false;
+  }
+  outState.hasBarracks = true;
+  if (auto *p = e->getComponent<Engine::Core::ProductionComponent>()) {
+    outState.inProgress = p->inProgress;
+    outState.timeRemaining = p->timeRemaining;
+    outState.buildTime = p->buildTime;
+    outState.producedCount = p->producedCount;
+    outState.maxUnits = p->maxUnits;
+  }
+  return true;
 }
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 28 - 24
game/systems/production_service.h

@@ -3,38 +3,42 @@
 #include <string>
 #include <vector>
 
-namespace Engine { namespace Core {
+namespace Engine {
+namespace Core {
 class World;
 using EntityID = unsigned int;
-} }
+} // namespace Core
+} // namespace Engine
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 struct ProductionState {
-    bool hasBarracks = false;
-    bool inProgress = false;
-    float timeRemaining = 0.0f;
-    float buildTime = 0.0f;
-    int producedCount = 0;
-    int maxUnits = 0;
+  bool hasBarracks = false;
+  bool inProgress = false;
+  float timeRemaining = 0.0f;
+  float buildTime = 0.0f;
+  int producedCount = 0;
+  int maxUnits = 0;
 };
 
 class ProductionService {
 public:
-    static bool startProductionForFirstSelectedBarracks(Engine::Core::World& world,
-                                                       const std::vector<Engine::Core::EntityID>& selected,
-                                                       int ownerId,
-                                                       const std::string& unitType);
-
-    static bool setRallyForFirstSelectedBarracks(Engine::Core::World& world,
-                                                 const std::vector<Engine::Core::EntityID>& selected,
-                                                 int ownerId,
-                                                 float x, float z);
-
-    static bool getSelectedBarracksState(Engine::Core::World& world,
-                                         const std::vector<Engine::Core::EntityID>& selected,
-                                         int ownerId,
-                                         ProductionState& outState);
+  static bool startProductionForFirstSelectedBarracks(
+      Engine::Core::World &world,
+      const std::vector<Engine::Core::EntityID> &selected, int ownerId,
+      const std::string &unitType);
+
+  static bool setRallyForFirstSelectedBarracks(
+      Engine::Core::World &world,
+      const std::vector<Engine::Core::EntityID> &selected, int ownerId, float x,
+      float z);
+
+  static bool
+  getSelectedBarracksState(Engine::Core::World &world,
+                           const std::vector<Engine::Core::EntityID> &selected,
+                           int ownerId, ProductionState &outState);
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 49 - 41
game/systems/production_system.cpp

@@ -1,51 +1,59 @@
 #include "production_system.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include "../map/map_transformer.h"
 #include "../units/factory.h"
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
+
+void ProductionSystem::update(Engine::Core::World *world, float deltaTime) {
+  if (!world)
+    return;
+  auto entities = world->getEntitiesWith<Engine::Core::ProductionComponent>();
+  for (auto *e : entities) {
+    auto *prod = e->getComponent<Engine::Core::ProductionComponent>();
+    if (!prod)
+      continue;
+    if (!prod->inProgress)
+      continue;
+    if (prod->producedCount >= prod->maxUnits) {
+      prod->inProgress = false;
+      continue;
+    }
+    prod->timeRemaining -= deltaTime;
+    if (prod->timeRemaining <= 0.0f) {
+
+      auto *t = e->getComponent<Engine::Core::TransformComponent>();
+      auto *u = e->getComponent<Engine::Core::UnitComponent>();
+      if (t && u) {
 
-void ProductionSystem::update(Engine::Core::World* world, float deltaTime) {
-    if (!world) return;
-    auto entities = world->getEntitiesWith<Engine::Core::ProductionComponent>();
-    for (auto* e : entities) {
-        auto* prod = e->getComponent<Engine::Core::ProductionComponent>();
-        if (!prod) continue;
-        if (!prod->inProgress) continue;
-        if (prod->producedCount >= prod->maxUnits) { prod->inProgress = false; continue; }
-        prod->timeRemaining -= deltaTime;
-        if (prod->timeRemaining <= 0.0f) {
-            // Spawn the unit near the building; use Transform to position
-            auto* t = e->getComponent<Engine::Core::TransformComponent>();
-            auto* u = e->getComponent<Engine::Core::UnitComponent>();
-            if (t && u) {
-                // Prefer rally point if set; otherwise place in a ring outside the building
-                QVector3D spawnPos;
-                if (prod->rallySet) {
-                    spawnPos = QVector3D(prod->rallyX, 0.0f, prod->rallyZ);
-                } else {
-                    float radius = 3.0f + 0.3f * float(prod->producedCount % 10);
-                    float angle = 0.6f * float(prod->producedCount);
-                    spawnPos = QVector3D(t->position.x + radius * std::cos(angle), 0.0f,
-                                         t->position.z + radius * std::sin(angle));
-                }
-                auto reg = Game::Map::MapTransformer::getFactoryRegistry();
-                if (reg) {
-                    Game::Units::SpawnParams sp;
-                    sp.position = spawnPos;
-                    sp.playerId = u->ownerId;
-                    sp.unitType = prod->productType;
-                    reg->create(prod->productType, *world, sp);
-                }
-                // Update production state
-                prod->producedCount += 1;
-            }
-            // If max reached, stop. Otherwise ready for next order.
-            prod->inProgress = false;
-            prod->timeRemaining = 0.0f;
+        QVector3D spawnPos;
+        if (prod->rallySet) {
+          spawnPos = QVector3D(prod->rallyX, 0.0f, prod->rallyZ);
+        } else {
+          float radius = 3.0f + 0.3f * float(prod->producedCount % 10);
+          float angle = 0.6f * float(prod->producedCount);
+          spawnPos = QVector3D(t->position.x + radius * std::cos(angle), 0.0f,
+                               t->position.z + radius * std::sin(angle));
+        }
+        auto reg = Game::Map::MapTransformer::getFactoryRegistry();
+        if (reg) {
+          Game::Units::SpawnParams sp;
+          sp.position = spawnPos;
+          sp.playerId = u->ownerId;
+          sp.unitType = prod->productType;
+          reg->create(prod->productType, *world, sp);
         }
+
+        prod->producedCount += 1;
+      }
+
+      prod->inProgress = false;
+      prod->timeRemaining = 0.0f;
     }
+  }
 }
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 5 - 3
game/systems/production_system.h

@@ -2,11 +2,13 @@
 
 #include "../core/system.h"
 
-namespace Game { namespace Systems {
+namespace Game {
+namespace Systems {
 
 class ProductionSystem : public Engine::Core::System {
 public:
-    void update(Engine::Core::World* world, float deltaTime) override;
+  void update(Engine::Core::World *world, float deltaTime) override;
 };
 
-} } // namespace Game::Systems
+} // namespace Systems
+} // namespace Game

+ 25 - 32
game/systems/selection_system.cpp

@@ -1,50 +1,43 @@
 #include "selection_system.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include <algorithm>
 
 namespace Game::Systems {
 
-void SelectionSystem::update(Engine::Core::World* world, float deltaTime) {
-    // Update selection visualization or handle selection-related logic
-    // This system is primarily event-driven through direct method calls
-}
+void SelectionSystem::update(Engine::Core::World *world, float deltaTime) {}
 
 void SelectionSystem::selectUnit(Engine::Core::EntityID unitId) {
-    auto it = std::find(m_selectedUnits.begin(), m_selectedUnits.end(), unitId);
-    if (it == m_selectedUnits.end()) {
-        m_selectedUnits.push_back(unitId);
-    }
+  auto it = std::find(m_selectedUnits.begin(), m_selectedUnits.end(), unitId);
+  if (it == m_selectedUnits.end()) {
+    m_selectedUnits.push_back(unitId);
+  }
 }
 
 void SelectionSystem::deselectUnit(Engine::Core::EntityID unitId) {
-    auto it = std::find(m_selectedUnits.begin(), m_selectedUnits.end(), unitId);
-    if (it != m_selectedUnits.end()) {
-        m_selectedUnits.erase(it);
-    }
+  auto it = std::find(m_selectedUnits.begin(), m_selectedUnits.end(), unitId);
+  if (it != m_selectedUnits.end()) {
+    m_selectedUnits.erase(it);
+  }
 }
 
-void SelectionSystem::clearSelection() {
-    m_selectedUnits.clear();
-}
+void SelectionSystem::clearSelection() { m_selectedUnits.clear(); }
 
-void SelectionSystem::selectUnitsInArea(float x1, float y1, float x2, float y2) {
-    // This would typically be called by the input system
-    // For now, it's a stub that would need world access to work properly
-    // In a real implementation, you'd pass the world or store a reference
-}
+void SelectionSystem::selectUnitsInArea(float x1, float y1, float x2,
+                                        float y2) {}
+
+bool SelectionSystem::isUnitInArea(Engine::Core::Entity *entity, float x1,
+                                   float y1, float x2, float y2) {
+  auto transform = entity->getComponent<Engine::Core::TransformComponent>();
+  if (!transform) {
+    return false;
+  }
+
+  float x = transform->position.x;
+  float z = transform->position.z;
 
-bool SelectionSystem::isUnitInArea(Engine::Core::Entity* entity, float x1, float y1, float x2, float y2) {
-    auto transform = entity->getComponent<Engine::Core::TransformComponent>();
-    if (!transform) {
-        return false;
-    }
-    
-    float x = transform->position.x;
-    float z = transform->position.z;
-    
-    return x >= std::min(x1, x2) && x <= std::max(x1, x2) &&
-           z >= std::min(y1, y2) && z <= std::max(y1, y2);
+  return x >= std::min(x1, x2) && x <= std::max(x1, x2) &&
+         z >= std::min(y1, y2) && z <= std::max(y1, y2);
 }
 
 } // namespace Game::Systems

+ 19 - 13
game/systems/selection_system.h

@@ -1,28 +1,34 @@
 #pragma once
 
-#include "../core/system.h"
 #include "../core/entity.h"
+#include "../core/system.h"
 #include <vector>
 
-namespace Engine { namespace Core { class Entity; } }
+namespace Engine {
+namespace Core {
+class Entity;
+}
+} // namespace Engine
 
 namespace Game::Systems {
 
 class SelectionSystem : public Engine::Core::System {
 public:
-    void update(Engine::Core::World* world, float deltaTime) override;
-    
-    // Selection management
-    void selectUnit(Engine::Core::EntityID unitId);
-    void deselectUnit(Engine::Core::EntityID unitId);
-    void clearSelection();
-    void selectUnitsInArea(float x1, float y1, float x2, float y2);
-    
-    const std::vector<Engine::Core::EntityID>& getSelectedUnits() const { return m_selectedUnits; }
+  void update(Engine::Core::World *world, float deltaTime) override;
+
+  void selectUnit(Engine::Core::EntityID unitId);
+  void deselectUnit(Engine::Core::EntityID unitId);
+  void clearSelection();
+  void selectUnitsInArea(float x1, float y1, float x2, float y2);
+
+  const std::vector<Engine::Core::EntityID> &getSelectedUnits() const {
+    return m_selectedUnits;
+  }
 
 private:
-    std::vector<Engine::Core::EntityID> m_selectedUnits;
-    bool isUnitInArea(Engine::Core::Entity* entity, float x1, float y1, float x2, float y2);
+  std::vector<Engine::Core::EntityID> m_selectedUnits;
+  bool isUnitInArea(Engine::Core::Entity *entity, float x1, float y1, float x2,
+                    float y2);
 };
 
 } // namespace Game::Systems

+ 49 - 36
game/units/archer.cpp

@@ -1,52 +1,65 @@
 #include "archer.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 static inline QVector3D teamColor(int ownerId) {
-    switch (ownerId) {
-        case 1: return QVector3D(0.20f, 0.55f, 1.00f); // blue
-        case 2: return QVector3D(1.00f, 0.30f, 0.30f); // red
-        case 3: return QVector3D(0.20f, 0.80f, 0.40f); // green
-        case 4: return QVector3D(1.00f, 0.80f, 0.20f); // yellow
-        default: return QVector3D(0.8f, 0.9f, 1.0f);
-    }
+  switch (ownerId) {
+  case 1:
+    return QVector3D(0.20f, 0.55f, 1.00f);
+  case 2:
+    return QVector3D(1.00f, 0.30f, 0.30f);
+  case 3:
+    return QVector3D(0.20f, 0.80f, 0.40f);
+  case 4:
+    return QVector3D(1.00f, 0.80f, 0.20f);
+  default:
+    return QVector3D(0.8f, 0.9f, 1.0f);
+  }
 }
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
-Archer::Archer(Engine::Core::World& world)
-    : Unit(world, "archer") {}
+Archer::Archer(Engine::Core::World &world) : Unit(world, "archer") {}
 
-std::unique_ptr<Archer> Archer::Create(Engine::Core::World& world, const SpawnParams& params) {
-    auto unit = std::unique_ptr<Archer>(new Archer(world));
-    unit->init(params);
-    return unit;
+std::unique_ptr<Archer> Archer::Create(Engine::Core::World &world,
+                                       const SpawnParams &params) {
+  auto unit = std::unique_ptr<Archer>(new Archer(world));
+  unit->init(params);
+  return unit;
 }
 
-void Archer::init(const SpawnParams& params) {
-    // Create ECS entity
-    auto* e = m_world->createEntity();
-    m_id = e->getId();
+void Archer::init(const SpawnParams &params) {
+
+  auto *e = m_world->createEntity();
+  m_id = e->getId();
+
+  m_t = e->addComponent<Engine::Core::TransformComponent>();
+  m_t->position = {params.position.x(), params.position.y(),
+                   params.position.z()};
+  m_t->scale = {0.5f, 0.5f, 0.5f};
 
-    // Core components
-    m_t = e->addComponent<Engine::Core::TransformComponent>();
-    m_t->position = {params.position.x(), params.position.y(), params.position.z()};
-    m_t->scale = {0.5f, 0.5f, 0.5f};
+  m_r = e->addComponent<Engine::Core::RenderableComponent>("", "");
+  m_r->visible = true;
 
-    m_r = e->addComponent<Engine::Core::RenderableComponent>("", "");
-    m_r->visible = true;
+  m_u = e->addComponent<Engine::Core::UnitComponent>();
+  m_u->unitType = m_type;
+  m_u->health = 80;
+  m_u->maxHealth = 80;
+  m_u->speed = 3.0f;
+  m_u->ownerId = params.playerId;
 
-    m_u = e->addComponent<Engine::Core::UnitComponent>();
-    m_u->unitType = m_type;
-    m_u->health = 80; m_u->maxHealth = 80; m_u->speed = 3.0f;
-    m_u->ownerId = params.playerId;
-    // Apply team color tint
-    QVector3D tc = teamColor(m_u->ownerId);
-    m_r->color[0] = tc.x(); m_r->color[1] = tc.y(); m_r->color[2] = tc.z();
+  QVector3D tc = teamColor(m_u->ownerId);
+  m_r->color[0] = tc.x();
+  m_r->color[1] = tc.y();
+  m_r->color[2] = tc.z();
 
-    m_mv = e->addComponent<Engine::Core::MovementComponent>();
+  m_mv = e->addComponent<Engine::Core::MovementComponent>();
 
-    m_atk = e->addComponent<Engine::Core::AttackComponent>();
-    m_atk->range = 6.0f; m_atk->damage = 12; m_atk->cooldown = 1.2f;
+  m_atk = e->addComponent<Engine::Core::AttackComponent>();
+  m_atk->range = 6.0f;
+  m_atk->damage = 12;
+  m_atk->cooldown = 1.2f;
 }
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 8 - 6
game/units/archer.h

@@ -2,16 +2,18 @@
 
 #include "unit.h"
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
 class Archer : public Unit {
 public:
-    // Create a new Archer unit in the given world with spawn params
-    static std::unique_ptr<Archer> Create(Engine::Core::World& world, const SpawnParams& params);
+  static std::unique_ptr<Archer> Create(Engine::Core::World &world,
+                                        const SpawnParams &params);
 
 private:
-    Archer(Engine::Core::World& world);
-    void init(const SpawnParams& params);
+  Archer(Engine::Core::World &world);
+  void init(const SpawnParams &params);
 };
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 49 - 46
game/units/barracks.cpp

@@ -1,56 +1,59 @@
 #include "barracks.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 #include "../visuals/team_colors.h"
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
-Barracks::Barracks(Engine::Core::World& world)
-    : Unit(world, "barracks") {}
+Barracks::Barracks(Engine::Core::World &world) : Unit(world, "barracks") {}
 
-std::unique_ptr<Barracks> Barracks::Create(Engine::Core::World& world, const SpawnParams& params) {
-    auto unit = std::unique_ptr<Barracks>(new Barracks(world));
-    unit->init(params);
-    return unit;
+std::unique_ptr<Barracks> Barracks::Create(Engine::Core::World &world,
+                                           const SpawnParams &params) {
+  auto unit = std::unique_ptr<Barracks>(new Barracks(world));
+  unit->init(params);
+  return unit;
 }
 
-void Barracks::init(const SpawnParams& params) {
-    auto* e = m_world->createEntity();
-    m_id = e->getId();
-
-    // Transform: bigger static building
-    m_t = e->addComponent<Engine::Core::TransformComponent>();
-    m_t->position = {params.position.x(), params.position.y(), params.position.z()};
-    m_t->scale = {1.8f, 1.2f, 1.8f};
-
-    // Renderable: use generic cube mesh and team color
-    m_r = e->addComponent<Engine::Core::RenderableComponent>("", "");
-    m_r->visible = true;
-    m_r->mesh = Engine::Core::RenderableComponent::MeshKind::Cube;
-
-    m_u = e->addComponent<Engine::Core::UnitComponent>();
-    m_u->unitType = m_type;
-    m_u->health = 2000; m_u->maxHealth = 2000; m_u->speed = 0.0f; // static - increased HP for strategic importance
-    m_u->ownerId = params.playerId;
-
-    QVector3D tc = Game::Visuals::teamColorForOwner(m_u->ownerId);
-    m_r->color[0] = tc.x(); m_r->color[1] = tc.y(); m_r->color[2] = tc.z();
-
-    // Tag as building (affects selection/display)
-    e->addComponent<Engine::Core::BuildingComponent>();
-
-    // Production capability: produces archers, capped by maxUnits
-    if (auto* prod = e->addComponent<Engine::Core::ProductionComponent>()) {
-        prod->productType = "archer";
-        prod->buildTime = 10.0f;  // seconds per archer
-        prod->maxUnits = 100;     // cap total archers produced by this barracks
-        prod->inProgress = false; // wait for order
-        prod->timeRemaining = 0.0f;
-        prod->producedCount = 0;
-        prod->rallyX = m_t->position.x + 4.0f; // basic rally default
-        prod->rallyZ = m_t->position.z + 2.0f;
-        prod->rallySet = true; // default rally is set near building
-    }
+void Barracks::init(const SpawnParams &params) {
+  auto *e = m_world->createEntity();
+  m_id = e->getId();
+
+  m_t = e->addComponent<Engine::Core::TransformComponent>();
+  m_t->position = {params.position.x(), params.position.y(),
+                   params.position.z()};
+  m_t->scale = {1.8f, 1.2f, 1.8f};
+
+  m_r = e->addComponent<Engine::Core::RenderableComponent>("", "");
+  m_r->visible = true;
+  m_r->mesh = Engine::Core::RenderableComponent::MeshKind::Cube;
+
+  m_u = e->addComponent<Engine::Core::UnitComponent>();
+  m_u->unitType = m_type;
+  m_u->health = 2000;
+  m_u->maxHealth = 2000;
+  m_u->speed = 0.0f;
+  m_u->ownerId = params.playerId;
+
+  QVector3D tc = Game::Visuals::teamColorForOwner(m_u->ownerId);
+  m_r->color[0] = tc.x();
+  m_r->color[1] = tc.y();
+  m_r->color[2] = tc.z();
+
+  e->addComponent<Engine::Core::BuildingComponent>();
+
+  if (auto *prod = e->addComponent<Engine::Core::ProductionComponent>()) {
+    prod->productType = "archer";
+    prod->buildTime = 10.0f;
+    prod->maxUnits = 100;
+    prod->inProgress = false;
+    prod->timeRemaining = 0.0f;
+    prod->producedCount = 0;
+    prod->rallyX = m_t->position.x + 4.0f;
+    prod->rallyZ = m_t->position.z + 2.0f;
+    prod->rallySet = true;
+  }
 }
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 8 - 5
game/units/barracks.h

@@ -2,15 +2,18 @@
 
 #include "unit.h"
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
 class Barracks : public Unit {
 public:
-    static std::unique_ptr<Barracks> Create(Engine::Core::World& world, const SpawnParams& params);
+  static std::unique_ptr<Barracks> Create(Engine::Core::World &world,
+                                          const SpawnParams &params);
 
 private:
-    Barracks(Engine::Core::World& world);
-    void init(const SpawnParams& params);
+  Barracks(Engine::Core::World &world);
+  void init(const SpawnParams &params);
 };
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 11 - 7
game/units/factory.cpp

@@ -2,15 +2,19 @@
 #include "archer.h"
 #include "barracks.h"
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
-void registerBuiltInUnits(UnitFactoryRegistry& reg) {
-    reg.registerFactory("archer", [](Engine::Core::World& world, const SpawnParams& params){
+void registerBuiltInUnits(UnitFactoryRegistry &reg) {
+  reg.registerFactory(
+      "archer", [](Engine::Core::World &world, const SpawnParams &params) {
         return Archer::Create(world, params);
-    });
-    reg.registerFactory("barracks", [](Engine::Core::World& world, const SpawnParams& params){
+      });
+  reg.registerFactory(
+      "barracks", [](Engine::Core::World &world, const SpawnParams &params) {
         return Barracks::Create(world, params);
-    });
+      });
 }
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 21 - 13
game/units/factory.h

@@ -1,28 +1,36 @@
 #pragma once
 
+#include "unit.h"
 #include <functional>
 #include <memory>
 #include <string>
 #include <unordered_map>
-#include "unit.h"
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
 class UnitFactoryRegistry {
 public:
-    using Factory = std::function<std::unique_ptr<Unit>(Engine::Core::World&, const SpawnParams&)>;
+  using Factory = std::function<std::unique_ptr<Unit>(Engine::Core::World &,
+                                                      const SpawnParams &)>;
+
+  void registerFactory(const std::string &type, Factory f) {
+    m_map[type] = std::move(f);
+  }
+  std::unique_ptr<Unit> create(const std::string &type,
+                               Engine::Core::World &world,
+                               const SpawnParams &params) const {
+    auto it = m_map.find(type);
+    if (it == m_map.end())
+      return nullptr;
+    return it->second(world, params);
+  }
 
-    void registerFactory(const std::string& type, Factory f) { m_map[type] = std::move(f); }
-    std::unique_ptr<Unit> create(const std::string& type, Engine::Core::World& world, const SpawnParams& params) const {
-        auto it = m_map.find(type);
-        if (it == m_map.end()) return nullptr;
-        return it->second(world, params);
-    }
 private:
-    std::unordered_map<std::string, Factory> m_map;
+  std::unordered_map<std::string, Factory> m_map;
 };
 
-// Install built-in unit factories (archer, etc.)
-void registerBuiltInUnits(UnitFactoryRegistry& reg);
+void registerBuiltInUnits(UnitFactoryRegistry &reg);
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 43 - 30
game/units/unit.cpp

@@ -1,49 +1,62 @@
 #include "unit.h"
-#include "../core/world.h"
 #include "../core/component.h"
+#include "../core/world.h"
 
-namespace Game { namespace Units {
+namespace Game {
+namespace Units {
 
-Unit::Unit(Engine::Core::World& world, const std::string& type)
-    : m_world(&world), m_type(type) {
-}
+Unit::Unit(Engine::Core::World &world, const std::string &type)
+    : m_world(&world), m_type(type) {}
 
-Engine::Core::Entity* Unit::entity() const {
-    return m_world ? m_world->getEntity(m_id) : nullptr;
+Engine::Core::Entity *Unit::entity() const {
+  return m_world ? m_world->getEntity(m_id) : nullptr;
 }
 
 void Unit::ensureCoreComponents() {
-    if (!m_world) return;
-    if (auto* e = entity()) {
-        if (!m_t)  m_t  = e->getComponent<Engine::Core::TransformComponent>();
-        if (!m_r)  m_r  = e->getComponent<Engine::Core::RenderableComponent>();
-        if (!m_u)  m_u  = e->getComponent<Engine::Core::UnitComponent>();
-        if (!m_mv) m_mv = e->getComponent<Engine::Core::MovementComponent>();
-        if (!m_atk) m_atk = e->getComponent<Engine::Core::AttackComponent>();
-    }
+  if (!m_world)
+    return;
+  if (auto *e = entity()) {
+    if (!m_t)
+      m_t = e->getComponent<Engine::Core::TransformComponent>();
+    if (!m_r)
+      m_r = e->getComponent<Engine::Core::RenderableComponent>();
+    if (!m_u)
+      m_u = e->getComponent<Engine::Core::UnitComponent>();
+    if (!m_mv)
+      m_mv = e->getComponent<Engine::Core::MovementComponent>();
+    if (!m_atk)
+      m_atk = e->getComponent<Engine::Core::AttackComponent>();
+  }
 }
 
 void Unit::moveTo(float x, float z) {
-    ensureCoreComponents();
-    if (!m_mv) {
-        if (auto* e = entity()) m_mv = e->addComponent<Engine::Core::MovementComponent>();
-    }
-    if (m_mv) { m_mv->targetX = x; m_mv->targetY = z; m_mv->hasTarget = true; }
+  ensureCoreComponents();
+  if (!m_mv) {
+    if (auto *e = entity())
+      m_mv = e->addComponent<Engine::Core::MovementComponent>();
+  }
+  if (m_mv) {
+    m_mv->targetX = x;
+    m_mv->targetY = z;
+    m_mv->hasTarget = true;
+  }
 }
 
 bool Unit::isAlive() const {
-    if (auto* e = entity()) {
-        if (auto* u = e->getComponent<Engine::Core::UnitComponent>()) return u->health > 0;
-    }
-    return false;
+  if (auto *e = entity()) {
+    if (auto *u = e->getComponent<Engine::Core::UnitComponent>())
+      return u->health > 0;
+  }
+  return false;
 }
 
 QVector3D Unit::position() const {
-    if (auto* e = entity()) {
-        if (auto* t = e->getComponent<Engine::Core::TransformComponent>())
-            return QVector3D(t->position.x, t->position.y, t->position.z);
-    }
-    return QVector3D();
+  if (auto *e = entity()) {
+    if (auto *t = e->getComponent<Engine::Core::TransformComponent>())
+      return QVector3D(t->position.x, t->position.y, t->position.z);
+  }
+  return QVector3D();
 }
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 42 - 35
game/units/unit.h

@@ -1,53 +1,60 @@
 #pragma once
 
+#include <QVector3D>
 #include <memory>
 #include <string>
 #include <utility>
-#include <QVector3D>
 
-namespace Engine { namespace Core {
-class World; class Entity; using EntityID = unsigned int;
-struct TransformComponent; struct RenderableComponent; struct UnitComponent; struct MovementComponent; struct AttackComponent;
-} }
-
-namespace Game { namespace Units {
+namespace Engine {
+namespace Core {
+class World;
+class Entity;
+using EntityID = unsigned int;
+struct TransformComponent;
+struct RenderableComponent;
+struct UnitComponent;
+struct MovementComponent;
+struct AttackComponent;
+} // namespace Core
+} // namespace Engine
+
+namespace Game {
+namespace Units {
 
 struct SpawnParams {
-    // world coordinates (XZ plane)
-    QVector3D position{0,0,0};
-    int playerId = 0;
-    std::string unitType; // optional label; renderer may use it
+
+  QVector3D position{0, 0, 0};
+  int playerId = 0;
+  std::string unitType;
 };
 
-// Thin OOP facade over ECS components; no duplicate state.
 class Unit {
 public:
-    virtual ~Unit() = default;
+  virtual ~Unit() = default;
 
-    Engine::Core::EntityID id() const { return m_id; }
-    const std::string& type() const { return m_type; }
+  Engine::Core::EntityID id() const { return m_id; }
+  const std::string &type() const { return m_type; }
 
-    // Convenience controls that mutate ECS components
-    void moveTo(float x, float z);
-    bool isAlive() const;
-    QVector3D position() const;
+  void moveTo(float x, float z);
+  bool isAlive() const;
+  QVector3D position() const;
 
 protected:
-    Unit(Engine::Core::World& world, const std::string& type);
-    Engine::Core::Entity* entity() const; // cached but validated
-
-    // Helpers for derived classes to configure components
-    void ensureCoreComponents();
-
-    Engine::Core::World* m_world = nullptr;
-    Engine::Core::EntityID m_id = 0;
-    std::string m_type;
-    // Cached pointers (owned by ECS entity)
-    Engine::Core::TransformComponent* m_t = nullptr;
-    Engine::Core::RenderableComponent* m_r = nullptr;
-    Engine::Core::UnitComponent*       m_u = nullptr;
-    Engine::Core::MovementComponent*   m_mv = nullptr;
-    Engine::Core::AttackComponent*     m_atk = nullptr;
+  Unit(Engine::Core::World &world, const std::string &type);
+  Engine::Core::Entity *entity() const;
+
+  void ensureCoreComponents();
+
+  Engine::Core::World *m_world = nullptr;
+  Engine::Core::EntityID m_id = 0;
+  std::string m_type;
+
+  Engine::Core::TransformComponent *m_t = nullptr;
+  Engine::Core::RenderableComponent *m_r = nullptr;
+  Engine::Core::UnitComponent *m_u = nullptr;
+  Engine::Core::MovementComponent *m_mv = nullptr;
+  Engine::Core::AttackComponent *m_atk = nullptr;
 };
 
-} } // namespace Game::Units
+} // namespace Units
+} // namespace Game

+ 13 - 8
game/visuals/team_colors.h

@@ -3,12 +3,17 @@
 
 namespace Game::Visuals {
 inline QVector3D teamColorForOwner(int ownerId) {
-    switch (ownerId) {
-        case 1: return QVector3D(0.20f, 0.55f, 1.00f); // blue
-        case 2: return QVector3D(1.00f, 0.30f, 0.30f); // red
-        case 3: return QVector3D(0.20f, 0.80f, 0.40f); // green
-        case 4: return QVector3D(1.00f, 0.80f, 0.20f); // yellow
-        default: return QVector3D(0.8f, 0.9f, 1.0f);
-    }
-}
+  switch (ownerId) {
+  case 1:
+    return QVector3D(0.20f, 0.55f, 1.00f);
+  case 2:
+    return QVector3D(1.00f, 0.30f, 0.30f);
+  case 3:
+    return QVector3D(0.20f, 0.80f, 0.40f);
+  case 4:
+    return QVector3D(1.00f, 0.80f, 0.20f);
+  default:
+    return QVector3D(0.8f, 0.9f, 1.0f);
+  }
 }
+} // namespace Game::Visuals

+ 80 - 59
game/visuals/visual_catalog.cpp

@@ -1,77 +1,98 @@
 #include "visual_catalog.h"
+#include "../core/component.h"
 #include <QFile>
+#include <QJsonArray>
 #include <QJsonDocument>
 #include <QJsonObject>
-#include <QJsonArray>
-#include "../core/component.h"
 
 namespace Game::Visuals {
 
-VisualDef::MeshKind meshKindFromString(const QString& s) {
-    const QString t = s.trimmed().toLower();
-    if (t == "quad") return VisualDef::MeshKind::Quad;
-    if (t == "plane") return VisualDef::MeshKind::Plane;
-    if (t == "cube") return VisualDef::MeshKind::Cube;
-    if (t == "capsule") return VisualDef::MeshKind::Capsule;
-    if (t == "ring") return VisualDef::MeshKind::Ring;
-    return VisualDef::MeshKind::None;
+VisualDef::MeshKind meshKindFromString(const QString &s) {
+  const QString t = s.trimmed().toLower();
+  if (t == "quad")
+    return VisualDef::MeshKind::Quad;
+  if (t == "plane")
+    return VisualDef::MeshKind::Plane;
+  if (t == "cube")
+    return VisualDef::MeshKind::Cube;
+  if (t == "capsule")
+    return VisualDef::MeshKind::Capsule;
+  if (t == "ring")
+    return VisualDef::MeshKind::Ring;
+  return VisualDef::MeshKind::None;
 }
 
-static Engine::Core::RenderableComponent::MeshKind toRenderableMesh(VisualDef::MeshKind k) {
-    using RM = Engine::Core::RenderableComponent::MeshKind;
-    switch (k) {
-        case VisualDef::MeshKind::Quad: return RM::Quad;
-        case VisualDef::MeshKind::Plane: return RM::Plane;
-        case VisualDef::MeshKind::Cube: return RM::Cube;
-        case VisualDef::MeshKind::Capsule: return RM::Capsule;
-        case VisualDef::MeshKind::Ring: return RM::Ring;
-        default: return RM::None;
-    }
+static Engine::Core::RenderableComponent::MeshKind
+toRenderableMesh(VisualDef::MeshKind k) {
+  using RM = Engine::Core::RenderableComponent::MeshKind;
+  switch (k) {
+  case VisualDef::MeshKind::Quad:
+    return RM::Quad;
+  case VisualDef::MeshKind::Plane:
+    return RM::Plane;
+  case VisualDef::MeshKind::Cube:
+    return RM::Cube;
+  case VisualDef::MeshKind::Capsule:
+    return RM::Capsule;
+  case VisualDef::MeshKind::Ring:
+    return RM::Ring;
+  default:
+    return RM::None;
+  }
 }
 
-bool VisualCatalog::loadFromJsonFile(const QString& path, QString* outError) {
-    QFile f(path);
-    if (!f.open(QIODevice::ReadOnly)) {
-        if (outError) *outError = QString("Failed to open visuals file: %1").arg(path);
-        return false;
-    }
-    const QByteArray data = f.readAll();
-    f.close();
-    QJsonParseError perr{};
-    const QJsonDocument doc = QJsonDocument::fromJson(data, &perr);
-    if (doc.isNull()) {
-        if (outError) *outError = QString("JSON parse error at %1: %2").arg(perr.offset).arg(perr.errorString());
-        return false;
-    }
-    QJsonObject root = doc.object();
-    QJsonObject units = root.value("units").toObject();
-    for (auto it = units.begin(); it != units.end(); ++it) {
-        VisualDef def;
-        QJsonObject o = it.value().toObject();
-        def.mesh = meshKindFromString(o.value("mesh").toString("cube"));
-        QJsonArray col = o.value("color").toArray();
-        if (col.size() == 3) def.color = QVector3D(float(col[0].toDouble(1.0)), float(col[1].toDouble(1.0)), float(col[2].toDouble(1.0)));
-        def.texture = o.value("texture").toString("");
-        m_units.emplace(it.key().toStdString(), def);
-    }
-    return true;
+bool VisualCatalog::loadFromJsonFile(const QString &path, QString *outError) {
+  QFile f(path);
+  if (!f.open(QIODevice::ReadOnly)) {
+    if (outError)
+      *outError = QString("Failed to open visuals file: %1").arg(path);
+    return false;
+  }
+  const QByteArray data = f.readAll();
+  f.close();
+  QJsonParseError perr{};
+  const QJsonDocument doc = QJsonDocument::fromJson(data, &perr);
+  if (doc.isNull()) {
+    if (outError)
+      *outError = QString("JSON parse error at %1: %2")
+                      .arg(perr.offset)
+                      .arg(perr.errorString());
+    return false;
+  }
+  QJsonObject root = doc.object();
+  QJsonObject units = root.value("units").toObject();
+  for (auto it = units.begin(); it != units.end(); ++it) {
+    VisualDef def;
+    QJsonObject o = it.value().toObject();
+    def.mesh = meshKindFromString(o.value("mesh").toString("cube"));
+    QJsonArray col = o.value("color").toArray();
+    if (col.size() == 3)
+      def.color =
+          QVector3D(float(col[0].toDouble(1.0)), float(col[1].toDouble(1.0)),
+                    float(col[2].toDouble(1.0)));
+    def.texture = o.value("texture").toString("");
+    m_units.emplace(it.key().toStdString(), def);
+  }
+  return true;
 }
 
-bool VisualCatalog::lookup(const std::string& unitType, VisualDef& out) const {
-    auto it = m_units.find(unitType);
-    if (it == m_units.end()) return false;
-    out = it->second;
-    return true;
+bool VisualCatalog::lookup(const std::string &unitType, VisualDef &out) const {
+  auto it = m_units.find(unitType);
+  if (it == m_units.end())
+    return false;
+  out = it->second;
+  return true;
 }
 
-void applyToRenderable(const VisualDef& def, Engine::Core::RenderableComponent& r) {
-    r.mesh = toRenderableMesh(def.mesh);
-    r.color[0] = def.color.x();
-    r.color[1] = def.color.y();
-    r.color[2] = def.color.z();
-    if (!def.texture.isEmpty()) {
-        r.texturePath = def.texture.toStdString();
-    }
+void applyToRenderable(const VisualDef &def,
+                       Engine::Core::RenderableComponent &r) {
+  r.mesh = toRenderableMesh(def.mesh);
+  r.color[0] = def.color.x();
+  r.color[1] = def.color.y();
+  r.color[2] = def.color.z();
+  if (!def.texture.isEmpty()) {
+    r.texturePath = def.texture.toStdString();
+  }
 }
 
 } // namespace Game::Visuals

+ 18 - 14
game/visuals/visual_catalog.h

@@ -2,33 +2,37 @@
 
 #include <QString>
 #include <QVector3D>
-#include <unordered_map>
 #include <string>
+#include <unordered_map>
 
-namespace Engine { namespace Core { class RenderableComponent; } }
+namespace Engine {
+namespace Core {
+class RenderableComponent;
+}
+} // namespace Engine
 
 namespace Game::Visuals {
 
 struct VisualDef {
-    // Mirrors Engine::Core::RenderableComponent::MeshKind by name
-    enum class MeshKind { None, Quad, Plane, Cube, Capsule, Ring };
-    MeshKind mesh = MeshKind::Cube;
-    QVector3D color{1.0f, 1.0f, 1.0f};
-    QString texture; // optional
+
+  enum class MeshKind { None, Quad, Plane, Cube, Capsule, Ring };
+  MeshKind mesh = MeshKind::Cube;
+  QVector3D color{1.0f, 1.0f, 1.0f};
+  QString texture;
 };
 
 class VisualCatalog {
 public:
-    bool loadFromJsonFile(const QString& path, QString* outError = nullptr);
-    bool lookup(const std::string& unitType, VisualDef& out) const;
+  bool loadFromJsonFile(const QString &path, QString *outError = nullptr);
+  bool lookup(const std::string &unitType, VisualDef &out) const;
+
 private:
-    std::unordered_map<std::string, VisualDef> m_units;
+  std::unordered_map<std::string, VisualDef> m_units;
 };
 
-// Utility to map string to MeshKind
-VisualDef::MeshKind meshKindFromString(const QString& s);
+VisualDef::MeshKind meshKindFromString(const QString &s);
 
-// Apply a VisualDef to an Engine::Core::RenderableComponent
-void applyToRenderable(const VisualDef& def, Engine::Core::RenderableComponent& r);
+void applyToRenderable(const VisualDef &def,
+                       Engine::Core::RenderableComponent &r);
 
 } // namespace Game::Visuals

+ 60 - 55
main.cpp

@@ -1,76 +1,81 @@
-#include <QGuiApplication>
-#include <QQmlApplicationEngine>
-#include <QOpenGLContext>
-#include <QSurfaceFormat>
 #include <QDebug>
 #include <QDir>
-#include <QQuickWindow>
+#include <QGuiApplication>
+#include <QOpenGLContext>
+#include <QQmlApplicationEngine>
 #include <QQmlContext>
+#include <QQuickWindow>
 #include <QSGRendererInterface>
+#include <QSurfaceFormat>
 
 #include "app/game_engine.h"
 #include "ui/gl_view.h"
 
-int main(int argc, char *argv[])
-{
-    if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY") && qEnvironmentVariableIsSet("DISPLAY")) {
-        qputenv("QT_QPA_PLATFORM", "xcb");
-    }
-    qputenv("QT_OPENGL", "desktop");
-    qputenv("QSG_RHI_BACKEND", "opengl");
-    QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
+int main(int argc, char *argv[]) {
+  if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY") &&
+      qEnvironmentVariableIsSet("DISPLAY")) {
+    qputenv("QT_QPA_PLATFORM", "xcb");
+  }
+  qputenv("QT_OPENGL", "desktop");
+  qputenv("QSG_RHI_BACKEND", "opengl");
+  QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
 
-    QSurfaceFormat fmt;
-    fmt.setVersion(3, 3);
-    fmt.setProfile(QSurfaceFormat::CoreProfile);
-    fmt.setDepthBufferSize(24);
-    fmt.setStencilBufferSize(8);
-    fmt.setSamples(0);
-    QSurfaceFormat::setDefaultFormat(fmt);
+  QSurfaceFormat fmt;
+  fmt.setVersion(3, 3);
+  fmt.setProfile(QSurfaceFormat::CoreProfile);
+  fmt.setDepthBufferSize(24);
+  fmt.setStencilBufferSize(8);
+  fmt.setSamples(0);
+  QSurfaceFormat::setDefaultFormat(fmt);
 
-    QGuiApplication app(argc, argv);
+  QGuiApplication app(argc, argv);
 
-    auto gameEngine = new GameEngine();
+  auto gameEngine = new GameEngine();
 
-    QQmlApplicationEngine engine;
-    engine.rootContext()->setContextProperty("game", gameEngine);
-    qmlRegisterType<GLView>("StandardOfIron", 1, 0, "GLView");
-    engine.load(QUrl(QStringLiteral("qrc:/StandardOfIron/ui/qml/Main.qml")));
-    if (engine.rootObjects().isEmpty()) {
-        qWarning() << "Failed to load QML file";
-        return -1;
-    }
+  QQmlApplicationEngine engine;
+  engine.rootContext()->setContextProperty("game", gameEngine);
+  qmlRegisterType<GLView>("StandardOfIron", 1, 0, "GLView");
+  engine.load(QUrl(QStringLiteral("qrc:/StandardOfIron/ui/qml/Main.qml")));
+  if (engine.rootObjects().isEmpty()) {
+    qWarning() << "Failed to load QML file";
+    return -1;
+  }
 
-    QObject* rootObj = engine.rootObjects().first();
-    QQuickWindow* window = qobject_cast<QQuickWindow*>(rootObj);
-    if (!window) window = rootObj->findChild<QQuickWindow*>();
-    if (!window) {
-        qWarning() << "No QQuickWindow found for OpenGL initialization.";
-        return -2;
-    }
+  QObject *rootObj = engine.rootObjects().first();
+  QQuickWindow *window = qobject_cast<QQuickWindow *>(rootObj);
+  if (!window)
+    window = rootObj->findChild<QQuickWindow *>();
+  if (!window) {
+    qWarning() << "No QQuickWindow found for OpenGL initialization.";
+    return -2;
+  }
 
-    gameEngine->setWindow(window);
+  gameEngine->setWindow(window);
 
-    QObject::connect(window, &QQuickWindow::sceneGraphInitialized, window, [window]() {
+  QObject::connect(
+      window, &QQuickWindow::sceneGraphInitialized, window, [window]() {
         if (auto *ri = window->rendererInterface()) {
-            auto api = ri->graphicsApi();
-            const char* name = api == QSGRendererInterface::OpenGLRhi     ? "OpenGLRhi"  :
-                               api == QSGRendererInterface::VulkanRhi      ? "VulkanRhi"  :
-                               api == QSGRendererInterface::Direct3D11Rhi  ? "D3D11Rhi"   :
-                               api == QSGRendererInterface::MetalRhi       ? "MetalRhi"   :
-                               api == QSGRendererInterface::Software       ? "Software"   : "Unknown";
-            qInfo() << "QSG graphicsApi:" << name;
+          auto api = ri->graphicsApi();
+          const char *name =
+              api == QSGRendererInterface::OpenGLRhi       ? "OpenGLRhi"
+              : api == QSGRendererInterface::VulkanRhi     ? "VulkanRhi"
+              : api == QSGRendererInterface::Direct3D11Rhi ? "D3D11Rhi"
+              : api == QSGRendererInterface::MetalRhi      ? "MetalRhi"
+              : api == QSGRendererInterface::Software      ? "Software"
+                                                           : "Unknown";
+          qInfo() << "QSG graphicsApi:" << name;
         }
-    });
+      });
 
-    QObject::connect(window, &QQuickWindow::sceneGraphError, &app,
-                     [&](QQuickWindow::SceneGraphError, const QString &msg){
-        qCritical() << "Failed to initialize OpenGL scene graph:" << msg;
-        app.exit(3);
-    });
+  QObject::connect(window, &QQuickWindow::sceneGraphError, &app,
+                   [&](QQuickWindow::SceneGraphError, const QString &msg) {
+                     qCritical()
+                         << "Failed to initialize OpenGL scene graph:" << msg;
+                     app.exit(3);
+                   });
 
-    qDebug() << "Application started successfully";
-    qDebug() << "Assets directory:" << QDir::currentPath() + "/assets";
+  qDebug() << "Application started successfully";
+  qDebug() << "Assets directory:" << QDir::currentPath() + "/assets";
 
-    return app.exec();
+  return app.exec();
 }

+ 2 - 6
render/draw_queue.cpp

@@ -1,7 +1,3 @@
 #include "draw_queue.h"
-namespace Render::GL {
-// Intentionally minimal; logic can be extended later
-}
-namespace Render {
-// Intentionally empty: DrawQueue is a POD-like container defined in the header.
-}
+namespace Render::GL {}
+namespace Render {}

+ 55 - 45
render/draw_queue.h

@@ -1,72 +1,82 @@
 #pragma once
 
-#include <vector>
-#include <variant>
-#include <algorithm>
 #include <QMatrix4x4>
 #include <QVector3D>
+#include <algorithm>
+#include <variant>
+#include <vector>
 
-namespace Render::GL { class Mesh; class Texture; }
+namespace Render::GL {
+class Mesh;
+class Texture;
+} // namespace Render::GL
 
 namespace Render::GL {
 
 struct MeshCmd {
-    Mesh* mesh = nullptr;
-    Texture* texture = nullptr;
-    QMatrix4x4 model;
-    QVector3D color{1,1,1};
-    float alpha = 1.0f;
+  Mesh *mesh = nullptr;
+  Texture *texture = nullptr;
+  QMatrix4x4 model;
+  QVector3D color{1, 1, 1};
+  float alpha = 1.0f;
 };
 
 struct GridCmd {
-    // Placeholder for future grid overlay; keep minimal to avoid churn now
-    QMatrix4x4 model;
-    QVector3D color{0.2f,0.25f,0.2f};
-    float cellSize = 1.0f;
-    float thickness = 0.06f;
-    float extent = 50.0f;
+
+  QMatrix4x4 model;
+  QVector3D color{0.2f, 0.25f, 0.2f};
+  float cellSize = 1.0f;
+  float thickness = 0.06f;
+  float extent = 50.0f;
 };
 
 struct SelectionRingCmd {
-    QMatrix4x4 model;
-    QVector3D color{0,0,0};
-    float alphaInner = 0.6f;
-    float alphaOuter = 0.25f;
+  QMatrix4x4 model;
+  QVector3D color{0, 0, 0};
+  float alphaInner = 0.6f;
+  float alphaOuter = 0.25f;
 };
 
 struct SelectionSmokeCmd {
-    QMatrix4x4 model;
-    QVector3D color{1,1,1};
-    float baseAlpha = 0.15f; // alpha for the innermost layer; outer layers fade down
+  QMatrix4x4 model;
+  QVector3D color{1, 1, 1};
+  float baseAlpha = 0.15f;
 };
 
-using DrawCmd = std::variant<MeshCmd, GridCmd, SelectionRingCmd, SelectionSmokeCmd>;
+using DrawCmd =
+    std::variant<MeshCmd, GridCmd, SelectionRingCmd, SelectionSmokeCmd>;
 
 class DrawQueue {
 public:
-    void clear() { m_items.clear(); }
-    void submit(const MeshCmd& c) { m_items.emplace_back(c); }
-    void submit(const GridCmd& c) { m_items.emplace_back(c); }
-    void submit(const SelectionRingCmd& c) { m_items.emplace_back(c); }
-    void submit(const SelectionSmokeCmd& c) { m_items.emplace_back(c); }
-    bool empty() const { return m_items.empty(); }
-    const std::vector<DrawCmd>& items() const { return m_items; }
-    std::vector<DrawCmd>& items() { return m_items; }
-    void sortForBatching() {
-        // Order: Grid first (background), then Mesh, then SelectionRing (foreground overlays)
-        auto weight = [](const DrawCmd& c) -> int {
-            if (std::holds_alternative<GridCmd>(c)) return 0;             // ground
-            if (std::holds_alternative<SelectionSmokeCmd>(c)) return 1;    // smoke base under meshes
-            if (std::holds_alternative<MeshCmd>(c)) return 2;              // entities
-            if (std::holds_alternative<SelectionRingCmd>(c)) return 3;     // thin overlays last
-            return 4;
-        };
-        std::stable_sort(m_items.begin(), m_items.end(), [&](const DrawCmd& a, const DrawCmd& b){
-            return weight(a) < weight(b);
-        });
-    }
+  void clear() { m_items.clear(); }
+  void submit(const MeshCmd &c) { m_items.emplace_back(c); }
+  void submit(const GridCmd &c) { m_items.emplace_back(c); }
+  void submit(const SelectionRingCmd &c) { m_items.emplace_back(c); }
+  void submit(const SelectionSmokeCmd &c) { m_items.emplace_back(c); }
+  bool empty() const { return m_items.empty(); }
+  const std::vector<DrawCmd> &items() const { return m_items; }
+  std::vector<DrawCmd> &items() { return m_items; }
+  void sortForBatching() {
+
+    auto weight = [](const DrawCmd &c) -> int {
+      if (std::holds_alternative<GridCmd>(c))
+        return 0;
+      if (std::holds_alternative<SelectionSmokeCmd>(c))
+        return 1;
+      if (std::holds_alternative<MeshCmd>(c))
+        return 2;
+      if (std::holds_alternative<SelectionRingCmd>(c))
+        return 3;
+      return 4;
+    };
+    std::stable_sort(m_items.begin(), m_items.end(),
+                     [&](const DrawCmd &a, const DrawCmd &b) {
+                       return weight(a) < weight(b);
+                     });
+  }
+
 private:
-    std::vector<DrawCmd> m_items;
+  std::vector<DrawCmd> m_items;
 };
 
 } // namespace Render::GL

+ 443 - 427
render/entity/archer_renderer.cpp

@@ -1,467 +1,483 @@
 #include "archer_renderer.h"
-#include "registry.h"
+#include "../../game/core/component.h"
+#include "../../game/core/entity.h"
+#include "../../game/visuals/team_colors.h"
+#include "../geom/selection_ring.h"
 #include "../gl/mesh.h"
 #include "../gl/texture.h"
-#include "../geom/selection_ring.h"
-#include "../../game/visuals/team_colors.h"
-#include "../../game/core/entity.h"
-#include "../../game/core/component.h"
-#include <QVector3D>
+#include "registry.h"
 #include <QMatrix4x4>
-#include <vector>
+#include <QVector3D>
+#include <algorithm>
 #include <cmath>
 #include <memory>
-#include <algorithm>
+#include <vector>
 
 namespace Render::GL {
 
-// ------------------------- Primitive builders (cached) -------------------------
-
-// Simple vertex helpers (assumes Vertex = {pos, normal, uv} from your Mesh)
-static Mesh* createUnitCylinderMesh() {
-    const int radial = 24;
-    const float radius = 1.0f;   // unit radius; scale in matrix later
-    const float halfH  = 0.5f;   // unit half-height; total height=1
-    std::vector<Vertex> v;
-    std::vector<unsigned int> idx;
-
-    // Side vertices
-    for (int y = 0; y <= 1; ++y) {
-        float py = y ? halfH : -halfH;
-        float vCoord = float(y);
-        for (int i = 0; i <= radial; ++i) {
-            float u = float(i) / float(radial);
-            float ang = u * 6.28318530718f;
-            float px = radius * std::cos(ang);
-            float pz = radius * std::sin(ang);
-            QVector3D n(px, 0.0f, pz);
-            n.normalize();
-            v.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {u, vCoord}});
-        }
-    }
-    int row = radial + 1;
-    // Side indices
-    for (int i = 0; i < radial; ++i) {
-        int a = 0 * row + i;
-        int b = 0 * row + i + 1;
-        int c = 1 * row + i + 1;
-        int d = 1 * row + i;
-        idx.push_back(a); idx.push_back(b); idx.push_back(c);
-        idx.push_back(c); idx.push_back(d); idx.push_back(a);
-    }
-    // Top cap
-    int baseTop = (int)v.size();
-    v.push_back({{0.0f, halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
-    for (int i = 0; i <= radial; ++i) {
-        float u = float(i) / float(radial);
-        float ang = u * 6.28318530718f;
-        float px = radius * std::cos(ang);
-        float pz = radius * std::sin(ang);
-        v.push_back({{px, halfH, pz}, {0.0f, 1.0f, 0.0f},
-                     {0.5f + 0.5f*std::cos(ang), 0.5f + 0.5f*std::sin(ang)}});
-    }
-    for (int i = 1; i <= radial; ++i) {
-        idx.push_back(baseTop); idx.push_back(baseTop + i); idx.push_back(baseTop + i + 1);
-    }
-    // Bottom cap
-    int baseBot = (int)v.size();
-    v.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
+static Mesh *createUnitCylinderMesh() {
+  const int radial = 24;
+  const float radius = 1.0f;
+  const float halfH = 0.5f;
+  std::vector<Vertex> v;
+  std::vector<unsigned int> idx;
+
+  for (int y = 0; y <= 1; ++y) {
+    float py = y ? halfH : -halfH;
+    float vCoord = float(y);
     for (int i = 0; i <= radial; ++i) {
-        float u = float(i) / float(radial);
-        float ang = u * 6.28318530718f;
-        float px = radius * std::cos(ang);
-        float pz = radius * std::sin(ang);
-        v.push_back({{px, -halfH, pz}, {0.0f, -1.0f, 0.0f},
-                     {0.5f + 0.5f*std::cos(ang), 0.5f + 0.5f*std::sin(ang)}});
-    }
-    for (int i = 1; i <= radial; ++i) {
-        idx.push_back(baseBot); idx.push_back(baseBot + i + 1); idx.push_back(baseBot + i);
+      float u = float(i) / float(radial);
+      float ang = u * 6.28318530718f;
+      float px = radius * std::cos(ang);
+      float pz = radius * std::sin(ang);
+      QVector3D n(px, 0.0f, pz);
+      n.normalize();
+      v.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {u, vCoord}});
     }
-    return new Mesh(v, idx);
+  }
+  int row = radial + 1;
+
+  for (int i = 0; i < radial; ++i) {
+    int a = 0 * row + i;
+    int b = 0 * row + i + 1;
+    int c = 1 * row + i + 1;
+    int d = 1 * row + i;
+    idx.push_back(a);
+    idx.push_back(b);
+    idx.push_back(c);
+    idx.push_back(c);
+    idx.push_back(d);
+    idx.push_back(a);
+  }
+
+  int baseTop = (int)v.size();
+  v.push_back({{0.0f, halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
+  for (int i = 0; i <= radial; ++i) {
+    float u = float(i) / float(radial);
+    float ang = u * 6.28318530718f;
+    float px = radius * std::cos(ang);
+    float pz = radius * std::sin(ang);
+    v.push_back({{px, halfH, pz},
+                 {0.0f, 1.0f, 0.0f},
+                 {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
+  }
+  for (int i = 1; i <= radial; ++i) {
+    idx.push_back(baseTop);
+    idx.push_back(baseTop + i);
+    idx.push_back(baseTop + i + 1);
+  }
+
+  int baseBot = (int)v.size();
+  v.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
+  for (int i = 0; i <= radial; ++i) {
+    float u = float(i) / float(radial);
+    float ang = u * 6.28318530718f;
+    float px = radius * std::cos(ang);
+    float pz = radius * std::sin(ang);
+    v.push_back({{px, -halfH, pz},
+                 {0.0f, -1.0f, 0.0f},
+                 {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
+  }
+  for (int i = 1; i <= radial; ++i) {
+    idx.push_back(baseBot);
+    idx.push_back(baseBot + i + 1);
+    idx.push_back(baseBot + i);
+  }
+  return new Mesh(v, idx);
 }
 
-static Mesh* createUnitSphereMesh() {
-    const int lat = 12;   // keep low-poly, scalable
-    const int lon = 24;
-    const float r = 1.0f;
-    std::vector<Vertex> v;
-    std::vector<unsigned int> idx;
-
-    for (int y = 0; y <= lat; ++y) {
-        float vy = float(y) / float(lat);
-        float phi = vy * 3.1415926535f;
-        float py = r * std::cos(phi - 1.57079632679f); // center at origin
-        float pr = r * std::sin(phi);
-        for (int x = 0; x <= lon; ++x) {
-            float vx = float(x) / float(lon);
-            float theta = vx * 6.28318530718f;
-            float px = pr * std::cos(theta);
-            float pz = pr * std::sin(theta);
-            QVector3D n(px, py, pz);
-            n.normalize();
-            v.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {vx, vy}});
-        }
+static Mesh *createUnitSphereMesh() {
+  const int lat = 12;
+  const int lon = 24;
+  const float r = 1.0f;
+  std::vector<Vertex> v;
+  std::vector<unsigned int> idx;
+
+  for (int y = 0; y <= lat; ++y) {
+    float vy = float(y) / float(lat);
+    float phi = vy * 3.1415926535f;
+    float py = r * std::cos(phi - 1.57079632679f);
+    float pr = r * std::sin(phi);
+    for (int x = 0; x <= lon; ++x) {
+      float vx = float(x) / float(lon);
+      float theta = vx * 6.28318530718f;
+      float px = pr * std::cos(theta);
+      float pz = pr * std::sin(theta);
+      QVector3D n(px, py, pz);
+      n.normalize();
+      v.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {vx, vy}});
     }
-    int row = lon + 1;
-    for (int y = 0; y < lat; ++y) {
-        for (int x = 0; x < lon; ++x) {
-            int a = y * row + x;
-            int b = a + 1;
-            int c = (y + 1) * row + x + 1;
-            int d = (y + 1) * row + x;
-            idx.push_back(a); idx.push_back(b); idx.push_back(c);
-            idx.push_back(c); idx.push_back(d); idx.push_back(a);
-        }
+  }
+  int row = lon + 1;
+  for (int y = 0; y < lat; ++y) {
+    for (int x = 0; x < lon; ++x) {
+      int a = y * row + x;
+      int b = a + 1;
+      int c = (y + 1) * row + x + 1;
+      int d = (y + 1) * row + x;
+      idx.push_back(a);
+      idx.push_back(b);
+      idx.push_back(c);
+      idx.push_back(c);
+      idx.push_back(d);
+      idx.push_back(a);
     }
-    return new Mesh(v, idx);
+  }
+  return new Mesh(v, idx);
 }
 
-static Mesh* createUnitConeMesh() {
-    const int radial = 24;
-    const float baseR = 1.0f;   // unit
-    const float halfH  = 0.5f;  // apex at +0.5, base at -0.5
-    std::vector<Vertex> v;
-    std::vector<unsigned int> idx;
-
-    // Apex
-    int apexIdx = 0;
-    v.push_back({{0.0f, +halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 1.0f}});
-    // Side ring
-    for (int i = 0; i <= radial; ++i) {
-        float u = float(i) / float(radial);
-        float ang = u * 6.28318530718f;
-        float px = baseR * std::cos(ang);
-        float pz = baseR * std::sin(ang);
-        // Side normal: from slant (approx)
-        QVector3D n(px, baseR, pz); n.normalize();
-        v.push_back({{px, -halfH, pz}, {n.x(), n.y(), n.z()}, {u, 0.0f}});
-    }
-    // Sides
-    for (int i = 1; i <= radial; ++i) {
-        int a = apexIdx;
-        int b = i;
-        int c = i + 1;
-        idx.push_back(a); idx.push_back(b); idx.push_back(c);
-    }
-    // Base center
-    int baseCenter = (int)v.size();
-    v.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
-    // Base rim (again, for clean UV)
-    int baseStart = (int)v.size();
-    for (int i = 0; i <= radial; ++i) {
-        float u = float(i) / float(radial);
-        float ang = u * 6.28318530718f;
-        float px = baseR * std::cos(ang);
-        float pz = baseR * std::sin(ang);
-        v.push_back({{px, -halfH, pz}, {0.0f, -1.0f, 0.0f},
-                     {0.5f + 0.5f*std::cos(ang), 0.5f + 0.5f*std::sin(ang)}});
-    }
-    for (int i = 0; i < radial; ++i) {
-        idx.push_back(baseCenter); idx.push_back(baseStart + i + 1); idx.push_back(baseStart + i);
-    }
-    return new Mesh(v, idx);
+static Mesh *createUnitConeMesh() {
+  const int radial = 24;
+  const float baseR = 1.0f;
+  const float halfH = 0.5f;
+  std::vector<Vertex> v;
+  std::vector<unsigned int> idx;
+
+  int apexIdx = 0;
+  v.push_back({{0.0f, +halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 1.0f}});
+
+  for (int i = 0; i <= radial; ++i) {
+    float u = float(i) / float(radial);
+    float ang = u * 6.28318530718f;
+    float px = baseR * std::cos(ang);
+    float pz = baseR * std::sin(ang);
+
+    QVector3D n(px, baseR, pz);
+    n.normalize();
+    v.push_back({{px, -halfH, pz}, {n.x(), n.y(), n.z()}, {u, 0.0f}});
+  }
+
+  for (int i = 1; i <= radial; ++i) {
+    int a = apexIdx;
+    int b = i;
+    int c = i + 1;
+    idx.push_back(a);
+    idx.push_back(b);
+    idx.push_back(c);
+  }
+
+  int baseCenter = (int)v.size();
+  v.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
+
+  int baseStart = (int)v.size();
+  for (int i = 0; i <= radial; ++i) {
+    float u = float(i) / float(radial);
+    float ang = u * 6.28318530718f;
+    float px = baseR * std::cos(ang);
+    float pz = baseR * std::sin(ang);
+    v.push_back({{px, -halfH, pz},
+                 {0.0f, -1.0f, 0.0f},
+                 {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
+  }
+  for (int i = 0; i < radial; ++i) {
+    idx.push_back(baseCenter);
+    idx.push_back(baseStart + i + 1);
+    idx.push_back(baseStart + i);
+  }
+  return new Mesh(v, idx);
 }
 
-// Your original capsule for the torso
-static Mesh* createCapsuleMesh() {
-    const int radial = 24;
-    const int heightSegments = 1;
-    const float radius = 0.25f;
-    const float halfH = 0.5f;
-    std::vector<Vertex> verts;
-    std::vector<unsigned int> idx;
-    // Sides
-    for (int y = 0; y <= heightSegments; ++y) {
-        float v = float(y) / float(heightSegments);
-        float py = -halfH + v * (2.0f * halfH);
-        for (int i = 0; i <= radial; ++i) {
-            float u = float(i) / float(radial);
-            float ang = u * 6.2831853f;
-            float px = radius * std::cos(ang);
-            float pz = radius * std::sin(ang);
-            QVector3D n(px, 0.0f, pz);
-            n.normalize();
-            verts.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {u, v}});
-        }
-    }
-    int row = radial + 1;
-    for (int y = 0; y < heightSegments; ++y) {
-        for (int i = 0; i < radial; ++i) {
-            int a = y * row + i;
-            int b = y * row + i + 1;
-            int c = (y + 1) * row + i + 1;
-            int d = (y + 1) * row + i;
-            idx.push_back(a); idx.push_back(b); idx.push_back(c);
-            idx.push_back(c); idx.push_back(d); idx.push_back(a);
-        }
-    }
-    // Top cap
-    int baseTop = (int)verts.size();
-    verts.push_back({{0.0f, halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
+static Mesh *createCapsuleMesh() {
+  const int radial = 24;
+  const int heightSegments = 1;
+  const float radius = 0.25f;
+  const float halfH = 0.5f;
+  std::vector<Vertex> verts;
+  std::vector<unsigned int> idx;
+
+  for (int y = 0; y <= heightSegments; ++y) {
+    float v = float(y) / float(heightSegments);
+    float py = -halfH + v * (2.0f * halfH);
     for (int i = 0; i <= radial; ++i) {
-        float u = float(i) / float(radial);
-        float ang = u * 6.2831853f;
-        float px = radius * std::cos(ang);
-        float pz = radius * std::sin(ang);
-        verts.push_back({{px, halfH, pz}, {0.0f, 1.0f, 0.0f}, {0.5f + 0.5f*std::cos(ang), 0.5f + 0.5f*std::sin(ang)}});
+      float u = float(i) / float(radial);
+      float ang = u * 6.2831853f;
+      float px = radius * std::cos(ang);
+      float pz = radius * std::sin(ang);
+      QVector3D n(px, 0.0f, pz);
+      n.normalize();
+      verts.push_back({{px, py, pz}, {n.x(), n.y(), n.z()}, {u, v}});
     }
-    for (int i = 1; i <= radial; ++i) {
-        idx.push_back(baseTop); idx.push_back(baseTop + i); idx.push_back(baseTop + i + 1);
-    }
-    // Bottom cap
-    int baseBot = (int)verts.size();
-    verts.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
-    for (int i = 0; i <= radial; ++i) {
-        float u = float(i) / float(radial);
-        float ang = u * 6.2831853f;
-        float px = radius * std::cos(ang);
-        float pz = radius * std::sin(ang);
-        verts.push_back({{px, -halfH, pz}, {0.0f, -1.0f, 0.0f}, {0.5f + 0.5f*std::cos(ang), 0.5f + 0.5f*std::sin(ang)}});
-    }
-    for (int i = 1; i <= radial; ++i) {
-        idx.push_back(baseBot); idx.push_back(baseBot + i + 1); idx.push_back(baseBot + i);
+  }
+  int row = radial + 1;
+  for (int y = 0; y < heightSegments; ++y) {
+    for (int i = 0; i < radial; ++i) {
+      int a = y * row + i;
+      int b = y * row + i + 1;
+      int c = (y + 1) * row + i + 1;
+      int d = (y + 1) * row + i;
+      idx.push_back(a);
+      idx.push_back(b);
+      idx.push_back(c);
+      idx.push_back(c);
+      idx.push_back(d);
+      idx.push_back(a);
     }
-    return new Mesh(verts, idx);
+  }
+
+  int baseTop = (int)verts.size();
+  verts.push_back({{0.0f, halfH, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
+  for (int i = 0; i <= radial; ++i) {
+    float u = float(i) / float(radial);
+    float ang = u * 6.2831853f;
+    float px = radius * std::cos(ang);
+    float pz = radius * std::sin(ang);
+    verts.push_back(
+        {{px, halfH, pz},
+         {0.0f, 1.0f, 0.0f},
+         {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
+  }
+  for (int i = 1; i <= radial; ++i) {
+    idx.push_back(baseTop);
+    idx.push_back(baseTop + i);
+    idx.push_back(baseTop + i + 1);
+  }
+
+  int baseBot = (int)verts.size();
+  verts.push_back({{0.0f, -halfH, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.5f, 0.5f}});
+  for (int i = 0; i <= radial; ++i) {
+    float u = float(i) / float(radial);
+    float ang = u * 6.2831853f;
+    float px = radius * std::cos(ang);
+    float pz = radius * std::sin(ang);
+    verts.push_back(
+        {{px, -halfH, pz},
+         {0.0f, -1.0f, 0.0f},
+         {0.5f + 0.5f * std::cos(ang), 0.5f + 0.5f * std::sin(ang)}});
+  }
+  for (int i = 1; i <= radial; ++i) {
+    idx.push_back(baseBot);
+    idx.push_back(baseBot + i + 1);
+    idx.push_back(baseBot + i);
+  }
+  return new Mesh(verts, idx);
 }
 
-// Mesh accessors (cached)
-static Mesh* getUnitCylinder() {
-    static std::unique_ptr<Mesh> m(createUnitCylinderMesh());
-    return m.get();
+static Mesh *getUnitCylinder() {
+  static std::unique_ptr<Mesh> m(createUnitCylinderMesh());
+  return m.get();
 }
-static Mesh* getUnitSphere() {
-    static std::unique_ptr<Mesh> m(createUnitSphereMesh());
-    return m.get();
+static Mesh *getUnitSphere() {
+  static std::unique_ptr<Mesh> m(createUnitSphereMesh());
+  return m.get();
 }
-static Mesh* getUnitCone() {
-    static std::unique_ptr<Mesh> m(createUnitConeMesh());
-    return m.get();
+static Mesh *getUnitCone() {
+  static std::unique_ptr<Mesh> m(createUnitConeMesh());
+  return m.get();
 }
-static Mesh* getArcherCapsule() {
-    static std::unique_ptr<Mesh> m(createCapsuleMesh());
-    return m.get();
+static Mesh *getArcherCapsule() {
+  static std::unique_ptr<Mesh> m(createCapsuleMesh());
+  return m.get();
 }
 
-// ------------------------- Transform helpers -------------------------
-
-static inline float clamp01(float x){ return std::max(0.0f, std::min(1.0f, x)); }
-
-// Build a matrix that positions a unit Y-aligned cylinder between two points.
-// Unit cylinder is radius=1, height=1 spanning y ∈ [-0.5, +0.5].
-static QMatrix4x4 cylinderBetween(const QVector3D& a, const QVector3D& b, float radius) {
-    QVector3D mid = (a + b) * 0.5f;
-    QVector3D dir = b - a;
-    float len = dir.length();
-    QMatrix4x4 M;
-    M.translate(mid);
-    if (len > 1e-6f) {
-        QVector3D yAxis(0,1,0);
-        QVector3D d = dir / len;
-        float dot = std::clamp(QVector3D::dotProduct(yAxis, d), -1.0f, 1.0f);
-        float angleDeg = std::acos(dot) * 57.2957795131f;
-        QVector3D axis = QVector3D::crossProduct(yAxis, d);
-        if (axis.lengthSquared() < 1e-6f) {
-            if (dot < 0.0f) M.rotate(180.0f, 1.0f, 0.0f, 0.0f);
-        } else {
-            axis.normalize();
-            M.rotate(angleDeg, axis);
-        }
-        M.scale(radius, len, radius);
+static inline float clamp01(float x) {
+  return std::max(0.0f, std::min(1.0f, x));
+}
+
+static QMatrix4x4 cylinderBetween(const QVector3D &a, const QVector3D &b,
+                                  float radius) {
+  QVector3D mid = (a + b) * 0.5f;
+  QVector3D dir = b - a;
+  float len = dir.length();
+  QMatrix4x4 M;
+  M.translate(mid);
+  if (len > 1e-6f) {
+    QVector3D yAxis(0, 1, 0);
+    QVector3D d = dir / len;
+    float dot = std::clamp(QVector3D::dotProduct(yAxis, d), -1.0f, 1.0f);
+    float angleDeg = std::acos(dot) * 57.2957795131f;
+    QVector3D axis = QVector3D::crossProduct(yAxis, d);
+    if (axis.lengthSquared() < 1e-6f) {
+      if (dot < 0.0f)
+        M.rotate(180.0f, 1.0f, 0.0f, 0.0f);
     } else {
-        M.scale(radius, 1.0f, radius);
+      axis.normalize();
+      M.rotate(angleDeg, axis);
     }
-    return M;
+    M.scale(radius, len, radius);
+  } else {
+    M.scale(radius, 1.0f, radius);
+  }
+  return M;
 }
 
-// Place a unit sphere at pos with given radius
-static QMatrix4x4 sphereAt(const QVector3D& pos, float radius) {
-    QMatrix4x4 M;
-    M.translate(pos);
-    M.scale(radius, radius, radius);
-    return M;
+static QMatrix4x4 sphereAt(const QVector3D &pos, float radius) {
+  QMatrix4x4 M;
+  M.translate(pos);
+  M.scale(radius, radius, radius);
+  return M;
 }
 
-// Place a unit cone whose base center is at 'baseCenter' and apex at 'apex', with base radius
-static QMatrix4x4 coneFromTo(const QVector3D& baseCenter, const QVector3D& apex, float baseRadius) {
-    return cylinderBetween(baseCenter, apex, baseRadius); // same orient/scale, but with cone mesh
+static QMatrix4x4 coneFromTo(const QVector3D &baseCenter, const QVector3D &apex,
+                             float baseRadius) {
+  return cylinderBetween(baseCenter, apex, baseRadius);
 }
 
-// ------------------------- Archer draw -------------------------
-
-void registerArcherRenderer(EntityRendererRegistry& registry) {
-    registry.registerRenderer("archer", [](const DrawContext& p, ISubmitter& out){
-
-        // --------- Colors (team-tinted outfit + readable materials) ----------
-        QVector3D tunic(0.8f, 0.9f, 1.0f);
-        Engine::Core::UnitComponent* unit = nullptr;
-        Engine::Core::RenderableComponent* rc = nullptr;
-        if (p.entity) {
-            unit = p.entity->getComponent<Engine::Core::UnitComponent>();
-            rc = p.entity->getComponent<Engine::Core::RenderableComponent>();
-        }
-        if (unit && unit->ownerId > 0) {
-            tunic = Game::Visuals::teamColorForOwner(unit->ownerId);
-        } else if (rc) {
-            tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
-        }
-        auto tint = [&](float k)->QVector3D{
-            // Lift/darken the team color slightly for readable materials
-            return QVector3D(clamp01(tunic.x()*k), clamp01(tunic.y()*k), clamp01(tunic.z()*k));
-        };
-
-        const QVector3D skin (0.96f, 0.80f, 0.69f);
-        const QVector3D leather(0.35f, 0.22f, 0.12f);
-        const QVector3D wood  (0.16f, 0.10f, 0.05f);
-        const QVector3D metal (0.65f, 0.66f, 0.70f);
-        const QVector3D fletch = tint(0.9f);
-
-        // --------- Base pose anchors (local space; X right, Y up, Z forward) ----------
-        // Torso is your capsule centered at y ≈ 0; top ~ +0.5
-        const QVector3D headPos(0.0f, 0.82f, 0.0f);
-        const QVector3D hipL(-0.12f, -0.45f,  0.03f);
-        const QVector3D hipR( 0.12f, -0.45f, -0.03f);
-        const QVector3D footL(-0.14f, -1.00f,  0.06f);
-        const QVector3D footR( 0.14f, -1.00f, -0.06f);
-
-        const QVector3D shoulderL(-0.18f,  0.35f, 0.00f);
-        const QVector3D shoulderR( 0.18f,  0.35f, 0.00f);
-
-        // Extended left arm holding bow
-        const QVector3D elbowL(-0.45f,  0.27f, 0.02f);
-        const QVector3D handL (-0.60f,  0.20f, 0.05f);
-
-        // Right arm drawing string near face
-        const QVector3D elbowR( 0.36f,  0.35f, 0.10f);
-        const QVector3D handR ( 0.25f,  0.45f, 0.15f);
-
-        // Bow vertical extents (near left hand)
-        const float bowTopY = 0.65f;
-        const float bowBotY = -0.15f;
-        const float bowX    = -0.60f;
-        const float bowZMid = 0.05f;
-        const float bowDepth= 0.10f; // curvature outward in +Z
-        const float bowRodR = 0.02f;
-        const float stringR = 0.006f;
-
-        // --------- Draw torso ---------
-        out.mesh(getArcherCapsule(), p.model, tint(0.8f), nullptr, 1.0f); // tunic
-
-        // --------- Head ---------
-        {
-            QMatrix4x4 M = p.model * sphereAt(headPos, 0.18f);
-            out.mesh(getUnitSphere(), M, skin, nullptr, 1.0f);
-        }
-
-        // --------- Legs (single-bone cylinders) ---------
-        {
-            QMatrix4x4 ML = p.model * cylinderBetween(hipL, footL, 0.08f);
-            QMatrix4x4 MR = p.model * cylinderBetween(hipR, footR, 0.08f);
-            out.mesh(getUnitCylinder(), ML, leather, nullptr, 1.0f);
-            out.mesh(getUnitCylinder(), MR, leather, nullptr, 1.0f);
-        }
-
-        // --------- Arms (upper + forearm) ---------
-        {
-            // Left
-            QMatrix4x4 M1 = p.model * cylinderBetween(shoulderL, elbowL, 0.06f);
-            QMatrix4x4 M2 = p.model * cylinderBetween(elbowL,    handL,  0.055f);
-            out.mesh(getUnitCylinder(), M1, leather, nullptr, 1.0f);
-            out.mesh(getUnitCylinder(), M2, leather, nullptr, 1.0f);
-
-            // Right
-            QMatrix4x4 M3 = p.model * cylinderBetween(shoulderR, elbowR, 0.06f);
-            QMatrix4x4 M4 = p.model * cylinderBetween(elbowR,    handR,  0.055f);
-            out.mesh(getUnitCylinder(), M3, leather, nullptr, 1.0f);
-            out.mesh(getUnitCylinder(), M4, leather, nullptr, 1.0f);
-
-            // Hands
-            out.mesh(getUnitSphere(), p.model * sphereAt(handL, 0.07f), skin, nullptr, 1.0f);
-            out.mesh(getUnitSphere(), p.model * sphereAt(handR, 0.07f), skin, nullptr, 1.0f);
-        }
-
-        // --------- Quiver on the back ---------
-        {
-            QVector3D qTop ( -0.05f, 0.85f, -0.28f);
-            QVector3D qBase(-0.10f, 0.25f, -0.22f);
-            QMatrix4x4 MQ = p.model * cylinderBetween(qBase, qTop, 0.06f);
-            out.mesh(getUnitCylinder(), MQ, leather, nullptr, 1.0f);
-
-            // A couple of arrow tops (fletching) sticking out
-            QVector3D a1 = qTop + QVector3D(0.00f, 0.06f, 0.00f);
-            QMatrix4x4 Mshaft1 = p.model * cylinderBetween(qTop, a1, 0.01f);
-            out.mesh(getUnitCylinder(), Mshaft1, wood, nullptr, 1.0f);
-            out.mesh(getUnitCone(), p.model * coneFromTo(a1, a1 + QVector3D(0.0f, 0.06f, 0.0f), 0.03f), fletch, nullptr, 1.0f);
-
-            QVector3D a2 = qTop + QVector3D(0.02f, 0.05f, 0.02f);
-            QMatrix4x4 Mshaft2 = p.model * cylinderBetween(qTop, a2, 0.01f);
-            out.mesh(getUnitCylinder(), Mshaft2, wood, nullptr, 1.0f);
-            out.mesh(getUnitCone(), p.model * coneFromTo(a2, a2 + QVector3D(0.0f, 0.06f, 0.0f), 0.03f), fletch, nullptr, 1.0f);
-        }
-
-        // --------- Bow (poly-arc built from short cylinders) ---------
-        {
-            const int segs = 12;
-            std::vector<QVector3D> bowPts; bowPts.reserve(segs+1);
-            for (int i = 0; i <= segs; ++i) {
-                float t = float(i) / float(segs);           // 0..1 bottom->top
-                float y = bowBotY + t * (bowTopY - bowBotY);
-                // gentle outward curve in +Z; zero at ends, max in middle
-                float z = bowZMid + bowDepth * std::sin((t - 0.5f) * 3.14159265f);
-                bowPts.push_back(QVector3D(bowX, y, z));
-            }
-            for (int i = 0; i < segs; ++i) {
-                QMatrix4x4 Mb = p.model * cylinderBetween(bowPts[i], bowPts[i+1], bowRodR);
-                out.mesh(getUnitCylinder(), Mb, wood, nullptr, 1.0f);
-            }
-
-            // Bowstring: pulled to right hand (two straight segments)
-            QVector3D topEnd = bowPts.back();
-            QVector3D botEnd = bowPts.front();
-            QMatrix4x4 Ms1 = p.model * cylinderBetween(topEnd, handR, stringR);
-            QMatrix4x4 Ms2 = p.model * cylinderBetween(handR,  botEnd, stringR);
-            out.mesh(getUnitCylinder(), Ms1, metal, nullptr, 1.0f);
-            out.mesh(getUnitCylinder(), Ms2, metal, nullptr, 1.0f);
-        }
-
-        // --------- Nocked arrow on the string ---------
-        {
-            QVector3D tail = handR + QVector3D(-0.05f, 0.0f, 0.0f);
-            QVector3D tip  = tail + QVector3D(0.0f, 0.0f, 0.70f); // points forward (+Z)
-            QMatrix4x4 Mshaft = p.model * cylinderBetween(tail, tip, 0.01f);
-            out.mesh(getUnitCylinder(), Mshaft, wood, nullptr, 1.0f);
-
-            // Arrowhead cone
-            float headLen = 0.08f;
-            QVector3D headBase = tip - QVector3D(0.0f, 0.0f, headLen);
-            QMatrix4x4 Mhead = p.model * coneFromTo(headBase, tip, 0.03f);
-            out.mesh(getUnitCone(), Mhead, metal, nullptr, 1.0f);
-
-            // Fletching (two tiny cones) at tail
-            QVector3D f1b = tail - QVector3D(0.0f, 0.0f, 0.02f);
-            QVector3D f1a = f1b - QVector3D(0.0f, 0.0f, 0.05f);
-            out.mesh(getUnitCone(), p.model * coneFromTo(f1b, f1a, 0.025f), fletch, nullptr, 1.0f);
-
-            QVector3D f2b = tail + QVector3D(0.0f, 0.0f, 0.02f);
-            QVector3D f2a = f2b + QVector3D(0.0f, 0.0f, 0.05f);
-            out.mesh(getUnitCone(), p.model * coneFromTo(f2a, f2b, 0.025f), fletch, nullptr, 1.0f);
-        }
-
-        // --------- Selection/hover rings (unchanged) ---------
-        if (p.selected) {
-            QMatrix4x4 ringM;
-            QVector3D pos = p.model.column(3).toVector3D();
-            ringM.translate(pos.x(), 0.01f, pos.z());
-            ringM.scale(0.5f, 1.0f, 0.5f);
-            out.selectionRing(ringM, 0.6f, 0.25f, QVector3D(0.2f, 0.8f, 0.2f));
-        }
-        if (p.hovered && !p.selected) {
-            QMatrix4x4 ringM;
-            QVector3D pos = p.model.column(3).toVector3D();
-            ringM.translate(pos.x(), 0.01f, pos.z());
-            ringM.scale(0.5f, 1.0f, 0.5f);
-            out.selectionRing(ringM, 0.35f, 0.15f, QVector3D(0.90f, 0.90f, 0.25f));
-        }
-    });
+void registerArcherRenderer(EntityRendererRegistry &registry) {
+  registry.registerRenderer("archer", [](const DrawContext &p,
+                                         ISubmitter &out) {
+    QVector3D tunic(0.8f, 0.9f, 1.0f);
+    Engine::Core::UnitComponent *unit = nullptr;
+    Engine::Core::RenderableComponent *rc = nullptr;
+    if (p.entity) {
+      unit = p.entity->getComponent<Engine::Core::UnitComponent>();
+      rc = p.entity->getComponent<Engine::Core::RenderableComponent>();
+    }
+    if (unit && unit->ownerId > 0) {
+      tunic = Game::Visuals::teamColorForOwner(unit->ownerId);
+    } else if (rc) {
+      tunic = QVector3D(rc->color[0], rc->color[1], rc->color[2]);
+    }
+    auto tint = [&](float k) -> QVector3D {
+      return QVector3D(clamp01(tunic.x() * k), clamp01(tunic.y() * k),
+                       clamp01(tunic.z() * k));
+    };
+
+    const QVector3D skin(0.96f, 0.80f, 0.69f);
+    const QVector3D leather(0.35f, 0.22f, 0.12f);
+    const QVector3D wood(0.16f, 0.10f, 0.05f);
+    const QVector3D metal(0.65f, 0.66f, 0.70f);
+    const QVector3D fletch = tint(0.9f);
+
+    const QVector3D headPos(0.0f, 0.82f, 0.0f);
+    const QVector3D hipL(-0.12f, -0.45f, 0.03f);
+    const QVector3D hipR(0.12f, -0.45f, -0.03f);
+    const QVector3D footL(-0.14f, -1.00f, 0.06f);
+    const QVector3D footR(0.14f, -1.00f, -0.06f);
+
+    const QVector3D shoulderL(-0.18f, 0.35f, 0.00f);
+    const QVector3D shoulderR(0.18f, 0.35f, 0.00f);
+
+    const QVector3D elbowL(-0.45f, 0.27f, 0.02f);
+    const QVector3D handL(-0.60f, 0.20f, 0.05f);
+
+    const QVector3D elbowR(0.36f, 0.35f, 0.10f);
+    const QVector3D handR(0.25f, 0.45f, 0.15f);
+
+    const float bowTopY = 0.65f;
+    const float bowBotY = -0.15f;
+    const float bowX = -0.60f;
+    const float bowZMid = 0.05f;
+    const float bowDepth = 0.10f;
+    const float bowRodR = 0.02f;
+    const float stringR = 0.006f;
+
+    out.mesh(getArcherCapsule(), p.model, tint(0.8f), nullptr, 1.0f);
+
+    {
+      QMatrix4x4 M = p.model * sphereAt(headPos, 0.18f);
+      out.mesh(getUnitSphere(), M, skin, nullptr, 1.0f);
+    }
+
+    {
+      QMatrix4x4 ML = p.model * cylinderBetween(hipL, footL, 0.08f);
+      QMatrix4x4 MR = p.model * cylinderBetween(hipR, footR, 0.08f);
+      out.mesh(getUnitCylinder(), ML, leather, nullptr, 1.0f);
+      out.mesh(getUnitCylinder(), MR, leather, nullptr, 1.0f);
+    }
+
+    {
+
+      QMatrix4x4 M1 = p.model * cylinderBetween(shoulderL, elbowL, 0.06f);
+      QMatrix4x4 M2 = p.model * cylinderBetween(elbowL, handL, 0.055f);
+      out.mesh(getUnitCylinder(), M1, leather, nullptr, 1.0f);
+      out.mesh(getUnitCylinder(), M2, leather, nullptr, 1.0f);
+
+      QMatrix4x4 M3 = p.model * cylinderBetween(shoulderR, elbowR, 0.06f);
+      QMatrix4x4 M4 = p.model * cylinderBetween(elbowR, handR, 0.055f);
+      out.mesh(getUnitCylinder(), M3, leather, nullptr, 1.0f);
+      out.mesh(getUnitCylinder(), M4, leather, nullptr, 1.0f);
+
+      out.mesh(getUnitSphere(), p.model * sphereAt(handL, 0.07f), skin, nullptr,
+               1.0f);
+      out.mesh(getUnitSphere(), p.model * sphereAt(handR, 0.07f), skin, nullptr,
+               1.0f);
+    }
+
+    {
+      QVector3D qTop(-0.05f, 0.85f, -0.28f);
+      QVector3D qBase(-0.10f, 0.25f, -0.22f);
+      QMatrix4x4 MQ = p.model * cylinderBetween(qBase, qTop, 0.06f);
+      out.mesh(getUnitCylinder(), MQ, leather, nullptr, 1.0f);
+
+      QVector3D a1 = qTop + QVector3D(0.00f, 0.06f, 0.00f);
+      QMatrix4x4 Mshaft1 = p.model * cylinderBetween(qTop, a1, 0.01f);
+      out.mesh(getUnitCylinder(), Mshaft1, wood, nullptr, 1.0f);
+      out.mesh(getUnitCone(),
+               p.model *
+                   coneFromTo(a1, a1 + QVector3D(0.0f, 0.06f, 0.0f), 0.03f),
+               fletch, nullptr, 1.0f);
+
+      QVector3D a2 = qTop + QVector3D(0.02f, 0.05f, 0.02f);
+      QMatrix4x4 Mshaft2 = p.model * cylinderBetween(qTop, a2, 0.01f);
+      out.mesh(getUnitCylinder(), Mshaft2, wood, nullptr, 1.0f);
+      out.mesh(getUnitCone(),
+               p.model *
+                   coneFromTo(a2, a2 + QVector3D(0.0f, 0.06f, 0.0f), 0.03f),
+               fletch, nullptr, 1.0f);
+    }
+
+    {
+      const int segs = 12;
+      std::vector<QVector3D> bowPts;
+      bowPts.reserve(segs + 1);
+      for (int i = 0; i <= segs; ++i) {
+        float t = float(i) / float(segs);
+        float y = bowBotY + t * (bowTopY - bowBotY);
+
+        float z = bowZMid + bowDepth * std::sin((t - 0.5f) * 3.14159265f);
+        bowPts.push_back(QVector3D(bowX, y, z));
+      }
+      for (int i = 0; i < segs; ++i) {
+        QMatrix4x4 Mb =
+            p.model * cylinderBetween(bowPts[i], bowPts[i + 1], bowRodR);
+        out.mesh(getUnitCylinder(), Mb, wood, nullptr, 1.0f);
+      }
+
+      QVector3D topEnd = bowPts.back();
+      QVector3D botEnd = bowPts.front();
+      QMatrix4x4 Ms1 = p.model * cylinderBetween(topEnd, handR, stringR);
+      QMatrix4x4 Ms2 = p.model * cylinderBetween(handR, botEnd, stringR);
+      out.mesh(getUnitCylinder(), Ms1, metal, nullptr, 1.0f);
+      out.mesh(getUnitCylinder(), Ms2, metal, nullptr, 1.0f);
+    }
+
+    {
+      QVector3D tail = handR + QVector3D(-0.05f, 0.0f, 0.0f);
+      QVector3D tip = tail + QVector3D(0.0f, 0.0f, 0.70f);
+      QMatrix4x4 Mshaft = p.model * cylinderBetween(tail, tip, 0.01f);
+      out.mesh(getUnitCylinder(), Mshaft, wood, nullptr, 1.0f);
+
+      float headLen = 0.08f;
+      QVector3D headBase = tip - QVector3D(0.0f, 0.0f, headLen);
+      QMatrix4x4 Mhead = p.model * coneFromTo(headBase, tip, 0.03f);
+      out.mesh(getUnitCone(), Mhead, metal, nullptr, 1.0f);
+
+      QVector3D f1b = tail - QVector3D(0.0f, 0.0f, 0.02f);
+      QVector3D f1a = f1b - QVector3D(0.0f, 0.0f, 0.05f);
+      out.mesh(getUnitCone(), p.model * coneFromTo(f1b, f1a, 0.025f), fletch,
+               nullptr, 1.0f);
+
+      QVector3D f2b = tail + QVector3D(0.0f, 0.0f, 0.02f);
+      QVector3D f2a = f2b + QVector3D(0.0f, 0.0f, 0.05f);
+      out.mesh(getUnitCone(), p.model * coneFromTo(f2a, f2b, 0.025f), fletch,
+               nullptr, 1.0f);
+    }
+
+    if (p.selected) {
+      QMatrix4x4 ringM;
+      QVector3D pos = p.model.column(3).toVector3D();
+      ringM.translate(pos.x(), 0.01f, pos.z());
+      ringM.scale(0.5f, 1.0f, 0.5f);
+      out.selectionRing(ringM, 0.6f, 0.25f, QVector3D(0.2f, 0.8f, 0.2f));
+    }
+    if (p.hovered && !p.selected) {
+      QMatrix4x4 ringM;
+      QVector3D pos = p.model.column(3).toVector3D();
+      ringM.translate(pos.x(), 0.01f, pos.z());
+      ringM.scale(0.5f, 1.0f, 0.5f);
+      out.selectionRing(ringM, 0.35f, 0.15f, QVector3D(0.90f, 0.90f, 0.25f));
+    }
+  });
 }
 
 } // namespace Render::GL

+ 2 - 3
render/entity/archer_renderer.h

@@ -4,7 +4,6 @@
 
 namespace Render::GL {
 
-// Registers the archer renderer which draws a slender capsule and optional selection ring
-void registerArcherRenderer(EntityRendererRegistry& registry);
+void registerArcherRenderer(EntityRendererRegistry &registry);
 
-} // namespace Render::GL
+}

+ 35 - 28
render/entity/arrow_vfx_renderer.cpp

@@ -1,39 +1,46 @@
 #include "arrow_vfx_renderer.h"
-#include "registry.h"
-#include "../scene_renderer.h"
-#include "../gl/resources.h"
 #include "../../game/systems/arrow_system.h"
+#include "../gl/resources.h"
+#include "../scene_renderer.h"
+#include "registry.h"
 #include <algorithm>
 #include <cmath>
 
 namespace Render::GL {
 
-void renderArrows(Renderer* renderer, ResourceManager* resources, const Game::Systems::ArrowSystem& arrowSystem) {
-    if (!renderer || !resources) return;
-    auto* arrowMesh = resources->arrow();
-    if (!arrowMesh) return;
-    const auto& arrows = arrowSystem.arrows();
-    for (const auto& arrow : arrows) {
-        if (!arrow.active) continue;
-        const QVector3D delta = arrow.end - arrow.start;
-        const float dist = std::max(0.001f, delta.length());
-        QVector3D pos = arrow.start + delta * arrow.t;
-        float h = arrow.arcHeight * 4.0f * arrow.t * (1.0f - arrow.t);
-        pos.setY(pos.y() + h);
-        QMatrix4x4 model;
-        model.translate(pos.x(), pos.y(), pos.z());
-        QVector3D dir = delta.normalized();
-        float yawDeg = std::atan2(dir.x(), dir.z()) * 180.0f / 3.14159265f;
-        model.rotate(yawDeg, QVector3D(0,1,0));
-        float vy = (arrow.end.y() - arrow.start.y()) / dist;
-        float pitchDeg = -std::atan2(vy - (8.0f * arrow.arcHeight * (arrow.t - 0.5f) / dist), 1.0f) * 180.0f / 3.14159265f;
-        model.rotate(pitchDeg, QVector3D(1,0,0));
-        const float zScale = 0.40f;
-        const float xyScale = 0.26f;
-        model.translate(0.0f, 0.0f, -zScale * 0.5f);
-        model.scale(xyScale, xyScale, zScale);
+void renderArrows(Renderer *renderer, ResourceManager *resources,
+                  const Game::Systems::ArrowSystem &arrowSystem) {
+  if (!renderer || !resources)
+    return;
+  auto *arrowMesh = resources->arrow();
+  if (!arrowMesh)
+    return;
+  const auto &arrows = arrowSystem.arrows();
+  for (const auto &arrow : arrows) {
+    if (!arrow.active)
+      continue;
+    const QVector3D delta = arrow.end - arrow.start;
+    const float dist = std::max(0.001f, delta.length());
+    QVector3D pos = arrow.start + delta * arrow.t;
+    float h = arrow.arcHeight * 4.0f * arrow.t * (1.0f - arrow.t);
+    pos.setY(pos.y() + h);
+    QMatrix4x4 model;
+    model.translate(pos.x(), pos.y(), pos.z());
+    QVector3D dir = delta.normalized();
+    float yawDeg = std::atan2(dir.x(), dir.z()) * 180.0f / 3.14159265f;
+    model.rotate(yawDeg, QVector3D(0, 1, 0));
+    float vy = (arrow.end.y() - arrow.start.y()) / dist;
+    float pitchDeg =
+        -std::atan2(vy - (8.0f * arrow.arcHeight * (arrow.t - 0.5f) / dist),
+                    1.0f) *
+        180.0f / 3.14159265f;
+    model.rotate(pitchDeg, QVector3D(1, 0, 0));
+    const float zScale = 0.40f;
+    const float xyScale = 0.26f;
+    model.translate(0.0f, 0.0f, -zScale * 0.5f);
+    model.scale(xyScale, xyScale, zScale);
     renderer->mesh(arrowMesh, model, arrow.color, nullptr, 1.0f);
-    }
+  }
 }
 
 } // namespace Render::GL

+ 14 - 5
render/entity/arrow_vfx_renderer.h

@@ -3,12 +3,21 @@
 #include <QMatrix4x4>
 #include <QVector3D>
 
-namespace Render { namespace GL { class Renderer; class ResourceManager; } }
-namespace Game { namespace Systems { class ArrowSystem; } }
+namespace Render {
+namespace GL {
+class Renderer;
+class ResourceManager;
+} // namespace GL
+} // namespace Render
+namespace Game {
+namespace Systems {
+class ArrowSystem;
+}
+} // namespace Game
 
 namespace Render::GL {
 
-// Draws projectile arrows from ArrowSystem using the shared Renderer/ResourceManager.
-void renderArrows(Renderer* renderer, ResourceManager* resources, const Game::Systems::ArrowSystem& arrowSystem);
+void renderArrows(Renderer *renderer, ResourceManager *resources,
+                  const Game::Systems::ArrowSystem &arrowSystem);
 
-} // namespace Render::GL
+}

+ 188 - 198
render/entity/barracks_renderer.cpp

@@ -1,215 +1,205 @@
 #include "barracks_renderer.h"
-#include "registry.h"
-#include "../gl/resources.h"
-#include "../geom/flag.h"
 #include "../../game/core/component.h"
+#include "../geom/flag.h"
+#include "../gl/resources.h"
+#include "registry.h"
 
 namespace Render::GL {
 
-static void drawBarracks(const DrawContext& p, ISubmitter& out) {
-    if (!p.resources || !p.entity) return;
-    auto* t = p.entity->getComponent<Engine::Core::TransformComponent>();
-    auto* r = p.entity->getComponent<Engine::Core::RenderableComponent>();
-    auto* u = p.entity->getComponent<Engine::Core::UnitComponent>();
-    if (!t || !r) return;
-
-    // Palette
-    const QVector3D stone(0.55f, 0.55f, 0.55f);
-    const QVector3D wood(0.50f, 0.33f, 0.18f);
-    const QVector3D woodDark(0.35f, 0.23f, 0.12f);
-    const QVector3D thatch(0.85f, 0.72f, 0.25f);
-    const QVector3D doorColor(0.25f, 0.18f, 0.10f);
-    const QVector3D crate(0.45f, 0.30f, 0.14f);
-    const QVector3D path(0.60f, 0.58f, 0.50f);
-    const QVector3D team = QVector3D(r->color[0], r->color[1], r->color[2]);
-
-    // Stone foundation slab (slightly larger footprint)
-    QMatrix4x4 foundation = p.model;
-    foundation.translate(0.0f, -0.15f, 0.0f);
-    foundation.scale(1.35f, 0.10f, 1.2f);
-    out.mesh(p.resources->unit(), foundation, stone, p.resources->white(), 1.0f);
-
-    // Wooden walls (main volume)
-    QMatrix4x4 walls = p.model;
-    walls.scale(1.15f, 0.65f, 0.95f);
-    out.mesh(p.resources->unit(), walls, wood, p.resources->white(), 1.0f);
-
-    // Timber beams: vertical corners
-    auto drawBeam = [&](float x, float z) {
-        QMatrix4x4 b = p.model;
-        b.translate(x, 0.20f, z);
-        b.scale(0.06f, 0.75f, 0.06f);
-           out.mesh(p.resources->unit(), b, woodDark, p.resources->white(), 1.0f);
-    };
-    drawBeam( 0.9f,  0.7f);
-    drawBeam(-0.9f,  0.7f);
-    drawBeam( 0.9f, -0.7f);
-    drawBeam(-0.9f, -0.7f);
-
-    // Pitched thatch roof: layered to suggest a gable
-    QMatrix4x4 roofBase = p.model;
-    roofBase.translate(0.0f, 0.55f, 0.0f);
-    roofBase.scale(1.25f, 0.18f, 1.05f);
-    out.mesh(p.resources->unit(), roofBase, thatch, p.resources->white(), 1.0f);
-
-    QMatrix4x4 roofMid = p.model;
-    roofMid.translate(0.0f, 0.72f, 0.0f);
-    roofMid.scale(1.05f, 0.14f, 0.95f);
-    out.mesh(p.resources->unit(), roofMid, thatch, p.resources->white(), 1.0f);
-
-    QMatrix4x4 roofRidge = p.model;
-    roofRidge.translate(0.0f, 0.86f, 0.0f);
-    roofRidge.scale(0.85f, 0.12f, 0.85f);
-    out.mesh(p.resources->unit(), roofRidge, thatch, p.resources->white(), 1.0f);
-
-    // Roof ridge beam
-    QMatrix4x4 ridge = p.model;
-    ridge.translate(0.0f, 0.96f, 0.0f);
-    ridge.scale(1.1f, 0.04f, 0.12f);
-    out.mesh(p.resources->unit(), ridge, woodDark, p.resources->white(), 1.0f);
-
-    // Door at front
-    QMatrix4x4 door = p.model;
-    door.translate(0.0f, -0.02f, 0.62f);
-    door.scale(0.25f, 0.38f, 0.06f);
-    out.mesh(p.resources->unit(), door, doorColor, p.resources->white(), 1.0f);
-
-    // Side annex (shed) on the right
-    QMatrix4x4 annex = p.model;
-    annex.translate(0.95f, -0.05f, -0.15f);
-    annex.scale(0.55f, 0.45f, 0.55f);
-    out.mesh(p.resources->unit(), annex, wood, p.resources->white(), 1.0f);
-
-    QMatrix4x4 annexRoof = p.model;
-    annexRoof.translate(0.95f, 0.30f, -0.15f);
-    annexRoof.scale(0.60f, 0.12f, 0.60f);
-    out.mesh(p.resources->unit(), annexRoof, thatch, p.resources->white(), 1.0f);
-
-    // Chimney on the back-left
-    QMatrix4x4 chimney = p.model;
-    chimney.translate(-0.65f, 0.75f, -0.55f);
-    chimney.scale(0.10f, 0.35f, 0.10f);
-    out.mesh(p.resources->unit(), chimney, stone, p.resources->white(), 1.0f);
-
-    QMatrix4x4 chimneyCap = p.model;
-    chimneyCap.translate(-0.65f, 0.95f, -0.55f);
-    chimneyCap.scale(0.16f, 0.05f, 0.16f);
-    out.mesh(p.resources->unit(), chimneyCap, stone, p.resources->white(), 1.0f);
-
-    // Path stones leading from door
-    auto drawPaver = [&](float ox, float oz, float sx, float sz) {
-        QMatrix4x4 paver = p.model;
-        paver.translate(ox, -0.14f, oz);
-        paver.scale(sx, 0.02f, sz);
+static void drawBarracks(const DrawContext &p, ISubmitter &out) {
+  if (!p.resources || !p.entity)
+    return;
+  auto *t = p.entity->getComponent<Engine::Core::TransformComponent>();
+  auto *r = p.entity->getComponent<Engine::Core::RenderableComponent>();
+  auto *u = p.entity->getComponent<Engine::Core::UnitComponent>();
+  if (!t || !r)
+    return;
+
+  const QVector3D stone(0.55f, 0.55f, 0.55f);
+  const QVector3D wood(0.50f, 0.33f, 0.18f);
+  const QVector3D woodDark(0.35f, 0.23f, 0.12f);
+  const QVector3D thatch(0.85f, 0.72f, 0.25f);
+  const QVector3D doorColor(0.25f, 0.18f, 0.10f);
+  const QVector3D crate(0.45f, 0.30f, 0.14f);
+  const QVector3D path(0.60f, 0.58f, 0.50f);
+  const QVector3D team = QVector3D(r->color[0], r->color[1], r->color[2]);
+
+  QMatrix4x4 foundation = p.model;
+  foundation.translate(0.0f, -0.15f, 0.0f);
+  foundation.scale(1.35f, 0.10f, 1.2f);
+  out.mesh(p.resources->unit(), foundation, stone, p.resources->white(), 1.0f);
+
+  QMatrix4x4 walls = p.model;
+  walls.scale(1.15f, 0.65f, 0.95f);
+  out.mesh(p.resources->unit(), walls, wood, p.resources->white(), 1.0f);
+
+  auto drawBeam = [&](float x, float z) {
+    QMatrix4x4 b = p.model;
+    b.translate(x, 0.20f, z);
+    b.scale(0.06f, 0.75f, 0.06f);
+    out.mesh(p.resources->unit(), b, woodDark, p.resources->white(), 1.0f);
+  };
+  drawBeam(0.9f, 0.7f);
+  drawBeam(-0.9f, 0.7f);
+  drawBeam(0.9f, -0.7f);
+  drawBeam(-0.9f, -0.7f);
+
+  QMatrix4x4 roofBase = p.model;
+  roofBase.translate(0.0f, 0.55f, 0.0f);
+  roofBase.scale(1.25f, 0.18f, 1.05f);
+  out.mesh(p.resources->unit(), roofBase, thatch, p.resources->white(), 1.0f);
+
+  QMatrix4x4 roofMid = p.model;
+  roofMid.translate(0.0f, 0.72f, 0.0f);
+  roofMid.scale(1.05f, 0.14f, 0.95f);
+  out.mesh(p.resources->unit(), roofMid, thatch, p.resources->white(), 1.0f);
+
+  QMatrix4x4 roofRidge = p.model;
+  roofRidge.translate(0.0f, 0.86f, 0.0f);
+  roofRidge.scale(0.85f, 0.12f, 0.85f);
+  out.mesh(p.resources->unit(), roofRidge, thatch, p.resources->white(), 1.0f);
+
+  QMatrix4x4 ridge = p.model;
+  ridge.translate(0.0f, 0.96f, 0.0f);
+  ridge.scale(1.1f, 0.04f, 0.12f);
+  out.mesh(p.resources->unit(), ridge, woodDark, p.resources->white(), 1.0f);
+
+  QMatrix4x4 door = p.model;
+  door.translate(0.0f, -0.02f, 0.62f);
+  door.scale(0.25f, 0.38f, 0.06f);
+  out.mesh(p.resources->unit(), door, doorColor, p.resources->white(), 1.0f);
+
+  QMatrix4x4 annex = p.model;
+  annex.translate(0.95f, -0.05f, -0.15f);
+  annex.scale(0.55f, 0.45f, 0.55f);
+  out.mesh(p.resources->unit(), annex, wood, p.resources->white(), 1.0f);
+
+  QMatrix4x4 annexRoof = p.model;
+  annexRoof.translate(0.95f, 0.30f, -0.15f);
+  annexRoof.scale(0.60f, 0.12f, 0.60f);
+  out.mesh(p.resources->unit(), annexRoof, thatch, p.resources->white(), 1.0f);
+
+  QMatrix4x4 chimney = p.model;
+  chimney.translate(-0.65f, 0.75f, -0.55f);
+  chimney.scale(0.10f, 0.35f, 0.10f);
+  out.mesh(p.resources->unit(), chimney, stone, p.resources->white(), 1.0f);
+
+  QMatrix4x4 chimneyCap = p.model;
+  chimneyCap.translate(-0.65f, 0.95f, -0.55f);
+  chimneyCap.scale(0.16f, 0.05f, 0.16f);
+  out.mesh(p.resources->unit(), chimneyCap, stone, p.resources->white(), 1.0f);
+
+  auto drawPaver = [&](float ox, float oz, float sx, float sz) {
+    QMatrix4x4 paver = p.model;
+    paver.translate(ox, -0.14f, oz);
+    paver.scale(sx, 0.02f, sz);
     out.mesh(p.resources->unit(), paver, path, p.resources->white(), 1.0f);
-    };
-    drawPaver( 0.0f,  0.9f, 0.25f, 0.20f);
-    drawPaver( 0.0f,  1.15f, 0.22f, 0.18f);
-    drawPaver( 0.0f,  1.35f, 0.20f, 0.16f);
-
-    // Crates near the door
-    QMatrix4x4 crate1 = p.model; crate1.translate(0.45f, -0.05f, 0.55f); crate1.scale(0.18f, 0.18f, 0.18f);
-    out.mesh(p.resources->unit(), crate1, crate, p.resources->white(), 1.0f);
-    QMatrix4x4 crate2 = p.model; crate2.translate(0.58f, 0.02f, 0.45f); crate2.scale(0.14f, 0.14f, 0.14f);
-    out.mesh(p.resources->unit(), crate2, crate, p.resources->white(), 1.0f);
-
-    // Simple fence posts on front-left
-    for (int i=0;i<3;++i) {
-        QMatrix4x4 post = p.model;
-        post.translate(-0.85f + 0.18f * i, -0.05f, 0.85f);
-        post.scale(0.05f, 0.25f, 0.05f);
+  };
+  drawPaver(0.0f, 0.9f, 0.25f, 0.20f);
+  drawPaver(0.0f, 1.15f, 0.22f, 0.18f);
+  drawPaver(0.0f, 1.35f, 0.20f, 0.16f);
+
+  QMatrix4x4 crate1 = p.model;
+  crate1.translate(0.45f, -0.05f, 0.55f);
+  crate1.scale(0.18f, 0.18f, 0.18f);
+  out.mesh(p.resources->unit(), crate1, crate, p.resources->white(), 1.0f);
+  QMatrix4x4 crate2 = p.model;
+  crate2.translate(0.58f, 0.02f, 0.45f);
+  crate2.scale(0.14f, 0.14f, 0.14f);
+  out.mesh(p.resources->unit(), crate2, crate, p.resources->white(), 1.0f);
+
+  for (int i = 0; i < 3; ++i) {
+    QMatrix4x4 post = p.model;
+    post.translate(-0.85f + 0.18f * i, -0.05f, 0.85f);
+    post.scale(0.05f, 0.25f, 0.05f);
     out.mesh(p.resources->unit(), post, woodDark, p.resources->white(), 1.0f);
+  }
+
+  QMatrix4x4 pole = p.model;
+  pole.translate(-0.9f, 0.55f, -0.55f);
+  pole.scale(0.04f, 0.8f, 0.04f);
+  out.mesh(p.resources->unit(), pole, woodDark, p.resources->white(), 1.0f);
+
+  QMatrix4x4 finial = p.model;
+  finial.translate(-0.9f, 0.98f, -0.55f);
+  finial.scale(0.08f, 0.08f, 0.08f);
+  out.mesh(p.resources->unit(), finial, QVector3D(0.9f, 0.8f, 0.3f),
+           p.resources->white(), 1.0f);
+
+  QMatrix4x4 banner = p.model;
+  banner.translate(-0.78f, 0.75f, -0.53f);
+  banner.scale(0.28f, 0.32f, 0.02f);
+  out.mesh(p.resources->unit(), banner, team, p.resources->white(), 1.0f);
+
+  QMatrix4x4 bannerTrim = p.model;
+  bannerTrim.translate(-0.78f, 0.75f, -0.51f);
+  bannerTrim.scale(0.30f, 0.04f, 0.01f);
+  out.mesh(p.resources->unit(), bannerTrim,
+           QVector3D(team.x() * 0.6f, team.y() * 0.6f, team.z() * 0.6f),
+           p.resources->white(), 1.0f);
+
+  if (auto *prod =
+          p.entity->getComponent<Engine::Core::ProductionComponent>()) {
+    if (prod->rallySet && p.resources) {
+      auto flag = Render::Geom::Flag::create(prod->rallyX, prod->rallyZ,
+                                             QVector3D(1.0f, 0.9f, 0.2f),
+                                             woodDark, 1.0f);
+
+      out.mesh(p.resources->unit(), flag.pole, flag.poleColor,
+               p.resources->white(), 1.0f);
+      out.mesh(p.resources->unit(), flag.pennant, flag.pennantColor,
+               p.resources->white(), 1.0f);
+      out.mesh(p.resources->unit(), flag.finial, flag.pennantColor,
+               p.resources->white(), 1.0f);
     }
-
-    // Banner pole with team-colored flag (improved proportions)
-    QMatrix4x4 pole = p.model;
-    pole.translate(-0.9f, 0.55f, -0.55f);
-    pole.scale(0.04f, 0.8f, 0.04f);
-    out.mesh(p.resources->unit(), pole, woodDark, p.resources->white(), 1.0f);
-
-    // Flag finial (decorative top)
-    QMatrix4x4 finial = p.model;
-    finial.translate(-0.9f, 0.98f, -0.55f);
-    finial.scale(0.08f, 0.08f, 0.08f);
-    out.mesh(p.resources->unit(), finial, QVector3D(0.9f, 0.8f, 0.3f), p.resources->white(), 1.0f);
-
-    // Team banner (taller and narrower, more flag-like)
-    QMatrix4x4 banner = p.model;
-    banner.translate(-0.78f, 0.75f, -0.53f);
-    banner.scale(0.28f, 0.32f, 0.02f);
-    out.mesh(p.resources->unit(), banner, team, p.resources->white(), 1.0f);
-    
-    // Banner trim/border (darker accent)
-    QMatrix4x4 bannerTrim = p.model;
-    bannerTrim.translate(-0.78f, 0.75f, -0.51f);
-    bannerTrim.scale(0.30f, 0.04f, 0.01f);
-    out.mesh(p.resources->unit(), bannerTrim, QVector3D(team.x() * 0.6f, team.y() * 0.6f, team.z() * 0.6f), p.resources->white(), 1.0f);
-
-    // Rally flag if set (using Flag utility)
-    if (auto* prod = p.entity->getComponent<Engine::Core::ProductionComponent>()) {
-        if (prod->rallySet && p.resources) {
-            auto flag = Render::Geom::Flag::create(
-                prod->rallyX, 
-                prod->rallyZ,
-                QVector3D(1.0f, 0.9f, 0.2f),  // Yellow rally flag
-                woodDark,                      // Dark wood pole
-                1.0f                           // Normal scale
-            );
-            
-            out.mesh(p.resources->unit(), flag.pole, flag.poleColor, p.resources->white(), 1.0f);
-            out.mesh(p.resources->unit(), flag.pennant, flag.pennantColor, p.resources->white(), 1.0f);
-            out.mesh(p.resources->unit(), flag.finial, flag.pennantColor, p.resources->white(), 1.0f);
-        }
+  }
+
+  if (u && u->maxHealth > 0) {
+    float healthRatio = float(u->health) / float(u->maxHealth);
+    QVector3D pos = p.model.column(3).toVector3D();
+
+    QMatrix4x4 bgBar;
+    bgBar.translate(pos.x(), 1.2f, pos.z());
+    bgBar.scale(1.5f, 0.08f, 0.04f);
+    out.mesh(p.resources->unit(), bgBar, QVector3D(0.3f, 0.1f, 0.1f),
+             p.resources->white(), 1.0f);
+
+    QVector3D healthColor;
+    if (healthRatio > 0.6f) {
+      healthColor = QVector3D(0.2f, 0.8f, 0.2f);
+    } else if (healthRatio > 0.3f) {
+      healthColor = QVector3D(0.9f, 0.7f, 0.2f);
+    } else {
+      healthColor = QVector3D(0.9f, 0.2f, 0.2f);
     }
 
-    // Health bar for barracks (positioned above the building)
-    if (u && u->maxHealth > 0) {
-        float healthRatio = float(u->health) / float(u->maxHealth);
-        QVector3D pos = p.model.column(3).toVector3D();
-        
-        // Health bar background (dark red)
-        QMatrix4x4 bgBar;
-        bgBar.translate(pos.x(), 1.2f, pos.z()); // Above the building
-        bgBar.scale(1.5f, 0.08f, 0.04f); // Wide horizontal bar
-        out.mesh(p.resources->unit(), bgBar, QVector3D(0.3f, 0.1f, 0.1f), p.resources->white(), 1.0f);
-        
-        // Health bar foreground (green to red based on health)
-        QVector3D healthColor;
-        if (healthRatio > 0.6f) {
-            healthColor = QVector3D(0.2f, 0.8f, 0.2f); // Green
-        } else if (healthRatio > 0.3f) {
-            healthColor = QVector3D(0.9f, 0.7f, 0.2f); // Orange
-        } else {
-            healthColor = QVector3D(0.9f, 0.2f, 0.2f); // Red
-        }
-        
-        QMatrix4x4 fgBar;
-        fgBar.translate(pos.x() - 0.75f + (0.75f * healthRatio), 1.21f, pos.z());
-        fgBar.scale(1.5f * healthRatio, 0.06f, 0.03f);
-        out.mesh(p.resources->unit(), fgBar, healthColor, p.resources->white(), 1.0f);
-    }
-    
-    // Selection smoke if selected (twice the previous diameter ~ scale *2.0)
-    if (p.selected) {
-        QMatrix4x4 m;
-        QVector3D pos = p.model.column(3).toVector3D();
-        // Force ground-level placement; ignore model's Y height for discs
-        m.translate(pos.x(), 0.0f, pos.z());
-        m.scale(1.8f, 1.0f, 1.8f); // previously ~0.9, now doubled
+    QMatrix4x4 fgBar;
+    fgBar.translate(pos.x() - 0.75f + (0.75f * healthRatio), 1.21f, pos.z());
+    fgBar.scale(1.5f * healthRatio, 0.06f, 0.03f);
+    out.mesh(p.resources->unit(), fgBar, healthColor, p.resources->white(),
+             1.0f);
+  }
+
+  if (p.selected) {
+    QMatrix4x4 m;
+    QVector3D pos = p.model.column(3).toVector3D();
+
+    m.translate(pos.x(), 0.0f, pos.z());
+    m.scale(1.8f, 1.0f, 1.8f);
     out.selectionSmoke(m, QVector3D(0.2f, 0.8f, 0.2f), 0.32f);
-    }
-    // Hover smoke if hovered and not selected (subtle)
-    else if (p.hovered) {
-        QMatrix4x4 m;
-        QVector3D pos = p.model.column(3).toVector3D();
-        m.translate(pos.x(), 0.0f, pos.z());
-        m.scale(1.8f, 1.0f, 1.8f);
+  }
+
+  else if (p.hovered) {
+    QMatrix4x4 m;
+    QVector3D pos = p.model.column(3).toVector3D();
+    m.translate(pos.x(), 0.0f, pos.z());
+    m.scale(1.8f, 1.0f, 1.8f);
     out.selectionSmoke(m, QVector3D(0.90f, 0.90f, 0.25f), 0.20f);
-    }
+  }
 }
 
-void registerBarracksRenderer(EntityRendererRegistry& registry) {
-    registry.registerRenderer("barracks", drawBarracks);
+void registerBarracksRenderer(EntityRendererRegistry &registry) {
+  registry.registerRenderer("barracks", drawBarracks);
 }
 
 } // namespace Render::GL

+ 7 - 3
render/entity/barracks_renderer.h

@@ -1,9 +1,13 @@
 #pragma once
 
-namespace Render { namespace GL { class EntityRendererRegistry; } }
+namespace Render {
+namespace GL {
+class EntityRendererRegistry;
+}
+} // namespace Render
 
 namespace Render::GL {
 
-void registerBarracksRenderer(EntityRendererRegistry& registry);
+void registerBarracksRenderer(EntityRendererRegistry &registry);
 
-} // namespace Render::GL
+}

+ 14 - 12
render/entity/registry.cpp

@@ -1,25 +1,27 @@
 #include "registry.h"
+#include "../../game/core/component.h"
+#include "../../game/core/entity.h"
+#include "../scene_renderer.h"
 #include "archer_renderer.h"
 #include "barracks_renderer.h"
-#include "../scene_renderer.h"
-#include "../../game/core/entity.h"
-#include "../../game/core/component.h"
 
 namespace Render::GL {
 
-void EntityRendererRegistry::registerRenderer(const std::string& type, RenderFunc func) {
-    m_map[type] = std::move(func);
+void EntityRendererRegistry::registerRenderer(const std::string &type,
+                                              RenderFunc func) {
+  m_map[type] = std::move(func);
 }
 
-RenderFunc EntityRendererRegistry::get(const std::string& type) const {
-    auto it = m_map.find(type);
-    if (it != m_map.end()) return it->second;
-    return {};
+RenderFunc EntityRendererRegistry::get(const std::string &type) const {
+  auto it = m_map.find(type);
+  if (it != m_map.end())
+    return it->second;
+  return {};
 }
 
-void registerBuiltInEntityRenderers(EntityRendererRegistry& registry) {
-    registerArcherRenderer(registry);
-    registerBarracksRenderer(registry);
+void registerBuiltInEntityRenderers(EntityRendererRegistry &registry) {
+  registerArcherRenderer(registry);
+  registerBarracksRenderer(registry);
 }
 
 } // namespace Render::GL

+ 26 - 16
render/entity/registry.h

@@ -1,37 +1,47 @@
 #pragma once
 
+#include "../submitter.h"
+#include <QMatrix4x4>
+#include <QVector3D>
 #include <functional>
 #include <memory>
 #include <string>
 #include <unordered_map>
-#include <QMatrix4x4>
-#include <QVector3D>
-#include "../submitter.h"
 
-namespace Engine { namespace Core { class Entity; } }
-namespace Render { namespace GL { class ResourceManager; class Mesh; class Texture; } }
+namespace Engine {
+namespace Core {
+class Entity;
+}
+} // namespace Engine
+namespace Render {
+namespace GL {
+class ResourceManager;
+class Mesh;
+class Texture;
+} // namespace GL
+} // namespace Render
 
 namespace Render::GL {
 
 struct DrawContext {
-    ResourceManager* resources = nullptr;
-    Engine::Core::Entity* entity = nullptr;
-    QMatrix4x4 model;
-    bool selected = false;
-    bool hovered = false;
+  ResourceManager *resources = nullptr;
+  Engine::Core::Entity *entity = nullptr;
+  QMatrix4x4 model;
+  bool selected = false;
+  bool hovered = false;
 };
 
-using RenderFunc = std::function<void(const DrawContext&, ISubmitter& out)>;
+using RenderFunc = std::function<void(const DrawContext &, ISubmitter &out)>;
 
 class EntityRendererRegistry {
 public:
-    void registerRenderer(const std::string& type, RenderFunc func);
-    RenderFunc get(const std::string& type) const;
+  void registerRenderer(const std::string &type, RenderFunc func);
+  RenderFunc get(const std::string &type) const;
+
 private:
-    std::unordered_map<std::string, RenderFunc> m_map;
+  std::unordered_map<std::string, RenderFunc> m_map;
 };
 
-// Registers built-in entity renderers (e.g., archer)
-void registerBuiltInEntityRenderers(EntityRendererRegistry& registry);
+void registerBuiltInEntityRenderers(EntityRendererRegistry &registry);
 
 } // namespace Render::GL

+ 108 - 101
render/geom/arrow.cpp

@@ -1,125 +1,132 @@
 #include "arrow.h"
-#include "../gl/mesh.h"
+#include "../../game/systems/arrow_system.h"
 #include "../entity/registry.h"
-#include "../scene_renderer.h"
+#include "../gl/mesh.h"
 #include "../gl/resources.h"
-#include "../../game/systems/arrow_system.h"
+#include "../scene_renderer.h"
 
-#include <vector>
-#include <cmath>
-#include <algorithm>
-#include <QVector3D>
 #include <QMatrix4x4>
-
+#include <QVector3D>
+#include <algorithm>
+#include <cmath>
+#include <vector>
 
 namespace Render {
 namespace Geom {
 
-static GL::Mesh* createArrowMesh() {
-    using GL::Vertex;
-    std::vector<GL::Vertex> verts;
-    std::vector<unsigned int> idx;
-
-    const int radial = 12;
-    const float shaftRadius = 0.05f;
-    const float shaftLen = 0.85f;
-    const float tipLen = 0.15f;
-    const float tipStartZ = shaftLen;
-    const float tipEndZ = shaftLen + tipLen;
-
-    int baseIndex = 0;
-    for (int ring = 0; ring < 2; ++ring) {
-        float z = (ring == 0) ? 0.0f : shaftLen;
-        for (int i = 0; i < radial; ++i) {
-            float a = (float(i) / radial) * 6.2831853f;
-            float x = std::cos(a) * shaftRadius;
-            float y = std::sin(a) * shaftRadius;
-            QVector3D n(x, y, 0.0f);
-            n.normalize();
-            verts.push_back({{x, y, z}, {n.x(), n.y(), n.z()}, {float(i)/radial, z}});
-        }
-    }
-
-    for (int i = 0; i < radial; ++i) {
-        int next = (i + 1) % radial;
-        int a = baseIndex + i;
-        int b = baseIndex + next;
-        int c = baseIndex + radial + next;
-        int d = baseIndex + radial + i;
-        idx.push_back(a); idx.push_back(b); idx.push_back(c);
-        idx.push_back(c); idx.push_back(d); idx.push_back(a);
-    }
-
-    int ringStart = verts.size();
-    for (int i = 0; i < radial; ++i) {
-        float a = (float(i) / radial) * 6.2831853f;
-        float x = std::cos(a) * shaftRadius * 1.4f;
-        float y = std::sin(a) * shaftRadius * 1.4f;
-        QVector3D n(x, y, 0.2f);
-        n.normalize();
-        verts.push_back({{x, y, tipStartZ}, {n.x(), n.y(), n.z()}, {float(i)/radial, 0.0f}});
-    }
-
-    int apexIndex = verts.size();
-    verts.push_back({{0.0f, 0.0f, tipEndZ}, {0.0f, 0.0f, 1.0f}, {0.5f, 1.0f}});
+static GL::Mesh *createArrowMesh() {
+  using GL::Vertex;
+  std::vector<GL::Vertex> verts;
+  std::vector<unsigned int> idx;
+
+  const int radial = 12;
+  const float shaftRadius = 0.05f;
+  const float shaftLen = 0.85f;
+  const float tipLen = 0.15f;
+  const float tipStartZ = shaftLen;
+  const float tipEndZ = shaftLen + tipLen;
+
+  int baseIndex = 0;
+  for (int ring = 0; ring < 2; ++ring) {
+    float z = (ring == 0) ? 0.0f : shaftLen;
     for (int i = 0; i < radial; ++i) {
-        int next = (i + 1) % radial;
-        idx.push_back(ringStart + i);
-        idx.push_back(apexIndex);
-        idx.push_back(ringStart + next);
+      float a = (float(i) / radial) * 6.2831853f;
+      float x = std::cos(a) * shaftRadius;
+      float y = std::sin(a) * shaftRadius;
+      QVector3D n(x, y, 0.0f);
+      n.normalize();
+      verts.push_back(
+          {{x, y, z}, {n.x(), n.y(), n.z()}, {float(i) / radial, z}});
     }
-
-    return new GL::Mesh(verts, idx);
+  }
+
+  for (int i = 0; i < radial; ++i) {
+    int next = (i + 1) % radial;
+    int a = baseIndex + i;
+    int b = baseIndex + next;
+    int c = baseIndex + radial + next;
+    int d = baseIndex + radial + i;
+    idx.push_back(a);
+    idx.push_back(b);
+    idx.push_back(c);
+    idx.push_back(c);
+    idx.push_back(d);
+    idx.push_back(a);
+  }
+
+  int ringStart = verts.size();
+  for (int i = 0; i < radial; ++i) {
+    float a = (float(i) / radial) * 6.2831853f;
+    float x = std::cos(a) * shaftRadius * 1.4f;
+    float y = std::sin(a) * shaftRadius * 1.4f;
+    QVector3D n(x, y, 0.2f);
+    n.normalize();
+    verts.push_back(
+        {{x, y, tipStartZ}, {n.x(), n.y(), n.z()}, {float(i) / radial, 0.0f}});
+  }
+
+  int apexIndex = verts.size();
+  verts.push_back({{0.0f, 0.0f, tipEndZ}, {0.0f, 0.0f, 1.0f}, {0.5f, 1.0f}});
+  for (int i = 0; i < radial; ++i) {
+    int next = (i + 1) % radial;
+    idx.push_back(ringStart + i);
+    idx.push_back(apexIndex);
+    idx.push_back(ringStart + next);
+  }
+
+  return new GL::Mesh(verts, idx);
 }
 
-GL::Mesh* Arrow::get() {
-    static GL::Mesh* mesh = createArrowMesh();
-    return mesh;
+GL::Mesh *Arrow::get() {
+  static GL::Mesh *mesh = createArrowMesh();
+  return mesh;
 }
 
 } // namespace Geom
 
 namespace GL {
 
-void renderArrows(Renderer* renderer,
-                  ResourceManager* resources,
-                  const Game::Systems::ArrowSystem& arrowSystem) {
-    if (!renderer || !resources) return;
-    auto* arrowMesh = resources->arrow();
-    if (!arrowMesh) return;
-
-    const auto& arrows = arrowSystem.arrows();
-    for (const auto& arrow : arrows) {
-        if (!arrow.active) continue;
-
-        const QVector3D delta = arrow.end - arrow.start;
-        const float dist = std::max(0.001f, delta.length());
-        QVector3D pos = arrow.start + delta * arrow.t;
-        float h = arrow.arcHeight * 4.0f * arrow.t * (1.0f - arrow.t);
-        pos.setY(pos.y() + h);
-
-        QMatrix4x4 model;
-        model.translate(pos.x(), pos.y(), pos.z());
-
-        QVector3D dir = delta.normalized();
-        float yawDeg = std::atan2(dir.x(), dir.z()) * 180.0f / 3.14159265f;
-        model.rotate(yawDeg, QVector3D(0,1,0));
-
-        float vy = (arrow.end.y() - arrow.start.y()) / dist;
-        float pitchDeg = -std::atan2(
-            vy - (8.0f * arrow.arcHeight * (arrow.t - 0.5f) / dist),
-            1.0f
-        ) * 180.0f / 3.14159265f;
-        model.rotate(pitchDeg, QVector3D(1,0,0));
-
-        constexpr float ARROW_Z_SCALE = 0.40f;
-        constexpr float ARROW_XY_SCALE = 0.26f;
-        constexpr float ARROW_Z_TRANSLATE_FACTOR = 0.5f;
-        model.translate(0.0f, 0.0f, -ARROW_Z_SCALE * ARROW_Z_TRANSLATE_FACTOR);
-        model.scale(ARROW_XY_SCALE, ARROW_XY_SCALE, ARROW_Z_SCALE);
+void renderArrows(Renderer *renderer, ResourceManager *resources,
+                  const Game::Systems::ArrowSystem &arrowSystem) {
+  if (!renderer || !resources)
+    return;
+  auto *arrowMesh = resources->arrow();
+  if (!arrowMesh)
+    return;
+
+  const auto &arrows = arrowSystem.arrows();
+  for (const auto &arrow : arrows) {
+    if (!arrow.active)
+      continue;
+
+    const QVector3D delta = arrow.end - arrow.start;
+    const float dist = std::max(0.001f, delta.length());
+    QVector3D pos = arrow.start + delta * arrow.t;
+    float h = arrow.arcHeight * 4.0f * arrow.t * (1.0f - arrow.t);
+    pos.setY(pos.y() + h);
+
+    QMatrix4x4 model;
+    model.translate(pos.x(), pos.y(), pos.z());
+
+    QVector3D dir = delta.normalized();
+    float yawDeg = std::atan2(dir.x(), dir.z()) * 180.0f / 3.14159265f;
+    model.rotate(yawDeg, QVector3D(0, 1, 0));
+
+    float vy = (arrow.end.y() - arrow.start.y()) / dist;
+    float pitchDeg =
+        -std::atan2(vy - (8.0f * arrow.arcHeight * (arrow.t - 0.5f) / dist),
+                    1.0f) *
+        180.0f / 3.14159265f;
+    model.rotate(pitchDeg, QVector3D(1, 0, 0));
+
+    constexpr float ARROW_Z_SCALE = 0.40f;
+    constexpr float ARROW_XY_SCALE = 0.26f;
+    constexpr float ARROW_Z_TRANSLATE_FACTOR = 0.5f;
+    model.translate(0.0f, 0.0f, -ARROW_Z_SCALE * ARROW_Z_TRANSLATE_FACTOR);
+    model.scale(ARROW_XY_SCALE, ARROW_XY_SCALE, ARROW_Z_SCALE);
 
     renderer->mesh(arrowMesh, model, arrow.color, nullptr, 1.0f);
-    }
+  }
 }
 
 } // namespace GL

+ 5 - 6
render/geom/arrow.h

@@ -8,7 +8,7 @@ namespace Render {
 namespace Geom {
 class Arrow {
 public:
-    static GL::Mesh* get();
+  static GL::Mesh *get();
 };
 } // namespace Geom
 
@@ -21,13 +21,12 @@ class ResourceManager;
 namespace Game {
 namespace Systems {
 class ArrowSystem;
-} // namespace Systems
+}
 } // namespace Game
 
 namespace Render {
 namespace GL {
-void renderArrows(Renderer* renderer,
-                  ResourceManager* resources,
-                  const Game::Systems::ArrowSystem& arrowSystem);
-} // namespace GL
+void renderArrows(Renderer *renderer, ResourceManager *resources,
+                  const Game::Systems::ArrowSystem &arrowSystem);
+}
 } // namespace Render

+ 24 - 29
render/geom/flag.cpp

@@ -1,34 +1,29 @@
 #include "flag.h"
 
-namespace Render { namespace Geom {
+namespace Render {
+namespace Geom {
 
-Flag::FlagMatrices Flag::create(
-    float worldX, 
-    float worldZ,
-    const QVector3D& flagColor,
-    const QVector3D& poleColor,
-    float scale
-) {
-    FlagMatrices result;
-    result.pennantColor = flagColor;
-    result.poleColor = poleColor;
-    
-    // Pole (vertical shaft)
-    result.pole.setToIdentity();
-    result.pole.translate(worldX, 0.15f * scale, worldZ);
-    result.pole.scale(0.03f * scale, 0.30f * scale, 0.03f * scale);
-    
-    // Pennant (flag cloth)
-    result.pennant.setToIdentity();
-    result.pennant.translate(worldX + 0.10f * scale, 0.25f * scale, worldZ);
-    result.pennant.scale(0.18f * scale, 0.12f * scale, 0.02f * scale);
-    
-    // Finial (decorative top)
-    result.finial.setToIdentity();
-    result.finial.translate(worldX, 0.32f * scale, worldZ);
-    result.finial.scale(0.05f * scale, 0.05f * scale, 0.05f * scale);
-    
-    return result;
+Flag::FlagMatrices Flag::create(float worldX, float worldZ,
+                                const QVector3D &flagColor,
+                                const QVector3D &poleColor, float scale) {
+  FlagMatrices result;
+  result.pennantColor = flagColor;
+  result.poleColor = poleColor;
+
+  result.pole.setToIdentity();
+  result.pole.translate(worldX, 0.15f * scale, worldZ);
+  result.pole.scale(0.03f * scale, 0.30f * scale, 0.03f * scale);
+
+  result.pennant.setToIdentity();
+  result.pennant.translate(worldX + 0.10f * scale, 0.25f * scale, worldZ);
+  result.pennant.scale(0.18f * scale, 0.12f * scale, 0.02f * scale);
+
+  result.finial.setToIdentity();
+  result.finial.translate(worldX, 0.32f * scale, worldZ);
+  result.finial.scale(0.05f * scale, 0.05f * scale, 0.05f * scale);
+
+  return result;
 }
 
-} } // namespace Render::Geom
+} // namespace Geom
+} // namespace Render

+ 17 - 32
render/geom/flag.h

@@ -3,40 +3,25 @@
 #include <QMatrix4x4>
 #include <QVector3D>
 
-namespace Render { namespace Geom {
+namespace Render {
+namespace Geom {
 
-/**
- * Utility for rendering flag markers at world positions.
- * Used for rally points, patrol waypoints, and other location markers.
- */
 class Flag {
 public:
-    /**
-     * Render a flag at the specified world position.
-     * 
-     * @param worldX X coordinate in world space
-     * @param worldY Y coordinate in world space (height)
-     * @param worldZ Z coordinate in world space
-     * @param color Color of the flag pennant
-     * @param poleColor Color of the pole (defaults to dark wood)
-     * @param scale Overall scale multiplier (default 1.0)
-     * @return Array of matrices: [pole, pennant, finial]
-     */
-    struct FlagMatrices {
-        QMatrix4x4 pole;
-        QMatrix4x4 pennant;
-        QMatrix4x4 finial;
-        QVector3D pennantColor;
-        QVector3D poleColor;
-    };
-    
-    static FlagMatrices create(
-        float worldX, 
-        float worldZ, 
-        const QVector3D& flagColor = QVector3D(1.0f, 0.9f, 0.2f),
-        const QVector3D& poleColor = QVector3D(0.3f, 0.2f, 0.1f),
-        float scale = 1.0f
-    );
+  struct FlagMatrices {
+    QMatrix4x4 pole;
+    QMatrix4x4 pennant;
+    QMatrix4x4 finial;
+    QVector3D pennantColor;
+    QVector3D poleColor;
+  };
+
+  static FlagMatrices
+  create(float worldX, float worldZ,
+         const QVector3D &flagColor = QVector3D(1.0f, 0.9f, 0.2f),
+         const QVector3D &poleColor = QVector3D(0.3f, 0.2f, 0.1f),
+         float scale = 1.0f);
 };
 
-} } // namespace Render::Geom
+} // namespace Geom
+} // namespace Render

+ 64 - 73
render/geom/patrol_flags.cpp

@@ -1,83 +1,74 @@
 #include "patrol_flags.h"
-#include "flag.h"
-#include "../scene_renderer.h"
-#include "../gl/resources.h"
-#include "../../game/core/world.h"
 #include "../../game/core/component.h"
+#include "../../game/core/world.h"
+#include "../gl/resources.h"
+#include "../scene_renderer.h"
+#include "flag.h"
 #include <unordered_set>
 
 namespace Render::GL {
 
-void renderPatrolFlags(
-    Renderer* renderer, 
-    ResourceManager* resources, 
-    Engine::Core::World& world,
-    const std::optional<QVector3D>& previewWaypoint
-) {
-    if (!renderer || !resources) return;
-    
-    // Track unique waypoint positions to avoid rendering duplicate flags
-    // when multiple units patrol the same route
-    std::unordered_set<uint64_t> renderedPositions;
-    
-    // Render preview waypoint (first waypoint during placement) with slightly different appearance
-    if (previewWaypoint.has_value()) {
-        auto flag = Geom::Flag::create(
-            previewWaypoint->x(),
-            previewWaypoint->z(),
-            QVector3D(0.3f, 1.0f, 0.4f),  // Brighter green for preview
-            QVector3D(0.3f, 0.2f, 0.1f),  // Dark wood pole
-            0.9f                           // Slightly larger to indicate it's temporary
-        );
-        
-        renderer->mesh(resources->unit(), flag.pole, flag.poleColor, resources->white(), 0.8f);      // Slightly transparent
-        renderer->mesh(resources->unit(), flag.pennant, flag.pennantColor, resources->white(), 0.8f);
-        renderer->mesh(resources->unit(), flag.finial, flag.pennantColor, resources->white(), 0.8f);
-        
-        // Mark this position as rendered to avoid duplicates
-        int32_t gridX = static_cast<int32_t>(previewWaypoint->x() * 10.0f);
-        int32_t gridZ = static_cast<int32_t>(previewWaypoint->z() * 10.0f);
-        uint64_t posHash = (static_cast<uint64_t>(gridX) << 32) | static_cast<uint64_t>(gridZ);
-        renderedPositions.insert(posHash);
-    }
-    
-    auto patrolEntities = world.getEntitiesWith<Engine::Core::PatrolComponent>();
-    
-    for (auto* entity : patrolEntities) {
-        auto* patrol = entity->getComponent<Engine::Core::PatrolComponent>();
-        if (!patrol || !patrol->patrolling || patrol->waypoints.empty()) continue;
-        
-        // Only render flags for alive units
-        auto* unit = entity->getComponent<Engine::Core::UnitComponent>();
-        if (!unit || unit->health <= 0) continue;
-        
-        // Render flags at each waypoint
-        for (const auto& waypoint : patrol->waypoints) {
-            // Create a simple hash for position deduplication (grid-snapped to 0.1 units)
-            int32_t gridX = static_cast<int32_t>(waypoint.first * 10.0f);
-            int32_t gridZ = static_cast<int32_t>(waypoint.second * 10.0f);
-            uint64_t posHash = (static_cast<uint64_t>(gridX) << 32) | static_cast<uint64_t>(gridZ);
-            
-            // Skip if we've already rendered a flag at this position
-            if (!renderedPositions.insert(posHash).second) {
-                continue;
-            }
-            
-            // Create green patrol flag (different from yellow rally flags)
-            auto flag = Geom::Flag::create(
-                waypoint.first,
-                waypoint.second,
-                QVector3D(0.2f, 0.9f, 0.3f),  // Green flag for patrol
-                QVector3D(0.3f, 0.2f, 0.1f),  // Dark wood pole
-                0.8f                           // Slightly smaller than rally flags
-            );
-            
-            // Submit flag geometry to renderer
-            renderer->mesh(resources->unit(), flag.pole, flag.poleColor, resources->white(), 1.0f);
-            renderer->mesh(resources->unit(), flag.pennant, flag.pennantColor, resources->white(), 1.0f);
-            renderer->mesh(resources->unit(), flag.finial, flag.pennantColor, resources->white(), 1.0f);
-        }
+void renderPatrolFlags(Renderer *renderer, ResourceManager *resources,
+                       Engine::Core::World &world,
+                       const std::optional<QVector3D> &previewWaypoint) {
+  if (!renderer || !resources)
+    return;
+
+  std::unordered_set<uint64_t> renderedPositions;
+
+  if (previewWaypoint.has_value()) {
+    auto flag = Geom::Flag::create(previewWaypoint->x(), previewWaypoint->z(),
+                                   QVector3D(0.3f, 1.0f, 0.4f),
+                                   QVector3D(0.3f, 0.2f, 0.1f), 0.9f);
+
+    renderer->mesh(resources->unit(), flag.pole, flag.poleColor,
+                   resources->white(), 0.8f);
+    renderer->mesh(resources->unit(), flag.pennant, flag.pennantColor,
+                   resources->white(), 0.8f);
+    renderer->mesh(resources->unit(), flag.finial, flag.pennantColor,
+                   resources->white(), 0.8f);
+
+    int32_t gridX = static_cast<int32_t>(previewWaypoint->x() * 10.0f);
+    int32_t gridZ = static_cast<int32_t>(previewWaypoint->z() * 10.0f);
+    uint64_t posHash =
+        (static_cast<uint64_t>(gridX) << 32) | static_cast<uint64_t>(gridZ);
+    renderedPositions.insert(posHash);
+  }
+
+  auto patrolEntities = world.getEntitiesWith<Engine::Core::PatrolComponent>();
+
+  for (auto *entity : patrolEntities) {
+    auto *patrol = entity->getComponent<Engine::Core::PatrolComponent>();
+    if (!patrol || !patrol->patrolling || patrol->waypoints.empty())
+      continue;
+
+    auto *unit = entity->getComponent<Engine::Core::UnitComponent>();
+    if (!unit || unit->health <= 0)
+      continue;
+
+    for (const auto &waypoint : patrol->waypoints) {
+
+      int32_t gridX = static_cast<int32_t>(waypoint.first * 10.0f);
+      int32_t gridZ = static_cast<int32_t>(waypoint.second * 10.0f);
+      uint64_t posHash =
+          (static_cast<uint64_t>(gridX) << 32) | static_cast<uint64_t>(gridZ);
+
+      if (!renderedPositions.insert(posHash).second) {
+        continue;
+      }
+
+      auto flag = Geom::Flag::create(waypoint.first, waypoint.second,
+                                     QVector3D(0.2f, 0.9f, 0.3f),
+                                     QVector3D(0.3f, 0.2f, 0.1f), 0.8f);
+
+      renderer->mesh(resources->unit(), flag.pole, flag.poleColor,
+                     resources->white(), 1.0f);
+      renderer->mesh(resources->unit(), flag.pennant, flag.pennantColor,
+                     resources->white(), 1.0f);
+      renderer->mesh(resources->unit(), flag.finial, flag.pennantColor,
+                     resources->white(), 1.0f);
     }
+  }
 }
 
 } // namespace Render::GL

+ 9 - 16
render/geom/patrol_flags.h

@@ -3,25 +3,18 @@
 #include <QVector3D>
 #include <optional>
 
-namespace Engine::Core { class World; }
-namespace Render::GL { 
-    class Renderer; 
-    class ResourceManager;
+namespace Engine::Core {
+class World;
 }
+namespace Render::GL {
+class Renderer;
+class ResourceManager;
+} // namespace Render::GL
 
 namespace Render::GL {
 
-/**
- * Render patrol waypoint flags for all units with active patrol routes.
- * Called during the rendering phase to visualize patrol waypoints.
- * 
- * @param previewWaypoint Optional first waypoint being placed (shows during placement)
- */
 void renderPatrolFlags(
-    Renderer* renderer, 
-    ResourceManager* resources, 
-    Engine::Core::World& world,
-    const std::optional<QVector3D>& previewWaypoint = std::nullopt
-);
+    Renderer *renderer, ResourceManager *resources, Engine::Core::World &world,
+    const std::optional<QVector3D> &previewWaypoint = std::nullopt);
 
-} // namespace Render::GL
+}

+ 25 - 23
render/geom/selection_disc.cpp

@@ -1,36 +1,38 @@
 #include "selection_disc.h"
 #include <QVector3D>
-#include <vector>
 #include <cmath>
+#include <vector>
 
 namespace Render::Geom {
 
 std::unique_ptr<Render::GL::Mesh> SelectionDisc::s_mesh;
 
-static Render::GL::Mesh* createDiscMesh() {
-    using namespace Render::GL;
-    std::vector<Vertex> verts;
-    std::vector<unsigned int> idx;
-    const int seg = 72;
-    // Center vertex
-    verts.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
-    for (int i = 0; i <= seg; ++i) {
-        float a = (i / float(seg)) * 6.2831853f;
-        float x = std::cos(a);
-        float z = std::sin(a);
-        verts.push_back({{x, 0.0f, z}, {0.0f, 1.0f, 0.0f}, {0.5f + 0.5f*x, 0.5f + 0.5f*z}});
-    }
-    for (int i = 1; i <= seg; ++i) {
-        idx.push_back(0);
-        idx.push_back(i);
-        idx.push_back(i+1);
-    }
-    return new Mesh(verts, idx);
+static Render::GL::Mesh *createDiscMesh() {
+  using namespace Render::GL;
+  std::vector<Vertex> verts;
+  std::vector<unsigned int> idx;
+  const int seg = 72;
+
+  verts.push_back({{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.5f, 0.5f}});
+  for (int i = 0; i <= seg; ++i) {
+    float a = (i / float(seg)) * 6.2831853f;
+    float x = std::cos(a);
+    float z = std::sin(a);
+    verts.push_back(
+        {{x, 0.0f, z}, {0.0f, 1.0f, 0.0f}, {0.5f + 0.5f * x, 0.5f + 0.5f * z}});
+  }
+  for (int i = 1; i <= seg; ++i) {
+    idx.push_back(0);
+    idx.push_back(i);
+    idx.push_back(i + 1);
+  }
+  return new Mesh(verts, idx);
 }
 
-Render::GL::Mesh* SelectionDisc::get() {
-    if (!s_mesh) s_mesh.reset(createDiscMesh());
-    return s_mesh.get();
+Render::GL::Mesh *SelectionDisc::get() {
+  if (!s_mesh)
+    s_mesh.reset(createDiscMesh());
+  return s_mesh.get();
 }
 
 } // namespace Render::Geom

+ 3 - 2
render/geom/selection_disc.h

@@ -7,9 +7,10 @@ namespace Render::Geom {
 
 class SelectionDisc {
 public:
-    static Render::GL::Mesh* get();
+  static Render::GL::Mesh *get();
+
 private:
-    static std::unique_ptr<Render::GL::Mesh> s_mesh;
+  static std::unique_ptr<Render::GL::Mesh> s_mesh;
 };
 
 } // namespace Render::Geom

+ 32 - 27
render/geom/selection_ring.cpp

@@ -5,35 +5,40 @@ namespace Render::Geom {
 
 std::unique_ptr<Render::GL::Mesh> SelectionRing::s_mesh;
 
-static Render::GL::Mesh* createRingMesh() {
-    using namespace Render::GL;
-    std::vector<Vertex> verts;
-    std::vector<unsigned int> idx;
-    const int seg = 48;
-    const float inner = 0.94f; // thinner ring band
-    const float outer = 1.0f;
-    for (int i = 0; i < seg; ++i) {
-        float a0 = (i / float(seg)) * 6.2831853f;
-        float a1 = ((i + 1) / float(seg)) * 6.2831853f;
-        QVector3D n(0, 1, 0);
-        QVector3D v0i(inner * std::cos(a0), 0.0f, inner * std::sin(a0));
-        QVector3D v0o(outer * std::cos(a0), 0.0f, outer * std::sin(a0));
-        QVector3D v1o(outer * std::cos(a1), 0.0f, outer * std::sin(a1));
-        QVector3D v1i(inner * std::cos(a1), 0.0f, inner * std::sin(a1));
-        size_t base = verts.size();
-        verts.push_back({{v0i.x(), 0.0f, v0i.z()}, {n.x(), n.y(), n.z()}, {0, 0}});
-        verts.push_back({{v0o.x(), 0.0f, v0o.z()}, {n.x(), n.y(), n.z()}, {1, 0}});
-        verts.push_back({{v1o.x(), 0.0f, v1o.z()}, {n.x(), n.y(), n.z()}, {1, 1}});
-        verts.push_back({{v1i.x(), 0.0f, v1i.z()}, {n.x(), n.y(), n.z()}, {0, 1}});
-        idx.push_back(base + 0); idx.push_back(base + 1); idx.push_back(base + 2);
-        idx.push_back(base + 2); idx.push_back(base + 3); idx.push_back(base + 0);
-    }
-    return new Mesh(verts, idx);
+static Render::GL::Mesh *createRingMesh() {
+  using namespace Render::GL;
+  std::vector<Vertex> verts;
+  std::vector<unsigned int> idx;
+  const int seg = 48;
+  const float inner = 0.94f;
+  const float outer = 1.0f;
+  for (int i = 0; i < seg; ++i) {
+    float a0 = (i / float(seg)) * 6.2831853f;
+    float a1 = ((i + 1) / float(seg)) * 6.2831853f;
+    QVector3D n(0, 1, 0);
+    QVector3D v0i(inner * std::cos(a0), 0.0f, inner * std::sin(a0));
+    QVector3D v0o(outer * std::cos(a0), 0.0f, outer * std::sin(a0));
+    QVector3D v1o(outer * std::cos(a1), 0.0f, outer * std::sin(a1));
+    QVector3D v1i(inner * std::cos(a1), 0.0f, inner * std::sin(a1));
+    size_t base = verts.size();
+    verts.push_back({{v0i.x(), 0.0f, v0i.z()}, {n.x(), n.y(), n.z()}, {0, 0}});
+    verts.push_back({{v0o.x(), 0.0f, v0o.z()}, {n.x(), n.y(), n.z()}, {1, 0}});
+    verts.push_back({{v1o.x(), 0.0f, v1o.z()}, {n.x(), n.y(), n.z()}, {1, 1}});
+    verts.push_back({{v1i.x(), 0.0f, v1i.z()}, {n.x(), n.y(), n.z()}, {0, 1}});
+    idx.push_back(base + 0);
+    idx.push_back(base + 1);
+    idx.push_back(base + 2);
+    idx.push_back(base + 2);
+    idx.push_back(base + 3);
+    idx.push_back(base + 0);
+  }
+  return new Mesh(verts, idx);
 }
 
-Render::GL::Mesh* SelectionRing::get() {
-    if (!s_mesh) s_mesh.reset(createRingMesh());
-    return s_mesh.get();
+Render::GL::Mesh *SelectionRing::get() {
+  if (!s_mesh)
+    s_mesh.reset(createRingMesh());
+  return s_mesh.get();
 }
 
 } // namespace Render::Geom

+ 3 - 3
render/geom/selection_ring.h

@@ -5,12 +5,12 @@
 
 namespace Render::Geom {
 
-// Lazily creates and caches a selection ring mesh (annulus) shared across renderers
 class SelectionRing {
 public:
-    static Render::GL::Mesh* get();
+  static Render::GL::Mesh *get();
+
 private:
-    static std::unique_ptr<Render::GL::Mesh> s_mesh;
+  static std::unique_ptr<Render::GL::Mesh> s_mesh;
 };
 
 } // namespace Render::Geom

+ 148 - 132
render/gl/backend.cpp

@@ -1,156 +1,172 @@
 #include "backend.h"
-#include "shader.h"
 #include "../draw_queue.h"
-#include "mesh.h"
-#include "texture.h"
-#include "../geom/selection_ring.h"
 #include "../geom/selection_disc.h"
+#include "../geom/selection_ring.h"
+#include "mesh.h"
+#include "shader.h"
 #include "state_scopes.h"
+#include "texture.h"
 #include <QDebug>
 
 namespace Render::GL {
 Backend::~Backend() = default;
 
 void Backend::initialize() {
-	initializeOpenGLFunctions();
-	glEnable(GL_DEPTH_TEST);
-	glDepthFunc(GL_LESS);
-	glEnable(GL_BLEND);
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	// Create resource and shader managers
-	m_resources = std::make_unique<ResourceManager>();
-	if (!m_resources->initialize()) {
-		qWarning() << "Backend: failed to initialize ResourceManager";
-	}
-	m_shaderCache = std::make_unique<ShaderCache>();
-	m_shaderCache->initializeDefaults();
-	m_basicShader = m_shaderCache->get(QStringLiteral("basic"));
-	m_gridShader  = m_shaderCache->get(QStringLiteral("grid"));
-	if (!m_basicShader) qWarning() << "Backend: basic shader missing";
-	if (!m_gridShader)  qWarning() << "Backend: grid shader missing";
+  initializeOpenGLFunctions();
+  glEnable(GL_DEPTH_TEST);
+  glDepthFunc(GL_LESS);
+  glEnable(GL_BLEND);
+  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+  m_resources = std::make_unique<ResourceManager>();
+  if (!m_resources->initialize()) {
+    qWarning() << "Backend: failed to initialize ResourceManager";
+  }
+  m_shaderCache = std::make_unique<ShaderCache>();
+  m_shaderCache->initializeDefaults();
+  m_basicShader = m_shaderCache->get(QStringLiteral("basic"));
+  m_gridShader = m_shaderCache->get(QStringLiteral("grid"));
+  if (!m_basicShader)
+    qWarning() << "Backend: basic shader missing";
+  if (!m_gridShader)
+    qWarning() << "Backend: grid shader missing";
 }
 
 void Backend::beginFrame() {
-	if (m_viewportWidth > 0 && m_viewportHeight > 0) {
-		glViewport(0, 0, m_viewportWidth, m_viewportHeight);
-	}
-	glClearColor(m_clearColor[0], m_clearColor[1], m_clearColor[2], m_clearColor[3]);
-	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  if (m_viewportWidth > 0 && m_viewportHeight > 0) {
+    glViewport(0, 0, m_viewportWidth, m_viewportHeight);
+  }
+  glClearColor(m_clearColor[0], m_clearColor[1], m_clearColor[2],
+               m_clearColor[3]);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 }
 
 void Backend::setViewport(int w, int h) {
-	m_viewportWidth = w;
-	m_viewportHeight = h;
+  m_viewportWidth = w;
+  m_viewportHeight = h;
 }
 
 void Backend::setClearColor(float r, float g, float b, float a) {
-	m_clearColor[0]=r; m_clearColor[1]=g; m_clearColor[2]=b; m_clearColor[3]=a;
+  m_clearColor[0] = r;
+  m_clearColor[1] = g;
+  m_clearColor[2] = b;
+  m_clearColor[3] = a;
 }
 
-void Backend::execute(const DrawQueue& queue, const Camera& cam) {
-	if (!m_basicShader) return;
-	// Bind once up front; we'll defensively rebind before draws that use it
-	m_basicShader->use();
-	m_basicShader->setUniform("u_view", cam.getViewMatrix());
-	m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
-	for (const auto& cmd : queue.items()) {
-		if (std::holds_alternative<MeshCmd>(cmd)) {
-			const auto& it = std::get<MeshCmd>(cmd);
-			if (!it.mesh) continue;
-			// Reset critical state for opaque mesh pass
-			glDepthMask(GL_TRUE);
-			if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) glDisable(GL_POLYGON_OFFSET_FILL);
-			// Ensure the correct program is bound in case a prior GridCmd changed it
-			m_basicShader->use();
-			m_basicShader->setUniform("u_view", cam.getViewMatrix());
-			m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
-			m_basicShader->setUniform("u_model", it.model);
-			if (it.texture) {
-				it.texture->bind(0);
-				m_basicShader->setUniform("u_texture", 0);
-				m_basicShader->setUniform("u_useTexture", true);
-			} else {
-				if (m_resources && m_resources->white()) {
-					m_resources->white()->bind(0);
-					m_basicShader->setUniform("u_texture", 0);
-				}
-				m_basicShader->setUniform("u_useTexture", false);
-			}
-			m_basicShader->setUniform("u_color", it.color);
-			m_basicShader->setUniform("u_alpha", it.alpha);
-			it.mesh->draw();
-		} else if (std::holds_alternative<GridCmd>(cmd)) {
-			if (!m_gridShader) continue;
-			const auto& gc = std::get<GridCmd>(cmd);
-			m_gridShader->use();
-			m_gridShader->setUniform("u_view", cam.getViewMatrix());
-			m_gridShader->setUniform("u_projection", cam.getProjectionMatrix());
-			// Model is already scaled appropriately; do not apply extent again
-			QMatrix4x4 model = gc.model;
-			// model.scale(gc.extent, 1.0f, gc.extent); // Removed redundant scaling
-			m_gridShader->setUniform("u_model", model);
-			m_gridShader->setUniform("u_gridColor", gc.color);
-			m_gridShader->setUniform("u_lineColor", QVector3D(0.22f, 0.25f, 0.22f));
-			m_gridShader->setUniform("u_cellSize", gc.cellSize);
-			m_gridShader->setUniform("u_thickness", gc.thickness);
-			// Draw a full plane using the default ground mesh if available
-			if (m_resources) {
-				if (auto* plane = m_resources->ground()) plane->draw();
-			}
-			// Do not release to program 0 here; subsequent draws will rebind their shader as needed
-		} else if (std::holds_alternative<SelectionRingCmd>(cmd)) {
-			const auto& sc = std::get<SelectionRingCmd>(cmd);
-			Mesh* ring = Render::Geom::SelectionRing::get();
-			if (!ring) continue;
-			// Ensure basic program is bound
-			m_basicShader->use();
-			m_basicShader->setUniform("u_view", cam.getViewMatrix());
-			m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
-			// Use white texture path for consistent shading
-			m_basicShader->setUniform("u_useTexture", false);
-			m_basicShader->setUniform("u_color", sc.color);
-			// Slight polygon offset and disable depth writes while blending
-			DepthMaskScope depthMask(false);
-			PolygonOffsetScope poly(-1.0f, -1.0f);
-			BlendScope blend(true);
-			// Outer feather
-			{
-				QMatrix4x4 m = sc.model; m.scale(1.08f, 1.0f, 1.08f);
-				m_basicShader->setUniform("u_model", m);
-				m_basicShader->setUniform("u_alpha", sc.alphaOuter);
-				ring->draw();
-			}
-			// Inner ring
-			{
-				m_basicShader->setUniform("u_model", sc.model);
-				m_basicShader->setUniform("u_alpha", sc.alphaInner);
-				ring->draw();
-			}
-		} else if (std::holds_alternative<SelectionSmokeCmd>(cmd)) {
-			const auto& sm = std::get<SelectionSmokeCmd>(cmd);
-			Mesh* disc = Render::Geom::SelectionDisc::get();
-			if (!disc) continue;
-			m_basicShader->use();
-			m_basicShader->setUniform("u_view", cam.getViewMatrix());
-			m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
-			m_basicShader->setUniform("u_useTexture", false);
-			m_basicShader->setUniform("u_color", sm.color);
-			DepthMaskScope depthMask(false);
-			DepthTestScope depthTest(false); // draw without depth test so smoke sits visible over ground
-			// Slight negative offset helps avoid coincident z with ground if depth test toggles elsewhere
-			PolygonOffsetScope poly(-0.1f, -0.1f);
-			BlendScope blend(true);
-			for (int i = 0; i < 7; ++i) {
-				float scale = 1.35f + 0.12f * i;
-				float a = sm.baseAlpha * (1.0f - 0.09f * i);
-				QMatrix4x4 m = sm.model; m.translate(0.0f, 0.02f, 0.0f); m.scale(scale, 1.0f, scale);
-				m_basicShader->setUniform("u_model", m);
-				m_basicShader->setUniform("u_alpha", a);
-				disc->draw();
-			}
-		}
-	}
-	m_basicShader->release();
+void Backend::execute(const DrawQueue &queue, const Camera &cam) {
+  if (!m_basicShader)
+    return;
+
+  m_basicShader->use();
+  m_basicShader->setUniform("u_view", cam.getViewMatrix());
+  m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
+  for (const auto &cmd : queue.items()) {
+    if (std::holds_alternative<MeshCmd>(cmd)) {
+      const auto &it = std::get<MeshCmd>(cmd);
+      if (!it.mesh)
+        continue;
+
+      glDepthMask(GL_TRUE);
+      if (glIsEnabled(GL_POLYGON_OFFSET_FILL))
+        glDisable(GL_POLYGON_OFFSET_FILL);
+
+      m_basicShader->use();
+      m_basicShader->setUniform("u_view", cam.getViewMatrix());
+      m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
+      m_basicShader->setUniform("u_model", it.model);
+      if (it.texture) {
+        it.texture->bind(0);
+        m_basicShader->setUniform("u_texture", 0);
+        m_basicShader->setUniform("u_useTexture", true);
+      } else {
+        if (m_resources && m_resources->white()) {
+          m_resources->white()->bind(0);
+          m_basicShader->setUniform("u_texture", 0);
+        }
+        m_basicShader->setUniform("u_useTexture", false);
+      }
+      m_basicShader->setUniform("u_color", it.color);
+      m_basicShader->setUniform("u_alpha", it.alpha);
+      it.mesh->draw();
+    } else if (std::holds_alternative<GridCmd>(cmd)) {
+      if (!m_gridShader)
+        continue;
+      const auto &gc = std::get<GridCmd>(cmd);
+      m_gridShader->use();
+      m_gridShader->setUniform("u_view", cam.getViewMatrix());
+      m_gridShader->setUniform("u_projection", cam.getProjectionMatrix());
+
+      QMatrix4x4 model = gc.model;
+
+      m_gridShader->setUniform("u_model", model);
+      m_gridShader->setUniform("u_gridColor", gc.color);
+      m_gridShader->setUniform("u_lineColor", QVector3D(0.22f, 0.25f, 0.22f));
+      m_gridShader->setUniform("u_cellSize", gc.cellSize);
+      m_gridShader->setUniform("u_thickness", gc.thickness);
+
+      if (m_resources) {
+        if (auto *plane = m_resources->ground())
+          plane->draw();
+      }
+
+    } else if (std::holds_alternative<SelectionRingCmd>(cmd)) {
+      const auto &sc = std::get<SelectionRingCmd>(cmd);
+      Mesh *ring = Render::Geom::SelectionRing::get();
+      if (!ring)
+        continue;
+
+      m_basicShader->use();
+      m_basicShader->setUniform("u_view", cam.getViewMatrix());
+      m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
+
+      m_basicShader->setUniform("u_useTexture", false);
+      m_basicShader->setUniform("u_color", sc.color);
+
+      DepthMaskScope depthMask(false);
+      PolygonOffsetScope poly(-1.0f, -1.0f);
+      BlendScope blend(true);
+
+      {
+        QMatrix4x4 m = sc.model;
+        m.scale(1.08f, 1.0f, 1.08f);
+        m_basicShader->setUniform("u_model", m);
+        m_basicShader->setUniform("u_alpha", sc.alphaOuter);
+        ring->draw();
+      }
+
+      {
+        m_basicShader->setUniform("u_model", sc.model);
+        m_basicShader->setUniform("u_alpha", sc.alphaInner);
+        ring->draw();
+      }
+    } else if (std::holds_alternative<SelectionSmokeCmd>(cmd)) {
+      const auto &sm = std::get<SelectionSmokeCmd>(cmd);
+      Mesh *disc = Render::Geom::SelectionDisc::get();
+      if (!disc)
+        continue;
+      m_basicShader->use();
+      m_basicShader->setUniform("u_view", cam.getViewMatrix());
+      m_basicShader->setUniform("u_projection", cam.getProjectionMatrix());
+      m_basicShader->setUniform("u_useTexture", false);
+      m_basicShader->setUniform("u_color", sm.color);
+      DepthMaskScope depthMask(false);
+      DepthTestScope depthTest(false);
+
+      PolygonOffsetScope poly(-0.1f, -0.1f);
+      BlendScope blend(true);
+      for (int i = 0; i < 7; ++i) {
+        float scale = 1.35f + 0.12f * i;
+        float a = sm.baseAlpha * (1.0f - 0.09f * i);
+        QMatrix4x4 m = sm.model;
+        m.translate(0.0f, 0.02f, 0.0f);
+        m.scale(scale, 1.0f, scale);
+        m_basicShader->setUniform("u_model", m);
+        m_basicShader->setUniform("u_alpha", a);
+        disc->draw();
+      }
+    }
+  }
+  m_basicShader->release();
 }
 
 } // namespace Render::GL

+ 58 - 45
render/gl/backend.h

@@ -1,61 +1,74 @@
 #pragma once
 
-#include <QOpenGLFunctions_3_3_Core>
 #include "../draw_queue.h"
 #include "camera.h"
 #include "resources.h"
-#include <array>
-#include <memory>
 #include "shader.h"
 #include "shader_cache.h"
+#include <QOpenGLFunctions_3_3_Core>
+#include <array>
+#include <memory>
 
 namespace Render::GL {
-// Shader included above
 
 class Backend : protected QOpenGLFunctions_3_3_Core {
 public:
-    Backend() = default;
-    ~Backend();
-    void initialize();
-    void beginFrame();
-    void setViewport(int w, int h);
-    void setClearColor(float r, float g, float b, float a);
-    void execute(const DrawQueue& queue, const Camera& cam);
-
-    // Access to GL resources for high-level systems that need mesh pointers
-    ResourceManager* resources() const { return m_resources.get(); }
-
-    // Lazy shader access/loading for optional, entity-specific effects
-    Shader* shader(const QString& name) const { return m_shaderCache ? m_shaderCache->get(name) : nullptr; }
-    Shader* getOrLoadShader(const QString& name, const QString& vertPath, const QString& fragPath) {
-        if (!m_shaderCache) return nullptr;
-        return m_shaderCache->load(name, vertPath, fragPath);
-    }
-
-    void enableDepthTest(bool enable) {
-        if (enable) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
-    }
-    void setDepthFunc(GLenum func) { glDepthFunc(func); }
-    void setDepthMask(bool write) { glDepthMask(write ? GL_TRUE : GL_FALSE); }
-
-    void enableBlend(bool enable) {
-        if (enable) glEnable(GL_BLEND); else glDisable(GL_BLEND);
-    }
-    void setBlendFunc(GLenum src, GLenum dst) { glBlendFunc(src, dst); }
-
-    void enablePolygonOffset(bool enable) {
-        if (enable) glEnable(GL_POLYGON_OFFSET_FILL); else glDisable(GL_POLYGON_OFFSET_FILL);
-    }
-    void setPolygonOffset(float factor, float units) { glPolygonOffset(factor, units); }
+  Backend() = default;
+  ~Backend();
+  void initialize();
+  void beginFrame();
+  void setViewport(int w, int h);
+  void setClearColor(float r, float g, float b, float a);
+  void execute(const DrawQueue &queue, const Camera &cam);
+
+  ResourceManager *resources() const { return m_resources.get(); }
+
+  Shader *shader(const QString &name) const {
+    return m_shaderCache ? m_shaderCache->get(name) : nullptr;
+  }
+  Shader *getOrLoadShader(const QString &name, const QString &vertPath,
+                          const QString &fragPath) {
+    if (!m_shaderCache)
+      return nullptr;
+    return m_shaderCache->load(name, vertPath, fragPath);
+  }
+
+  void enableDepthTest(bool enable) {
+    if (enable)
+      glEnable(GL_DEPTH_TEST);
+    else
+      glDisable(GL_DEPTH_TEST);
+  }
+  void setDepthFunc(GLenum func) { glDepthFunc(func); }
+  void setDepthMask(bool write) { glDepthMask(write ? GL_TRUE : GL_FALSE); }
+
+  void enableBlend(bool enable) {
+    if (enable)
+      glEnable(GL_BLEND);
+    else
+      glDisable(GL_BLEND);
+  }
+  void setBlendFunc(GLenum src, GLenum dst) { glBlendFunc(src, dst); }
+
+  void enablePolygonOffset(bool enable) {
+    if (enable)
+      glEnable(GL_POLYGON_OFFSET_FILL);
+    else
+      glDisable(GL_POLYGON_OFFSET_FILL);
+  }
+  void setPolygonOffset(float factor, float units) {
+    glPolygonOffset(factor, units);
+  }
+
 private:
-    int m_viewportWidth{0};
-    int m_viewportHeight{0};
-    std::array<float,4> m_clearColor{0.2f,0.3f,0.3f,0.0f};
-    std::unique_ptr<ShaderCache> m_shaderCache;
-    std::unique_ptr<ResourceManager> m_resources;
-    // Cached pointers to named shaders (owned by m_shaderCache)
-    Shader* m_basicShader = nullptr;
-    Shader* m_gridShader = nullptr;
+  int m_viewportWidth{0};
+  int m_viewportHeight{0};
+  std::array<float, 4> m_clearColor{0.2f, 0.3f, 0.3f, 0.0f};
+  std::unique_ptr<ShaderCache> m_shaderCache;
+  std::unique_ptr<ResourceManager> m_resources;
+
+  Shader *m_basicShader = nullptr;
+  Shader *m_gridShader = nullptr;
 };
 
 } // namespace Render::GL

+ 17 - 16
render/gl/bootstrap.cpp

@@ -2,24 +2,25 @@
 #include "../scene_renderer.h"
 #include "camera.h"
 #include "resources.h"
-#include <QOpenGLContext>
 #include <QDebug>
+#include <QOpenGLContext>
 
-namespace Render { namespace GL {
+namespace Render {
+namespace GL {
 
-bool RenderBootstrap::initialize(Renderer& renderer,
-                                 Camera& camera) {
-    QOpenGLContext* ctx = QOpenGLContext::currentContext();
-    if (!ctx || !ctx->isValid()) {
-        qWarning() << "RenderBootstrap: no current valid OpenGL context";
-        return false;
-    }
-    if (!renderer.initialize()) {
-        qWarning() << "RenderBootstrap: renderer initialize failed";
-        return false;
-    }
-    renderer.setCamera(&camera);
-    return true;
+bool RenderBootstrap::initialize(Renderer &renderer, Camera &camera) {
+  QOpenGLContext *ctx = QOpenGLContext::currentContext();
+  if (!ctx || !ctx->isValid()) {
+    qWarning() << "RenderBootstrap: no current valid OpenGL context";
+    return false;
+  }
+  if (!renderer.initialize()) {
+    qWarning() << "RenderBootstrap: renderer initialize failed";
+    return false;
+  }
+  renderer.setCamera(&camera);
+  return true;
 }
 
-} } // namespace Render::GL
+} // namespace GL
+} // namespace Render

+ 5 - 5
render/gl/bootstrap.h

@@ -4,16 +4,16 @@
 
 class QOpenGLContext;
 
-namespace Render { namespace GL {
+namespace Render {
+namespace GL {
 class Renderer;
 class Camera;
 class ResourceManager;
 
 class RenderBootstrap {
 public:
-    // Initializes GL renderer and binds camera. Returns false if no valid GL context or renderer init fails.
-    static bool initialize(Renderer& renderer,
-                           Camera& camera);
+  static bool initialize(Renderer &renderer, Camera &camera);
 };
 
-} } // namespace Render::GL
+} // namespace GL
+} // namespace Render

+ 60 - 62
render/gl/buffer.cpp

@@ -2,96 +2,94 @@
 
 namespace Render::GL {
 
-Buffer::Buffer(Type type) : m_type(type) {
-    // Do not call GL without a current context; creation will be deferred to first bind
-}
+Buffer::Buffer(Type type) : m_type(type) {}
 
 Buffer::~Buffer() {
-    if (m_buffer != 0) {
-        glDeleteBuffers(1, &m_buffer);
-    }
+  if (m_buffer != 0) {
+    glDeleteBuffers(1, &m_buffer);
+  }
 }
 
 void Buffer::bind() {
-    if (!m_buffer) {
-        initializeOpenGLFunctions();
-        glGenBuffers(1, &m_buffer);
-    }
-    glBindBuffer(getGLType(), m_buffer);
+  if (!m_buffer) {
+    initializeOpenGLFunctions();
+    glGenBuffers(1, &m_buffer);
+  }
+  glBindBuffer(getGLType(), m_buffer);
 }
 
-void Buffer::unbind() {
-    glBindBuffer(getGLType(), 0);
-}
+void Buffer::unbind() { glBindBuffer(getGLType(), 0); }
 
-void Buffer::setData(const void* data, size_t size, Usage usage) {
-    bind();
-    glBufferData(getGLType(), size, data, getGLUsage(usage));
+void Buffer::setData(const void *data, size_t size, Usage usage) {
+  bind();
+  glBufferData(getGLType(), size, data, getGLUsage(usage));
 }
 
 GLenum Buffer::getGLType() const {
-    switch (m_type) {
-        case Type::Vertex: return GL_ARRAY_BUFFER;
-        case Type::Index: return GL_ELEMENT_ARRAY_BUFFER;
-        case Type::Uniform: return GL_UNIFORM_BUFFER;
-    }
+  switch (m_type) {
+  case Type::Vertex:
     return GL_ARRAY_BUFFER;
+  case Type::Index:
+    return GL_ELEMENT_ARRAY_BUFFER;
+  case Type::Uniform:
+    return GL_UNIFORM_BUFFER;
+  }
+  return GL_ARRAY_BUFFER;
 }
 
 GLenum Buffer::getGLUsage(Usage usage) const {
-    switch (usage) {
-        case Usage::Static: return GL_STATIC_DRAW;
-        case Usage::Dynamic: return GL_DYNAMIC_DRAW;
-        case Usage::Stream: return GL_STREAM_DRAW;
-    }
+  switch (usage) {
+  case Usage::Static:
     return GL_STATIC_DRAW;
+  case Usage::Dynamic:
+    return GL_DYNAMIC_DRAW;
+  case Usage::Stream:
+    return GL_STREAM_DRAW;
+  }
+  return GL_STATIC_DRAW;
 }
 
-// VertexArray implementation
-VertexArray::VertexArray() {
-    // Defer creation until first bind when a context is current
-}
+VertexArray::VertexArray() {}
 
 VertexArray::~VertexArray() {
-    if (m_vao != 0) {
-        glDeleteVertexArrays(1, &m_vao);
-    }
+  if (m_vao != 0) {
+    glDeleteVertexArrays(1, &m_vao);
+  }
 }
 
 void VertexArray::bind() {
-    if (!m_vao) {
-        initializeOpenGLFunctions();
-        glGenVertexArrays(1, &m_vao);
-    }
-    glBindVertexArray(m_vao);
+  if (!m_vao) {
+    initializeOpenGLFunctions();
+    glGenVertexArrays(1, &m_vao);
+  }
+  glBindVertexArray(m_vao);
 }
 
-void VertexArray::unbind() {
-    glBindVertexArray(0);
-}
+void VertexArray::unbind() { glBindVertexArray(0); }
+
+void VertexArray::addVertexBuffer(Buffer &buffer,
+                                  const std::vector<int> &layout) {
+  bind();
+  buffer.bind();
+
+  int stride = 0;
+  for (int size : layout) {
+    stride += size * sizeof(float);
+  }
 
-void VertexArray::addVertexBuffer(Buffer& buffer, const std::vector<int>& layout) {
-    bind();
-    buffer.bind();
-    
-    int stride = 0;
-    for (int size : layout) {
-        stride += size * sizeof(float);
-    }
-    
-    int offset = 0;
-    for (int size : layout) {
-        glEnableVertexAttribArray(m_currentAttribIndex);
-        glVertexAttribPointer(m_currentAttribIndex, size, GL_FLOAT, GL_FALSE, 
-                             stride, reinterpret_cast<void*>(offset));
-        offset += size * sizeof(float);
-        m_currentAttribIndex++;
-    }
+  int offset = 0;
+  for (int size : layout) {
+    glEnableVertexAttribArray(m_currentAttribIndex);
+    glVertexAttribPointer(m_currentAttribIndex, size, GL_FLOAT, GL_FALSE,
+                          stride, reinterpret_cast<void *>(offset));
+    offset += size * sizeof(float);
+    m_currentAttribIndex++;
+  }
 }
 
-void VertexArray::setIndexBuffer(Buffer& buffer) {
-    bind();
-    buffer.bind();
+void VertexArray::setIndexBuffer(Buffer &buffer) {
+  bind();
+  buffer.bind();
 }
 
 } // namespace Render::GL

+ 29 - 37
render/gl/buffer.h

@@ -7,52 +7,44 @@ namespace Render::GL {
 
 class Buffer : protected QOpenGLFunctions_3_3_Core {
 public:
-    enum class Type {
-        Vertex,
-        Index,
-        Uniform
-    };
-    
-    enum class Usage {
-        Static,
-        Dynamic,
-        Stream
-    };
-
-    Buffer(Type type);
-    ~Buffer();
-
-    void bind();
-    void unbind();
-    
-    void setData(const void* data, size_t size, Usage usage = Usage::Static);
-    
-    template<typename T>
-    void setData(const std::vector<T>& data, Usage usage = Usage::Static) {
-        setData(data.data(), data.size() * sizeof(T), usage);
-    }
+  enum class Type { Vertex, Index, Uniform };
+
+  enum class Usage { Static, Dynamic, Stream };
+
+  Buffer(Type type);
+  ~Buffer();
+
+  void bind();
+  void unbind();
+
+  void setData(const void *data, size_t size, Usage usage = Usage::Static);
+
+  template <typename T>
+  void setData(const std::vector<T> &data, Usage usage = Usage::Static) {
+    setData(data.data(), data.size() * sizeof(T), usage);
+  }
 
 private:
-    GLuint m_buffer = 0;
-    Type m_type;
-    GLenum getGLType() const;
-    GLenum getGLUsage(Usage usage) const;
+  GLuint m_buffer = 0;
+  Type m_type;
+  GLenum getGLType() const;
+  GLenum getGLUsage(Usage usage) const;
 };
 
 class VertexArray : protected QOpenGLFunctions_3_3_Core {
 public:
-    VertexArray();
-    ~VertexArray();
+  VertexArray();
+  ~VertexArray();
+
+  void bind();
+  void unbind();
 
-    void bind();
-    void unbind();
-    
-    void addVertexBuffer(Buffer& buffer, const std::vector<int>& layout);
-    void setIndexBuffer(Buffer& buffer);
+  void addVertexBuffer(Buffer &buffer, const std::vector<int> &layout);
+  void setIndexBuffer(Buffer &buffer);
 
 private:
-    GLuint m_vao = 0;
-    int m_currentAttribIndex = 0;
+  GLuint m_vao = 0;
+  int m_currentAttribIndex = 0;
 };
 
 } // namespace Render::GL

+ 200 - 185
render/gl/camera.cpp

@@ -4,253 +4,268 @@
 
 namespace Render::GL {
 
-Camera::Camera() {
-    updateVectors();
-}
+Camera::Camera() { updateVectors(); }
 
-void Camera::setPosition(const QVector3D& position) {
-    m_position = position;
-}
+void Camera::setPosition(const QVector3D &position) { m_position = position; }
 
-void Camera::setTarget(const QVector3D& target) {
-    m_target = target;
-    m_front = (m_target - m_position).normalized();
-    updateVectors();
+void Camera::setTarget(const QVector3D &target) {
+  m_target = target;
+  m_front = (m_target - m_position).normalized();
+  updateVectors();
 }
 
-void Camera::setUp(const QVector3D& up) {
-    m_up = up.normalized();
-    updateVectors();
+void Camera::setUp(const QVector3D &up) {
+  m_up = up.normalized();
+  updateVectors();
 }
 
-void Camera::lookAt(const QVector3D& position, const QVector3D& target, const QVector3D& up) {
-    m_position = position;
-    m_target = target;
-    m_up = up.normalized();
-    m_front = (m_target - m_position).normalized();
-    updateVectors();
+void Camera::lookAt(const QVector3D &position, const QVector3D &target,
+                    const QVector3D &up) {
+  m_position = position;
+  m_target = target;
+  m_up = up.normalized();
+  m_front = (m_target - m_position).normalized();
+  updateVectors();
 }
 
-void Camera::setPerspective(float fov, float aspect, float nearPlane, float farPlane) {
-    m_isPerspective = true;
-    m_fov = fov;
-    m_aspect = aspect;
-    m_nearPlane = nearPlane;
-    m_farPlane = farPlane;
+void Camera::setPerspective(float fov, float aspect, float nearPlane,
+                            float farPlane) {
+  m_isPerspective = true;
+  m_fov = fov;
+  m_aspect = aspect;
+  m_nearPlane = nearPlane;
+  m_farPlane = farPlane;
 }
 
-void Camera::setOrthographic(float left, float right, float bottom, float top, float nearPlane, float farPlane) {
-    m_isPerspective = false;
-    m_orthoLeft = left;
-    m_orthoRight = right;
-    m_orthoBottom = bottom;
-    m_orthoTop = top;
-    m_nearPlane = nearPlane;
-    m_farPlane = farPlane;
+void Camera::setOrthographic(float left, float right, float bottom, float top,
+                             float nearPlane, float farPlane) {
+  m_isPerspective = false;
+  m_orthoLeft = left;
+  m_orthoRight = right;
+  m_orthoBottom = bottom;
+  m_orthoTop = top;
+  m_nearPlane = nearPlane;
+  m_farPlane = farPlane;
 }
 
 void Camera::moveForward(float distance) {
-    m_position += m_front * distance;
-    m_target = m_position + m_front;
+  m_position += m_front * distance;
+  m_target = m_position + m_front;
 }
 
 void Camera::moveRight(float distance) {
-    m_position += m_right * distance;
-    m_target = m_position + m_front;
+  m_position += m_right * distance;
+  m_target = m_position + m_front;
 }
 
 void Camera::moveUp(float distance) {
-    // Elevate along world up to avoid roll artifacts
-    m_position += QVector3D(0,1,0) * distance;
-    clampAboveGround();
-    m_target = m_position + m_front;
+
+  m_position += QVector3D(0, 1, 0) * distance;
+  clampAboveGround();
+  m_target = m_position + m_front;
 }
 
 void Camera::zoom(float delta) {
-    if (m_isPerspective) {
-        m_fov = qBound(1.0f, m_fov - delta, 89.0f);
-    } else {
-        float scale = 1.0f + delta * 0.1f;
-        m_orthoLeft *= scale;
-        m_orthoRight *= scale;
-        m_orthoBottom *= scale;
-        m_orthoTop *= scale;
-    }
+  if (m_isPerspective) {
+    m_fov = qBound(1.0f, m_fov - delta, 89.0f);
+  } else {
+    float scale = 1.0f + delta * 0.1f;
+    m_orthoLeft *= scale;
+    m_orthoRight *= scale;
+    m_orthoBottom *= scale;
+    m_orthoTop *= scale;
+  }
 }
 
-void Camera::rotate(float yaw, float pitch) {
-    // Delegate to orbit with clamping/world-up behavior
-    orbit(yaw, pitch);
-}
+void Camera::rotate(float yaw, float pitch) { orbit(yaw, pitch); }
 
 void Camera::pan(float rightDist, float forwardDist) {
-    // Move along camera right and ground-projected front
-    QVector3D right = m_right;
-    QVector3D front = m_front; front.setY(0.0f); if (front.lengthSquared()>0) front.normalize();
-    QVector3D delta = right * rightDist + front * forwardDist;
-    m_position += delta;
-    m_target   += delta;
-    clampAboveGround();
+
+  QVector3D right = m_right;
+  QVector3D front = m_front;
+  front.setY(0.0f);
+  if (front.lengthSquared() > 0)
+    front.normalize();
+  QVector3D delta = right * rightDist + front * forwardDist;
+  m_position += delta;
+  m_target += delta;
+  clampAboveGround();
 }
 
 void Camera::elevate(float dy) {
-    m_position.setY(m_position.y() + dy);
-    clampAboveGround();
+  m_position.setY(m_position.y() + dy);
+  clampAboveGround();
 }
 
 void Camera::yaw(float degrees) {
-    // Pure yaw: keep current pitch and radius exactly
-    QVector3D offset = m_position - m_target;
-    float curYaw=0.f, curPitch=0.f;
-    computeYawPitchFromOffset(offset, curYaw, curPitch);
-    orbit(degrees, 0.0f);
+
+  QVector3D offset = m_position - m_target;
+  float curYaw = 0.f, curPitch = 0.f;
+  computeYawPitchFromOffset(offset, curYaw, curPitch);
+  orbit(degrees, 0.0f);
 }
 
 void Camera::orbit(float yawDeg, float pitchDeg) {
-    // Rotate around target with pitch clamped and world-up locked
-    QVector3D offset = m_position - m_target;
-    if (offset.lengthSquared() < 1e-6f) {
-        // Nudge back if somehow at target
-        offset = QVector3D(0, 0, 1);
-    }
-    float curYaw=0.f, curPitch=0.f;
-    computeYawPitchFromOffset(offset, curYaw, curPitch);
-    float newYaw = curYaw + yawDeg;
-    float newPitch = qBound(m_pitchMinDeg, curPitch + pitchDeg, m_pitchMaxDeg);
-    float r = offset.length();
-    float yawRad = qDegreesToRadians(newYaw);
-    float pitchRad = qDegreesToRadians(newPitch);
-    QVector3D newDir(
-        std::sin(yawRad) * std::cos(pitchRad),
-        std::sin(pitchRad),
-        std::cos(yawRad) * std::cos(pitchRad)
-    );
-    m_position = m_target - newDir.normalized() * r;
-    clampAboveGround();
-    m_front = (m_target - m_position).normalized();
-    updateVectors();
+
+  QVector3D offset = m_position - m_target;
+  if (offset.lengthSquared() < 1e-6f) {
+
+    offset = QVector3D(0, 0, 1);
+  }
+  float curYaw = 0.f, curPitch = 0.f;
+  computeYawPitchFromOffset(offset, curYaw, curPitch);
+  float newYaw = curYaw + yawDeg;
+  float newPitch = qBound(m_pitchMinDeg, curPitch + pitchDeg, m_pitchMaxDeg);
+  float r = offset.length();
+  float yawRad = qDegreesToRadians(newYaw);
+  float pitchRad = qDegreesToRadians(newPitch);
+  QVector3D newDir(std::sin(yawRad) * std::cos(pitchRad), std::sin(pitchRad),
+                   std::cos(yawRad) * std::cos(pitchRad));
+  m_position = m_target - newDir.normalized() * r;
+  clampAboveGround();
+  m_front = (m_target - m_position).normalized();
+  updateVectors();
 }
 
-bool Camera::screenToGround(float sx, float sy, float screenW, float screenH, QVector3D& outWorld) const {
-    if (screenW <= 0 || screenH <= 0) return false;
-    float x = (2.0f * sx / screenW) - 1.0f;
-    float y = 1.0f - (2.0f * sy / screenH);
-    bool ok=false;
-    QMatrix4x4 invVP = (getProjectionMatrix() * getViewMatrix()).inverted(&ok);
-    if (!ok) return false;
-    QVector4D nearClip(x, y, 0.0f, 1.0f);
-    QVector4D farClip (x, y, 1.0f, 1.0f);
-    QVector4D nearWorld4 = invVP * nearClip;
-    QVector4D farWorld4  = invVP * farClip;
-    if (nearWorld4.w() == 0.0f || farWorld4.w() == 0.0f) return false;
-    QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
-    QVector3D rayEnd    = (farWorld4  / farWorld4.w()).toVector3D();
-    QVector3D rayDir    = (rayEnd - rayOrigin).normalized();
-    if (qFuzzyIsNull(rayDir.y())) return false;
-    // Intersect with plane y = m_groundY
-    float t = (m_groundY - rayOrigin.y()) / rayDir.y();
-    if (t < 0.0f) return false;
-    outWorld = rayOrigin + rayDir * t;
-    return true;
+bool Camera::screenToGround(float sx, float sy, float screenW, float screenH,
+                            QVector3D &outWorld) const {
+  if (screenW <= 0 || screenH <= 0)
+    return false;
+  float x = (2.0f * sx / screenW) - 1.0f;
+  float y = 1.0f - (2.0f * sy / screenH);
+  bool ok = false;
+  QMatrix4x4 invVP = (getProjectionMatrix() * getViewMatrix()).inverted(&ok);
+  if (!ok)
+    return false;
+  QVector4D nearClip(x, y, 0.0f, 1.0f);
+  QVector4D farClip(x, y, 1.0f, 1.0f);
+  QVector4D nearWorld4 = invVP * nearClip;
+  QVector4D farWorld4 = invVP * farClip;
+  if (nearWorld4.w() == 0.0f || farWorld4.w() == 0.0f)
+    return false;
+  QVector3D rayOrigin = (nearWorld4 / nearWorld4.w()).toVector3D();
+  QVector3D rayEnd = (farWorld4 / farWorld4.w()).toVector3D();
+  QVector3D rayDir = (rayEnd - rayOrigin).normalized();
+  if (qFuzzyIsNull(rayDir.y()))
+    return false;
+
+  float t = (m_groundY - rayOrigin.y()) / rayDir.y();
+  if (t < 0.0f)
+    return false;
+  outWorld = rayOrigin + rayDir * t;
+  return true;
 }
 
-bool Camera::worldToScreen(const QVector3D& world, int screenW, int screenH, QPointF& outScreen) const {
-    if (screenW <= 0 || screenH <= 0) return false;
-    QVector4D clip = getProjectionMatrix() * getViewMatrix() * QVector4D(world, 1.0f);
-    if (clip.w() == 0.0f) return false;
-    QVector3D ndc = (clip / clip.w()).toVector3D();
-    if (ndc.z() < -1.0f || ndc.z() > 1.0f) return false;
-    float sx = (ndc.x() * 0.5f + 0.5f) * float(screenW);
-    float sy = (1.0f - (ndc.y() * 0.5f + 0.5f)) * float(screenH);
-    outScreen = QPointF(sx, sy);
-    return true;
+bool Camera::worldToScreen(const QVector3D &world, int screenW, int screenH,
+                           QPointF &outScreen) const {
+  if (screenW <= 0 || screenH <= 0)
+    return false;
+  QVector4D clip =
+      getProjectionMatrix() * getViewMatrix() * QVector4D(world, 1.0f);
+  if (clip.w() == 0.0f)
+    return false;
+  QVector3D ndc = (clip / clip.w()).toVector3D();
+  if (ndc.z() < -1.0f || ndc.z() > 1.0f)
+    return false;
+  float sx = (ndc.x() * 0.5f + 0.5f) * float(screenW);
+  float sy = (1.0f - (ndc.y() * 0.5f + 0.5f)) * float(screenH);
+  outScreen = QPointF(sx, sy);
+  return true;
 }
 
-void Camera::updateFollow(const QVector3D& targetCenter) {
-    if (!m_followEnabled) return;
-    if (m_followOffset.lengthSquared() < 1e-5f) {
-        // Initialize offset from current camera state
-        m_followOffset = m_position - m_target;
-    }
-    QVector3D desiredPos = targetCenter + m_followOffset;
-    QVector3D newPos = (m_followLerp >= 0.999f) ? desiredPos
-                        : (m_position + (desiredPos - m_position) * m_followLerp);
-    m_target = targetCenter;
-    m_position = newPos;
-    // Always look at the target while following
-    m_front = (m_target - m_position).normalized();
-    clampAboveGround();
-    updateVectors();
+void Camera::updateFollow(const QVector3D &targetCenter) {
+  if (!m_followEnabled)
+    return;
+  if (m_followOffset.lengthSquared() < 1e-5f) {
+
+    m_followOffset = m_position - m_target;
+  }
+  QVector3D desiredPos = targetCenter + m_followOffset;
+  QVector3D newPos =
+      (m_followLerp >= 0.999f)
+          ? desiredPos
+          : (m_position + (desiredPos - m_position) * m_followLerp);
+  m_target = targetCenter;
+  m_position = newPos;
+
+  m_front = (m_target - m_position).normalized();
+  clampAboveGround();
+  updateVectors();
 }
 
-void Camera::setRTSView(const QVector3D& center, float distance, float angle) {
-    m_target = center;
-    
-    // Calculate position based on angle and distance
-    float radians = qDegreesToRadians(angle);
-    m_position = center + QVector3D(0, distance * qSin(radians), distance * qCos(radians));
-    
-    m_up = QVector3D(0, 1, 0);
-    m_front = (m_target - m_position).normalized();
-    updateVectors();
+void Camera::setRTSView(const QVector3D &center, float distance, float angle) {
+  m_target = center;
+
+  float radians = qDegreesToRadians(angle);
+  m_position =
+      center + QVector3D(0, distance * qSin(radians), distance * qCos(radians));
+
+  m_up = QVector3D(0, 1, 0);
+  m_front = (m_target - m_position).normalized();
+  updateVectors();
 }
 
-void Camera::setTopDownView(const QVector3D& center, float distance) {
-    m_target = center;
-    m_position = center + QVector3D(0, distance, 0);
-    m_up = QVector3D(0, 0, -1);
-    m_front = (m_target - m_position).normalized();
-    updateVectors();
+void Camera::setTopDownView(const QVector3D &center, float distance) {
+  m_target = center;
+  m_position = center + QVector3D(0, distance, 0);
+  m_up = QVector3D(0, 0, -1);
+  m_front = (m_target - m_position).normalized();
+  updateVectors();
 }
 
 QMatrix4x4 Camera::getViewMatrix() const {
-    QMatrix4x4 view;
-    view.lookAt(m_position, m_target, m_up);
-    return view;
+  QMatrix4x4 view;
+  view.lookAt(m_position, m_target, m_up);
+  return view;
 }
 
 QMatrix4x4 Camera::getProjectionMatrix() const {
-    QMatrix4x4 projection;
-    
-    if (m_isPerspective) {
-        projection.perspective(m_fov, m_aspect, m_nearPlane, m_farPlane);
-    } else {
-        projection.ortho(m_orthoLeft, m_orthoRight, m_orthoBottom, m_orthoTop, m_nearPlane, m_farPlane);
-    }
-    
-    return projection;
+  QMatrix4x4 projection;
+
+  if (m_isPerspective) {
+    projection.perspective(m_fov, m_aspect, m_nearPlane, m_farPlane);
+  } else {
+    projection.ortho(m_orthoLeft, m_orthoRight, m_orthoBottom, m_orthoTop,
+                     m_nearPlane, m_farPlane);
+  }
+
+  return projection;
 }
 
 QMatrix4x4 Camera::getViewProjectionMatrix() const {
-    return getProjectionMatrix() * getViewMatrix();
+  return getProjectionMatrix() * getViewMatrix();
 }
 
 void Camera::updateVectors() {
-    // Keep camera upright: lock roll by using world up for building right vector
-    QVector3D worldUp(0,1,0);
-    // If front is parallel to worldUp, skip to a safe right
-    QVector3D r = QVector3D::crossProduct(m_front, worldUp);
-    if (r.lengthSquared() < 1e-6f) r = QVector3D(1,0,0);
-    m_right = r.normalized();
-    m_up = QVector3D::crossProduct(m_right, m_front).normalized();
+
+  QVector3D worldUp(0, 1, 0);
+
+  QVector3D r = QVector3D::crossProduct(m_front, worldUp);
+  if (r.lengthSquared() < 1e-6f)
+    r = QVector3D(1, 0, 0);
+  m_right = r.normalized();
+  m_up = QVector3D::crossProduct(m_right, m_front).normalized();
 }
 
 void Camera::clampAboveGround() {
-    if (m_position.y() < m_groundY + m_minHeight) {
-        m_position.setY(m_groundY + m_minHeight);
-    }
+  if (m_position.y() < m_groundY + m_minHeight) {
+    m_position.setY(m_groundY + m_minHeight);
+  }
 }
 
-void Camera::computeYawPitchFromOffset(const QVector3D& off, float& yawDeg, float& pitchDeg) const {
-    QVector3D dir = -off; // look direction from position to target
-    if (dir.lengthSquared() < 1e-6f) {
-        yawDeg = 0.f; pitchDeg = 0.f; return;
-    }
-    float yaw = qRadiansToDegrees(std::atan2(dir.x(), dir.z()));
-    float lenXZ = std::sqrt(dir.x()*dir.x() + dir.z()*dir.z());
-    float pitch = qRadiansToDegrees(std::atan2(dir.y(), lenXZ));
-    yawDeg = yaw;
-    pitchDeg = pitch;
+void Camera::computeYawPitchFromOffset(const QVector3D &off, float &yawDeg,
+                                       float &pitchDeg) const {
+  QVector3D dir = -off;
+  if (dir.lengthSquared() < 1e-6f) {
+    yawDeg = 0.f;
+    pitchDeg = 0.f;
+    return;
+  }
+  float yaw = qRadiansToDegrees(std::atan2(dir.x(), dir.z()));
+  float lenXZ = std::sqrt(dir.x() * dir.x() + dir.z() * dir.z());
+  float pitch = qRadiansToDegrees(std::atan2(dir.y(), lenXZ));
+  yawDeg = yaw;
+  pitchDeg = pitch;
 }
 
 } // namespace Render::GL

+ 81 - 86
render/gl/camera.h

@@ -1,100 +1,95 @@
 #pragma once
 
 #include <QMatrix4x4>
-#include <QVector3D>
 #include <QPointF>
+#include <QVector3D>
 
 namespace Render::GL {
 
 class Camera {
 public:
-    Camera();
-    
-    // View matrix
-    void setPosition(const QVector3D& position);
-    void setTarget(const QVector3D& target);
-    void setUp(const QVector3D& up);
-    void lookAt(const QVector3D& position, const QVector3D& target, const QVector3D& up);
-    
-    // Projection matrix
-    void setPerspective(float fov, float aspect, float nearPlane, float farPlane);
-    void setOrthographic(float left, float right, float bottom, float top, float nearPlane, float farPlane);
-    
-    // Camera movement for RTS-style controls
-    void moveForward(float distance);
-    void moveRight(float distance);
-    void moveUp(float distance);
-    void zoom(float delta);
-    void rotate(float yaw, float pitch);
-    // High-level RTS utilities
-    void pan(float rightDist, float forwardDist);   // move position & target along right and ground-forward (XZ)
-    void elevate(float dy);                         // move position vertically; target unchanged
-    void yaw(float degrees);                        // rotate around target (yaw only)
-    void orbit(float yawDeg, float pitchDeg);       // rotate around target (yaw & pitch)
-    bool screenToGround(float sx, float sy, float screenW, float screenH, QVector3D& outWorld) const; // ray-plane y=0
-    bool worldToScreen(const QVector3D& world, int screenW, int screenH, QPointF& outScreen) const;    // to pixels
-
-    // Follow helpers (kept in camera to avoid UI/engine low-level math)
-    void setFollowEnabled(bool enable) { m_followEnabled = enable; }
-    bool isFollowEnabled() const { return m_followEnabled; }
-    void setFollowLerp(float alpha) { m_followLerp = alpha; }
-    void setFollowOffset(const QVector3D& off) { m_followOffset = off; }
-    void captureFollowOffset() { m_followOffset = m_position - m_target; }
-    void updateFollow(const QVector3D& targetCenter); // call each frame with current target center
-    
-    // RTS camera presets
-    void setRTSView(const QVector3D& center, float distance = 10.0f, float angle = 45.0f);
-    void setTopDownView(const QVector3D& center, float distance = 10.0f);
-    
-    // Matrix getters
-    QMatrix4x4 getViewMatrix() const;
-    QMatrix4x4 getProjectionMatrix() const;
-    QMatrix4x4 getViewProjectionMatrix() const;
-    
-    // Getters
-    const QVector3D& getPosition() const { return m_position; }
-    const QVector3D& getTarget() const { return m_target; }
-    float getFOV() const { return m_fov; }
-    float getAspect() const { return m_aspect; }
-    float getNear() const { return m_nearPlane; }
-    float getFar() const { return m_farPlane; }
+  Camera();
+
+  void setPosition(const QVector3D &position);
+  void setTarget(const QVector3D &target);
+  void setUp(const QVector3D &up);
+  void lookAt(const QVector3D &position, const QVector3D &target,
+              const QVector3D &up);
+
+  void setPerspective(float fov, float aspect, float nearPlane, float farPlane);
+  void setOrthographic(float left, float right, float bottom, float top,
+                       float nearPlane, float farPlane);
+
+  void moveForward(float distance);
+  void moveRight(float distance);
+  void moveUp(float distance);
+  void zoom(float delta);
+  void rotate(float yaw, float pitch);
+
+  void pan(float rightDist, float forwardDist);
+
+  void elevate(float dy);
+  void yaw(float degrees);
+  void orbit(float yawDeg, float pitchDeg);
+  bool screenToGround(float sx, float sy, float screenW, float screenH,
+                      QVector3D &outWorld) const;
+  bool worldToScreen(const QVector3D &world, int screenW, int screenH,
+                     QPointF &outScreen) const;
+
+  void setFollowEnabled(bool enable) { m_followEnabled = enable; }
+  bool isFollowEnabled() const { return m_followEnabled; }
+  void setFollowLerp(float alpha) { m_followLerp = alpha; }
+  void setFollowOffset(const QVector3D &off) { m_followOffset = off; }
+  void captureFollowOffset() { m_followOffset = m_position - m_target; }
+  void updateFollow(const QVector3D &targetCenter);
+
+  void setRTSView(const QVector3D &center, float distance = 10.0f,
+                  float angle = 45.0f);
+  void setTopDownView(const QVector3D &center, float distance = 10.0f);
+
+  QMatrix4x4 getViewMatrix() const;
+  QMatrix4x4 getProjectionMatrix() const;
+  QMatrix4x4 getViewProjectionMatrix() const;
+
+  const QVector3D &getPosition() const { return m_position; }
+  const QVector3D &getTarget() const { return m_target; }
+  float getFOV() const { return m_fov; }
+  float getAspect() const { return m_aspect; }
+  float getNear() const { return m_nearPlane; }
+  float getFar() const { return m_farPlane; }
 
 private:
-    QVector3D m_position{0.0f, 0.0f, 0.0f};
-    QVector3D m_target{0.0f, 0.0f, -1.0f};
-    QVector3D m_up{0.0f, 1.0f, 0.0f};
-    QVector3D m_front{0.0f, 0.0f, -1.0f};
-    QVector3D m_right{1.0f, 0.0f, 0.0f};
-    
-    // Projection parameters
-    bool m_isPerspective = true;
-    float m_fov = 45.0f;
-    float m_aspect = 16.0f / 9.0f;
-    float m_nearPlane = 0.1f;
-    float m_farPlane = 1000.0f;
-    
-    // Orthographic parameters
-    float m_orthoLeft = -10.0f;
-    float m_orthoRight = 10.0f;
-    float m_orthoBottom = -10.0f;
-    float m_orthoTop = 10.0f;
-    
-    // Follow state
-    bool m_followEnabled = false;
-    QVector3D m_followOffset{0,0,0};
-    float m_followLerp = 0.15f;
-
-    // RTS constraints
-    float m_groundY = 0.0f;
-    float m_minHeight = 0.5f;          // do not go below this height above ground
-    // Pitch is measured from the XZ plane; negative means looking down.
-    // Clamp to a downward-looking range to keep ground at bottom and avoid flips.
-    float m_pitchMinDeg = -85.0f;      // min (most downward) pitch
-    float m_pitchMaxDeg = -5.0f;       // max (least downward) pitch
-
-    void updateVectors();
-    void clampAboveGround();
-    void computeYawPitchFromOffset(const QVector3D& off, float& yawDeg, float& pitchDeg) const;
+  QVector3D m_position{0.0f, 0.0f, 0.0f};
+  QVector3D m_target{0.0f, 0.0f, -1.0f};
+  QVector3D m_up{0.0f, 1.0f, 0.0f};
+  QVector3D m_front{0.0f, 0.0f, -1.0f};
+  QVector3D m_right{1.0f, 0.0f, 0.0f};
+
+  bool m_isPerspective = true;
+  float m_fov = 45.0f;
+  float m_aspect = 16.0f / 9.0f;
+  float m_nearPlane = 0.1f;
+  float m_farPlane = 1000.0f;
+
+  float m_orthoLeft = -10.0f;
+  float m_orthoRight = 10.0f;
+  float m_orthoBottom = -10.0f;
+  float m_orthoTop = 10.0f;
+
+  bool m_followEnabled = false;
+  QVector3D m_followOffset{0, 0, 0};
+  float m_followLerp = 0.15f;
+
+  float m_groundY = 0.0f;
+  float m_minHeight = 0.5f;
+
+  float m_pitchMinDeg = -85.0f;
+  float m_pitchMaxDeg = -5.0f;
+
+  void updateVectors();
+  void clampAboveGround();
+  void computeYawPitchFromOffset(const QVector3D &off, float &yawDeg,
+                                 float &pitchDeg) const;
 };
 
 } // namespace Render::GL

+ 102 - 119
render/gl/mesh.cpp

@@ -3,140 +3,123 @@
 
 namespace Render::GL {
 
-Mesh::Mesh(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices)
-    : m_vertices(vertices), m_indices(indices) {
-    // Defer GL buffer setup until a context is current; will be created on first draw
-}
+Mesh::Mesh(const std::vector<Vertex> &vertices,
+           const std::vector<unsigned int> &indices)
+    : m_vertices(vertices), m_indices(indices) {}
 
 Mesh::~Mesh() = default;
 
 void Mesh::setupBuffers() {
-    initializeOpenGLFunctions();
-    m_vao = std::make_unique<VertexArray>();
-    m_vbo = std::make_unique<Buffer>(Buffer::Type::Vertex);
-    m_ebo = std::make_unique<Buffer>(Buffer::Type::Index);
-    
-    m_vao->bind();
-    
-    // Set vertex data
-    m_vbo->setData(m_vertices);
-    
-    // Set index data
-    m_ebo->setData(m_indices);
-    
-    // Define vertex layout: position (3), normal (3), texCoord (2)
-    std::vector<int> layout = {3, 3, 2};
-    m_vao->addVertexBuffer(*m_vbo, layout);
-    m_vao->setIndexBuffer(*m_ebo);
-    
-    m_vao->unbind();
+  initializeOpenGLFunctions();
+  m_vao = std::make_unique<VertexArray>();
+  m_vbo = std::make_unique<Buffer>(Buffer::Type::Vertex);
+  m_ebo = std::make_unique<Buffer>(Buffer::Type::Index);
+
+  m_vao->bind();
+
+  m_vbo->setData(m_vertices);
+
+  m_ebo->setData(m_indices);
+
+  std::vector<int> layout = {3, 3, 2};
+  m_vao->addVertexBuffer(*m_vbo, layout);
+  m_vao->setIndexBuffer(*m_ebo);
+
+  m_vao->unbind();
 }
 
 void Mesh::draw() {
-    if (!m_vao) {
-        setupBuffers();
-    }
-    m_vao->bind();
-    
-    // Draw elements
-    initializeOpenGLFunctions();
-    glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indices.size()), GL_UNSIGNED_INT, nullptr);
-    
-    m_vao->unbind();
+  if (!m_vao) {
+    setupBuffers();
+  }
+  m_vao->bind();
+
+  initializeOpenGLFunctions();
+  glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indices.size()),
+                 GL_UNSIGNED_INT, nullptr);
+
+  m_vao->unbind();
 }
 
-Mesh* createQuadMesh() {
-    std::vector<Vertex> vertices = {
-        // Position           Normal            TexCoord
-        {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
-        {{ 1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
-        {{ 1.0f,  1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
-        {{-1.0f,  1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}
-    };
-    
-    std::vector<unsigned int> indices = {
-        0, 1, 2,
-        2, 3, 0
-    };
-    
-    return new Mesh(vertices, indices);
+Mesh *createQuadMesh() {
+  std::vector<Vertex> vertices = {
+
+      {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
+      {{1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
+      {{1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
+      {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}};
+
+  std::vector<unsigned int> indices = {0, 1, 2, 2, 3, 0};
+
+  return new Mesh(vertices, indices);
 }
 
-Mesh* createCubeMesh() {
-    std::vector<Vertex> vertices = {
-        // Front face
-        {{-1.0f, -1.0f,  1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
-        {{ 1.0f, -1.0f,  1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
-        {{ 1.0f,  1.0f,  1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
-        {{-1.0f,  1.0f,  1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}},
-        
-        // Back face
-        {{-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f}},
-        {{-1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f}},
-        {{ 1.0f,  1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f}},
-        {{ 1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f}},
-    };
-    
-    std::vector<unsigned int> indices = {
-        // Front face
-        0, 1, 2, 2, 3, 0,
-        // Back face
-        4, 5, 6, 6, 7, 4,
-        // Left face
-        4, 0, 3, 3, 5, 4,
-        // Right face
-        1, 7, 6, 6, 2, 1,
-        // Top face
-        3, 2, 6, 6, 5, 3,
-        // Bottom face
-        4, 7, 1, 1, 0, 4
-    };
-    
-    return new Mesh(vertices, indices);
+Mesh *createCubeMesh() {
+  std::vector<Vertex> vertices = {
+
+      {{-1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
+      {{1.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
+      {{1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
+      {{-1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}},
+
+      {{-1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f}},
+      {{-1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f}},
+      {{1.0f, 1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f}},
+      {{1.0f, -1.0f, -1.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f}},
+  };
+
+  std::vector<unsigned int> indices = {0, 1, 2, 2, 3, 0,
+
+                                       4, 5, 6, 6, 7, 4,
+
+                                       4, 0, 3, 3, 5, 4,
+
+                                       1, 7, 6, 6, 2, 1,
+
+                                       3, 2, 6, 6, 5, 3,
+
+                                       4, 7, 1, 1, 0, 4};
+
+  return new Mesh(vertices, indices);
 }
 
-Mesh* createPlaneMesh(float width, float height, int subdivisions) {
-    std::vector<Vertex> vertices;
-    std::vector<unsigned int> indices;
-    
-    float halfWidth = width * 0.5f;
-    float halfHeight = height * 0.5f;
-    
-    // Generate vertices
-    for (int z = 0; z <= subdivisions; ++z) {
-        for (int x = 0; x <= subdivisions; ++x) {
-            float xPos = (x / static_cast<float>(subdivisions)) * width - halfWidth;
-            float zPos = (z / static_cast<float>(subdivisions)) * height - halfHeight;
-            
-            vertices.push_back({
-                {xPos, 0.0f, zPos},
-                {0.0f, 1.0f, 0.0f},
-                {x / static_cast<float>(subdivisions), z / static_cast<float>(subdivisions)}
-            });
-        }
+Mesh *createPlaneMesh(float width, float height, int subdivisions) {
+  std::vector<Vertex> vertices;
+  std::vector<unsigned int> indices;
+
+  float halfWidth = width * 0.5f;
+  float halfHeight = height * 0.5f;
+
+  for (int z = 0; z <= subdivisions; ++z) {
+    for (int x = 0; x <= subdivisions; ++x) {
+      float xPos = (x / static_cast<float>(subdivisions)) * width - halfWidth;
+      float zPos = (z / static_cast<float>(subdivisions)) * height - halfHeight;
+
+      vertices.push_back({{xPos, 0.0f, zPos},
+                          {0.0f, 1.0f, 0.0f},
+                          {x / static_cast<float>(subdivisions),
+                           z / static_cast<float>(subdivisions)}});
     }
-    
-    // Generate indices
-    for (int z = 0; z < subdivisions; ++z) {
-        for (int x = 0; x < subdivisions; ++x) {
-            int topLeft = z * (subdivisions + 1) + x;
-            int topRight = topLeft + 1;
-            int bottomLeft = (z + 1) * (subdivisions + 1) + x;
-            int bottomRight = bottomLeft + 1;
-            
-            // First triangle
-            indices.push_back(topLeft);
-            indices.push_back(bottomLeft);
-            indices.push_back(topRight);
-            
-            // Second triangle
-            indices.push_back(topRight);
-            indices.push_back(bottomLeft);
-            indices.push_back(bottomRight);
-        }
+  }
+
+  for (int z = 0; z < subdivisions; ++z) {
+    for (int x = 0; x < subdivisions; ++x) {
+      int topLeft = z * (subdivisions + 1) + x;
+      int topRight = topLeft + 1;
+      int bottomLeft = (z + 1) * (subdivisions + 1) + x;
+      int bottomRight = bottomLeft + 1;
+
+      indices.push_back(topLeft);
+      indices.push_back(bottomLeft);
+      indices.push_back(topRight);
+
+      indices.push_back(topRight);
+      indices.push_back(bottomLeft);
+      indices.push_back(bottomRight);
     }
-    
-    return new Mesh(vertices, indices);
+  }
+
+  return new Mesh(vertices, indices);
 }
 
 } // namespace Render::GL

+ 22 - 22
render/gl/mesh.h

@@ -2,41 +2,41 @@
 
 #include "buffer.h"
 #include <QOpenGLFunctions_3_3_Core>
-#include <vector>
 #include <memory>
+#include <vector>
 
 namespace Render::GL {
 
 struct Vertex {
-    float position[3];
-    float normal[3];
-    float texCoord[2];
+  float position[3];
+  float normal[3];
+  float texCoord[2];
 };
 
 class Mesh : protected QOpenGLFunctions_3_3_Core {
 public:
-    Mesh(const std::vector<Vertex>& vertices, const std::vector<unsigned int>& indices);
-    ~Mesh();
+  Mesh(const std::vector<Vertex> &vertices,
+       const std::vector<unsigned int> &indices);
+  ~Mesh();
+
+  void draw();
 
-    void draw();
-    
-    const std::vector<Vertex>& getVertices() const { return m_vertices; }
-    const std::vector<unsigned int>& getIndices() const { return m_indices; }
+  const std::vector<Vertex> &getVertices() const { return m_vertices; }
+  const std::vector<unsigned int> &getIndices() const { return m_indices; }
 
 private:
-    std::vector<Vertex> m_vertices;
-    std::vector<unsigned int> m_indices;
-    
-    std::unique_ptr<VertexArray> m_vao;
-    std::unique_ptr<Buffer> m_vbo;
-    std::unique_ptr<Buffer> m_ebo;
-    
-    void setupBuffers();
+  std::vector<Vertex> m_vertices;
+  std::vector<unsigned int> m_indices;
+
+  std::unique_ptr<VertexArray> m_vao;
+  std::unique_ptr<Buffer> m_vbo;
+  std::unique_ptr<Buffer> m_ebo;
+
+  void setupBuffers();
 };
 
-// Factory functions for common meshes
-Mesh* createQuadMesh();
-Mesh* createCubeMesh();
-Mesh* createPlaneMesh(float width, float height, int subdivisions = 1);
+Mesh *createQuadMesh();
+Mesh *createCubeMesh();
+Mesh *createPlaneMesh(float width, float height, int subdivisions = 1);
 
 } // namespace Render::GL

+ 12 - 12
render/gl/resources.cpp

@@ -1,23 +1,23 @@
 #include "resources.h"
-#include <cmath>
 #include <QVector3D>
+#include <cmath>
 
 namespace Render::GL {
 
 bool ResourceManager::initialize() {
-    initializeOpenGLFunctions();
+  initializeOpenGLFunctions();
 
-    m_quadMesh.reset(createQuadMesh());
-    m_groundMesh.reset(createPlaneMesh(1.0f, 1.0f, 64));
-    m_unitMesh.reset(createCubeMesh());
+  m_quadMesh.reset(createQuadMesh());
+  m_groundMesh.reset(createPlaneMesh(1.0f, 1.0f, 64));
+  m_unitMesh.reset(createCubeMesh());
 
-    // White texture
-    m_whiteTexture = std::make_unique<Texture>();
-    m_whiteTexture->createEmpty(1, 1, Texture::Format::RGBA);
-    unsigned char whitePixel[4] = {255, 255, 255, 255};
-    m_whiteTexture->bind();
-    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel);
-    return true;
+  m_whiteTexture = std::make_unique<Texture>();
+  m_whiteTexture->createEmpty(1, 1, Texture::Format::RGBA);
+  unsigned char whitePixel[4] = {255, 255, 255, 255};
+  m_whiteTexture->bind();
+  glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
+                  whitePixel);
+  return true;
 }
 
 } // namespace Render::GL

+ 15 - 16
render/gl/resources.h

@@ -1,9 +1,9 @@
 #pragma once
 
-#include <memory>
-#include <QOpenGLFunctions_3_3_Core>
 #include "mesh.h"
 #include "texture.h"
+#include <QOpenGLFunctions_3_3_Core>
+#include <memory>
 
 #include "../geom/arrow.h"
 
@@ -11,24 +11,23 @@ namespace Render::GL {
 
 class ResourceManager : protected QOpenGLFunctions_3_3_Core {
 public:
-    ResourceManager() = default;
-    ~ResourceManager() = default;
+  ResourceManager() = default;
+  ~ResourceManager() = default;
 
-    // Must be called with a current, valid GL context
-    bool initialize();
+  bool initialize();
 
-    Mesh* quad() const { return m_quadMesh.get(); }
-    Mesh* ground() const { return m_groundMesh.get(); }
-    Mesh* unit() const { return m_unitMesh.get(); }
-    Mesh* arrow() const { return Render::Geom::Arrow::get(); }
-    Texture* white() const { return m_whiteTexture.get(); }
+  Mesh *quad() const { return m_quadMesh.get(); }
+  Mesh *ground() const { return m_groundMesh.get(); }
+  Mesh *unit() const { return m_unitMesh.get(); }
+  Mesh *arrow() const { return Render::Geom::Arrow::get(); }
+  Texture *white() const { return m_whiteTexture.get(); }
 
 private:
-    std::unique_ptr<Mesh> m_quadMesh;
-    std::unique_ptr<Mesh> m_groundMesh;
-    std::unique_ptr<Mesh> m_unitMesh;  // generic placeholder unit mesh
-    // Capsule and selection ring meshes have been moved to entity/geom modules
-    std::unique_ptr<Texture> m_whiteTexture;
+  std::unique_ptr<Mesh> m_quadMesh;
+  std::unique_ptr<Mesh> m_groundMesh;
+  std::unique_ptr<Mesh> m_unitMesh;
+
+  std::unique_ptr<Texture> m_whiteTexture;
 };
 
 } // namespace Render::GL

+ 103 - 102
render/gl/shader.cpp

@@ -1,138 +1,139 @@
 #include "shader.h"
+#include <QDebug>
 #include <QFile>
 #include <QTextStream>
-#include <QDebug>
 
 namespace Render::GL {
 
-Shader::Shader() {
-    // Defer OpenGL function initialization until a context is current
-}
+Shader::Shader() {}
 
 Shader::~Shader() {
-    if (m_program != 0) {
-        glDeleteProgram(m_program);
-    }
+  if (m_program != 0) {
+    glDeleteProgram(m_program);
+  }
 }
 
-bool Shader::loadFromFiles(const QString& vertexPath, const QString& fragmentPath) {
-    QFile vertexFile(vertexPath);
-    QFile fragmentFile(fragmentPath);
-    
-    if (!vertexFile.open(QIODevice::ReadOnly) || !fragmentFile.open(QIODevice::ReadOnly)) {
-        qWarning() << "Failed to open shader files";
-        return false;
-    }
-    
-    QTextStream vertexStream(&vertexFile);
-    QTextStream fragmentStream(&fragmentFile);
-    
-    QString vertexSource = vertexStream.readAll();
-    QString fragmentSource = fragmentStream.readAll();
-    
-    return loadFromSource(vertexSource, fragmentSource);
+bool Shader::loadFromFiles(const QString &vertexPath,
+                           const QString &fragmentPath) {
+  QFile vertexFile(vertexPath);
+  QFile fragmentFile(fragmentPath);
+
+  if (!vertexFile.open(QIODevice::ReadOnly) ||
+      !fragmentFile.open(QIODevice::ReadOnly)) {
+    qWarning() << "Failed to open shader files";
+    return false;
+  }
+
+  QTextStream vertexStream(&vertexFile);
+  QTextStream fragmentStream(&fragmentFile);
+
+  QString vertexSource = vertexStream.readAll();
+  QString fragmentSource = fragmentStream.readAll();
+
+  return loadFromSource(vertexSource, fragmentSource);
 }
 
-bool Shader::loadFromSource(const QString& vertexSource, const QString& fragmentSource) {
-    initializeOpenGLFunctions();
-    GLuint vertexShader = compileShader(vertexSource, GL_VERTEX_SHADER);
-    GLuint fragmentShader = compileShader(fragmentSource, GL_FRAGMENT_SHADER);
-    
-    if (vertexShader == 0 || fragmentShader == 0) {
-        return false;
-    }
-    
-    bool success = linkProgram(vertexShader, fragmentShader);
-    
-    glDeleteShader(vertexShader);
-    glDeleteShader(fragmentShader);
-    
-    return success;
+bool Shader::loadFromSource(const QString &vertexSource,
+                            const QString &fragmentSource) {
+  initializeOpenGLFunctions();
+  GLuint vertexShader = compileShader(vertexSource, GL_VERTEX_SHADER);
+  GLuint fragmentShader = compileShader(fragmentSource, GL_FRAGMENT_SHADER);
+
+  if (vertexShader == 0 || fragmentShader == 0) {
+    return false;
+  }
+
+  bool success = linkProgram(vertexShader, fragmentShader);
+
+  glDeleteShader(vertexShader);
+  glDeleteShader(fragmentShader);
+
+  return success;
 }
 
 void Shader::use() {
-    initializeOpenGLFunctions();
-    glUseProgram(m_program);
+  initializeOpenGLFunctions();
+  glUseProgram(m_program);
 }
 
 void Shader::release() {
-    initializeOpenGLFunctions();
-    glUseProgram(0);
+  initializeOpenGLFunctions();
+  glUseProgram(0);
 }
 
-void Shader::setUniform(const QString& name, float value) {
-    GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
-    if (location != -1) {
-        glUniform1f(location, value);
-    }
+void Shader::setUniform(const QString &name, float value) {
+  GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
+  if (location != -1) {
+    glUniform1f(location, value);
+  }
 }
 
-void Shader::setUniform(const QString& name, const QVector3D& value) {
-    GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
-    if (location != -1) {
-        glUniform3f(location, value.x(), value.y(), value.z());
-    }
+void Shader::setUniform(const QString &name, const QVector3D &value) {
+  GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
+  if (location != -1) {
+    glUniform3f(location, value.x(), value.y(), value.z());
+  }
 }
 
-void Shader::setUniform(const QString& name, const QMatrix4x4& value) {
-    GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
-    if (location != -1) {
-        glUniformMatrix4fv(location, 1, GL_FALSE, value.constData());
-    }
+void Shader::setUniform(const QString &name, const QMatrix4x4 &value) {
+  GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
+  if (location != -1) {
+    glUniformMatrix4fv(location, 1, GL_FALSE, value.constData());
+  }
 }
 
-void Shader::setUniform(const QString& name, int value) {
-    GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
-    if (location != -1) {
-        glUniform1i(location, value);
-    }
+void Shader::setUniform(const QString &name, int value) {
+  GLint location = glGetUniformLocation(m_program, name.toUtf8().constData());
+  if (location != -1) {
+    glUniform1i(location, value);
+  }
 }
 
-void Shader::setUniform(const QString& name, bool value) {
-    setUniform(name, static_cast<int>(value));
+void Shader::setUniform(const QString &name, bool value) {
+  setUniform(name, static_cast<int>(value));
 }
 
-GLuint Shader::compileShader(const QString& source, GLenum type) {
-    initializeOpenGLFunctions();
-    GLuint shader = glCreateShader(type);
-    
-    QByteArray sourceBytes = source.toUtf8();
-    const char* sourcePtr = sourceBytes.constData();
-    glShaderSource(shader, 1, &sourcePtr, nullptr);
-    glCompileShader(shader);
-    
-    GLint success;
-    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
-    if (!success) {
-        GLchar infoLog[512];
-        glGetShaderInfoLog(shader, 512, nullptr, infoLog);
-        qWarning() << "Shader compilation failed:" << infoLog;
-        glDeleteShader(shader);
-        return 0;
-    }
-    
-    return shader;
+GLuint Shader::compileShader(const QString &source, GLenum type) {
+  initializeOpenGLFunctions();
+  GLuint shader = glCreateShader(type);
+
+  QByteArray sourceBytes = source.toUtf8();
+  const char *sourcePtr = sourceBytes.constData();
+  glShaderSource(shader, 1, &sourcePtr, nullptr);
+  glCompileShader(shader);
+
+  GLint success;
+  glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+  if (!success) {
+    GLchar infoLog[512];
+    glGetShaderInfoLog(shader, 512, nullptr, infoLog);
+    qWarning() << "Shader compilation failed:" << infoLog;
+    glDeleteShader(shader);
+    return 0;
+  }
+
+  return shader;
 }
 
 bool Shader::linkProgram(GLuint vertexShader, GLuint fragmentShader) {
-    initializeOpenGLFunctions();
-    m_program = glCreateProgram();
-    glAttachShader(m_program, vertexShader);
-    glAttachShader(m_program, fragmentShader);
-    glLinkProgram(m_program);
-    
-    GLint success;
-    glGetProgramiv(m_program, GL_LINK_STATUS, &success);
-    if (!success) {
-        GLchar infoLog[512];
-        glGetProgramInfoLog(m_program, 512, nullptr, infoLog);
-        qWarning() << "Shader linking failed:" << infoLog;
-        glDeleteProgram(m_program);
-        m_program = 0;
-        return false;
-    }
-    
-    return true;
+  initializeOpenGLFunctions();
+  m_program = glCreateProgram();
+  glAttachShader(m_program, vertexShader);
+  glAttachShader(m_program, fragmentShader);
+  glLinkProgram(m_program);
+
+  GLint success;
+  glGetProgramiv(m_program, GL_LINK_STATUS, &success);
+  if (!success) {
+    GLchar infoLog[512];
+    glGetProgramInfoLog(m_program, 512, nullptr, infoLog);
+    qWarning() << "Shader linking failed:" << infoLog;
+    glDeleteProgram(m_program);
+    m_program = 0;
+    return false;
+  }
+
+  return true;
 }
 
 } // namespace Render::GL

Some files were not shown because too many files changed in this diff