Browse Source

improve banner rendering and fix healer aura

djeada 1 week ago
parent
commit
39c183feb1
100 changed files with 3910 additions and 738 deletions
  1. 2 0
      CMakeLists.txt
  2. 332 0
      HEALER_VISUAL_IMPROVEMENTS.md
  3. 1 1
      README.md
  4. 1 1
      app/controllers/action_vfx.cpp
  5. 326 69
      app/core/game_engine.cpp
  6. 25 5
      app/core/game_engine.h
  7. 34 0
      app/models/minimap_image_provider.cpp
  8. 17 0
      app/models/minimap_image_provider.h
  9. 4 0
      assets.qrc
  10. 1 1
      assets/data/nations/carthage.json
  11. 1 1
      assets/data/nations/roman_republic.json
  12. 1 1
      assets/data/troops/base.json
  13. 96 0
      assets/shaders/banner.frag
  14. 59 0
      assets/shaders/banner.vert
  15. 65 0
      assets/shaders/healing_aura.frag
  16. 44 0
      assets/shaders/healing_aura.vert
  17. 102 0
      assets/shaders/healing_beam.frag
  18. 92 0
      assets/shaders/healing_beam.vert
  19. 7 0
      game/CMakeLists.txt
  20. 1 0
      game/core/component.h
  21. 8 8
      game/core/serialization.cpp
  22. 1 1
      game/core/serialization.h
  23. 4 3
      game/core/world.cpp
  24. 59 59
      game/game_config.h
  25. 7 7
      game/map/environment.cpp
  26. 13 12
      game/map/level_loader.cpp
  27. 14 14
      game/map/map_catalog.cpp
  28. 5 5
      game/map/map_catalog.h
  29. 13 12
      game/map/map_transformer.cpp
  30. 1 1
      game/map/map_transformer.h
  31. 359 0
      game/map/minimap/fog_of_war_mask.cpp
  32. 112 0
      game/map/minimap/fog_of_war_mask.h
  33. 92 0
      game/map/minimap/minimap_example.cpp
  34. 647 0
      game/map/minimap/minimap_generator.cpp
  35. 66 0
      game/map/minimap/minimap_generator.h
  36. 179 0
      game/map/minimap/minimap_manager.cpp
  37. 82 0
      game/map/minimap/minimap_manager.h
  38. 39 0
      game/map/minimap/minimap_texture_manager.cpp
  39. 31 0
      game/map/minimap/minimap_texture_manager.h
  40. 145 0
      game/map/minimap/unit_layer.cpp
  41. 106 0
      game/map/minimap/unit_layer.h
  42. 70 69
      game/map/skirmish_loader.cpp
  43. 19 19
      game/map/skirmish_loader.h
  44. 17 17
      game/map/terrain_service.cpp
  45. 23 23
      game/map/terrain_service.h
  46. 1 1
      game/map/visibility_service.cpp
  47. 1 1
      game/map/visibility_service.h
  48. 1 1
      game/map/world_bootstrap.cpp
  49. 12 12
      game/systems/ai_system.cpp
  50. 3 3
      game/systems/ai_system.h
  51. 1 1
      game/systems/ai_system/ai_command_applier.cpp
  52. 53 50
      game/systems/ai_system/ai_reasoner.cpp
  53. 2 2
      game/systems/ai_system/ai_snapshot_builder.cpp
  54. 23 23
      game/systems/ai_system/ai_tactical.cpp
  55. 12 12
      game/systems/ai_system/ai_tactical.h
  56. 17 17
      game/systems/ai_system/ai_types.h
  57. 4 4
      game/systems/ai_system/ai_utils.h
  58. 7 7
      game/systems/ai_system/behaviors/attack_behavior.cpp
  59. 10 10
      game/systems/ai_system/behaviors/defend_behavior.cpp
  60. 4 4
      game/systems/ai_system/behaviors/gather_behavior.cpp
  61. 4 4
      game/systems/ai_system/behaviors/production_behavior.cpp
  62. 4 3
      game/systems/ai_system/behaviors/retreat_behavior.cpp
  63. 2 2
      game/systems/arrow_system.cpp
  64. 8 8
      game/systems/building_collision_registry.cpp
  65. 7 6
      game/systems/building_collision_registry.h
  66. 22 21
      game/systems/camera_controller.cpp
  67. 4 4
      game/systems/camera_controller.h
  68. 2 2
      game/systems/camera_follow_system.cpp
  69. 8 8
      game/systems/camera_service.cpp
  70. 1 1
      game/systems/camera_service.h
  71. 3 2
      game/systems/capture_system.cpp
  72. 16 16
      game/systems/combat_system.cpp
  73. 4 4
      game/systems/combat_system.h
  74. 2 2
      game/systems/formation_system.cpp
  75. 4 4
      game/systems/formation_system.h
  76. 13 13
      game/systems/game_state_serializer.cpp
  77. 1 1
      game/systems/global_stats_registry.cpp
  78. 40 0
      game/systems/healing_beam.cpp
  79. 51 0
      game/systems/healing_beam.h
  80. 30 0
      game/systems/healing_beam_system.cpp
  81. 38 0
      game/systems/healing_beam_system.h
  82. 17 10
      game/systems/healing_system.cpp
  83. 1 1
      game/systems/healing_system.h
  84. 4 4
      game/systems/movement_system.cpp
  85. 8 7
      game/systems/nation_registry.cpp
  86. 6 6
      game/systems/nation_registry.h
  87. 64 61
      game/systems/owner_registry.cpp
  88. 25 25
      game/systems/owner_registry.h
  89. 3 3
      game/systems/pathfinding.cpp
  90. 4 4
      game/systems/picking_service.cpp
  91. 1 1
      game/systems/picking_service.h
  92. 3 2
      game/systems/production_service.cpp
  93. 3 3
      game/systems/production_system.cpp
  94. 6 6
      game/systems/projectile_system.cpp
  95. 7 7
      game/systems/save_load_service.cpp
  96. 7 7
      game/systems/save_load_service.h
  97. 6 6
      game/systems/save_storage.cpp
  98. 4 4
      game/systems/save_storage.h
  99. 2 2
      game/systems/terrain_alignment_system.cpp
  100. 1 1
      game/systems/troop_count_registry.cpp

+ 2 - 0
CMakeLists.txt

@@ -130,6 +130,7 @@ if(QT_VERSION_MAJOR EQUAL 6)
         app/models/cursor_manager.cpp
         app/models/hover_tracker.cpp
         app/models/selected_units_model.cpp
+        app/models/minimap_image_provider.cpp
         app/controllers/command_controller.cpp
         app/controllers/action_vfx.cpp
         app/utils/json_vec_utils.cpp
@@ -146,6 +147,7 @@ else()
         app/models/cursor_manager.cpp
         app/models/hover_tracker.cpp
         app/models/selected_units_model.cpp
+        app/models/minimap_image_provider.cpp
         app/controllers/command_controller.cpp
         app/controllers/action_vfx.cpp
         app/utils/json_vec_utils.cpp

+ 332 - 0
HEALER_VISUAL_IMPROVEMENTS.md

@@ -0,0 +1,332 @@
+# Healer Visual Animation Improvements
+
+## Current System Analysis
+
+### How Healers Currently Work
+- **Healing Mechanism**: Uses the `HealingSystem` which spawns green arrow projectiles
+- **Implementation**: 
+  - `HealingSystem::process_healing()` iterates through healers
+  - For each healer, it finds targets within `healing_range`
+  - Spawns arrows via `arrow_system->spawnArrow()` with green color `(0.2, 1.0, 0.4)`
+  - Arrows use standard projectile physics (arc trajectory)
+- **Current Visual**: Static green arrows that look like damage projectiles but are green
+- **Problem**: No visual distinction from combat; doesn't feel like healing at all
+
+### Key Components
+- **Healer Classes**: `Roman::HealerRenderer`, `Carthage::HealerRenderer`
+- **Healing Component**: `Engine::Core::HealerComponent` with healing_range, healing_amount, healing_cooldown
+- **Arrow System**: Generic `ArrowSystem` used for both damage and healing
+- **Rendering**: Uses healer-specific shaders for visual customization
+
+---
+
+## Proposed Healing Visual Animations
+
+### Option 1: **Healing Aura & Radiant Beam** (RECOMMENDED - Most Impressive)
+
+#### Core Concept
+Replace arrow projectiles with a **radiant healing beam** that flows from healer to target with an aura effect.
+
+#### Visual Elements
+
+1. **Healing Beam** (GPU Shader-based)
+   - Flowing cylindrical beam with animated texture
+   - Color: Bright golden-green with internal shimmer
+   - Path: Curved arc from healer hand to target body
+   - Animation: Flowing particles along the beam (like light flowing)
+   - Effect: Beam pulses in sync with healing cooldown
+
+2. **Healer Aura**
+   - Expanding concentric rings around healer during healing
+   - Color: Soft golden glow
+   - Radius: Extends to healing_range
+   - Opacity: Pulsing 0.3-0.6 based on active healing
+   - Effect: Shows healing area of effect visually
+
+3. **Target Healing Aura**
+   - Bright glow around target being healed
+   - Color: Golden-white with green tint
+   - Animation: Spiral energy flowing upward
+   - Duration: Brief burst when healing hits
+
+4. **Particle Trails**
+   - Small spheres traveling along beam path
+   - Fade as they approach target
+   - Create "droplets of healing energy" effect
+
+#### Technical Implementation
+- Create `healing_beam.vert/frag` shaders with animated texture coordinates
+- New `HealingBeamProjectile` class (separate from arrows)
+- GPU particle system for visual richness
+- Animation time-based for smooth streaming effect
+
+#### Advantages
+- ✅ Instantly recognizable as healing (not damage)
+- ✅ Visually impressive and satisfying
+- ✅ Shows healing area of effect clearly
+- ✅ Professional/polished feeling
+- ✅ Can be disabled/shown based on settings
+- ✅ Scales well for many simultaneous heals
+
+---
+
+### Option 2: **Ethereal Light Spiral** (Alternative - More Mystical)
+
+#### Core Concept
+Floating light spirals that materialize around target, ascending toward the sky.
+
+#### Visual Elements
+
+1. **Healer Cast Animation**
+   - Hands glow with healing energy
+   - Aura burst outward in a sphere
+   - Duration: ~0.5 seconds
+
+2. **Healing Spirals**
+   - 3-4 spiral geometry elements per heal
+   - Color: Iridescent green→gold gradient
+   - Path: Spiral helix from healer to target center
+   - Animation: Twist + upward motion simultaneously
+   - Lifetime: 1-1.5 seconds
+
+3. **Target Reception**
+   - Golden light descends into target
+   - Body gains soft golden glow
+   - Health bar flashes green
+
+#### Advantages
+- ✅ Very distinct and magical
+- ✅ Easier particle effect (no beam geometry)
+- ✅ Fits fantasy aesthetic well
+
+#### Disadvantages
+- ❌ Less "professional" looking
+- ❌ Harder to see when many units are healed
+- ❌ May feel too slow/non-responsive
+
+---
+
+### Option 3: **Cross-Channeled Energy Field** (Most Technical)
+
+#### Core Concept
+Healer creates a zone effect with energy lines connecting to all healable targets.
+
+#### Visual Elements
+
+1. **Healing Zone**
+   - Semi-transparent dome/sphere around healer
+   - Animated wireframe pattern flowing inward
+   - Color: Soft green with golden highlights
+
+2. **Energy Connections**
+   - Lines connecting healer to each nearby target
+   - Pulsing brightness based on healing active/cooldown
+   - Fading in/out smoothly
+
+3. **Target Impact**
+   - Energy node at target location
+   - Glow pulses when healing is applied
+   - Healing bar flashes simultaneously
+
+#### Advantages
+- ✅ Shows range and all targets at once
+- ✅ Very technical/cool appearance
+- ✅ Clear visual feedback on who is being healed
+
+#### Disadvantages
+- ❌ Complex to implement (line rendering)
+- ❌ Can become cluttered with many healers
+- ❌ More GPU cost
+
+---
+
+### Option 4: **Hybrid: Beam + Particle Aura** (Balanced)
+
+#### Core Concept
+Combines beam from Option 1 with particle effects for maximum impact.
+
+#### Visual Elements
+
+1. **Primary Beam** (like Option 1)
+   - Solid energy beam from healer to target
+   - Animated flowing texture
+
+2. **Secondary Particle System**
+   - Small healing droplets orbit the beam
+   - Emit particles on impact at target
+   - Create "healing energy splash" effect at destination
+
+3. **Healer Visual Enhancement**
+   - Hands glow with healing power
+   - Body has subtle aura
+   - Animation: Prayer/meditation pose during active healing
+
+#### Advantages
+- ✅ Combines best of multiple approaches
+- ✅ Maximum visual feedback
+- ✅ Multiple layers add depth
+- ✅ Can be partially disabled if performance needed
+
+---
+
+## Implementation Strategy for Option 1 (Recommended)
+
+### Phase 1: Core System
+```
+1. Create HealingBeamProjectile class (game/systems/)
+   - Similar to StoneProjectile but for beams
+   - Track start/end positions, progress, duration
+   - No damage application (healing already handled in HealingSystem)
+
+2. Modify HealingSystem
+   - Replace arrow_system->spawnArrow() calls
+   - Call healing_beam_system->spawnBeam() instead
+   - Pass healer position, target position, healing duration
+
+3. Create HealingBeamRenderer (render/geom/)
+   - Render ProjectileBeam meshes
+   - Apply healing_beam.vert/frag shaders
+   - Animate texture coordinates based on time
+
+4. Create Shaders (assets/shaders/)
+   - healing_beam.vert: Curved path interpolation
+   - healing_beam.frag: Golden-green color with inner glow
+```
+
+### Phase 2: Visual Enhancement
+```
+1. Create HealingAuraRenderer
+   - Render expanding rings around active healer
+   - Pulsing based on healing cooldown
+   - CPU-based circle generation or GPU instanced rendering
+
+2. Create ParticleTrail
+   - Small sphere meshes following beam path
+   - Fade opacity over lifetime
+   - Staggered spawn for flowing effect
+
+3. Target Impact Effect
+   - Brief bright glow at target location
+   - Ascending spiral particles
+   - Synchronized with health bar visual feedback
+```
+
+### Phase 3: Audio-Visual Sync
+```
+1. Add healing sound effects
+   - Healer cast sound (whoosh)
+   - Beam travel sound (soft hum)
+   - Impact sound at target (sparkle/ding)
+
+2. Synchronize animations
+   - Sound plays when beam starts
+   - Beam speed affects sound duration
+```
+
+---
+
+## Technical Considerations
+
+### Performance
+- **GPU Cost**: Shaders + particles, minimal compared to healer rendering
+- **CPU Cost**: Beam system parallel to arrow system, negligible
+- **Memory**: New mesh types (beams) but reusable across heals
+- **Optimization**: Could disable in large-scale battles if needed
+
+### Compatibility
+- Works with existing healer system without breaking changes
+- Arrow system remains for damage projectiles
+- Can coexist during transition period
+
+### Quality Levels
+- **Ultra**: Full beam + aura + particles + impact effects
+- **High**: Beam + particles only
+- **Medium**: Beam only (no aura/particles)
+- **Low**: Simplified beam or reverted to arrows
+
+---
+
+## Animation Timing Reference
+
+### Heal Application
+```
+Time 0.0s:   Healer enters casting state
+Time 0.1s:   Beam spawns, starts flowing
+Time 0.2s:   Target receives healing (aura glow)
+Time 0.8s:   Beam fades out
+Time 1.0s:   Animation complete
+Time 1.5s:   Next heal can occur (cooldown)
+```
+
+### Aura Pulsing
+```
+Breathing pattern: 1.5-2 second cycle
+- 0.0-0.5s: Expand from healer center
+- 0.5-1.0s: Maintain radius
+- 1.0-1.5s: Pulse brighter
+- 1.5-2.0s: Fade slightly, repeat
+```
+
+---
+
+## Shader Pseudocode (healing_beam.vert)
+
+```glsl
+// Beam travels from point A to point B along curved arc
+vec3 beam_position(float t, vec3 start, vec3 end) {
+    // Linear interpolation with vertical arc
+    vec3 mid = (start + end) * 0.5;
+    mid.y += 0.3; // Arc height
+    
+    vec3 pos = mix(mix(start, mid, t), mix(mid, end, t), t);
+    
+    // Add spiral twist
+    float twist = t * 6.28; // Full rotation
+    float radius = 0.15 * (1.0 - abs(t - 0.5) * 2.0); // Barrel shape
+    pos.x += cos(twist) * radius;
+    pos.z += sin(twist) * radius;
+    
+    return pos;
+}
+
+// Fragment: Golden-green gradient with glow
+vec4 healing_color(vec3 normal, float edge_dist) {
+    float core = exp(-edge_dist * 10.0); // Center is bright
+    
+    // Golden-green gradient
+    vec3 color = mix(vec3(0.2, 1.0, 0.4), vec3(1.0, 0.8, 0.2), core);
+    
+    // Add glow based on normal
+    float glow = pow(1.0 - abs(normal.z), 2.0) * 0.5;
+    color += glow;
+    
+    return vec4(color, core);
+}
+```
+
+---
+
+## Recommendation
+
+**Go with Option 1: Healing Aura & Radiant Beam**
+
+### Reasons
+1. **Most Impressive**: Instantly elevates healer feedback
+2. **Professional**: Looks polished and intentional
+3. **Performant**: GPU-based, scales well
+4. **Clear Feedback**: Unambiguous what's happening
+5. **Sustainable**: Can be iteratively improved
+6. **Reusable**: Beam system useful for other effects (buffs, debuffs, etc.)
+
+### Quick Win Path
+Start with a simple **curved beam** (no particles), add aura next, then particles/impact effects as polish passes.
+
+---
+
+## Questions for Design
+
+1. Should heals of all units be visible or only friendly/player-visible ones?
+2. Color preference: Golden-green, pure green, or faction-specific?
+3. Should beam speed vary by healing amount?
+4. Should multiple heals show multiple beams or combine into one?
+5. Should impact zone be visible (where healing lands)?

+ 1 - 1
README.md

@@ -437,7 +437,7 @@ This roadmap replaces the single nation template with a scalable civilization la
 ### Phase 3 — Multi-Nation Support
 - Author Roman and Carthaginian JSON definitions with differentiated stats, formations, and renderer ids; set the default nation in `NationRegistry` to one of them.
 - Rename the shared melee infantry profile to `Swordsman` so nations can share core assets while still tuning stats in their override files.
-- Audit gameplay systems (AI build orders, UI panels, tutorials) to resolve troop data via `NationRegistry::getNationForPlayer` instead of assuming Kingdom of Iron.
+- Audit gameplay systems (AI build orders, UI panels, tutorials) to resolve troop data via `NationRegistry::get_nation_for_player` instead of assuming Kingdom of Iron.
 - Register renderer variants (e.g., `render/entity/roman_archer_renderer.cpp`) keyed by the profile’s renderer id, with graceful fallbacks to baseline assets.
 - Add hooks for balance levers (passive modifiers, tech prerequisites) inside `NationTroopVariant` so future expansions require data changes rather than engine rewrites.
 

+ 1 - 1
app/controllers/action_vfx.cpp

@@ -37,7 +37,7 @@ void ActionVFX::spawnAttackArrow(Engine::Core::World *world,
 
   arrow_system->spawnArrow(above_target, target_pos,
                            QVector3D(1.0F, 0.2F, 0.2F),
-                           Game::GameConfig::instance().arrow().speedAttack);
+                           Game::GameConfig::instance().arrow().speed_attack);
 }
 
 } // namespace App::Controllers

+ 326 - 69
app/core/game_engine.cpp

@@ -20,6 +20,7 @@
 #include <QDebug>
 #include <QImage>
 #include <QOpenGLContext>
+#include <QPainter>
 #include <QQuickWindow>
 #include <QSize>
 #include <QVariant>
@@ -42,6 +43,7 @@
 #include <qstringview.h>
 #include <qtmetamacros.h>
 #include <qvectornd.h>
+#include <unordered_set>
 
 #include "../models/selected_units_model.h"
 #include "game/core/component.h"
@@ -53,6 +55,8 @@
 #include "game/map/map_catalog.h"
 #include "game/map/map_loader.h"
 #include "game/map/map_transformer.h"
+#include "game/map/minimap/minimap_generator.h"
+#include "game/map/minimap/unit_layer.h"
 #include "game/map/skirmish_loader.h"
 #include "game/map/terrain_service.h"
 #include "game/map/visibility_service.h"
@@ -70,6 +74,7 @@
 #include "game/systems/formation_planner.h"
 #include "game/systems/game_state_serializer.h"
 #include "game/systems/global_stats_registry.h"
+#include "game/systems/healing_beam_system.h"
 #include "game/systems/healing_system.h"
 #include "game/systems/movement_system.h"
 #include "game/systems/nation_id.h"
@@ -87,9 +92,12 @@
 #include "game/systems/victory_service.h"
 #include "game/units/factory.h"
 #include "game/units/troop_config.h"
+#include "render/entity/healing_beam_renderer.h"
+#include "render/entity/healer_aura_renderer.h"
 #include "render/geom/arrow.h"
 #include "render/geom/patrol_flags.h"
 #include "render/geom/stone.h"
+#include "render/gl/backend.h"
 #include "render/gl/bootstrap.h"
 #include "render/gl/camera.h"
 #include "render/ground/biome_renderer.h"
@@ -121,7 +129,7 @@ GameEngine::GameEngine(QObject *parent)
     : QObject(parent),
       m_selectedUnitsModel(new SelectedUnitsModel(this, this)) {
 
-  Game::Systems::NationRegistry::instance().initializeDefaults();
+  Game::Systems::NationRegistry::instance().initialize_defaults();
   Game::Systems::TroopCountRegistry::instance().initialize();
   Game::Systems::GlobalStatsRegistry::instance().initialize();
 
@@ -161,6 +169,7 @@ GameEngine::GameEngine(QObject *parent)
   m_world->add_system(std::make_unique<Game::Systems::CombatSystem>());
   m_world->add_system(std::make_unique<Game::Systems::CatapultAttackSystem>());
   m_world->add_system(std::make_unique<Game::Systems::BallistaAttackSystem>());
+  m_world->add_system(std::make_unique<Game::Systems::HealingBeamSystem>());
   m_world->add_system(std::make_unique<Game::Systems::HealingSystem>());
   m_world->add_system(std::make_unique<Game::Systems::CaptureSystem>());
   m_world->add_system(std::make_unique<Game::Systems::AISystem>());
@@ -191,17 +200,17 @@ GameEngine::GameEngine(QObject *parent)
   m_hoverTracker = std::make_unique<HoverTracker>(m_pickingService.get());
 
   m_mapCatalog = std::make_unique<Game::Map::MapCatalog>(this);
-  connect(m_mapCatalog.get(), &Game::Map::MapCatalog::mapLoaded, this,
+  connect(m_mapCatalog.get(), &Game::Map::MapCatalog::map_loaded, this,
           [this](const QVariantMap &mapData) {
             m_available_maps.append(mapData);
             emit available_maps_changed();
           });
-  connect(m_mapCatalog.get(), &Game::Map::MapCatalog::loadingChanged, this,
+  connect(m_mapCatalog.get(), &Game::Map::MapCatalog::loading_changed, this,
           [this](bool loading) {
             m_maps_loading = loading;
             emit maps_loading_changed();
           });
-  connect(m_mapCatalog.get(), &Game::Map::MapCatalog::allMapsLoaded, this,
+  connect(m_mapCatalog.get(), &Game::Map::MapCatalog::all_maps_loaded, this,
           [this]() { emit available_maps_changed(); });
 
   if (AudioSystem::getInstance().initialize()) {
@@ -428,7 +437,7 @@ void GameEngine::on_right_click(qreal sx, qreal sy) {
                                            hit)) {
       auto targets = Game::Systems::FormationPlanner::spreadFormation(
           int(sel.size()), hit,
-          Game::GameConfig::instance().gameplay().formationSpacingDefault);
+          Game::GameConfig::instance().gameplay().formation_spacing_default);
       Game::Systems::CommandService::MoveOptions opts;
       opts.group_move = sel.size() > 1;
       Game::Systems::CommandService::moveUnits(*m_world, sel, targets, opts);
@@ -699,7 +708,7 @@ void GameEngine::update(float dt) {
     m_world->update(dt);
 
     auto &visibility_service = Game::Map::VisibilityService::instance();
-    if (visibility_service.isInitialized()) {
+    if (visibility_service.is_initialized()) {
 
       m_runtime.visibilityUpdateAccumulator += dt;
       const float visibility_update_interval =
@@ -712,14 +721,16 @@ void GameEngine::update(float dt) {
       const auto new_version = visibility_service.version();
       if (new_version != m_runtime.visibilityVersion) {
         if (m_fog) {
-          m_fog->updateMask(visibility_service.getWidth(),
-                            visibility_service.getHeight(),
-                            visibility_service.getTileSize(),
-                            visibility_service.snapshotCells());
+          m_fog->update_mask(visibility_service.getWidth(),
+                             visibility_service.getHeight(),
+                             visibility_service.getTileSize(),
+                             visibility_service.snapshotCells());
         }
         m_runtime.visibilityVersion = new_version;
       }
     }
+
+    update_minimap_fog(dt);
   }
 
   if (m_victoryService && m_world) {
@@ -778,7 +789,7 @@ void GameEngine::render(int pixelWidth, int pixelHeight) {
   m_renderer->render_world(m_world.get());
   if (auto *arrow_system = m_world->get_system<Game::Systems::ArrowSystem>()) {
     if (auto *res = m_renderer->resources()) {
-      Render::GL::renderArrows(m_renderer.get(), res, *arrow_system);
+      Render::GL::render_arrows(m_renderer.get(), res, *arrow_system);
     }
   }
   if (auto *projectile_system =
@@ -787,6 +798,19 @@ void GameEngine::render(int pixelWidth, int pixelHeight) {
       Render::GL::render_projectiles(m_renderer.get(), res, *projectile_system);
     }
   }
+  if (auto *healing_beam_system =
+          m_world->get_system<Game::Systems::HealingBeamSystem>()) {
+    if (auto *res = m_renderer->resources()) {
+      Render::GL::render_healing_beams(m_renderer.get(), res,
+                                       *healing_beam_system);
+    }
+  }
+
+  // Render healer auras
+  {
+    Render::GL::render_healer_auras(m_renderer.get(), m_renderer->resources(),
+                                    m_world.get());
+  }
 
   if (auto *res = m_renderer->resources()) {
     std::optional<QVector3D> preview_waypoint;
@@ -930,7 +954,7 @@ void GameEngine::camera_set_follow_lerp(float alpha) {
     return;
   }
 
-  m_cameraService->setFollowLerp(*m_camera, alpha);
+  m_cameraService->set_follow_lerp(*m_camera, alpha);
 }
 
 auto GameEngine::selected_units_model() -> QAbstractItemModel * {
@@ -1096,7 +1120,7 @@ void GameEngine::set_rally_at_screen(qreal sx, qreal sy) {
 void GameEngine::start_loading_maps() {
   m_available_maps.clear();
   if (m_mapCatalog) {
-    m_mapCatalog->loadMapsAsync();
+    m_mapCatalog->load_maps_async();
   }
   load_campaigns();
 }
@@ -1108,7 +1132,7 @@ auto GameEngine::available_maps() const -> QVariantList {
 auto GameEngine::available_nations() const -> QVariantList {
   QVariantList nations;
   const auto &registry = Game::Systems::NationRegistry::instance();
-  const auto &all = registry.getAllNations();
+  const auto &all = registry.get_all_nations();
   QList<QVariantMap> ordered;
   ordered.reserve(static_cast<int>(all.size()));
   for (const auto &nation : all) {
@@ -1265,34 +1289,34 @@ void GameEngine::start_skirmish(const QString &map_path,
     m_entityCache.reset();
 
     Game::Map::SkirmishLoader loader(*m_world, *m_renderer, *m_camera);
-    loader.setGroundRenderer(m_ground.get());
-    loader.setTerrainRenderer(m_terrain.get());
-    loader.setBiomeRenderer(m_biome.get());
-    loader.setRiverRenderer(m_river.get());
-    loader.setRoadRenderer(m_road.get());
-    loader.setRiverbankRenderer(m_riverbank.get());
-    loader.setBridgeRenderer(m_bridge.get());
-    loader.setFogRenderer(m_fog.get());
-    loader.setStoneRenderer(m_stone.get());
-    loader.setPlantRenderer(m_plant.get());
-    loader.setPineRenderer(m_pine.get());
-    loader.setOliveRenderer(m_olive.get());
-    loader.setFireCampRenderer(m_firecamp.get());
-
-    loader.setOnOwnersUpdated([this]() { emit owner_info_changed(); });
-
-    loader.setOnVisibilityMaskReady([this]() {
+    loader.set_ground_renderer(m_ground.get());
+    loader.set_terrain_renderer(m_terrain.get());
+    loader.set_biome_renderer(m_biome.get());
+    loader.set_river_renderer(m_river.get());
+    loader.set_road_renderer(m_road.get());
+    loader.set_riverbank_renderer(m_riverbank.get());
+    loader.set_bridge_renderer(m_bridge.get());
+    loader.set_fog_renderer(m_fog.get());
+    loader.set_stone_renderer(m_stone.get());
+    loader.set_plant_renderer(m_plant.get());
+    loader.set_pine_renderer(m_pine.get());
+    loader.set_olive_renderer(m_olive.get());
+    loader.set_fire_camp_renderer(m_firecamp.get());
+
+    loader.set_on_owners_updated([this]() { emit owner_info_changed(); });
+
+    loader.set_on_visibility_mask_ready([this]() {
       m_runtime.visibilityVersion =
           Game::Map::VisibilityService::instance().version();
       m_runtime.visibilityUpdateAccumulator = 0.0F;
     });
 
-    int updated_player_id = m_selectedPlayerId;
-    auto result = loader.start(map_path, playerConfigs, m_selectedPlayerId,
+    int updated_player_id = m_selected_player_id;
+    auto result = loader.start(map_path, playerConfigs, m_selected_player_id,
                                updated_player_id);
 
-    if (updated_player_id != m_selectedPlayerId) {
-      m_selectedPlayerId = updated_player_id;
+    if (updated_player_id != m_selected_player_id) {
+      m_selected_player_id = updated_player_id;
       emit selected_player_id_changed();
     }
 
@@ -1308,7 +1332,7 @@ void GameEngine::start_skirmish(const QString &map_path,
     m_level.cam_far = result.cam_far;
     m_level.max_troops_per_player = result.max_troops_per_player;
 
-    Game::GameConfig::instance().setMaxTroopsPerPlayer(
+    Game::GameConfig::instance().set_max_troops_per_player(
         result.max_troops_per_player);
 
     if (m_victoryService) {
@@ -1326,10 +1350,19 @@ void GameEngine::start_skirmish(const QString &map_path,
       });
     }
 
-    if (result.hasFocusPosition && m_camera) {
+    if (result.has_focus_position && m_camera) {
       const auto &cam_config = Game::GameConfig::instance().camera();
-      m_camera->setRTSView(result.focusPosition, cam_config.defaultDistance,
-                           cam_config.defaultPitch, cam_config.defaultYaw);
+      m_camera->set_rts_view(result.focusPosition, cam_config.default_distance,
+                             cam_config.default_pitch, cam_config.default_yaw);
+    }
+
+    Game::Map::MapDefinition map_def;
+    QString map_error;
+    if (Game::Map::MapLoader::loadFromJsonFile(map_path, map_def, &map_error)) {
+      generate_minimap_for_map(map_def);
+    } else {
+      qWarning() << "GameEngine: Failed to load map for minimap generation:"
+                 << map_error;
     }
 
     m_runtime.loading = false;
@@ -1346,7 +1379,7 @@ void GameEngine::start_skirmish(const QString &map_path,
     stats_registry.rebuild_from_world(*m_world);
 
     auto &owner_registry = Game::Systems::OwnerRegistry::instance();
-    const auto &all_owners = owner_registry.getAllOwners();
+    const auto &all_owners = owner_registry.get_all_owners();
     for (const auto &owner : all_owners) {
       if (owner.type == Game::Systems::OwnerType::Player ||
           owner.type == Game::Systems::OwnerType::AI) {
@@ -1394,7 +1427,7 @@ auto GameEngine::load_from_slot(const QString &slot) -> bool {
 
   m_runtime.loading = true;
 
-  if (!m_saveLoadService->loadGameFromSlot(*m_world, slot)) {
+  if (!m_saveLoadService->load_game_from_slot(*m_world, slot)) {
     set_error(m_saveLoadService->getLastError());
     m_runtime.loading = false;
     return false;
@@ -1465,7 +1498,7 @@ auto GameEngine::get_save_slots() const -> QVariantList {
     return {};
   }
 
-  return m_saveLoadService->getSaveSlots();
+  return m_saveLoadService->get_save_slots();
 }
 
 void GameEngine::refresh_save_slots() { emit save_slots_changed(); }
@@ -1498,7 +1531,7 @@ void GameEngine::exit_game() {
 auto GameEngine::get_owner_info() const -> QVariantList {
   QVariantList result;
   const auto &owner_registry = Game::Systems::OwnerRegistry::instance();
-  const auto &owners = owner_registry.getAllOwners();
+  const auto &owners = owner_registry.get_all_owners();
 
   for (const auto &owner : owners) {
     QVariantMap owner_map;
@@ -1575,7 +1608,7 @@ void GameEngine::on_unit_spawned(const Engine::Core::UnitSpawnedEvent &event) {
               event.spawn_type);
       m_entityCache.playerTroopCount += production_cost;
     }
-  } else if (owners.isAI(event.owner_id)) {
+  } else if (owners.is_ai(event.owner_id)) {
     if (event.spawn_type == Game::Units::SpawnType::Barracks) {
       m_entityCache.enemyBarracksCount++;
       m_entityCache.enemyBarracksAlive = true;
@@ -1605,7 +1638,7 @@ void GameEngine::on_unit_died(const Engine::Core::UnitDiedEvent &event) {
       m_entityCache.playerTroopCount =
           std::max(0, m_entityCache.playerTroopCount);
     }
-  } else if (owners.isAI(event.owner_id)) {
+  } else if (owners.is_ai(event.owner_id)) {
     if (event.spawn_type == Game::Units::SpawnType::Barracks) {
       m_entityCache.enemyBarracksCount--;
       m_entityCache.enemyBarracksCount =
@@ -1650,7 +1683,7 @@ void GameEngine::rebuild_entity_cache() {
                 unit->spawn_type);
         m_entityCache.playerTroopCount += production_cost;
       }
-    } else if (owners.isAI(unit->owner_id)) {
+    } else if (owners.is_ai(unit->owner_id)) {
       if (unit->spawn_type == Game::Units::SpawnType::Barracks) {
         m_entityCache.enemyBarracksCount++;
         m_entityCache.enemyBarracksAlive = true;
@@ -1673,7 +1706,7 @@ void GameEngine::rebuild_registries_after_load() {
   }
 
   auto &owner_registry = Game::Systems::OwnerRegistry::instance();
-  m_runtime.local_owner_id = owner_registry.getLocalPlayerId();
+  m_runtime.local_owner_id = owner_registry.get_local_player_id();
 
   auto &troops = Game::Systems::TroopCountRegistry::instance();
   troops.rebuild_from_world(*m_world);
@@ -1681,7 +1714,7 @@ void GameEngine::rebuild_registries_after_load() {
   auto &stats_registry = Game::Systems::GlobalStatsRegistry::instance();
   stats_registry.rebuild_from_world(*m_world);
 
-  const auto &all_owners = owner_registry.getAllOwners();
+  const auto &all_owners = owner_registry.get_all_owners();
   for (const auto &owner : all_owners) {
     if (owner.type == Game::Systems::OwnerType::Player ||
         owner.type == Game::Systems::OwnerType::AI) {
@@ -1704,8 +1737,8 @@ void GameEngine::rebuild_registries_after_load() {
     }
   }
 
-  if (m_selectedPlayerId != m_runtime.local_owner_id) {
-    m_selectedPlayerId = m_runtime.local_owner_id;
+  if (m_selected_player_id != m_runtime.local_owner_id) {
+    m_selected_player_id = m_runtime.local_owner_id;
     emit selected_player_id_changed();
   }
 }
@@ -1726,7 +1759,7 @@ void GameEngine::rebuild_building_collisions() {
       continue;
     }
 
-    registry.registerBuilding(
+    registry.register_building(
         entity->get_id(), Game::Units::spawn_typeToString(unit->spawn_type),
         transform->position.x, transform->position.z, unit->owner_id);
   }
@@ -1739,7 +1772,7 @@ auto GameEngine::to_runtime_snapshot() const -> Game::Systems::RuntimeSnapshot {
   snap.local_owner_id = m_runtime.local_owner_id;
   snap.victory_state = m_runtime.victory_state;
   snap.cursor_mode = CursorModeUtils::toInt(m_runtime.cursor_mode);
-  snap.selected_player_id = m_selectedPlayerId;
+  snap.selected_player_id = m_selected_player_id;
   snap.follow_selection = m_followSelectionEnabled;
   return snap;
 }
@@ -1757,8 +1790,8 @@ void GameEngine::apply_runtime_snapshot(
 
   set_cursor_mode(CursorModeUtils::fromInt(snapshot.cursor_mode));
 
-  if (snapshot.selected_player_id != m_selectedPlayerId) {
-    m_selectedPlayerId = snapshot.selected_player_id;
+  if (snapshot.selected_player_id != m_selected_player_id) {
+    m_selected_player_id = snapshot.selected_player_id;
     emit selected_player_id_changed();
   }
 
@@ -1811,7 +1844,7 @@ void GameEngine::restore_environment_from_metadata(
 
   auto &terrain_service = Game::Map::TerrainService::instance();
 
-  bool const terrain_already_restored = terrain_service.isInitialized();
+  bool const terrain_already_restored = terrain_service.is_initialized();
 
   Game::Map::MapDefinition def;
   QString map_error;
@@ -1842,13 +1875,15 @@ void GameEngine::restore_environment_from_metadata(
   if (m_renderer && m_camera) {
     if (loaded_definition) {
       Game::Map::Environment::apply(def, *m_renderer, *m_camera);
+
+      generate_minimap_for_map(def);
     } else {
       Game::Map::Environment::applyDefault(*m_renderer, *m_camera);
     }
   }
 
-  if (terrain_service.isInitialized()) {
-    const auto *height_map = terrain_service.getHeightMap();
+  if (terrain_service.is_initialized()) {
+    const auto *height_map = terrain_service.get_height_map();
     const int grid_width =
         (height_map != nullptr) ? height_map->getWidth() : fallback_grid_width;
     const int grid_height = (height_map != nullptr) ? height_map->getHeight()
@@ -1858,12 +1893,12 @@ void GameEngine::restore_environment_from_metadata(
 
     if (m_ground) {
       m_ground->configure(tile_size, grid_width, grid_height);
-      m_ground->setBiome(terrain_service.biomeSettings());
+      m_ground->setBiome(terrain_service.biome_settings());
     }
 
     if (height_map != nullptr) {
       if (m_terrain) {
-        m_terrain->configure(*height_map, terrain_service.biomeSettings());
+        m_terrain->configure(*height_map, terrain_service.biome_settings());
       }
       if (m_river) {
         m_river->configure(height_map->getRiverSegments(),
@@ -1881,23 +1916,23 @@ void GameEngine::restore_environment_from_metadata(
                             height_map->getTileSize());
       }
       if (m_biome) {
-        m_biome->configure(*height_map, terrain_service.biomeSettings());
+        m_biome->configure(*height_map, terrain_service.biome_settings());
         m_biome->refreshGrass();
       }
       if (m_stone) {
-        m_stone->configure(*height_map, terrain_service.biomeSettings());
+        m_stone->configure(*height_map, terrain_service.biome_settings());
       }
       if (m_plant) {
-        m_plant->configure(*height_map, terrain_service.biomeSettings());
+        m_plant->configure(*height_map, terrain_service.biome_settings());
       }
       if (m_pine) {
-        m_pine->configure(*height_map, terrain_service.biomeSettings());
+        m_pine->configure(*height_map, terrain_service.biome_settings());
       }
       if (m_olive) {
-        m_olive->configure(*height_map, terrain_service.biomeSettings());
+        m_olive->configure(*height_map, terrain_service.biome_settings());
       }
       if (m_firecamp) {
-        m_firecamp->configure(*height_map, terrain_service.biomeSettings());
+        m_firecamp->configure(*height_map, terrain_service.biome_settings());
       }
     }
 
@@ -1907,8 +1942,8 @@ void GameEngine::restore_environment_from_metadata(
     visibility_service.initialize(grid_width, grid_height, tile_size);
     visibility_service.computeImmediate(*m_world, m_runtime.local_owner_id);
 
-    if (m_fog && visibility_service.isInitialized()) {
-      m_fog->updateMask(
+    if (m_fog && visibility_service.is_initialized()) {
+      m_fog->update_mask(
           visibility_service.getWidth(), visibility_service.getHeight(),
           visibility_service.getTileSize(), visibility_service.snapshotCells());
     }
@@ -1939,8 +1974,9 @@ void GameEngine::restore_environment_from_metadata(
     visibility_service.initialize(fallback_grid_width, fallback_grid_height,
                                   fallback_tile_size);
     visibility_service.computeImmediate(*m_world, m_runtime.local_owner_id);
-    if (m_fog && visibility_service.isInitialized()) {
-      m_fog->updateMask(
+
+    if (m_fog && visibility_service.is_initialized()) {
+      m_fog->update_mask(
           visibility_service.getWidth(), visibility_service.getHeight(),
           visibility_service.getTileSize(), visibility_service.snapshotCells());
     }
@@ -2137,3 +2173,224 @@ void GameEngine::load_audio_resources() {
 
   qInfo() << "Audio resources loading complete";
 }
+
+auto GameEngine::minimap_image() const -> QImage { return m_minimap_image; }
+
+void GameEngine::generate_minimap_for_map(
+    const Game::Map::MapDefinition &map_def) {
+  Game::Map::Minimap::MinimapGenerator generator;
+  m_minimap_base_image = generator.generate(map_def);
+
+  if (!m_minimap_base_image.isNull()) {
+    qDebug() << "GameEngine: Generated minimap of size"
+             << m_minimap_base_image.width() << "x"
+             << m_minimap_base_image.height();
+
+    m_world_width = static_cast<float>(map_def.grid.width);
+    m_world_height = static_cast<float>(map_def.grid.height);
+
+    m_unit_layer = std::make_unique<Game::Map::Minimap::UnitLayer>();
+    m_unit_layer->init(m_minimap_base_image.width(),
+                       m_minimap_base_image.height(), m_world_width,
+                       m_world_height);
+    qDebug() << "GameEngine: Initialized unit layer for world" << m_world_width
+             << "x" << m_world_height;
+
+    m_minimap_fog_version = 0;
+    m_minimap_update_timer = MINIMAP_UPDATE_INTERVAL;
+    update_minimap_fog(0.0F);
+  } else {
+    qWarning() << "GameEngine: Failed to generate minimap";
+  }
+}
+
+void GameEngine::update_minimap_fog(float dt) {
+  if (m_minimap_base_image.isNull()) {
+    return;
+  }
+
+  m_minimap_update_timer += dt;
+  if (m_minimap_update_timer < MINIMAP_UPDATE_INTERVAL) {
+    return;
+  }
+  m_minimap_update_timer = 0.0F;
+
+  auto &visibility_service = Game::Map::VisibilityService::instance();
+  if (!visibility_service.is_initialized()) {
+
+    if (m_minimap_image != m_minimap_base_image) {
+      m_minimap_image = m_minimap_base_image;
+      emit minimap_image_changed();
+    }
+    return;
+  }
+
+  const auto current_version = visibility_service.version();
+  if (current_version == m_minimap_fog_version && !m_minimap_image.isNull()) {
+
+    update_minimap_units();
+    return;
+  }
+  m_minimap_fog_version = current_version;
+
+  const int vis_width = visibility_service.getWidth();
+  const int vis_height = visibility_service.getHeight();
+  const auto cells = visibility_service.snapshotCells();
+
+  if (cells.empty() || vis_width <= 0 || vis_height <= 0) {
+    m_minimap_image = m_minimap_base_image;
+    emit minimap_image_changed();
+    return;
+  }
+
+  m_minimap_image = m_minimap_base_image.copy();
+
+  const int img_width = m_minimap_image.width();
+  const int img_height = m_minimap_image.height();
+
+  constexpr float k_inv_cos = -0.70710678118F;
+  constexpr float k_inv_sin = 0.70710678118F;
+
+  const float scale_x =
+      static_cast<float>(vis_width) / static_cast<float>(img_width);
+  const float scale_y =
+      static_cast<float>(vis_height) / static_cast<float>(img_height);
+
+  constexpr int FOG_R = 45;
+  constexpr int FOG_G = 38;
+  constexpr int FOG_B = 30;
+  constexpr int ALPHA_UNSEEN = 180;
+  constexpr int ALPHA_EXPLORED = 60;
+  constexpr int ALPHA_VISIBLE = 0;
+  constexpr float ALPHA_THRESHOLD = 0.5F;
+  constexpr float ALPHA_SCALE = 1.0F / 255.0F;
+
+  auto get_alpha = [&cells, vis_width, ALPHA_VISIBLE, ALPHA_EXPLORED,
+                    ALPHA_UNSEEN](int vx, int vy) -> float {
+    const size_t idx = static_cast<size_t>(vy * vis_width + vx);
+    if (idx >= cells.size()) {
+      return static_cast<float>(ALPHA_UNSEEN);
+    }
+    const auto state = static_cast<Game::Map::VisibilityState>(cells[idx]);
+    switch (state) {
+    case Game::Map::VisibilityState::Visible:
+      return static_cast<float>(ALPHA_VISIBLE);
+    case Game::Map::VisibilityState::Explored:
+      return static_cast<float>(ALPHA_EXPLORED);
+    default:
+      return static_cast<float>(ALPHA_UNSEEN);
+    }
+  };
+
+  const float half_img_w = static_cast<float>(img_width) * 0.5F;
+  const float half_img_h = static_cast<float>(img_height) * 0.5F;
+  const float half_vis_w = static_cast<float>(vis_width) * 0.5F;
+  const float half_vis_h = static_cast<float>(vis_height) * 0.5F;
+
+  for (int y = 0; y < img_height; ++y) {
+    auto *scanline = reinterpret_cast<QRgb *>(m_minimap_image.scanLine(y));
+
+    for (int x = 0; x < img_width; ++x) {
+
+      const float centered_x = static_cast<float>(x) - half_img_w;
+      const float centered_y = static_cast<float>(y) - half_img_h;
+
+      const float world_x = centered_x * k_inv_cos - centered_y * k_inv_sin;
+      const float world_y = centered_x * k_inv_sin + centered_y * k_inv_cos;
+
+      const float vis_x = (world_x * scale_x) + half_vis_w;
+      const float vis_y = (world_y * scale_y) + half_vis_h;
+
+      const int vx0 = std::clamp(static_cast<int>(vis_x), 0, vis_width - 1);
+      const int vx1 = std::clamp(vx0 + 1, 0, vis_width - 1);
+      const float fx = vis_x - static_cast<float>(vx0);
+
+      const int vy0 = std::clamp(static_cast<int>(vis_y), 0, vis_height - 1);
+      const int vy1 = std::clamp(vy0 + 1, 0, vis_height - 1);
+      const float fy = vis_y - static_cast<float>(vy0);
+
+      const float a00 = get_alpha(vx0, vy0);
+      const float a10 = get_alpha(vx1, vy0);
+      const float a01 = get_alpha(vx0, vy1);
+      const float a11 = get_alpha(vx1, vy1);
+
+      const float alpha_top = a00 + (a10 - a00) * fx;
+      const float alpha_bot = a01 + (a11 - a01) * fx;
+      const float fog_alpha = alpha_top + (alpha_bot - alpha_top) * fy;
+
+      if (fog_alpha > ALPHA_THRESHOLD) {
+        const QRgb original = scanline[x];
+        const int orig_r = qRed(original);
+        const int orig_g = qGreen(original);
+        const int orig_b = qBlue(original);
+
+        const float blend = fog_alpha * ALPHA_SCALE;
+        const float inv_blend = 1.0F - blend;
+
+        const int new_r = static_cast<int>(orig_r * inv_blend + FOG_R * blend);
+        const int new_g = static_cast<int>(orig_g * inv_blend + FOG_G * blend);
+        const int new_b = static_cast<int>(orig_b * inv_blend + FOG_B * blend);
+
+        scanline[x] = qRgba(new_r, new_g, new_b, 255);
+      }
+    }
+  }
+
+  update_minimap_units();
+}
+
+void GameEngine::update_minimap_units() {
+  if (m_minimap_image.isNull() || !m_unit_layer || !m_world) {
+    emit minimap_image_changed();
+    return;
+  }
+
+  std::vector<Game::Map::Minimap::UnitMarker> markers;
+  markers.reserve(128);
+
+  std::unordered_set<Engine::Core::EntityID> selected_ids;
+  if (auto *selection_system =
+          m_world->get_system<Game::Systems::SelectionSystem>()) {
+    const auto &sel = selection_system->get_selected_units();
+    selected_ids.insert(sel.begin(), sel.end());
+  }
+
+  {
+    const std::lock_guard<std::recursive_mutex> lock(
+        m_world->get_entity_mutex());
+    const auto &entities = m_world->get_entities();
+
+    for (const auto &[entity_id, entity] : entities) {
+      const auto *unit = entity->get_component<Engine::Core::UnitComponent>();
+      if (!unit) {
+        continue;
+      }
+
+      const auto *transform =
+          entity->get_component<Engine::Core::TransformComponent>();
+      if (!transform) {
+        continue;
+      }
+
+      Game::Map::Minimap::UnitMarker marker;
+      marker.world_x = transform->position.x;
+      marker.world_z = transform->position.z;
+      marker.owner_id = unit->owner_id;
+      marker.is_selected = selected_ids.count(entity_id) > 0;
+      marker.is_building = Game::Units::is_building_spawn(unit->spawn_type);
+
+      markers.push_back(marker);
+    }
+  }
+
+  m_unit_layer->update(markers);
+
+  const QImage &unit_overlay = m_unit_layer->get_image();
+  if (!unit_overlay.isNull()) {
+    QPainter painter(&m_minimap_image);
+    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+    painter.drawImage(0, 0, unit_overlay);
+  }
+
+  emit minimap_image_changed();
+}

+ 25 - 5
app/core/game_engine.h

@@ -52,6 +52,9 @@ struct IRenderPass;
 } // namespace Render::GL
 
 namespace Game {
+namespace Map::Minimap {
+class UnitLayer;
+}
 namespace Systems {
 class SelectionSystem;
 class SelectionController;
@@ -63,7 +66,8 @@ class SaveLoadService;
 } // namespace Systems
 namespace Map {
 class MapCatalog;
-}
+struct MapDefinition;
+} // namespace Map
 } // namespace Game
 
 namespace App {
@@ -117,6 +121,8 @@ public:
                  set_selected_player_id NOTIFY selected_player_id_changed)
   Q_PROPERTY(QString last_error READ last_error NOTIFY last_error_changed)
   Q_PROPERTY(QObject *audio_system READ audio_system CONSTANT)
+  Q_PROPERTY(
+      QImage minimap_image READ minimap_image NOTIFY minimap_image_changed)
 
   Q_INVOKABLE void on_map_clicked(qreal sx, qreal sy);
   Q_INVOKABLE void on_right_click(qreal sx, qreal sy);
@@ -167,10 +173,10 @@ public:
 
   Q_INVOKABLE [[nodiscard]] static QVariantMap get_player_stats(int owner_id);
 
-  [[nodiscard]] int selected_player_id() const { return m_selectedPlayerId; }
+  [[nodiscard]] int selected_player_id() const { return m_selected_player_id; }
   void set_selected_player_id(int id) {
-    if (m_selectedPlayerId != id) {
-      m_selectedPlayerId = id;
+    if (m_selected_player_id != id) {
+      m_selected_player_id = id;
       emit selected_player_id_changed();
     }
   }
@@ -209,6 +215,8 @@ public:
   Q_INVOKABLE void exit_game();
   Q_INVOKABLE [[nodiscard]] QVariantList get_owner_info() const;
 
+  [[nodiscard]] QImage minimap_image() const;
+
   QObject *audio_system();
 
   void setWindow(QQuickWindow *w) { m_window = w; }
@@ -309,6 +317,14 @@ private:
   std::unique_ptr<Game::Map::MapCatalog> m_mapCatalog;
   std::unique_ptr<Game::Audio::AudioEventHandler> m_audioEventHandler;
   std::unique_ptr<App::Models::AudioSystemProxy> m_audio_systemProxy;
+  QImage m_minimap_image;
+  QImage m_minimap_base_image;
+  std::uint64_t m_minimap_fog_version = 0;
+  std::unique_ptr<Game::Map::Minimap::UnitLayer> m_unit_layer;
+  float m_world_width = 0.0F;
+  float m_world_height = 0.0F;
+  float m_minimap_update_timer = 0.0F;
+  static constexpr float MINIMAP_UPDATE_INTERVAL = 0.1F;
   QQuickWindow *m_window = nullptr;
   RuntimeState m_runtime;
   ViewportState m_viewport;
@@ -316,7 +332,7 @@ private:
   Game::Systems::LevelSnapshot m_level;
   SelectedUnitsModel *m_selectedUnitsModel = nullptr;
   int m_enemyTroopsDefeated = 0;
-  int m_selectedPlayerId = 1;
+  int m_selected_player_id = 1;
   QVariantList m_available_maps;
   QVariantList m_available_campaigns;
   bool m_maps_loading = false;
@@ -334,6 +350,9 @@ private:
   [[nodiscard]] bool is_player_in_combat() const;
   static void load_audio_resources();
   void load_campaigns();
+  void generate_minimap_for_map(const Game::Map::MapDefinition &map_def);
+  void update_minimap_fog(float dt);
+  void update_minimap_units();
 signals:
   void selected_units_changed();
   void selected_units_data_changed();
@@ -348,6 +367,7 @@ signals:
   void selected_player_id_changed();
   void last_error_changed();
   void maps_loading_changed();
+  void minimap_image_changed();
   void save_slots_changed();
   void hold_mode_changed(bool active);
 };

+ 34 - 0
app/models/minimap_image_provider.cpp

@@ -0,0 +1,34 @@
+#include "minimap_image_provider.h"
+
+MinimapImageProvider::MinimapImageProvider()
+    : QQuickImageProvider(QQuickImageProvider::Image) {}
+
+QImage MinimapImageProvider::requestImage(const QString &id, QSize *size,
+                                          const QSize &requested_size) {
+  Q_UNUSED(id);
+
+  if (m_minimap_image.isNull()) {
+
+    QImage placeholder(64, 64, QImage::Format_RGBA8888);
+    placeholder.fill(QColor(15, 26, 34));
+    if (size) {
+      *size = placeholder.size();
+    }
+    return placeholder;
+  }
+
+  if (size) {
+    *size = m_minimap_image.size();
+  }
+
+  if (requested_size.isValid() && !requested_size.isEmpty()) {
+    return m_minimap_image.scaled(requested_size, Qt::KeepAspectRatio,
+                                  Qt::SmoothTransformation);
+  }
+
+  return m_minimap_image;
+}
+
+void MinimapImageProvider::set_minimap_image(const QImage &image) {
+  m_minimap_image = image;
+}

+ 17 - 0
app/models/minimap_image_provider.h

@@ -0,0 +1,17 @@
+#pragma once
+
+#include <QImage>
+#include <QQuickImageProvider>
+
+class MinimapImageProvider : public QQuickImageProvider {
+public:
+  MinimapImageProvider();
+
+  QImage requestImage(const QString &id, QSize *size,
+                      const QSize &requested_size) override;
+
+  void set_minimap_image(const QImage &image);
+
+private:
+  QImage m_minimap_image;
+};

+ 4 - 0
assets.qrc

@@ -70,6 +70,10 @@
         <file>assets/shaders/terrain_chunk.vert</file>
         <file>assets/shaders/troop_shadow.frag</file>
         <file>assets/shaders/troop_shadow.vert</file>
+        <file>assets/shaders/healing_beam.frag</file>
+        <file>assets/shaders/healing_beam.vert</file>
+        <file>assets/shaders/healing_aura.frag</file>
+        <file>assets/shaders/healing_aura.vert</file>
         
         <!-- Map files -->
         <file>assets/maps/map_forest.json</file>

+ 1 - 1
assets/data/nations/carthage.json

@@ -156,7 +156,7 @@
         "health": 855,
         "max_health": 855,
         "speed": 2.7,
-        "vision_range": 14.0,
+        "vision_range": 7.0,
         "ranged_range": 7.5,
         "ranged_damage": 4,
         "ranged_cooldown": 2.2,

+ 1 - 1
assets/data/nations/roman_republic.json

@@ -156,7 +156,7 @@
         "health": 945,
         "max_health": 945,
         "speed": 2.6,
-        "vision_range": 14.5,
+        "vision_range": 7.0,
         "ranged_range": 8.5,
         "ranged_damage": 6,
         "ranged_cooldown": 1.8,

+ 1 - 1
assets/data/troops/base.json

@@ -220,7 +220,7 @@
         "health": 900,
         "max_health": 900,
         "speed": 2.5,
-        "vision_range": 14.0,
+        "vision_range": 7.0,
         "ranged_range": 8.0,
         "ranged_damage": 5,
         "ranged_cooldown": 2.0,

+ 96 - 0
assets/shaders/banner.frag

@@ -0,0 +1,96 @@
+#version 330 core
+
+in vec3 v_normal;
+in vec2 v_texCoord;
+in vec3 v_worldPos;
+in float v_waveOffset;
+in float v_clothDepth;
+
+uniform sampler2D u_texture;
+uniform vec3 u_color;
+uniform vec3 u_trimColor;
+uniform bool u_useTexture;
+uniform float u_alpha;
+uniform float u_time;
+
+out vec4 FragColor;
+
+float hash(vec2 p) {
+  vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+  p3 += dot(p3, p3.yzx + 33.33);
+  return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+  vec2 i = floor(p);
+  vec2 f = fract(p);
+  f = f * f * (3.0 - 2.0 * f);
+  float a = hash(i);
+  float b = hash(i + vec2(1.0, 0.0));
+  float c = hash(i + vec2(0.0, 1.0));
+  float d = hash(i + vec2(1.0, 1.0));
+  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
+}
+
+float fbm(vec2 p) {
+  float value = 0.0;
+  float amplitude = 0.5;
+  for (int i = 0; i < 3; i++) {
+    value += amplitude * noise(p);
+    p *= 2.0;
+    amplitude *= 0.5;
+  }
+  return value;
+}
+
+float clothWeave(vec2 p) {
+  float warp = sin(p.x * 80.0) * 0.5 + 0.5;
+  float weft = sin(p.y * 80.0) * 0.5 + 0.5;
+  return warp * weft * 0.08;
+}
+
+void main() {
+  vec3 color = u_color;
+  if (u_useTexture) {
+    color *= texture(u_texture, v_texCoord).rgb;
+  }
+
+  vec3 normal = normalize(v_normal);
+  vec2 uv = v_worldPos.xz;
+
+  float weave = clothWeave(uv);
+
+  float foldShadow = 1.0 - abs(v_waveOffset) * 3.0;
+  foldShadow = clamp(foldShadow, 0.7, 1.0);
+
+  float fabricNoise = fbm(uv * 15.0) * 0.08 - 0.04;
+
+  vec3 viewDir = normalize(vec3(0.0, 1.0, 0.5));
+  float viewAngle = abs(dot(normal, viewDir));
+  float sheen = pow(1.0 - viewAngle, 4.0) * 0.2;
+
+  color = color * (1.0 + fabricNoise + weave) * foldShadow;
+  color += vec3(sheen);
+
+  float borderWidth = 0.08;
+  float edgeDist = min(min(v_texCoord.x, 1.0 - v_texCoord.x),
+                       min(v_texCoord.y, 1.0 - v_texCoord.y));
+  if (edgeDist < borderWidth) {
+    float trimBlend = smoothstep(0.0, borderWidth, edgeDist);
+    color = mix(u_trimColor, color, trimBlend);
+  }
+
+  vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
+  float nDotL = dot(normal, lightDir);
+
+  float wrapAmount = 0.5;
+  float diff = max(nDotL * (1.0 - wrapAmount) + wrapAmount, 0.25);
+
+  float ao = 1.0 - v_clothDepth * 0.15;
+
+  color *= diff * ao;
+
+  color = mix(color, color * 0.92, v_clothDepth * 0.3);
+
+  FragColor = vec4(clamp(color, 0.0, 1.0), u_alpha);
+}

+ 59 - 0
assets/shaders/banner.vert

@@ -0,0 +1,59 @@
+#version 330 core
+
+layout(location = 0) in vec3 a_position;
+layout(location = 1) in vec3 a_normal;
+layout(location = 2) in vec2 a_texCoord;
+
+uniform mat4 u_mvp;
+uniform mat4 u_model;
+uniform float u_time;
+uniform float u_windStrength;
+
+out vec3 v_normal;
+out vec2 v_texCoord;
+out vec3 v_worldPos;
+out float v_waveOffset;
+out float v_clothDepth;
+
+float hash(float n) { return fract(sin(n) * 43758.5453123); }
+
+void main() {
+  vec3 pos = a_position;
+
+  float horizontalPos = pos.x + 0.5;
+
+  float verticalPos = clamp(0.5 - pos.y, 0.0, 1.0);
+
+  float wavePhase = u_time * 3.0 + horizontalPos * 4.0 + verticalPos * 2.0;
+  float waveAmplitude = u_windStrength * horizontalPos * horizontalPos * 0.25;
+  float zOffset = sin(wavePhase) * waveAmplitude;
+
+  float ripplePhase = u_time * 4.5 + horizontalPos * 6.0 + verticalPos * 3.0;
+  float ripple = sin(ripplePhase) * u_windStrength * horizontalPos * 0.08;
+
+  float flutterPhase = u_time * 8.0 + horizontalPos * 10.0;
+  float flutter =
+      sin(flutterPhase) * u_windStrength * horizontalPos * horizontalPos * 0.04;
+
+  float swayPhase = u_time * 1.5;
+  float yOffset = sin(swayPhase + horizontalPos * 0.8) * u_windStrength *
+                  horizontalPos * 0.02;
+
+  pos.z += zOffset + ripple + flutter;
+  pos.y += yOffset;
+
+  pos.y -= horizontalPos * horizontalPos * 0.015 * u_windStrength;
+
+  vec3 normal = a_normal;
+  float normalWave = cos(wavePhase) * waveAmplitude * 3.0;
+  normal.z += normalWave;
+  normal = normalize(normal);
+
+  v_normal = mat3(transpose(inverse(u_model))) * normal;
+  v_texCoord = a_texCoord;
+  v_worldPos = vec3(u_model * vec4(pos, 1.0));
+  v_waveOffset = zOffset + ripple;
+  v_clothDepth = horizontalPos;
+
+  gl_Position = u_mvp * vec4(pos, 1.0);
+}

+ 65 - 0
assets/shaders/healing_aura.frag

@@ -0,0 +1,65 @@
+#version 330 core
+
+in vec3 v_worldPos;
+in vec3 v_normal;
+in vec2 v_texCoord;
+in float v_height;
+in float v_radialDist;
+
+uniform float u_time;
+uniform float u_intensity;
+uniform vec3 u_auraColor;
+
+out vec4 FragColor;
+
+float hash(vec2 p) {
+  vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+  p3 += dot(p3, p3.yzx + 33.33);
+  return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+  vec2 i = floor(p);
+  vec2 f = fract(p);
+  f = f * f * (3.0 - 2.0 * f);
+  return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
+             mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y);
+}
+
+void main() {
+
+  vec3 coreColor = vec3(1.0, 1.0, 0.7);
+  vec3 midColor = u_auraColor;
+  vec3 edgeColor = u_auraColor * 0.7;
+
+  // Dome effect: stronger at edges (high radialDist), fading toward top
+  float edgeFade = smoothstep(0.2, 0.9, v_radialDist);
+  float heightFade = 1.0 - smoothstep(0.0, 1.0, v_height);
+  
+  // Combine for dome shell effect
+  float shellFade = edgeFade * heightFade;
+
+  vec3 color = mix(midColor, edgeColor, v_radialDist);
+  
+  // Animated swirl effect
+  float angle = atan(v_worldPos.z, v_worldPos.x);
+  float swirl = sin(angle * 4.0 + u_time * 2.0 + v_height * 5.0) * 0.5 + 0.5;
+  color += coreColor * swirl * 0.2 * shellFade;
+
+  // Flowing rings
+  float ring = sin(v_height * 15.0 - u_time * 3.0) * 0.5 + 0.5;
+  ring = pow(ring, 2.0);
+  color += midColor * ring * 0.3 * edgeFade;
+
+  // Particle effect
+  vec2 particleUV = vec2(angle * 2.0, v_height * 3.0 - u_time * 1.5);
+  float particles = noise(particleUV * 6.0);
+  particles = pow(particles, 2.0) * 2.0;
+  color += coreColor * particles * shellFade * 0.2;
+
+  // Alpha: visible shell around the dome
+  float alpha = shellFade * u_intensity * 0.7;
+  alpha += edgeFade * 0.15 * u_intensity;  // Base visibility at edges
+  
+  FragColor = vec4(color, clamp(alpha, 0.0, 0.85));
+}

+ 44 - 0
assets/shaders/healing_aura.vert

@@ -0,0 +1,44 @@
+#version 330 core
+
+layout(location = 0) in vec3 a_position;
+layout(location = 1) in vec3 a_normal;
+layout(location = 2) in vec2 a_texCoord;
+
+uniform mat4 u_mvp;
+uniform mat4 u_model;
+uniform float u_time;
+uniform float u_auraRadius;
+uniform float u_intensity;
+
+out vec3 v_worldPos;
+out vec3 v_normal;
+out vec2 v_texCoord;
+out float v_height;
+out float v_radialDist;
+
+void main() {
+  vec3 pos = a_position;
+
+  pos.xz *= u_auraRadius;
+
+  v_height = a_position.y;
+
+  v_radialDist = length(a_position.xz);
+
+  float breathe = 1.0 + 0.05 * sin(u_time * 2.0);
+  pos.xz *= breathe;
+
+  float spiralAngle = atan(pos.z, pos.x);
+  float spiralOffset =
+      sin(spiralAngle * 3.0 + u_time * 2.0 + v_height * 5.0) * 0.1;
+  pos.xz *= (1.0 + spiralOffset * u_intensity);
+
+  float wave = sin(v_radialDist * 10.0 - u_time * 3.0) * 0.05 * u_intensity;
+  pos.y += wave;
+
+  v_worldPos = vec3(u_model * vec4(pos, 1.0));
+  v_normal = mat3(u_model) * a_normal;
+  v_texCoord = a_texCoord;
+
+  gl_Position = u_mvp * vec4(pos, 1.0);
+}

+ 102 - 0
assets/shaders/healing_beam.frag

@@ -0,0 +1,102 @@
+#version 330 core
+
+in vec3 v_worldPos;
+in vec3 v_normal;
+in vec2 v_texCoord;
+in float v_beamT;
+in float v_edgeDist;
+in float v_glowIntensity;
+
+uniform float u_time;
+uniform float u_progress;
+uniform vec3 u_healColor;
+uniform float u_alpha;
+
+out vec4 FragColor;
+
+float hash(vec2 p) {
+  vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+  p3 += dot(p3, p3.yzx + 33.33);
+  return fract((p3.x + p3.y) * p3.z);
+}
+
+float noise(vec2 p) {
+  vec2 i = floor(p);
+  vec2 f = fract(p);
+  f = f * f * (3.0 - 2.0 * f);
+
+  float a = hash(i);
+  float b = hash(i + vec2(1.0, 0.0));
+  float c = hash(i + vec2(0.0, 1.0));
+  float d = hash(i + vec2(1.0, 1.0));
+
+  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
+}
+
+float fbm(vec2 p) {
+  float value = 0.0;
+  float amplitude = 0.5;
+  for (int i = 0; i < 4; i++) {
+    value += amplitude * noise(p);
+    p *= 2.0;
+    amplitude *= 0.5;
+  }
+  return value;
+}
+
+void main() {
+
+  vec3 coreColor = vec3(0.95, 0.95, 0.6);
+  vec3 innerColor = vec3(0.4, 1.0, 0.5);
+  vec3 outerColor = vec3(0.2, 0.8, 0.3);
+  vec3 sparkleColor = vec3(1.0, 1.0, 0.8);
+
+  innerColor = mix(innerColor, u_healColor, 0.5);
+  outerColor = mix(outerColor, u_healColor * 0.7, 0.5);
+
+  float centerDist = v_edgeDist;
+
+  float coreGlow = exp(-centerDist * 8.0);
+
+  float innerGlow = exp(-centerDist * 3.0);
+
+  float outerGlow = exp(-centerDist * 1.5);
+
+  vec3 color = outerColor * outerGlow;
+  color = mix(color, innerColor, innerGlow);
+  color = mix(color, coreColor, coreGlow);
+
+  vec2 sparkleUV = v_texCoord * 40.0 + vec2(u_time * 3.0, 0.0);
+  float sparkle = noise(sparkleUV) * noise(sparkleUV * 1.5);
+  sparkle = pow(sparkle, 4.0) * 2.0;
+  color += sparkleColor * sparkle * innerGlow;
+
+  vec2 flowUV = vec2(v_beamT * 10.0 - u_time * 4.0, centerDist * 5.0);
+  float flow = fbm(flowUV);
+  color += innerColor * flow * 0.3 * innerGlow;
+
+  float pulse = 0.8 + 0.2 * sin(u_time * 8.0 + v_beamT * 15.0);
+  color *= pulse;
+
+  float edgeShimmer = sin(v_beamT * 50.0 - u_time * 10.0) * 0.5 + 0.5;
+  edgeShimmer *= (1.0 - innerGlow) * outerGlow;
+  color += sparkleColor * edgeShimmer * 0.2;
+
+  float alpha = v_glowIntensity * outerGlow * u_alpha;
+
+  float startFade = smoothstep(0.0, 0.1, v_beamT);
+  float endFade = smoothstep(u_progress, u_progress - 0.1, v_beamT);
+  alpha *= startFade * endFade;
+
+  if (v_beamT > 0.9 && u_progress > 0.95) {
+    float impactGlow = (v_beamT - 0.9) * 10.0;
+    impactGlow *= sin(u_time * 15.0) * 0.5 + 0.5;
+    color += coreColor * impactGlow * 0.5;
+    alpha += impactGlow * 0.3;
+  }
+
+  float bloom = max(0.0, (coreGlow - 0.5) * 2.0);
+  color += coreColor * bloom * 0.3;
+
+  FragColor = vec4(color, clamp(alpha, 0.0, 1.0));
+}

+ 92 - 0
assets/shaders/healing_beam.vert

@@ -0,0 +1,92 @@
+#version 330 core
+
+layout(location = 0) in vec3 a_position;
+layout(location = 1) in vec3 a_normal;
+layout(location = 2) in vec2 a_texCoord;
+
+uniform mat4 u_mvp;
+uniform float u_time;
+uniform float u_progress;
+uniform vec3 u_startPos;
+uniform vec3 u_endPos;
+uniform float u_beamWidth;
+
+out vec3 v_worldPos;
+out vec3 v_normal;
+out vec2 v_texCoord;
+out float v_beamT;
+out float v_edgeDist;
+out float v_glowIntensity;
+
+vec3 bezierArc(float t, vec3 start, vec3 end, float arcHeight) {
+  vec3 mid = (start + end) * 0.5;
+  mid.y += arcHeight;
+
+  vec3 p0 = mix(start, mid, t);
+  vec3 p1 = mix(mid, end, t);
+  return mix(p0, p1, t);
+}
+
+vec3 bezierTangent(float t, vec3 start, vec3 end, float arcHeight) {
+  vec3 mid = (start + end) * 0.5;
+  mid.y += arcHeight;
+
+  vec3 d0 = mid - start;
+  vec3 d1 = end - mid;
+  return normalize(mix(d0, d1, t));
+}
+
+void main() {
+
+  float t = a_position.z;
+  v_beamT = t;
+
+  float visibleT = min(t, u_progress * 1.2);
+
+  float dist = length(u_endPos - u_startPos);
+  float arcHeight = dist * 0.25;
+
+  vec3 beamCenter = bezierArc(visibleT, u_startPos, u_endPos, arcHeight);
+  vec3 tangent = bezierTangent(visibleT, u_startPos, u_endPos, arcHeight);
+
+  // Create local coordinate frame with safe handling for vertical tangents
+  vec3 up = vec3(0.0, 1.0, 0.0);
+  if (abs(dot(tangent, up)) > 0.99) {
+    up = vec3(1.0, 0.0, 0.0);
+  }
+  vec3 right = normalize(cross(tangent, up));
+  vec3 localUp = normalize(cross(right, tangent));
+
+  float spiralAngle = t * 12.56637 + u_time * 4.0;
+  float spiralRadius = u_beamWidth * (0.3 + 0.7 * sin(t * 3.14159));
+
+  vec3 spiralOffset = right * cos(spiralAngle) * spiralRadius +
+                      localUp * sin(spiralAngle) * spiralRadius;
+
+  float sectionRadius = u_beamWidth * (1.0 - abs(a_position.x));
+  vec3 sectionOffset = right * a_position.x * sectionRadius +
+                       localUp * a_position.y * sectionRadius;
+
+  vec3 worldPos = beamCenter + sectionOffset * 0.3 + spiralOffset * 0.7;
+
+  float wave = sin(t * 20.0 - u_time * 8.0) * 0.02 * u_beamWidth;
+  worldPos += localUp * wave;
+
+  v_worldPos = worldPos;
+
+  v_edgeDist =
+      clamp(length(sectionOffset) / max(u_beamWidth, 0.0001), 0.0, 1.0);
+
+  float pulse = 0.7 + 0.3 * sin(u_time * 6.0 + t * 10.0);
+  v_glowIntensity = (1.0 - v_edgeDist) * pulse;
+
+  if (t > u_progress * 1.1) {
+    v_glowIntensity *= 0.0;
+  }
+
+  // Normal doesn't need transformation since we're in world space already
+  v_normal = a_normal;
+  v_texCoord = vec2(t, a_position.x * 0.5 + 0.5);
+
+  gl_Position = u_mvp * vec4(worldPos, 1.0);
+}

+ 7 - 0
game/CMakeLists.txt

@@ -48,6 +48,8 @@ add_library(game_systems STATIC
     systems/capture_system.cpp
     systems/terrain_alignment_system.cpp
     systems/healing_system.cpp
+    systems/healing_beam.cpp
+    systems/healing_beam_system.cpp
     systems/owner_registry.cpp
     systems/troop_count_registry.cpp
     systems/global_stats_registry.cpp
@@ -69,6 +71,11 @@ add_library(game_systems STATIC
     map/world_bootstrap.cpp
     map/map_catalog.cpp
     map/skirmish_loader.cpp
+    map/minimap/minimap_generator.cpp
+    map/minimap/minimap_texture_manager.cpp
+    map/minimap/fog_of_war_mask.cpp
+    map/minimap/minimap_manager.cpp
+    map/minimap/unit_layer.cpp
     visuals/visual_catalog.cpp
     units/unit.cpp
     units/archer.cpp

+ 1 - 0
game/core/component.h

@@ -238,6 +238,7 @@ public:
   int healing_amount{5};
   float healing_cooldown{2.0F};
   float time_since_last_heal{0.0F};
+  bool is_healing_active{false};
 };
 
 class CatapultLoadingComponent : public Component {

+ 8 - 8
game/core/serialization.cpp

@@ -789,13 +789,13 @@ auto Serialization::serializeWorld(const World *world) -> QJsonDocument {
   world_obj["nextEntityId"] = static_cast<qint64>(world->get_next_entity_id());
   world_obj["schemaVersion"] = 1;
   world_obj["owner_registry"] =
-      Game::Systems::OwnerRegistry::instance().toJson();
+      Game::Systems::OwnerRegistry::instance().to_json();
 
   const auto &terrain_service = Game::Map::TerrainService::instance();
-  if (terrain_service.isInitialized() &&
-      (terrain_service.getHeightMap() != nullptr)) {
-    world_obj["terrain"] = serializeTerrain(terrain_service.getHeightMap(),
-                                            terrain_service.biomeSettings(),
+  if (terrain_service.is_initialized() &&
+      (terrain_service.get_height_map() != nullptr)) {
+    world_obj["terrain"] = serializeTerrain(terrain_service.get_height_map(),
+                                            terrain_service.biome_settings(),
                                             terrain_service.road_segments());
   }
 
@@ -824,7 +824,7 @@ void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
   }
 
   if (world_obj.contains("owner_registry")) {
-    Game::Systems::OwnerRegistry::instance().fromJson(
+    Game::Systems::OwnerRegistry::instance().from_json(
         world_obj["owner_registry"].toObject());
   }
 
@@ -847,7 +847,7 @@ void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
     deserializeTerrain(temp_height_map.get(), biome, roads, terrain_obj);
 
     auto &terrain_service = Game::Map::TerrainService::instance();
-    terrain_service.restoreFromSerialized(
+    terrain_service.restore_from_serialized(
         width, height, tile_size, temp_height_map->getHeightData(),
         temp_height_map->getTerrainTypes(), temp_height_map->getRiverSegments(),
         roads, temp_height_map->getBridges(), biome);
@@ -865,7 +865,7 @@ auto Serialization::saveToFile(const QString &filename,
   return true;
 }
 
-auto Serialization::loadFromFile(const QString &filename) -> QJsonDocument {
+auto Serialization::load_from_file(const QString &filename) -> QJsonDocument {
   QFile file(filename);
   if (!file.open(QIODevice::ReadOnly)) {
     qWarning() << "Could not open file for reading:" << filename;

+ 1 - 1
game/core/serialization.h

@@ -32,7 +32,7 @@ public:
 
   static auto saveToFile(const QString &filename,
                          const QJsonDocument &doc) -> bool;
-  static auto loadFromFile(const QString &filename) -> QJsonDocument;
+  static auto load_from_file(const QString &filename) -> QJsonDocument;
 };
 
 } // namespace Engine::Core

+ 4 - 3
game/core/world.cpp

@@ -114,7 +114,7 @@ auto World::get_allied_units(int owner_id) const -> std::vector<Entity *> {
     }
 
     if (unit->owner_id == owner_id ||
-        owner_registry.areAllies(owner_id, unit->owner_id)) {
+        owner_registry.are_allies(owner_id, unit->owner_id)) {
       result.push_back(entity.get());
     }
   }
@@ -133,7 +133,7 @@ auto World::get_enemy_units(int owner_id) const -> std::vector<Entity *> {
       continue;
     }
 
-    if (owner_registry.areEnemies(owner_id, unit->owner_id)) {
+    if (owner_registry.are_enemies(owner_id, unit->owner_id)) {
       result.push_back(entity.get());
     }
   }
@@ -141,7 +141,8 @@ auto World::get_enemy_units(int owner_id) const -> std::vector<Entity *> {
 }
 
 auto World::count_troops_for_player(int owner_id) -> int {
-  return Game::Systems::TroopCountRegistry::instance().getTroopCount(owner_id);
+  return Game::Systems::TroopCountRegistry::instance().get_troop_count(
+      owner_id);
 }
 
 auto World::get_next_entity_id() const -> EntityID {

+ 59 - 59
game/game_config.h

@@ -3,24 +3,24 @@
 namespace Game {
 
 struct CameraConfig {
-  float defaultDistance = 12.0F;
-  float defaultPitch = 45.0F;
-  float defaultYaw = 225.0F;
-  float orbitStepNormal = 4.0F;
-  float orbitStepShift = 8.0F;
+  float default_distance = 12.0F;
+  float default_pitch = 45.0F;
+  float default_yaw = 225.0F;
+  float orbit_step_normal = 4.0F;
+  float orbit_step_shift = 8.0F;
 };
 
 struct ArrowConfig {
-  float arcHeightMultiplier = 0.15F;
-  float arcHeightMin = 0.2F;
-  float arcHeightMax = 1.2F;
-  float speedDefault = 8.0F;
-  float speedAttack = 6.0F;
+  float arc_height_multiplier = 0.15F;
+  float arc_height_min = 0.2F;
+  float arc_height_max = 1.2F;
+  float speed_default = 8.0F;
+  float speed_attack = 6.0F;
 };
 
 struct GameplayConfig {
   float visibility_update_interval = 0.075F;
-  float formationSpacingDefault = 1.0F;
+  float formation_spacing_default = 1.0F;
   int max_troops_per_player = 500;
 };
 
@@ -41,95 +41,95 @@ public:
     return m_gameplay;
   }
 
-  [[nodiscard]] auto getCameraDefaultDistance() const noexcept -> float {
-    return m_camera.defaultDistance;
+  [[nodiscard]] auto get_camera_default_distance() const noexcept -> float {
+    return m_camera.default_distance;
   }
-  [[nodiscard]] auto getCameraDefaultPitch() const noexcept -> float {
-    return m_camera.defaultPitch;
+  [[nodiscard]] auto get_camera_default_pitch() const noexcept -> float {
+    return m_camera.default_pitch;
   }
-  [[nodiscard]] auto getCameraDefaultYaw() const noexcept -> float {
-    return m_camera.defaultYaw;
+  [[nodiscard]] auto get_camera_default_yaw() const noexcept -> float {
+    return m_camera.default_yaw;
   }
 
-  [[nodiscard]] auto getArrowArcHeightMultiplier() const noexcept -> float {
-    return m_arrow.arcHeightMultiplier;
+  [[nodiscard]] auto get_arrow_arc_height_multiplier() const noexcept -> float {
+    return m_arrow.arc_height_multiplier;
   }
-  [[nodiscard]] auto getArrowArcHeightMin() const noexcept -> float {
-    return m_arrow.arcHeightMin;
+  [[nodiscard]] auto get_arrow_arc_height_min() const noexcept -> float {
+    return m_arrow.arc_height_min;
   }
-  [[nodiscard]] auto getArrowArcHeightMax() const noexcept -> float {
-    return m_arrow.arcHeightMax;
+  [[nodiscard]] auto get_arrow_arc_height_max() const noexcept -> float {
+    return m_arrow.arc_height_max;
   }
 
-  [[nodiscard]] auto getArrowSpeedDefault() const noexcept -> float {
-    return m_arrow.speedDefault;
+  [[nodiscard]] auto get_arrow_speed_default() const noexcept -> float {
+    return m_arrow.speed_default;
   }
-  [[nodiscard]] auto getArrowSpeedAttack() const noexcept -> float {
-    return m_arrow.speedAttack;
+  [[nodiscard]] auto get_arrow_speed_attack() const noexcept -> float {
+    return m_arrow.speed_attack;
   }
 
-  [[nodiscard]] auto getVisibilityUpdateInterval() const noexcept -> float {
+  [[nodiscard]] auto get_visibility_update_interval() const noexcept -> float {
     return m_gameplay.visibility_update_interval;
   }
 
-  [[nodiscard]] auto getFormationSpacingDefault() const noexcept -> float {
-    return m_gameplay.formationSpacingDefault;
+  [[nodiscard]] auto get_formation_spacing_default() const noexcept -> float {
+    return m_gameplay.formation_spacing_default;
   }
 
-  [[nodiscard]] auto getCameraOrbitStepNormal() const noexcept -> float {
-    return m_camera.orbitStepNormal;
+  [[nodiscard]] auto get_camera_orbit_step_normal() const noexcept -> float {
+    return m_camera.orbit_step_normal;
   }
-  [[nodiscard]] auto getCameraOrbitStepShift() const noexcept -> float {
-    return m_camera.orbitStepShift;
+  [[nodiscard]] auto get_camera_orbit_step_shift() const noexcept -> float {
+    return m_camera.orbit_step_shift;
   }
 
-  void setCameraDefaultDistance(float value) noexcept {
-    m_camera.defaultDistance = value;
+  void set_camera_default_distance(float value) noexcept {
+    m_camera.default_distance = value;
   }
-  void setCameraDefaultPitch(float value) noexcept {
-    m_camera.defaultPitch = value;
+  void set_camera_default_pitch(float value) noexcept {
+    m_camera.default_pitch = value;
   }
-  void setCameraDefaultYaw(float value) noexcept {
-    m_camera.defaultYaw = value;
+  void set_camera_default_yaw(float value) noexcept {
+    m_camera.default_yaw = value;
   }
 
-  void setArrowArcHeightMultiplier(float value) noexcept {
-    m_arrow.arcHeightMultiplier = value;
+  void set_arrow_arc_height_multiplier(float value) noexcept {
+    m_arrow.arc_height_multiplier = value;
   }
-  void setArrowArcHeightMin(float value) noexcept {
-    m_arrow.arcHeightMin = value;
+  void set_arrow_arc_height_min(float value) noexcept {
+    m_arrow.arc_height_min = value;
   }
-  void setArrowArcHeightMax(float value) noexcept {
-    m_arrow.arcHeightMax = value;
+  void set_arrow_arc_height_max(float value) noexcept {
+    m_arrow.arc_height_max = value;
   }
 
-  void setArrowSpeedDefault(float value) noexcept {
-    m_arrow.speedDefault = value;
+  void set_arrow_speed_default(float value) noexcept {
+    m_arrow.speed_default = value;
   }
-  void setArrowSpeedAttack(float value) noexcept {
-    m_arrow.speedAttack = value;
+  void set_arrow_speed_attack(float value) noexcept {
+    m_arrow.speed_attack = value;
   }
 
-  void setVisibilityUpdateInterval(float value) noexcept {
+  void set_visibility_update_interval(float value) noexcept {
     m_gameplay.visibility_update_interval = value;
   }
 
-  void setFormationSpacingDefault(float value) noexcept {
-    m_gameplay.formationSpacingDefault = value;
+  void set_formation_spacing_default(float value) noexcept {
+    m_gameplay.formation_spacing_default = value;
   }
 
-  void setCameraOrbitStepNormal(float value) noexcept {
-    m_camera.orbitStepNormal = value;
+  void set_camera_orbit_step_normal(float value) noexcept {
+    m_camera.orbit_step_normal = value;
   }
-  void setCameraOrbitStepShift(float value) noexcept {
-    m_camera.orbitStepShift = value;
+  void set_camera_orbit_step_shift(float value) noexcept {
+    m_camera.orbit_step_shift = value;
   }
 
-  [[nodiscard]] auto getMaxTroopsPerPlayer() const noexcept -> int {
+  [[nodiscard]] auto get_max_troops_per_player() const noexcept -> int {
     return m_gameplay.max_troops_per_player;
   }
 
-  void setMaxTroopsPerPlayer(int value) noexcept {
+  void set_max_troops_per_player(int value) noexcept {
     m_gameplay.max_troops_per_player = value;
   }
 

+ 7 - 7
game/map/environment.cpp

@@ -11,10 +11,10 @@ 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,
-                    def.camera.yaw_deg);
-  camera.setPerspective(def.camera.fovY, 16.0F / 9.0F, def.camera.near_plane,
-                        def.camera.far_plane);
+  camera.set_rts_view(def.camera.center, def.camera.distance,
+                      def.camera.tiltDeg, def.camera.yaw_deg);
+  camera.set_perspective(def.camera.fovY, 16.0F / 9.0F, def.camera.near_plane,
+                         def.camera.far_plane);
   Render::GL::Renderer::GridParams gp;
   gp.cell_size = def.grid.tile_size;
   gp.extent =
@@ -25,10 +25,10 @@ void Environment::apply(const MapDefinition &def,
 void Environment::applyDefault(Render::GL::Renderer &renderer,
                                Render::GL::Camera &camera) {
   const auto &camera_config = Game::GameConfig::instance().camera();
-  camera.setRTSView(QVector3D(0, 0, 0), 15.0F, camera_config.defaultPitch,
-                    camera_config.defaultYaw);
+  camera.set_rts_view(QVector3D(0, 0, 0), 15.0F, camera_config.default_pitch,
+                      camera_config.default_yaw);
 
-  camera.setPerspective(45.0F, 16.0F / 9.0F, 1.0F, 200.0F);
+  camera.set_perspective(45.0F, 16.0F / 9.0F, 1.0F, 200.0F);
   Render::GL::Renderer::GridParams gp;
   gp.cell_size = 1.0F;
   gp.extent = 50.0F;

+ 13 - 12
game/map/level_loader.cpp

@@ -75,7 +75,8 @@ auto LevelLoader::loadFromAssets(
 
     const Game::Visuals::VisualCatalog *catalog_ptr =
         visuals_loaded ? &visual_catalog : nullptr;
-    auto rt = Game::Map::MapTransformer::applyToWorld(def, world, catalog_ptr);
+    auto rt =
+        Game::Map::MapTransformer::apply_to_world(def, world, catalog_ptr);
     if (!rt.unit_ids.empty()) {
       res.player_unit_id = rt.unit_ids.front();
     } else {
@@ -87,9 +88,9 @@ auto LevelLoader::loadFromAssets(
         sp.position = QVector3D(0.0F, 0.0F, 0.0F);
         sp.player_id = 0;
         sp.spawn_type = Game::Units::SpawnType::Archer;
-        sp.ai_controlled = !owners.isPlayer(sp.player_id);
+        sp.ai_controlled = !owners.is_player(sp.player_id);
         if (const auto *nation =
-                nationRegistry.getNationForPlayer(sp.player_id)) {
+                nationRegistry.get_nation_for_player(sp.player_id)) {
           sp.nation_id = nation->id;
         } else {
           sp.nation_id = nationRegistry.default_nation_id();
@@ -107,7 +108,7 @@ auto LevelLoader::loadFromAssets(
     for (auto *e : world.get_entities_with<Engine::Core::UnitComponent>()) {
       if (auto *u = e->get_component<Engine::Core::UnitComponent>()) {
         if (u->spawn_type == Game::Units::SpawnType::Barracks &&
-            owners.isPlayer(u->owner_id)) {
+            owners.is_player(u->owner_id)) {
           has_barracks = true;
           break;
         }
@@ -119,11 +120,11 @@ auto LevelLoader::loadFromAssets(
       if (reg2) {
         Game::Units::SpawnParams sp;
         sp.position = QVector3D(-4.0F, 0.0F, -3.0F);
-        sp.player_id = owners.getLocalPlayerId();
+        sp.player_id = owners.get_local_player_id();
         sp.spawn_type = Game::Units::SpawnType::Barracks;
-        sp.ai_controlled = !owners.isPlayer(sp.player_id);
+        sp.ai_controlled = !owners.is_player(sp.player_id);
         if (const auto *nation =
-                nationRegistry.getNationForPlayer(sp.player_id)) {
+                nationRegistry.get_nation_for_player(sp.player_id)) {
           sp.nation_id = nation->id;
         } else {
           sp.nation_id = nationRegistry.default_nation_id();
@@ -139,9 +140,9 @@ auto LevelLoader::loadFromAssets(
                << "- applying default environment";
     Game::Map::Environment::applyDefault(renderer, camera);
     res.ok = false;
-    res.cam_fov = camera.getFOV();
-    res.cam_near = camera.getNear();
-    res.cam_far = camera.getFar();
+    res.cam_fov = camera.get_fov();
+    res.cam_near = camera.get_near();
+    res.cam_far = camera.get_far();
     res.grid_width = 50;
     res.grid_height = 50;
     res.tile_size = 1.0F;
@@ -153,9 +154,9 @@ auto LevelLoader::loadFromAssets(
       sp.position = QVector3D(0.0F, 0.0F, 0.0F);
       sp.player_id = 0;
       sp.spawn_type = Game::Units::SpawnType::Archer;
-      sp.ai_controlled = !owners.isPlayer(sp.player_id);
+      sp.ai_controlled = !owners.is_player(sp.player_id);
       if (const auto *nation =
-              nationRegistry.getNationForPlayer(sp.player_id)) {
+              nationRegistry.get_nation_for_player(sp.player_id)) {
         sp.nation_id = nation->id;
       } else {
         sp.nation_id = nationRegistry.default_nation_id();

+ 14 - 14
game/map/map_catalog.cpp

@@ -127,7 +127,7 @@ auto MapCatalog::availableMaps() -> QVariantList {
   return list;
 }
 
-void MapCatalog::loadMapsAsync() {
+void MapCatalog::load_maps_async() {
   if (m_loading) {
     return;
   }
@@ -135,15 +135,15 @@ void MapCatalog::loadMapsAsync() {
   m_maps.clear();
   m_pendingFiles.clear();
   m_loading = true;
-  emit loadingChanged(true);
+  emit loading_changed(true);
 
   const QString maps_root =
       Utils::Resources::resolveResourcePath(QStringLiteral(":/assets/maps"));
   QDir const maps_dir(maps_root);
   if (!maps_dir.exists()) {
     m_loading = false;
-    emit loadingChanged(false);
-    emit allMapsLoaded();
+    emit loading_changed(false);
+    emit all_maps_loaded();
     return;
   }
 
@@ -152,19 +152,19 @@ void MapCatalog::loadMapsAsync() {
 
   if (m_pendingFiles.isEmpty()) {
     m_loading = false;
-    emit loadingChanged(false);
-    emit allMapsLoaded();
+    emit loading_changed(false);
+    emit all_maps_loaded();
     return;
   }
 
-  QTimer::singleShot(0, this, &MapCatalog::loadNextMap);
+  QTimer::singleShot(0, this, &MapCatalog::load_next_map);
 }
 
-void MapCatalog::loadNextMap() {
+void MapCatalog::load_next_map() {
   if (m_pendingFiles.isEmpty()) {
     m_loading = false;
-    emit loadingChanged(false);
-    emit allMapsLoaded();
+    emit loading_changed(false);
+    emit all_maps_loaded();
     return;
   }
 
@@ -178,15 +178,15 @@ void MapCatalog::loadNextMap() {
   QVariantMap const entry = loadSingleMap(path);
   if (!entry.isEmpty()) {
     m_maps.append(entry);
-    emit mapLoaded(entry);
+    emit map_loaded(entry);
   }
 
   if (!m_pendingFiles.isEmpty()) {
-    QTimer::singleShot(10, this, &MapCatalog::loadNextMap);
+    QTimer::singleShot(10, this, &MapCatalog::load_next_map);
   } else {
     m_loading = false;
-    emit loadingChanged(false);
-    emit allMapsLoaded();
+    emit loading_changed(false);
+    emit all_maps_loaded();
   }
 }
 

+ 5 - 5
game/map/map_catalog.h

@@ -13,18 +13,18 @@ public:
 
   static auto availableMaps() -> QVariantList;
 
-  Q_INVOKABLE void loadMapsAsync();
+  Q_INVOKABLE void load_maps_async();
 
   [[nodiscard]] auto isLoading() const -> bool { return m_loading; }
   [[nodiscard]] auto maps() const -> const QVariantList & { return m_maps; }
 
 signals:
-  void mapLoaded(QVariantMap mapData);
-  void allMapsLoaded();
-  void loadingChanged(bool loading);
+  void map_loaded(QVariantMap mapData);
+  void all_maps_loaded();
+  void loading_changed(bool loading);
 
 private:
-  void loadNextMap();
+  void load_next_map();
   static auto loadSingleMap(const QString &filePath) -> QVariantMap;
 
   QStringList m_pendingFiles;

+ 13 - 12
game/map/map_transformer.cpp

@@ -41,12 +41,12 @@ auto MapTransformer::getFactoryRegistry()
 
 void MapTransformer::set_local_owner_id(int owner_id) {
   auto &owners = Game::Systems::OwnerRegistry::instance();
-  owners.setLocalPlayerId(owner_id);
+  owners.set_local_player_id(owner_id);
 }
 
 auto MapTransformer::local_owner_id() -> int {
   auto &owners = Game::Systems::OwnerRegistry::instance();
-  return owners.getLocalPlayerId();
+  return owners.get_local_player_id();
 }
 
 void MapTransformer::setPlayerTeamOverrides(
@@ -58,7 +58,7 @@ void MapTransformer::clearPlayerTeamOverrides() {
   s_player_team_overrides.clear();
 }
 
-auto MapTransformer::applyToWorld(
+auto MapTransformer::apply_to_world(
     const MapDefinition &def, Engine::Core::World &world,
     const Game::Visuals::VisualCatalog *visuals) -> MapRuntime {
   MapRuntime rt;
@@ -87,11 +87,11 @@ auto MapTransformer::applyToWorld(
       continue;
     }
 
-    if (owner_registry.getOwnerType(player_id) ==
+    if (owner_registry.get_owner_type(player_id) ==
         Game::Systems::OwnerType::Neutral) {
 
       bool const is_local_player =
-          (player_id == owner_registry.getLocalPlayerId());
+          (player_id == owner_registry.get_local_player_id());
       Game::Systems::OwnerType const owner_type =
           is_local_player ? Game::Systems::OwnerType::Player
                           : Game::Systems::OwnerType::AI;
@@ -100,7 +100,7 @@ auto MapTransformer::applyToWorld(
           is_local_player ? "Player " + std::to_string(player_id)
                           : "AI Player " + std::to_string(player_id);
 
-      owner_registry.registerOwnerWithId(player_id, owner_type, owner_name);
+      owner_registry.register_owner_with_id(player_id, owner_type, owner_name);
     }
 
     int final_team_id = 0;
@@ -116,7 +116,7 @@ auto MapTransformer::applyToWorld(
       } else {
       }
     }
-    owner_registry.setOwnerTeam(player_id, final_team_id);
+    owner_registry.set_owner_team(player_id, final_team_id);
   }
 
   for (const auto &s : def.spawns) {
@@ -141,7 +141,8 @@ auto MapTransformer::applyToWorld(
     }
 
     auto &terrain = Game::Map::TerrainService::instance();
-    if (terrain.isInitialized() && terrain.isForbiddenWorld(world_x, world_z)) {
+    if (terrain.is_initialized() &&
+        terrain.is_forbidden_world(world_x, world_z)) {
       const float tile = std::max(0.0001F, def.grid.tile_size);
       bool found = false;
       const int max_radius = 12;
@@ -154,7 +155,7 @@ auto MapTransformer::applyToWorld(
             }
             float const cand_x = world_x + float(ox) * tile;
             float const cand_z = world_z + float(oz) * tile;
-            if (!terrain.isForbiddenWorld(cand_x, cand_z)) {
+            if (!terrain.is_forbidden_world(cand_x, cand_z)) {
               world_x = cand_x;
               world_z = cand_z;
               found = true;
@@ -175,14 +176,14 @@ auto MapTransformer::applyToWorld(
       sp.position = QVector3D(world_x, 0.0F, world_z);
       sp.player_id = effective_player_id;
       sp.spawn_type = s.type;
-      sp.ai_controlled = !owner_registry.isPlayer(effective_player_id);
+      sp.ai_controlled = !owner_registry.is_player(effective_player_id);
       sp.max_population = s.max_population;
 
       if (s.nation.has_value()) {
         sp.nation_id = s.nation.value();
       } else if (const auto *nation =
                      Game::Systems::NationRegistry::instance()
-                         .getNationForPlayer(effective_player_id)) {
+                         .get_nation_for_player(effective_player_id)) {
         sp.nation_id = nation->id;
       } else {
         sp.nation_id =
@@ -211,7 +212,7 @@ auto MapTransformer::applyToWorld(
       if (visuals != nullptr) {
         Game::Visuals::VisualDef defv;
         if (visuals->lookup(Game::Units::spawn_typeToString(s.type), defv)) {
-          Game::Visuals::applyToRenderable(defv, *r);
+          Game::Visuals::apply_to_renderable(defv, *r);
         }
       }
       if (r->color[0] == 0.0F && r->color[1] == 0.0F && r->color[2] == 0.0F) {

+ 1 - 1
game/map/map_transformer.h

@@ -21,7 +21,7 @@ struct MapRuntime {
 
 class MapTransformer {
 public:
-  static auto applyToWorld(
+  static auto apply_to_world(
       const MapDefinition &def, Engine::Core::World &world,
       const Game::Visuals::VisualCatalog *visuals = nullptr) -> MapRuntime;
 

+ 359 - 0
game/map/minimap/fog_of_war_mask.cpp

@@ -0,0 +1,359 @@
+#include "fog_of_war_mask.h"
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+
+namespace Game::Map::Minimap {
+
+FogOfWarMask::FogOfWarMask(int map_width, int map_height, float tile_size,
+                           const FogOfWarConfig &config)
+    : m_config(config), m_map_width(map_width), m_map_height(map_height),
+      m_tile_size(tile_size) {
+
+  m_fog_width = std::max(1, map_width / m_config.resolution_divisor);
+  m_fog_height = std::max(1, map_height / m_config.resolution_divisor);
+
+  m_fog_cell_size = tile_size * static_cast<float>(m_config.resolution_divisor);
+
+  const size_t total_cells =
+      static_cast<size_t>(m_fog_width) * static_cast<size_t>(m_fog_height);
+  const size_t bytes_needed = (total_cells + 3) / 4;
+
+  m_visibility_data.resize(bytes_needed, 0);
+}
+
+FogOfWarMask::~FogOfWarMask() = default;
+
+FogOfWarMask::FogOfWarMask(FogOfWarMask &&) noexcept = default;
+auto FogOfWarMask::operator=(FogOfWarMask &&) noexcept -> FogOfWarMask & =
+                                                              default;
+
+void FogOfWarMask::set_cell(int fog_x, int fog_y, VisibilityState state) {
+  if (fog_x < 0 || fog_x >= m_fog_width || fog_y < 0 || fog_y >= m_fog_height) {
+    return;
+  }
+
+  const size_t cell_index =
+      static_cast<size_t>(fog_y) * static_cast<size_t>(m_fog_width) +
+      static_cast<size_t>(fog_x);
+  const size_t byte_index = cell_index / 4;
+  const int bit_offset = static_cast<int>((cell_index % 4) * 2);
+
+  const uint8_t mask = static_cast<uint8_t>(~(0x03 << bit_offset));
+  const uint8_t value =
+      static_cast<uint8_t>(static_cast<uint8_t>(state) << bit_offset);
+
+  m_visibility_data[byte_index] =
+      static_cast<uint8_t>((m_visibility_data[byte_index] & mask) | value);
+}
+
+auto FogOfWarMask::get_cell(int fog_x, int fog_y) const -> VisibilityState {
+  if (fog_x < 0 || fog_x >= m_fog_width || fog_y < 0 || fog_y >= m_fog_height) {
+    return VisibilityState::Unseen;
+  }
+
+  const size_t cell_index =
+      static_cast<size_t>(fog_y) * static_cast<size_t>(m_fog_width) +
+      static_cast<size_t>(fog_x);
+  const size_t byte_index = cell_index / 4;
+  const int bit_offset = static_cast<int>((cell_index % 4) * 2);
+
+  const uint8_t value = static_cast<uint8_t>(
+      (m_visibility_data[byte_index] >> bit_offset) & 0x03);
+  return static_cast<VisibilityState>(value);
+}
+
+auto FogOfWarMask::world_to_fog(float world_x,
+                                float world_z) const -> std::pair<int, int> {
+
+  const float world_width = static_cast<float>(m_map_width) * m_tile_size;
+  const float world_height = static_cast<float>(m_map_height) * m_tile_size;
+
+  const float norm_x = (world_x + world_width * 0.5F) / world_width;
+  const float norm_z = (world_z + world_height * 0.5F) / world_height;
+
+  const int fog_x =
+      std::clamp(static_cast<int>(norm_x * static_cast<float>(m_fog_width)), 0,
+                 m_fog_width - 1);
+  const int fog_y =
+      std::clamp(static_cast<int>(norm_z * static_cast<float>(m_fog_height)), 0,
+                 m_fog_height - 1);
+
+  return {fog_x, fog_y};
+}
+
+void FogOfWarMask::clear_current_visibility() {
+
+  const size_t total_cells =
+      static_cast<size_t>(m_fog_width) * static_cast<size_t>(m_fog_height);
+
+  for (size_t i = 0; i < total_cells; ++i) {
+    const size_t byte_index = i / 4;
+    const int bit_offset = static_cast<int>((i % 4) * 2);
+
+    const uint8_t current = static_cast<uint8_t>(
+        (m_visibility_data[byte_index] >> bit_offset) & 0x03);
+
+    if (current == static_cast<uint8_t>(VisibilityState::Visible)) {
+
+      const uint8_t mask = static_cast<uint8_t>(~(0x03 << bit_offset));
+      const uint8_t value = static_cast<uint8_t>(
+          static_cast<uint8_t>(VisibilityState::Revealed) << bit_offset);
+      m_visibility_data[byte_index] =
+          static_cast<uint8_t>((m_visibility_data[byte_index] & mask) | value);
+    }
+  }
+}
+
+void FogOfWarMask::reveal_circle(int center_x, int center_y,
+                                 float radius_cells) {
+
+  const int radius_int = static_cast<int>(std::ceil(radius_cells));
+  const float radius_sq = radius_cells * radius_cells;
+
+  const int min_y = std::max(0, center_y - radius_int);
+  const int max_y = std::min(m_fog_height - 1, center_y + radius_int);
+  const int min_x = std::max(0, center_x - radius_int);
+  const int max_x = std::min(m_fog_width - 1, center_x + radius_int);
+
+  for (int y = min_y; y <= max_y; ++y) {
+    const float dy = static_cast<float>(y - center_y);
+    const float dy_sq = dy * dy;
+
+    for (int x = min_x; x <= max_x; ++x) {
+      const float dx = static_cast<float>(x - center_x);
+      const float dist_sq = dx * dx + dy_sq;
+
+      if (dist_sq <= radius_sq) {
+        set_cell(x, y, VisibilityState::Visible);
+      }
+    }
+  }
+}
+
+void FogOfWarMask::update_vision(const std::vector<VisionSource> &sources,
+                                 int player_id) {
+
+  clear_current_visibility();
+
+  for (const auto &source : sources) {
+    if (source.player_id != player_id) {
+      continue;
+    }
+
+    const auto [fog_x, fog_y] = world_to_fog(source.world_x, source.world_z);
+
+    const float radius_cells = source.vision_radius / m_fog_cell_size;
+
+    reveal_circle(fog_x, fog_y, radius_cells);
+  }
+
+  m_dirty = true;
+}
+
+auto FogOfWarMask::tick(const std::vector<VisionSource> &sources,
+                        int player_id) -> bool {
+  ++m_frame_counter;
+
+  if (m_frame_counter >= m_config.update_interval) {
+    m_frame_counter = 0;
+    update_vision(sources, player_id);
+    return true;
+  }
+
+  return false;
+}
+
+auto FogOfWarMask::get_visibility(int fog_x,
+                                  int fog_y) const -> VisibilityState {
+  return get_cell(fog_x, fog_y);
+}
+
+auto FogOfWarMask::is_revealed(float world_x, float world_z) const -> bool {
+  const auto [fog_x, fog_y] = world_to_fog(world_x, world_z);
+  const auto state = get_cell(fog_x, fog_y);
+  return state != VisibilityState::Unseen;
+}
+
+auto FogOfWarMask::is_visible(float world_x, float world_z) const -> bool {
+  const auto [fog_x, fog_y] = world_to_fog(world_x, world_z);
+  return get_cell(fog_x, fog_y) == VisibilityState::Visible;
+}
+
+void FogOfWarMask::reset() {
+  std::memset(m_visibility_data.data(), 0, m_visibility_data.size());
+  m_dirty = true;
+}
+
+void FogOfWarMask::reveal_all() {
+
+  const size_t total_cells =
+      static_cast<size_t>(m_fog_width) * static_cast<size_t>(m_fog_height);
+
+  for (size_t i = 0; i < total_cells; ++i) {
+    const size_t byte_index = i / 4;
+    const int bit_offset = static_cast<int>((i % 4) * 2);
+
+    const uint8_t mask = static_cast<uint8_t>(~(0x03 << bit_offset));
+    const uint8_t value = static_cast<uint8_t>(
+        static_cast<uint8_t>(VisibilityState::Revealed) << bit_offset);
+    m_visibility_data[byte_index] =
+        static_cast<uint8_t>((m_visibility_data[byte_index] & mask) | value);
+  }
+
+  m_dirty = true;
+}
+
+auto FogOfWarMask::memory_usage() const -> size_t {
+  size_t total = sizeof(*this);
+  total += m_visibility_data.capacity();
+  total += static_cast<size_t>(m_cached_mask.sizeInBytes());
+  return total;
+}
+
+void FogOfWarMask::apply_gaussian_blur(std::vector<uint8_t> &alpha_buffer,
+                                       int width, int height) const {
+  if (m_config.blur_radius <= 0) {
+    return;
+  }
+
+  const int radius = m_config.blur_radius;
+
+  const int kernel_size = radius * 2 + 1;
+  std::vector<float> kernel(static_cast<size_t>(kernel_size));
+  float kernel_sum = 0.0F;
+
+  const float sigma = static_cast<float>(radius) / 2.0F;
+  const float sigma_sq_2 = 2.0F * sigma * sigma;
+
+  for (int i = 0; i < kernel_size; ++i) {
+    const float x = static_cast<float>(i - radius);
+    kernel[static_cast<size_t>(i)] = std::exp(-(x * x) / sigma_sq_2);
+    kernel_sum += kernel[static_cast<size_t>(i)];
+  }
+
+  for (auto &k : kernel) {
+    k /= kernel_sum;
+  }
+
+  std::vector<float> temp(static_cast<size_t>(width * height));
+
+  for (int y = 0; y < height; ++y) {
+    for (int x = 0; x < width; ++x) {
+      float sum = 0.0F;
+
+      for (int k = -radius; k <= radius; ++k) {
+        const int sample_x = std::clamp(x + k, 0, width - 1);
+        const size_t src_idx = static_cast<size_t>(y * width + sample_x);
+        sum += static_cast<float>(alpha_buffer[src_idx]) *
+               kernel[static_cast<size_t>(k + radius)];
+      }
+
+      temp[static_cast<size_t>(y * width + x)] = sum;
+    }
+  }
+
+  for (int y = 0; y < height; ++y) {
+    for (int x = 0; x < width; ++x) {
+      float sum = 0.0F;
+
+      for (int k = -radius; k <= radius; ++k) {
+        const int sample_y = std::clamp(y + k, 0, height - 1);
+        const size_t src_idx = static_cast<size_t>(sample_y * width + x);
+        sum += temp[src_idx] * kernel[static_cast<size_t>(k + radius)];
+      }
+
+      alpha_buffer[static_cast<size_t>(y * width + x)] =
+          static_cast<uint8_t>(std::clamp(sum, 0.0F, 255.0F));
+    }
+  }
+}
+
+auto FogOfWarMask::generate_mask(int target_width,
+                                 int target_height) const -> QImage {
+
+  if (!m_dirty && m_cached_width == target_width &&
+      m_cached_height == target_height && !m_cached_mask.isNull()) {
+    return m_cached_mask;
+  }
+
+  std::vector<uint8_t> fog_alpha(static_cast<size_t>(m_fog_width) *
+                                 static_cast<size_t>(m_fog_height));
+
+  for (int y = 0; y < m_fog_height; ++y) {
+    for (int x = 0; x < m_fog_width; ++x) {
+      const auto state = get_cell(x, y);
+      uint8_t alpha = m_config.alpha_unseen;
+
+      switch (state) {
+      case VisibilityState::Unseen:
+        alpha = m_config.alpha_unseen;
+        break;
+      case VisibilityState::Revealed:
+        alpha = m_config.alpha_revealed;
+        break;
+      case VisibilityState::Visible:
+        alpha = m_config.alpha_visible;
+        break;
+      }
+
+      fog_alpha[static_cast<size_t>(y * m_fog_width + x)] = alpha;
+    }
+  }
+
+  apply_gaussian_blur(fog_alpha, m_fog_width, m_fog_height);
+
+  QImage mask(target_width, target_height, QImage::Format_RGBA8888);
+
+  const float scale_x =
+      static_cast<float>(m_fog_width) / static_cast<float>(target_width);
+  const float scale_y =
+      static_cast<float>(m_fog_height) / static_cast<float>(target_height);
+
+  for (int y = 0; y < target_height; ++y) {
+    auto *scanline = reinterpret_cast<uint32_t *>(mask.scanLine(y));
+
+    for (int x = 0; x < target_width; ++x) {
+
+      const float fog_x = static_cast<float>(x) * scale_x;
+      const float fog_y = static_cast<float>(y) * scale_y;
+
+      const int x0 = std::min(static_cast<int>(fog_x), m_fog_width - 1);
+      const int y0 = std::min(static_cast<int>(fog_y), m_fog_height - 1);
+      const int x1 = std::min(x0 + 1, m_fog_width - 1);
+      const int y1 = std::min(y0 + 1, m_fog_height - 1);
+
+      const float fx = fog_x - static_cast<float>(x0);
+      const float fy = fog_y - static_cast<float>(y0);
+
+      const float a00 = static_cast<float>(
+          fog_alpha[static_cast<size_t>(y0 * m_fog_width + x0)]);
+      const float a10 = static_cast<float>(
+          fog_alpha[static_cast<size_t>(y0 * m_fog_width + x1)]);
+      const float a01 = static_cast<float>(
+          fog_alpha[static_cast<size_t>(y1 * m_fog_width + x0)]);
+      const float a11 = static_cast<float>(
+          fog_alpha[static_cast<size_t>(y1 * m_fog_width + x1)]);
+
+      const float alpha_top = a00 + (a10 - a00) * fx;
+      const float alpha_bot = a01 + (a11 - a01) * fx;
+      const float alpha = alpha_top + (alpha_bot - alpha_top) * fy;
+
+      const uint8_t final_alpha =
+          static_cast<uint8_t>(std::clamp(alpha, 0.0F, 255.0F));
+
+      scanline[x] = (static_cast<uint32_t>(final_alpha) << 24) |
+                    (static_cast<uint32_t>(m_config.fog_color_b) << 16) |
+                    (static_cast<uint32_t>(m_config.fog_color_g) << 8) |
+                    static_cast<uint32_t>(m_config.fog_color_r);
+    }
+  }
+
+  m_cached_mask = mask;
+  m_cached_width = target_width;
+  m_cached_height = target_height;
+
+  return mask;
+}
+
+} // namespace Game::Map::Minimap

+ 112 - 0
game/map/minimap/fog_of_war_mask.h

@@ -0,0 +1,112 @@
+#pragma once
+
+#include <QImage>
+#include <QVector2D>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace Game::Map::Minimap {
+
+enum class VisibilityState : uint8_t { Unseen = 0, Revealed = 1, Visible = 2 };
+
+struct FogOfWarConfig {
+
+  int update_interval = 15;
+
+  int resolution_divisor = 2;
+
+  int blur_radius = 2;
+
+  uint8_t alpha_unseen = 220;
+  uint8_t alpha_revealed = 120;
+  uint8_t alpha_visible = 0;
+
+  uint8_t fog_color_r = 30;
+  uint8_t fog_color_g = 25;
+  uint8_t fog_color_b = 20;
+
+  FogOfWarConfig() = default;
+};
+
+struct VisionSource {
+  float world_x = 0.0F;
+  float world_z = 0.0F;
+  float vision_radius = 10.0F;
+  int player_id = 0;
+};
+
+class FogOfWarMask {
+public:
+  FogOfWarMask(int map_width, int map_height, float tile_size,
+               const FogOfWarConfig &config = FogOfWarConfig());
+
+  ~FogOfWarMask();
+
+  FogOfWarMask(const FogOfWarMask &) = delete;
+  auto operator=(const FogOfWarMask &) -> FogOfWarMask & = delete;
+  FogOfWarMask(FogOfWarMask &&) noexcept;
+  auto operator=(FogOfWarMask &&) noexcept -> FogOfWarMask &;
+
+  void update_vision(const std::vector<VisionSource> &sources, int player_id);
+
+  auto tick(const std::vector<VisionSource> &sources, int player_id) -> bool;
+
+  [[nodiscard]] auto generate_mask(int target_width,
+                                   int target_height) const -> QImage;
+
+  [[nodiscard]] auto get_visibility(int fog_x,
+                                    int fog_y) const -> VisibilityState;
+
+  [[nodiscard]] auto is_revealed(float world_x, float world_z) const -> bool;
+
+  [[nodiscard]] auto is_visible(float world_x, float world_z) const -> bool;
+
+  void reset();
+
+  void reveal_all();
+
+  [[nodiscard]] auto fog_width() const -> int { return m_fog_width; }
+  [[nodiscard]] auto fog_height() const -> int { return m_fog_height; }
+
+  [[nodiscard]] auto memory_usage() const -> size_t;
+
+  [[nodiscard]] auto is_dirty() const -> bool { return m_dirty; }
+
+  void clear_dirty() { m_dirty = false; }
+
+private:
+  void set_cell(int fog_x, int fog_y, VisibilityState state);
+  [[nodiscard]] auto get_cell(int fog_x, int fog_y) const -> VisibilityState;
+
+  [[nodiscard]] auto world_to_fog(float world_x,
+                                  float world_z) const -> std::pair<int, int>;
+
+  void reveal_circle(int center_x, int center_y, float radius_cells);
+  void clear_current_visibility();
+
+  void apply_gaussian_blur(std::vector<uint8_t> &alpha_buffer, int width,
+                           int height) const;
+
+  FogOfWarConfig m_config;
+
+  int m_map_width;
+  int m_map_height;
+  float m_tile_size;
+
+  int m_fog_width;
+  int m_fog_height;
+  float m_fog_cell_size;
+
+  std::vector<uint8_t> m_visibility_data;
+
+  int m_frame_counter = 0;
+
+  bool m_dirty = true;
+
+  mutable QImage m_cached_mask;
+  mutable int m_cached_width = 0;
+  mutable int m_cached_height = 0;
+};
+
+} // namespace Game::Map::Minimap

+ 92 - 0
game/map/minimap/minimap_example.cpp

@@ -0,0 +1,92 @@
+
+
+#include "map/map_loader.h"
+#include "map/minimap/minimap_generator.h"
+#include "map/minimap/minimap_texture_manager.h"
+#include <QCoreApplication>
+#include <QDebug>
+
+using namespace Game::Map;
+using namespace Game::Map::Minimap;
+
+auto main(int argc, char *argv[]) -> int {
+  QCoreApplication app(argc, argv);
+
+  qDebug() << "=== Minimap Generation Example ===";
+  qDebug() << "";
+  qDebug() << "Step 1: Loading map from JSON...";
+
+  const QString mapPath = "assets/maps/map_rivers.json";
+  MapDefinition mapDef;
+  QString error;
+
+  if (!MapLoader::loadFromJsonFile(mapPath, mapDef, &error)) {
+    qCritical() << "Failed to load map:" << error;
+    return 1;
+  }
+
+  qDebug() << "  ✓ Loaded map:" << mapDef.name;
+  qDebug() << "  ✓ Grid size:" << mapDef.grid.width << "x"
+           << mapDef.grid.height;
+  qDebug() << "  ✓ Terrain features:" << mapDef.terrain.size();
+  qDebug() << "  ✓ Rivers:" << mapDef.rivers.size();
+  qDebug() << "  ✓ Roads:" << mapDef.roads.size();
+  qDebug() << "  ✓ Spawns:" << mapDef.spawns.size();
+  qDebug() << "";
+
+  qDebug() << "Step 2: Generating minimap texture...";
+
+  MinimapGenerator generator;
+  QImage minimapImage = generator.generate(mapDef);
+
+  if (minimapImage.isNull()) {
+    qCritical() << "Failed to generate minimap image";
+    return 1;
+  }
+
+  qDebug() << "  ✓ Generated minimap texture";
+  qDebug() << "  ✓ Size:" << minimapImage.width() << "x"
+           << minimapImage.height();
+  qDebug() << "  ✓ Format:" << minimapImage.format();
+  qDebug() << "";
+
+  const QString outputPath = "/tmp/minimap_example.png";
+  if (minimapImage.save(outputPath)) {
+    qDebug() << "  ✓ Saved minimap to:" << outputPath;
+  } else {
+    qWarning() << "  ⚠ Could not save minimap to:" << outputPath;
+  }
+  qDebug() << "";
+
+  qDebug() << "Step 4: Using MinimapTextureManager (recommended approach)...";
+
+  MinimapTextureManager manager;
+  if (!manager.generateForMap(mapDef)) {
+    qCritical() << "Failed to generate minimap via manager";
+    return 1;
+  }
+
+  qDebug() << "  ✓ Minimap texture manager initialized";
+  qDebug() << "  ✓ Texture ready for OpenGL upload";
+  qDebug() << "";
+
+  qDebug() << "=== Integration Guide ===";
+  qDebug() << "";
+  qDebug() << "In your game initialization code:";
+  qDebug() << "";
+  qDebug() << "  1. Load your map JSON:";
+  qDebug() << "     MapDefinition mapDef;";
+  qDebug() << "     MapLoader::loadFromJsonFile(path, mapDef);";
+  qDebug() << "";
+  qDebug() << "  2. Generate the minimap:";
+  qDebug() << "     MinimapTextureManager minimap;";
+  qDebug() << "     minimap.generateForMap(mapDef);";
+  qDebug() << "";
+  qDebug() << "  3. Use the texture in your renderer:";
+  qDebug() << "     auto* texture = minimap.getTexture();";
+  qDebug() << "     // Bind and render in your UI";
+  qDebug() << "";
+  qDebug() << "=== Complete ===";
+
+  return 0;
+}

+ 647 - 0
game/map/minimap/minimap_generator.cpp

@@ -0,0 +1,647 @@
+#include "minimap_generator.h"
+#include <QColor>
+#include <QLinearGradient>
+#include <QPainter>
+#include <QPainterPath>
+#include <QPen>
+#include <QRadialGradient>
+#include <algorithm>
+#include <cmath>
+#include <random>
+
+namespace Game::Map::Minimap {
+
+namespace {
+
+constexpr float k_camera_yaw_cos = -0.70710678118F;
+constexpr float k_camera_yaw_sin = -0.70710678118F;
+
+namespace Palette {
+
+constexpr QColor PARCHMENT_BASE{235, 220, 190};
+constexpr QColor PARCHMENT_LIGHT{245, 235, 215};
+constexpr QColor PARCHMENT_DARK{200, 180, 150};
+constexpr QColor PARCHMENT_STAIN{180, 160, 130, 40};
+
+constexpr QColor INK_DARK{45, 35, 25};
+constexpr QColor INK_MEDIUM{80, 65, 50};
+constexpr QColor INK_LIGHT{120, 100, 80};
+
+constexpr QColor MOUNTAIN_SHADOW{95, 80, 65};
+constexpr QColor MOUNTAIN_FACE{140, 125, 105};
+constexpr QColor MOUNTAIN_HIGHLIGHT{180, 165, 145};
+constexpr QColor HILL_BASE{160, 145, 120};
+
+constexpr QColor WATER_DARK{55, 95, 130};
+constexpr QColor WATER_MAIN{75, 120, 160};
+constexpr QColor WATER_LIGHT{100, 145, 180};
+
+constexpr QColor ROAD_MAIN{130, 105, 75};
+constexpr QColor ROAD_HIGHLIGHT{165, 140, 110};
+
+constexpr QColor STRUCTURE_STONE{160, 150, 135};
+constexpr QColor STRUCTURE_SHADOW{100, 85, 70};
+
+constexpr QColor TEAM_BLUE{65, 105, 165};
+constexpr QColor TEAM_BLUE_DARK{40, 65, 100};
+constexpr QColor TEAM_RED{175, 65, 55};
+constexpr QColor TEAM_RED_DARK{110, 40, 35};
+
+} // namespace Palette
+
+auto hash_coords(int x, int y, int seed = 0) -> float {
+  const int n = x + y * 57 + seed * 131;
+  const int shifted = (n << 13) ^ n;
+  return 1.0F -
+         static_cast<float>(
+             (shifted * (shifted * shifted * 15731 + 789221) + 1376312589) &
+             0x7fffffff) /
+             1073741824.0F;
+}
+
+} // namespace
+
+MinimapGenerator::MinimapGenerator() : m_config() {}
+
+MinimapGenerator::MinimapGenerator(const Config &config) : m_config(config) {}
+
+auto MinimapGenerator::generate(const MapDefinition &map_def) -> QImage {
+
+  const int img_width =
+      static_cast<int>(map_def.grid.width * m_config.pixels_per_tile);
+  const int img_height =
+      static_cast<int>(map_def.grid.height * m_config.pixels_per_tile);
+
+  QImage image(img_width, img_height, QImage::Format_RGBA8888);
+  image.fill(Palette::PARCHMENT_BASE);
+
+  render_parchment_background(image);
+  render_terrain_base(image, map_def);
+  render_terrain_features(image, map_def);
+  render_rivers(image, map_def);
+  render_roads(image, map_def);
+  render_bridges(image, map_def);
+  render_structures(image, map_def);
+  apply_historical_styling(image);
+
+  return image;
+}
+
+auto MinimapGenerator::world_to_pixel(float world_x, float world_z,
+                                      const GridDefinition &grid) const
+    -> std::pair<float, float> {
+
+  const float rotated_x =
+      world_x * k_camera_yaw_cos - world_z * k_camera_yaw_sin;
+  const float rotated_z =
+      world_x * k_camera_yaw_sin + world_z * k_camera_yaw_cos;
+
+  const float world_width = grid.width * grid.tile_size;
+  const float world_height = grid.height * grid.tile_size;
+  const float img_width = grid.width * m_config.pixels_per_tile;
+  const float img_height = grid.height * m_config.pixels_per_tile;
+
+  const float px = (rotated_x + world_width * 0.5F) * (img_width / world_width);
+  const float py =
+      (rotated_z + world_height * 0.5F) * (img_height / world_height);
+
+  return {px, py};
+}
+
+auto MinimapGenerator::world_to_pixel_size(
+    float world_size, const GridDefinition &grid) const -> float {
+
+  return (world_size / grid.tile_size) * m_config.pixels_per_tile;
+}
+
+void MinimapGenerator::render_parchment_background(QImage &image) {
+  QPainter painter(&image);
+
+  for (int y = 0; y < image.height(); ++y) {
+    for (int x = 0; x < image.width(); ++x) {
+
+      const float noise = hash_coords(x / 3, y / 3, 42) * 0.08F;
+
+      QColor pixel = Palette::PARCHMENT_BASE;
+      int r = pixel.red() + static_cast<int>(noise * 20);
+      int g = pixel.green() + static_cast<int>(noise * 18);
+      int b = pixel.blue() + static_cast<int>(noise * 15);
+
+      pixel.setRgb(std::clamp(r, 0, 255), std::clamp(g, 0, 255),
+                   std::clamp(b, 0, 255));
+      image.setPixelColor(x, y, pixel);
+    }
+  }
+
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  std::mt19937 rng(12345);
+  std::uniform_real_distribution<float> dist_x(
+      0.0F, static_cast<float>(image.width()));
+  std::uniform_real_distribution<float> dist_y(
+      0.0F, static_cast<float>(image.height()));
+  std::uniform_real_distribution<float> dist_size(5.0F, 25.0F);
+  std::uniform_real_distribution<float> dist_alpha(0.02F, 0.06F);
+
+  const int num_stains = (image.width() * image.height()) / 8000;
+  for (int i = 0; i < num_stains; ++i) {
+    const float cx = dist_x(rng);
+    const float cy = dist_y(rng);
+    const float radius = dist_size(rng);
+    const float alpha = dist_alpha(rng);
+
+    QRadialGradient stain(cx, cy, radius);
+    QColor stain_color = Palette::PARCHMENT_STAIN;
+    stain_color.setAlphaF(static_cast<double>(alpha));
+    stain.setColorAt(0, stain_color);
+    stain.setColorAt(1, Qt::transparent);
+
+    painter.setBrush(stain);
+    painter.setPen(Qt::NoPen);
+    painter.drawEllipse(QPointF(cx, cy), radius, radius);
+  }
+}
+
+void MinimapGenerator::render_terrain_base(QImage &image,
+                                           const MapDefinition &map_def) {
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  const QColor biome_color = biome_to_base_color(map_def.biome);
+
+  painter.setCompositionMode(QPainter::CompositionMode_Multiply);
+  painter.setOpacity(0.15);
+  painter.fillRect(image.rect(), biome_color);
+  painter.setOpacity(1.0);
+  painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+}
+
+void MinimapGenerator::render_terrain_features(QImage &image,
+                                               const MapDefinition &map_def) {
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  for (const auto &feature : map_def.terrain) {
+    const auto [px, py] =
+        world_to_pixel(feature.center_x, feature.center_z, map_def.grid);
+
+    float pixel_width = world_to_pixel_size(feature.width, map_def.grid);
+    float pixel_depth = world_to_pixel_size(feature.depth, map_def.grid);
+
+    constexpr float MIN_FEATURE_SIZE = 4.0F;
+    pixel_width = std::max(pixel_width, MIN_FEATURE_SIZE);
+    pixel_depth = std::max(pixel_depth, MIN_FEATURE_SIZE);
+
+    if (feature.type == TerrainType::Mountain) {
+      draw_mountain_symbol(painter, px, py, pixel_width, pixel_depth);
+    } else if (feature.type == TerrainType::Hill) {
+      draw_hill_symbol(painter, px, py, pixel_width, pixel_depth);
+    }
+  }
+}
+
+void MinimapGenerator::draw_mountain_symbol(QPainter &painter, float cx,
+                                            float cy, float width,
+                                            float height) {
+
+  const float peak_height = height * 0.6F;
+  const float base_width = width * 0.5F;
+
+  QPainterPath shadow_path;
+  shadow_path.moveTo(cx, cy - peak_height);
+  shadow_path.lineTo(cx - base_width, cy + height * 0.3F);
+  shadow_path.lineTo(cx, cy + height * 0.1F);
+  shadow_path.closeSubpath();
+
+  painter.setBrush(Palette::MOUNTAIN_SHADOW);
+  painter.setPen(Qt::NoPen);
+  painter.drawPath(shadow_path);
+
+  QPainterPath lit_path;
+  lit_path.moveTo(cx, cy - peak_height);
+  lit_path.lineTo(cx + base_width, cy + height * 0.3F);
+  lit_path.lineTo(cx, cy + height * 0.1F);
+  lit_path.closeSubpath();
+
+  painter.setBrush(Palette::MOUNTAIN_FACE);
+  painter.drawPath(lit_path);
+
+  QPainterPath snow_path;
+  snow_path.moveTo(cx, cy - peak_height);
+  snow_path.lineTo(cx - base_width * 0.3F, cy - peak_height * 0.5F);
+  snow_path.lineTo(cx + base_width * 0.2F, cy - peak_height * 0.6F);
+  snow_path.closeSubpath();
+
+  painter.setBrush(Palette::MOUNTAIN_HIGHLIGHT);
+  painter.drawPath(snow_path);
+
+  painter.setBrush(Qt::NoBrush);
+  painter.setPen(QPen(Palette::INK_MEDIUM, 0.8));
+
+  QPainterPath outline;
+  outline.moveTo(cx - base_width, cy + height * 0.3F);
+  outline.lineTo(cx, cy - peak_height);
+  outline.lineTo(cx + base_width, cy + height * 0.3F);
+  painter.drawPath(outline);
+}
+
+void MinimapGenerator::draw_hill_symbol(QPainter &painter, float cx, float cy,
+                                        float width, float height) {
+
+  const float hill_height = height * 0.35F;
+  const float base_width = width * 0.6F;
+
+  QPainterPath hill_path;
+  hill_path.moveTo(cx - base_width, cy + hill_height * 0.2F);
+  hill_path.quadTo(cx - base_width * 0.3F, cy - hill_height, cx,
+                   cy - hill_height);
+  hill_path.quadTo(cx + base_width * 0.3F, cy - hill_height, cx + base_width,
+                   cy + hill_height * 0.2F);
+  hill_path.closeSubpath();
+
+  QLinearGradient gradient(cx - base_width, cy, cx + base_width, cy);
+  gradient.setColorAt(0.0, Palette::MOUNTAIN_SHADOW);
+  gradient.setColorAt(0.4, Palette::HILL_BASE);
+  gradient.setColorAt(1.0, Palette::MOUNTAIN_FACE);
+
+  painter.setBrush(gradient);
+  painter.setPen(QPen(Palette::INK_LIGHT, 0.6));
+  painter.drawPath(hill_path);
+}
+
+void MinimapGenerator::render_rivers(QImage &image,
+                                     const MapDefinition &map_def) {
+  if (map_def.rivers.empty()) {
+    return;
+  }
+
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  for (const auto &river : map_def.rivers) {
+    const auto [x1, y1] =
+        world_to_pixel(river.start.x(), river.start.z(), map_def.grid);
+    const auto [x2, y2] =
+        world_to_pixel(river.end.x(), river.end.z(), map_def.grid);
+
+    float pixel_width = world_to_pixel_size(river.width, map_def.grid);
+    pixel_width = std::max(pixel_width, 1.5F);
+
+    draw_river_segment(painter, x1, y1, x2, y2, pixel_width);
+  }
+}
+
+void MinimapGenerator::draw_river_segment(QPainter &painter, float x1, float y1,
+                                          float x2, float y2, float width) {
+
+  QPainterPath river_path;
+  river_path.moveTo(x1, y1);
+
+  const float dx = x2 - x1;
+  const float dy = y2 - y1;
+  const float length = std::sqrt(dx * dx + dy * dy);
+
+  if (length > 10.0F) {
+
+    const float mid_x = (x1 + x2) * 0.5F;
+    const float mid_y = (y1 + y2) * 0.5F;
+
+    const float perp_x = -dy / length;
+    const float perp_y = dx / length;
+    const float wave_amount =
+        hash_coords(static_cast<int>(x1), static_cast<int>(y1)) * width * 0.5F;
+
+    river_path.quadTo(mid_x + perp_x * wave_amount,
+                      mid_y + perp_y * wave_amount, x2, y2);
+  } else {
+    river_path.lineTo(x2, y2);
+  }
+
+  QPen outline_pen(Palette::WATER_DARK);
+  outline_pen.setWidthF(width * 1.4F);
+  outline_pen.setCapStyle(Qt::RoundCap);
+  outline_pen.setJoinStyle(Qt::RoundJoin);
+  painter.setPen(outline_pen);
+  painter.setBrush(Qt::NoBrush);
+  painter.drawPath(river_path);
+
+  QPen main_pen(Palette::WATER_MAIN);
+  main_pen.setWidthF(width);
+  main_pen.setCapStyle(Qt::RoundCap);
+  main_pen.setJoinStyle(Qt::RoundJoin);
+  painter.setPen(main_pen);
+  painter.drawPath(river_path);
+
+  if (width > 2.0F) {
+    QPen highlight_pen(Palette::WATER_LIGHT);
+    highlight_pen.setWidthF(width * 0.4F);
+    highlight_pen.setCapStyle(Qt::RoundCap);
+    painter.setPen(highlight_pen);
+    painter.drawPath(river_path);
+  }
+}
+
+void MinimapGenerator::render_roads(QImage &image,
+                                    const MapDefinition &map_def) {
+  if (map_def.roads.empty()) {
+    return;
+  }
+
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  for (const auto &road : map_def.roads) {
+    const auto [x1, y1] =
+        world_to_pixel(road.start.x(), road.start.z(), map_def.grid);
+    const auto [x2, y2] =
+        world_to_pixel(road.end.x(), road.end.z(), map_def.grid);
+
+    float pixel_width = world_to_pixel_size(road.width, map_def.grid);
+    pixel_width = std::max(pixel_width, 1.5F);
+
+    draw_road_segment(painter, x1, y1, x2, y2, pixel_width);
+  }
+}
+
+void MinimapGenerator::draw_road_segment(QPainter &painter, float x1, float y1,
+                                         float x2, float y2, float width) {
+
+  QPen road_pen(Palette::ROAD_MAIN);
+  road_pen.setWidthF(width);
+  road_pen.setCapStyle(Qt::RoundCap);
+
+  QVector<qreal> dash_pattern;
+  dash_pattern << 3.0 << 2.0;
+  road_pen.setDashPattern(dash_pattern);
+
+  painter.setPen(road_pen);
+  painter.drawLine(QPointF(x1, y1), QPointF(x2, y2));
+
+  const float dx = x2 - x1;
+  const float dy = y2 - y1;
+  const float length = std::sqrt(dx * dx + dy * dy);
+
+  if (length > 8.0F) {
+    painter.setPen(Qt::NoPen);
+    painter.setBrush(Palette::ROAD_HIGHLIGHT);
+
+    const int num_dots = static_cast<int>(length / 6.0F);
+    for (int i = 1; i < num_dots; ++i) {
+      const float t = static_cast<float>(i) / static_cast<float>(num_dots);
+      const float dot_x = x1 + dx * t;
+      const float dot_y = y1 + dy * t;
+      painter.drawEllipse(QPointF(dot_x, dot_y), width * 0.25F, width * 0.25F);
+    }
+  }
+}
+
+void MinimapGenerator::render_bridges(QImage &image,
+                                      const MapDefinition &map_def) {
+  if (map_def.bridges.empty()) {
+    return;
+  }
+
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  for (const auto &bridge : map_def.bridges) {
+    const auto [x1, y1] =
+        world_to_pixel(bridge.start.x(), bridge.start.z(), map_def.grid);
+    const auto [x2, y2] =
+        world_to_pixel(bridge.end.x(), bridge.end.z(), map_def.grid);
+
+    float pixel_width = world_to_pixel_size(bridge.width, map_def.grid);
+    pixel_width = std::max(pixel_width, 2.0F);
+
+    painter.setPen(QPen(Palette::INK_DARK, 1.0));
+    painter.setBrush(Palette::STRUCTURE_STONE);
+
+    const float dx = x2 - x1;
+    const float dy = y2 - y1;
+    const float length = std::sqrt(dx * dx + dy * dy);
+
+    if (length > 0.01F) {
+
+      const float perp_x = -dy / length * pixel_width * 0.5F;
+      const float perp_y = dx / length * pixel_width * 0.5F;
+
+      QPolygonF bridge_poly;
+      bridge_poly << QPointF(x1 - perp_x, y1 - perp_y)
+                  << QPointF(x1 + perp_x, y1 + perp_y)
+                  << QPointF(x2 + perp_x, y2 + perp_y)
+                  << QPointF(x2 - perp_x, y2 - perp_y);
+      painter.drawPolygon(bridge_poly);
+
+      painter.setPen(QPen(Palette::INK_LIGHT, 0.5));
+      const int num_planks = static_cast<int>(length / 3.0F);
+      for (int i = 1; i < num_planks; ++i) {
+        const float t = static_cast<float>(i) / static_cast<float>(num_planks);
+        const float plank_x = x1 + dx * t;
+        const float plank_y = y1 + dy * t;
+        painter.drawLine(QPointF(plank_x - perp_x, plank_y - perp_y),
+                         QPointF(plank_x + perp_x, plank_y + perp_y));
+      }
+    }
+  }
+}
+
+void MinimapGenerator::render_structures(QImage &image,
+                                         const MapDefinition &map_def) {
+  if (map_def.spawns.empty()) {
+    return;
+  }
+
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  for (const auto &spawn : map_def.spawns) {
+    if (!Game::Units::is_building_spawn(spawn.type)) {
+      continue;
+    }
+
+    const auto [px, py] = world_to_pixel(spawn.x, spawn.z, map_def.grid);
+
+    QColor fill_color = Palette::STRUCTURE_STONE;
+    QColor border_color = Palette::STRUCTURE_SHADOW;
+
+    if (spawn.player_id == 1) {
+      fill_color = Palette::TEAM_BLUE;
+      border_color = Palette::TEAM_BLUE_DARK;
+    } else if (spawn.player_id == 2) {
+      fill_color = Palette::TEAM_RED;
+      border_color = Palette::TEAM_RED_DARK;
+    } else if (spawn.player_id > 0) {
+
+      const int hue = (spawn.player_id * 47 + 30) % 360;
+      fill_color.setHsv(hue, 140, 180);
+      border_color.setHsv(hue, 180, 100);
+    }
+
+    draw_fortress_icon(painter, px, py, fill_color, border_color);
+  }
+}
+
+void MinimapGenerator::draw_fortress_icon(QPainter &painter, float cx, float cy,
+                                          const QColor &fill,
+                                          const QColor &border) {
+
+  constexpr float SIZE = 10.0F;
+  constexpr float HALF = SIZE * 0.5F;
+
+  painter.setBrush(fill);
+  painter.setPen(QPen(border, 1.5));
+  painter.drawRect(
+      QRectF(cx - HALF * 0.7F, cy - HALF * 0.7F, SIZE * 0.7F, SIZE * 0.7F));
+
+  constexpr float TOWER_SIZE = SIZE * 0.35F;
+  constexpr float TOWER_OFFSET = HALF * 0.85F;
+
+  painter.setBrush(fill);
+  painter.setPen(QPen(border, 1.0));
+
+  for (int i = 0; i < 4; ++i) {
+    const float tx = cx + ((i & 1) != 0 ? TOWER_OFFSET : -TOWER_OFFSET);
+    const float ty = cy + ((i & 2) != 0 ? TOWER_OFFSET : -TOWER_OFFSET);
+    painter.drawRect(QRectF(tx - TOWER_SIZE * 0.5F, ty - TOWER_SIZE * 0.5F,
+                            TOWER_SIZE, TOWER_SIZE));
+  }
+
+  painter.setBrush(border);
+  painter.setPen(Qt::NoPen);
+  painter.drawRect(
+      QRectF(cx - SIZE * 0.12F, cy + SIZE * 0.15F, SIZE * 0.24F, SIZE * 0.25F));
+
+  constexpr float MERLON_W = SIZE * 0.15F;
+  constexpr float MERLON_H = SIZE * 0.12F;
+  painter.setBrush(fill);
+  painter.setPen(QPen(border, 0.8));
+
+  for (int i = 0; i < 3; ++i) {
+    const float mx = cx - SIZE * 0.25F + static_cast<float>(i) * SIZE * 0.25F;
+    const float my = cy - HALF * 0.7F - MERLON_H;
+    painter.drawRect(QRectF(mx, my, MERLON_W, MERLON_H));
+  }
+}
+
+void MinimapGenerator::apply_historical_styling(QImage &image) {
+  QPainter painter(&image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  draw_map_border(painter, image.width(), image.height());
+
+  apply_vignette(painter, image.width(), image.height());
+
+  draw_compass_rose(painter, image.width(), image.height());
+}
+
+void MinimapGenerator::draw_map_border(QPainter &painter, int width,
+                                       int height) {
+
+  constexpr float OUTER_MARGIN = 2.0F;
+  constexpr float INNER_MARGIN = 5.0F;
+
+  painter.setPen(QPen(Palette::INK_MEDIUM, 1.5));
+  painter.setBrush(Qt::NoBrush);
+  painter.drawRect(QRectF(OUTER_MARGIN, OUTER_MARGIN,
+                          static_cast<float>(width) - OUTER_MARGIN * 2,
+                          static_cast<float>(height) - OUTER_MARGIN * 2));
+
+  painter.setPen(QPen(Palette::INK_LIGHT, 0.8));
+  painter.drawRect(QRectF(INNER_MARGIN, INNER_MARGIN,
+                          static_cast<float>(width) - INNER_MARGIN * 2,
+                          static_cast<float>(height) - INNER_MARGIN * 2));
+}
+
+void MinimapGenerator::apply_vignette(QPainter &painter, int width,
+                                      int height) {
+
+  const float radius = static_cast<float>(std::max(width, height)) * 0.75F;
+  QRadialGradient vignette(static_cast<float>(width) * 0.5F,
+                           static_cast<float>(height) * 0.5F, radius);
+  vignette.setColorAt(0.0, Qt::transparent);
+  vignette.setColorAt(0.7, Qt::transparent);
+  vignette.setColorAt(1.0, QColor(60, 45, 30, 35));
+
+  painter.setCompositionMode(QPainter::CompositionMode_Multiply);
+  painter.fillRect(0, 0, width, height, vignette);
+  painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+}
+
+void MinimapGenerator::draw_compass_rose(QPainter &painter, int width,
+                                         int height) {
+
+  const float cx = static_cast<float>(width) - 18.0F;
+  const float cy = static_cast<float>(height) - 18.0F;
+  constexpr float SIZE = 10.0F;
+
+  painter.setPen(QPen(Palette::INK_MEDIUM, 1.2));
+  painter.setBrush(Qt::NoBrush);
+
+  QPainterPath north_arrow;
+  north_arrow.moveTo(cx, cy - SIZE);
+  north_arrow.lineTo(cx - SIZE * 0.3F, cy);
+  north_arrow.lineTo(cx + SIZE * 0.3F, cy);
+  north_arrow.closeSubpath();
+
+  painter.setBrush(Palette::INK_DARK);
+  painter.drawPath(north_arrow);
+
+  QPainterPath south_arrow;
+  south_arrow.moveTo(cx, cy + SIZE);
+  south_arrow.lineTo(cx - SIZE * 0.3F, cy);
+  south_arrow.lineTo(cx + SIZE * 0.3F, cy);
+  south_arrow.closeSubpath();
+
+  painter.setBrush(Palette::PARCHMENT_LIGHT);
+  painter.drawPath(south_arrow);
+
+  painter.drawLine(QPointF(cx - SIZE * 0.7F, cy),
+                   QPointF(cx + SIZE * 0.7F, cy));
+
+  painter.setBrush(Palette::INK_MEDIUM);
+  painter.drawEllipse(QPointF(cx, cy), 2.0, 2.0);
+
+  painter.setPen(QPen(Palette::INK_DARK, 1.2F));
+  const float n_left = cx - 3.5F;
+  const float n_right = cx + 3.5F;
+  const float n_top = cy - SIZE - 7.0F;
+  const float n_bottom = cy - SIZE - 1.5F;
+
+  QPainterPath n_path;
+  n_path.moveTo(n_left, n_bottom);
+  n_path.lineTo(n_left, n_top);
+  n_path.lineTo(n_right, n_bottom);
+  n_path.lineTo(n_right, n_top);
+  painter.drawPath(n_path);
+}
+
+auto MinimapGenerator::biome_to_base_color(const BiomeSettings &biome)
+    -> QColor {
+
+  const auto &grass = biome.grass_primary;
+  QColor base = QColor::fromRgbF(static_cast<double>(grass.x()),
+                                 static_cast<double>(grass.y()),
+                                 static_cast<double>(grass.z()));
+
+  int h, s, v;
+  base.getHsv(&h, &s, &v);
+  base.setHsv(h, static_cast<int>(s * 0.4), static_cast<int>(v * 0.85));
+
+  return base;
+}
+
+auto MinimapGenerator::terrain_feature_color(TerrainType type) -> QColor {
+  switch (type) {
+  case TerrainType::Mountain:
+    return Palette::MOUNTAIN_SHADOW;
+  case TerrainType::Hill:
+    return Palette::HILL_BASE;
+  case TerrainType::River:
+    return Palette::WATER_MAIN;
+  case TerrainType::Flat:
+  default:
+    return Palette::PARCHMENT_DARK;
+  }
+}
+
+} // namespace Game::Map::Minimap

+ 66 - 0
game/map/minimap/minimap_generator.h

@@ -0,0 +1,66 @@
+#pragma once
+
+#include "../map_definition.h"
+#include <QImage>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+class QPainter;
+
+namespace Game::Map::Minimap {
+
+class MinimapGenerator {
+public:
+  struct Config {
+    float pixels_per_tile = 2.0F;
+
+    Config() = default;
+  };
+
+  MinimapGenerator();
+  explicit MinimapGenerator(const Config &config);
+
+  [[nodiscard]] auto generate(const MapDefinition &map_def) -> QImage;
+
+private:
+  Config m_config;
+
+  void render_parchment_background(QImage &image);
+  void render_terrain_base(QImage &image, const MapDefinition &map_def);
+  void render_terrain_features(QImage &image, const MapDefinition &map_def);
+  void render_rivers(QImage &image, const MapDefinition &map_def);
+  void render_roads(QImage &image, const MapDefinition &map_def);
+  void render_bridges(QImage &image, const MapDefinition &map_def);
+  void render_structures(QImage &image, const MapDefinition &map_def);
+  void apply_historical_styling(QImage &image);
+
+  static void draw_mountain_symbol(QPainter &painter, float cx, float cy,
+                                   float width, float height);
+  static void draw_hill_symbol(QPainter &painter, float cx, float cy,
+                               float width, float height);
+  static void draw_river_segment(QPainter &painter, float x1, float y1,
+                                 float x2, float y2, float width);
+  static void draw_road_segment(QPainter &painter, float x1, float y1, float x2,
+                                float y2, float width);
+  static void draw_fortress_icon(QPainter &painter, float cx, float cy,
+                                 const QColor &fill, const QColor &border);
+
+  static void draw_map_border(QPainter &painter, int width, int height);
+  static void apply_vignette(QPainter &painter, int width, int height);
+  static void draw_compass_rose(QPainter &painter, int width, int height);
+
+  [[nodiscard]] auto
+  world_to_pixel(float world_x, float world_z,
+                 const GridDefinition &grid) const -> std::pair<float, float>;
+
+  [[nodiscard]] auto
+  world_to_pixel_size(float world_size,
+                      const GridDefinition &grid) const -> float;
+
+  [[nodiscard]] static auto
+  biome_to_base_color(const BiomeSettings &biome) -> QColor;
+  [[nodiscard]] static auto terrain_feature_color(TerrainType type) -> QColor;
+};
+
+} // namespace Game::Map::Minimap

+ 179 - 0
game/map/minimap/minimap_manager.cpp

@@ -0,0 +1,179 @@
+#include "minimap_manager.h"
+#include <QPainter>
+
+namespace Game::Map::Minimap {
+
+MinimapManager::MinimapManager(const MapDefinition &map_def,
+                               const MinimapManagerConfig &config)
+    : m_config(config), m_grid(map_def.grid) {
+
+  m_generator = std::make_unique<MinimapGenerator>(config.generator_config);
+  m_base_image = m_generator->generate(map_def);
+
+  if (config.fog_enabled) {
+    m_fog_mask = std::make_unique<FogOfWarMask>(
+        map_def.grid.width, map_def.grid.height, map_def.grid.tile_size,
+        config.fog_config);
+  }
+
+  m_composite_dirty = true;
+}
+
+MinimapManager::~MinimapManager() = default;
+
+MinimapManager::MinimapManager(MinimapManager &&) noexcept = default;
+auto MinimapManager::operator=(MinimapManager &&) noexcept -> MinimapManager & =
+                                                                  default;
+
+void MinimapManager::tick(const std::vector<VisionSource> &vision_sources,
+                          int player_id) {
+  if (m_fog_mask && m_config.fog_enabled) {
+    if (m_fog_mask->tick(vision_sources, player_id)) {
+      m_composite_dirty = true;
+    }
+  }
+}
+
+void MinimapManager::force_fog_update(
+    const std::vector<VisionSource> &vision_sources, int player_id) {
+  if (m_fog_mask && m_config.fog_enabled) {
+    m_fog_mask->update_vision(vision_sources, player_id);
+    m_composite_dirty = true;
+  }
+}
+
+auto MinimapManager::get_base_image() const -> const QImage & {
+  return m_base_image;
+}
+
+auto MinimapManager::get_fog_mask() const -> QImage {
+  if (!m_fog_mask || !m_config.fog_enabled) {
+
+    QImage empty(m_base_image.size(), QImage::Format_RGBA8888);
+    empty.fill(Qt::transparent);
+    return empty;
+  }
+
+  return m_fog_mask->generate_mask(m_base_image.width(), m_base_image.height());
+}
+
+auto MinimapManager::get_composite_image() const -> QImage {
+  if (m_composite_dirty) {
+    regenerate_composite();
+  }
+  return m_composite_image;
+}
+
+auto MinimapManager::get_composite_image(int width,
+                                         int height) const -> QImage {
+  QImage composite = get_composite_image();
+
+  if (composite.width() != width || composite.height() != height) {
+    return composite.scaled(width, height, Qt::KeepAspectRatio,
+                            Qt::SmoothTransformation);
+  }
+
+  return composite;
+}
+
+void MinimapManager::regenerate_composite() const {
+
+  m_composite_image = m_base_image.copy();
+
+  if (m_fog_mask && m_config.fog_enabled) {
+    QImage fog = m_fog_mask->generate_mask(m_composite_image.width(),
+                                           m_composite_image.height());
+
+    QPainter painter(&m_composite_image);
+
+    if (m_config.fog_multiply_blend) {
+
+      painter.setCompositionMode(QPainter::CompositionMode_Multiply);
+    } else {
+
+      painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+    }
+
+    painter.drawImage(0, 0, fog);
+    painter.end();
+  }
+
+  m_composite_dirty = false;
+
+  if (m_fog_mask) {
+    m_fog_mask->clear_dirty();
+  }
+}
+
+auto MinimapManager::is_position_visible(float world_x,
+                                         float world_z) const -> bool {
+  if (!m_fog_mask || !m_config.fog_enabled) {
+    return true;
+  }
+  return m_fog_mask->is_visible(world_x, world_z);
+}
+
+auto MinimapManager::is_position_revealed(float world_x,
+                                          float world_z) const -> bool {
+  if (!m_fog_mask || !m_config.fog_enabled) {
+    return true;
+  }
+  return m_fog_mask->is_revealed(world_x, world_z);
+}
+
+void MinimapManager::reset_fog() {
+  if (m_fog_mask) {
+    m_fog_mask->reset();
+    m_composite_dirty = true;
+  }
+}
+
+void MinimapManager::reveal_all() {
+  if (m_fog_mask) {
+    m_fog_mask->reveal_all();
+    m_composite_dirty = true;
+  }
+}
+
+void MinimapManager::set_fog_enabled(bool enabled) {
+  if (m_config.fog_enabled != enabled) {
+    m_config.fog_enabled = enabled;
+    m_composite_dirty = true;
+
+    if (enabled && !m_fog_mask) {
+      m_fog_mask = std::make_unique<FogOfWarMask>(
+          m_grid.width, m_grid.height, m_grid.tile_size, m_config.fog_config);
+    }
+  }
+}
+
+auto MinimapManager::is_fog_enabled() const -> bool {
+  return m_config.fog_enabled;
+}
+
+auto MinimapManager::memory_usage() const -> size_t {
+  size_t total = sizeof(*this);
+  total += static_cast<size_t>(m_base_image.sizeInBytes());
+  total += static_cast<size_t>(m_composite_image.sizeInBytes());
+
+  if (m_fog_mask) {
+    total += m_fog_mask->memory_usage();
+  }
+
+  return total;
+}
+
+void MinimapManager::regenerate_base(const MapDefinition &map_def) {
+  m_grid = map_def.grid;
+  m_base_image = m_generator->generate(map_def);
+
+  if (m_fog_mask) {
+    m_fog_mask = std::make_unique<FogOfWarMask>(
+        map_def.grid.width, map_def.grid.height, map_def.grid.tile_size,
+        m_config.fog_config);
+  }
+
+  m_composite_dirty = true;
+}
+
+} // namespace Game::Map::Minimap

+ 82 - 0
game/map/minimap/minimap_manager.h

@@ -0,0 +1,82 @@
+#pragma once
+
+#include "fog_of_war_mask.h"
+#include "minimap_generator.h"
+#include <QImage>
+#include <memory>
+#include <vector>
+
+namespace Game::Map::Minimap {
+
+struct MinimapManagerConfig {
+  MinimapGenerator::Config generator_config;
+  FogOfWarConfig fog_config;
+
+  bool fog_enabled = true;
+
+  bool fog_multiply_blend = true;
+
+  MinimapManagerConfig() = default;
+};
+
+class MinimapManager {
+public:
+  MinimapManager(const MapDefinition &map_def,
+                 const MinimapManagerConfig &config = MinimapManagerConfig());
+
+  ~MinimapManager();
+
+  MinimapManager(const MinimapManager &) = delete;
+  auto operator=(const MinimapManager &) -> MinimapManager & = delete;
+  MinimapManager(MinimapManager &&) noexcept;
+  auto operator=(MinimapManager &&) noexcept -> MinimapManager &;
+
+  void tick(const std::vector<VisionSource> &vision_sources, int player_id);
+
+  void force_fog_update(const std::vector<VisionSource> &vision_sources,
+                        int player_id);
+
+  [[nodiscard]] auto get_base_image() const -> const QImage &;
+
+  [[nodiscard]] auto get_fog_mask() const -> QImage;
+
+  [[nodiscard]] auto get_composite_image() const -> QImage;
+
+  [[nodiscard]] auto get_composite_image(int width, int height) const -> QImage;
+
+  [[nodiscard]] auto is_position_visible(float world_x,
+                                         float world_z) const -> bool;
+
+  [[nodiscard]] auto is_position_revealed(float world_x,
+                                          float world_z) const -> bool;
+
+  void reset_fog();
+
+  void reveal_all();
+
+  void set_fog_enabled(bool enabled);
+
+  [[nodiscard]] auto is_fog_enabled() const -> bool;
+
+  [[nodiscard]] auto memory_usage() const -> size_t;
+
+  void regenerate_base(const MapDefinition &map_def);
+
+  [[nodiscard]] auto grid() const -> const GridDefinition & { return m_grid; }
+
+private:
+  void regenerate_composite() const;
+
+  MinimapManagerConfig m_config;
+  GridDefinition m_grid;
+
+  std::unique_ptr<MinimapGenerator> m_generator;
+  QImage m_base_image;
+
+  std::unique_ptr<FogOfWarMask> m_fog_mask;
+
+  mutable QImage m_composite_image;
+  mutable bool m_composite_dirty = true;
+};
+
+} // namespace Game::Map::Minimap

+ 39 - 0
game/map/minimap/minimap_texture_manager.cpp

@@ -0,0 +1,39 @@
+#include "minimap_texture_manager.h"
+#include "minimap_generator.h"
+#include <QDebug>
+
+namespace Game::Map::Minimap {
+
+MinimapTextureManager::MinimapTextureManager()
+    : m_generator(std::make_unique<MinimapGenerator>()),
+      m_texture(std::make_unique<Render::GL::Texture>()) {}
+
+MinimapTextureManager::~MinimapTextureManager() = default;
+
+auto MinimapTextureManager::generate_for_map(const MapDefinition &map_def)
+    -> bool {
+
+  m_image = m_generator->generate(map_def);
+
+  if (m_image.isNull()) {
+    qWarning() << "MinimapTextureManager: Failed to generate minimap image";
+    return false;
+  }
+
+  qDebug() << "MinimapTextureManager: Generated minimap of size"
+           << m_image.width() << "x" << m_image.height();
+
+  return true;
+}
+
+auto MinimapTextureManager::get_texture() const -> Render::GL::Texture * {
+  return m_texture.get();
+}
+
+auto MinimapTextureManager::get_image() const -> const QImage & {
+  return m_image;
+}
+
+void MinimapTextureManager::clear() { m_image = QImage(); }
+
+} // namespace Game::Map::Minimap

+ 31 - 0
game/map/minimap/minimap_texture_manager.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "../map_definition.h"
+#include "render/gl/texture.h"
+#include <QImage>
+#include <memory>
+
+namespace Game::Map::Minimap {
+
+class MinimapGenerator;
+
+class MinimapTextureManager {
+public:
+  MinimapTextureManager();
+  ~MinimapTextureManager();
+
+  auto generate_for_map(const MapDefinition &map_def) -> bool;
+
+  [[nodiscard]] auto get_texture() const -> Render::GL::Texture *;
+
+  [[nodiscard]] auto get_image() const -> const QImage &;
+
+  void clear();
+
+private:
+  std::unique_ptr<MinimapGenerator> m_generator;
+  std::unique_ptr<Render::GL::Texture> m_texture;
+  QImage m_image;
+};
+
+} // namespace Game::Map::Minimap

+ 145 - 0
game/map/minimap/unit_layer.cpp

@@ -0,0 +1,145 @@
+#include "unit_layer.h"
+
+#include <QPainter>
+#include <algorithm>
+#include <cmath>
+
+namespace Game::Map::Minimap {
+
+namespace {
+constexpr float k_camera_yaw_cos = -0.70710678118F;
+constexpr float k_camera_yaw_sin = -0.70710678118F;
+} // namespace
+
+void UnitLayer::init(int width, int height, float world_width,
+                     float world_height) {
+  m_width = width;
+  m_height = height;
+  m_world_width = world_width;
+  m_world_height = world_height;
+
+  m_scale_x = static_cast<float>(width - 1) / world_width;
+  m_scale_y = static_cast<float>(height - 1) / world_height;
+  m_offset_x = world_width * 0.5F;
+  m_offset_y = world_height * 0.5F;
+
+  m_image = QImage(width, height, QImage::Format_ARGB32);
+  m_image.fill(Qt::transparent);
+}
+
+auto UnitLayer::world_to_pixel(float world_x,
+                               float world_z) const -> std::pair<float, float> {
+
+  const float rotated_x =
+      world_x * k_camera_yaw_cos - world_z * k_camera_yaw_sin;
+  const float rotated_z =
+      world_x * k_camera_yaw_sin + world_z * k_camera_yaw_cos;
+
+  const float px = (rotated_x + m_offset_x) * m_scale_x;
+  const float py = (rotated_z + m_offset_y) * m_scale_y;
+
+  return {px, py};
+}
+
+void UnitLayer::update(const std::vector<UnitMarker> &markers) {
+  if (m_image.isNull()) {
+    return;
+  }
+
+  m_image.fill(Qt::transparent);
+
+  if (markers.empty()) {
+    return;
+  }
+
+  QPainter painter(&m_image);
+  painter.setRenderHint(QPainter::Antialiasing, true);
+
+  std::vector<const UnitMarker *> buildings;
+  std::vector<const UnitMarker *> units;
+  std::vector<const UnitMarker *> selected;
+
+  for (const auto &marker : markers) {
+    if (marker.is_selected) {
+      selected.push_back(&marker);
+    } else if (marker.is_building) {
+      buildings.push_back(&marker);
+    } else {
+      units.push_back(&marker);
+    }
+  }
+
+  for (const auto *marker : buildings) {
+    const auto [px, py] = world_to_pixel(marker->world_x, marker->world_z);
+    const auto colors = TeamColors::get_color(marker->owner_id);
+    draw_building_marker(painter, px, py, colors, false);
+  }
+
+  for (const auto *marker : units) {
+    const auto [px, py] = world_to_pixel(marker->world_x, marker->world_z);
+    const auto colors = TeamColors::get_color(marker->owner_id);
+    draw_unit_marker(painter, px, py, colors, false);
+  }
+
+  for (const auto *marker : selected) {
+    const auto [px, py] = world_to_pixel(marker->world_x, marker->world_z);
+    const auto colors = TeamColors::get_color(marker->owner_id);
+    if (marker->is_building) {
+      draw_building_marker(painter, px, py, colors, true);
+    } else {
+      draw_unit_marker(painter, px, py, colors, true);
+    }
+  }
+}
+
+void UnitLayer::draw_unit_marker(QPainter &painter, float px, float py,
+                                 const TeamColors::ColorSet &colors,
+                                 bool is_selected) {
+  const QPointF center(static_cast<qreal>(px), static_cast<qreal>(py));
+
+  if (is_selected) {
+    painter.setBrush(Qt::NoBrush);
+    QPen glow_pen(QColor(TeamColors::SELECT_R, TeamColors::SELECT_G,
+                         TeamColors::SELECT_B, 200));
+    glow_pen.setWidthF(2.0);
+    painter.setPen(glow_pen);
+    painter.drawEllipse(center, m_unit_radius + 2.0, m_unit_radius + 2.0);
+  }
+
+  QColor fill_color(colors.r, colors.g, colors.b);
+  QColor border_color(colors.border_r, colors.border_g, colors.border_b);
+
+  painter.setBrush(fill_color);
+  painter.setPen(QPen(border_color, 1.2));
+  painter.drawEllipse(center, m_unit_radius, m_unit_radius);
+}
+
+void UnitLayer::draw_building_marker(QPainter &painter, float px, float py,
+                                     const TeamColors::ColorSet &colors,
+                                     bool is_selected) {
+  const qreal half = static_cast<qreal>(m_building_half_size);
+  const QRectF rect(px - half, py - half, half * 2.0, half * 2.0);
+
+  if (is_selected) {
+    painter.setBrush(Qt::NoBrush);
+    QPen glow_pen(QColor(TeamColors::SELECT_R, TeamColors::SELECT_G,
+                         TeamColors::SELECT_B, 200));
+    glow_pen.setWidthF(2.5);
+    painter.setPen(glow_pen);
+    painter.drawRect(rect.adjusted(-2.5, -2.5, 2.5, 2.5));
+  }
+
+  QColor fill_color(colors.r, colors.g, colors.b);
+  QColor border_color(colors.border_r, colors.border_g, colors.border_b);
+
+  painter.setBrush(fill_color);
+  painter.setPen(QPen(border_color, 1.5));
+  painter.drawRect(rect);
+
+  const qreal inner = half * 0.4;
+  painter.setBrush(border_color);
+  painter.setPen(Qt::NoPen);
+  painter.drawRect(QRectF(px - inner, py - inner, inner * 2.0, inner * 2.0));
+}
+
+} // namespace Game::Map::Minimap

+ 106 - 0
game/map/minimap/unit_layer.h

@@ -0,0 +1,106 @@
+#pragma once
+
+#include <QImage>
+#include <cstdint>
+#include <vector>
+
+class QPainter;
+
+namespace Game::Map::Minimap {
+
+struct UnitMarker {
+  float world_x = 0.0F;
+  float world_z = 0.0F;
+  int owner_id = 0;
+  bool is_selected = false;
+  bool is_building = false;
+};
+
+struct TeamColors {
+  struct ColorSet {
+    std::uint8_t r, g, b;
+    std::uint8_t border_r, border_g, border_b;
+  };
+
+  static constexpr ColorSet PLAYER_1 = {70, 100, 160, 35, 50, 80};
+
+  static constexpr ColorSet PLAYER_2 = {180, 60, 50, 90, 30, 25};
+
+  static constexpr ColorSet PLAYER_3 = {60, 130, 70, 30, 65, 35};
+
+  static constexpr ColorSet PLAYER_4 = {190, 160, 60, 95, 80, 30};
+
+  static constexpr ColorSet PLAYER_5 = {120, 60, 140, 60, 30, 70};
+
+  static constexpr ColorSet PLAYER_6 = {60, 140, 140, 30, 70, 70};
+
+  static constexpr ColorSet NEUTRAL = {100, 95, 85, 50, 48, 43};
+
+  static constexpr std::uint8_t SELECT_R = 255;
+  static constexpr std::uint8_t SELECT_G = 215;
+  static constexpr std::uint8_t SELECT_B = 0;
+
+  static constexpr auto get_color(int owner_id) -> ColorSet {
+    switch (owner_id) {
+    case 1:
+      return PLAYER_1;
+    case 2:
+      return PLAYER_2;
+    case 3:
+      return PLAYER_3;
+    case 4:
+      return PLAYER_4;
+    case 5:
+      return PLAYER_5;
+    case 6:
+      return PLAYER_6;
+    default:
+      return NEUTRAL;
+    }
+  }
+};
+
+class UnitLayer {
+public:
+  UnitLayer() = default;
+
+  void init(int width, int height, float world_width, float world_height);
+
+  [[nodiscard]] auto is_initialized() const -> bool {
+    return !m_image.isNull();
+  }
+
+  void update(const std::vector<UnitMarker> &markers);
+
+  [[nodiscard]] auto get_image() const -> const QImage & { return m_image; }
+
+  void set_unit_radius(float radius) { m_unit_radius = radius; }
+
+  void set_building_size(float size) { m_building_half_size = size; }
+
+private:
+  [[nodiscard]] auto
+  world_to_pixel(float world_x, float world_z) const -> std::pair<float, float>;
+
+  void draw_unit_marker(QPainter &painter, float px, float py,
+                        const TeamColors::ColorSet &colors, bool is_selected);
+
+  void draw_building_marker(QPainter &painter, float px, float py,
+                            const TeamColors::ColorSet &colors,
+                            bool is_selected);
+
+  QImage m_image;
+  int m_width = 0;
+  int m_height = 0;
+  float m_world_width = 0.0F;
+  float m_world_height = 0.0F;
+  float m_unit_radius = 3.0F;
+  float m_building_half_size = 5.0F;
+
+  float m_scale_x = 1.0F;
+  float m_scale_y = 1.0F;
+  float m_offset_x = 0.0F;
+  float m_offset_y = 0.0F;
+};
+
+} // namespace Game::Map::Minimap

+ 70 - 69
game/map/skirmish_loader.cpp

@@ -63,7 +63,7 @@ SkirmishLoader::SkirmishLoader(Engine::Core::World &world,
                                Render::GL::Camera &camera)
     : m_world(world), m_renderer(renderer), m_camera(camera) {}
 
-void SkirmishLoader::resetGameState() {
+void SkirmishLoader::reset_game_state() {
   if (auto *selection_system =
           m_world.get_system<Game::Systems::SelectionSystem>()) {
     selection_system->clear_selection();
@@ -95,20 +95,20 @@ void SkirmishLoader::resetGameState() {
   auto &troop_registry = Game::Systems::TroopCountRegistry::instance();
   troop_registry.clear();
 
-  Game::Systems::NationRegistry::instance().clearPlayerAssignments();
+  Game::Systems::NationRegistry::instance().clear_player_assignments();
 
   if (m_fog != nullptr) {
-    m_fog->updateMask(0, 0, 1.0F, {});
+    m_fog->update_mask(0, 0, 1.0F, {});
   }
 }
 
 auto SkirmishLoader::start(const QString &map_path,
                            const QVariantList &playerConfigs,
-                           int selectedPlayerId,
-                           int &outSelectedPlayerId) -> SkirmishLoadResult {
+                           int selected_player_id,
+                           int &out_selected_player_id) -> SkirmishLoadResult {
   SkirmishLoadResult result;
 
-  resetGameState();
+  reset_game_state();
 
   QSet<int> map_player_ids;
   QFile map_file(map_path);
@@ -140,17 +140,17 @@ auto SkirmishLoader::start(const QString &map_path,
 
   auto &owner_registry = Game::Systems::OwnerRegistry::instance();
 
-  int player_owner_id = selectedPlayerId;
+  int player_owner_id = selected_player_id;
 
   if (!map_player_ids.contains(player_owner_id)) {
     if (!map_player_ids.isEmpty()) {
       QList<int> sorted_ids = map_player_ids.values();
       std::sort(sorted_ids.begin(), sorted_ids.end());
       player_owner_id = sorted_ids.first();
-      qWarning() << "Selected player ID" << selectedPlayerId
+      qWarning() << "Selected player ID" << selected_player_id
                  << "not found in map spawns. Using" << player_owner_id
                  << "instead.";
-      outSelectedPlayerId = player_owner_id;
+      out_selected_player_id = player_owner_id;
     } else {
       qWarning() << "No valid player spawns found in map. Using default "
                     "player ID"
@@ -158,7 +158,7 @@ auto SkirmishLoader::start(const QString &map_path,
     }
   }
 
-  owner_registry.setLocalPlayerId(player_owner_id);
+  owner_registry.set_local_player_id(player_owner_id);
 
   std::unordered_map<int, int> team_overrides;
   std::unordered_map<int, Game::Systems::NationID> nation_overrides;
@@ -229,20 +229,20 @@ auto SkirmishLoader::start(const QString &map_path,
     int player_id = *it;
     auto nat_it = nation_overrides.find(player_id);
     if (nat_it != nation_overrides.end()) {
-      nation_registry.setPlayerNation(player_id, nat_it->second);
+      nation_registry.set_player_nation(player_id, nat_it->second);
     } else {
-      nation_registry.setPlayerNation(player_id,
-                                      nation_registry.default_nation_id());
+      nation_registry.set_player_nation(player_id,
+                                        nation_registry.default_nation_id());
     }
   }
 
   if (map_player_ids.isEmpty()) {
     auto nat_it = nation_overrides.find(player_owner_id);
     if (nat_it != nation_overrides.end()) {
-      nation_registry.setPlayerNation(player_owner_id, nat_it->second);
+      nation_registry.set_player_nation(player_owner_id, nat_it->second);
     } else {
-      nation_registry.setPlayerNation(player_owner_id,
-                                      nation_registry.default_nation_id());
+      nation_registry.set_player_nation(player_owner_id,
+                                        nation_registry.default_nation_id());
     }
   }
 
@@ -272,8 +272,8 @@ auto SkirmishLoader::start(const QString &map_path,
         const int red = color_hex.mid(1, 2).toInt(&conversion_ok, hex_base);
         const int green = color_hex.mid(3, 2).toInt(&conversion_ok, hex_base);
         const int blue = color_hex.mid(5, 2).toInt(&conversion_ok, hex_base);
-        owner_registry.setOwnerColor(player_id, red / color_scale,
-                                     green / color_scale, blue / color_scale);
+        owner_registry.set_owner_color(player_id, red / color_scale,
+                                       green / color_scale, blue / color_scale);
       }
     }
 
@@ -305,98 +305,99 @@ auto SkirmishLoader::start(const QString &map_path,
       m_ground->configure(level_result.tile_size, level_result.grid_width,
                           level_result.grid_height);
     } else {
-      m_ground->configureExtent(50.0F);
+      m_ground->configure_extent(50.0F);
     }
-    if (terrain_service.isInitialized()) {
-      m_ground->setBiome(terrain_service.biomeSettings());
+    if (terrain_service.is_initialized()) {
+      m_ground->setBiome(terrain_service.biome_settings());
     }
   }
 
   if (m_terrain != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_terrain->configure(*terrain_service.getHeightMap(),
-                           terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_terrain->configure(*terrain_service.get_height_map(),
+                           terrain_service.biome_settings());
     }
   }
 
   if (m_biome != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_biome->configure(*terrain_service.getHeightMap(),
-                         terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_biome->configure(*terrain_service.get_height_map(),
+                         terrain_service.biome_settings());
     }
   }
 
   if (m_river != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_river->configure(terrain_service.getHeightMap()->getRiverSegments(),
-                         terrain_service.getHeightMap()->getTileSize());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_river->configure(terrain_service.get_height_map()->getRiverSegments(),
+                         terrain_service.get_height_map()->getTileSize());
     }
   }
 
   if (m_road != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
       m_road->configure(terrain_service.road_segments(),
-                        terrain_service.getHeightMap()->getTileSize());
+                        terrain_service.get_height_map()->getTileSize());
     }
   }
 
   if (m_riverbank != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_riverbank->configure(terrain_service.getHeightMap()->getRiverSegments(),
-                             *terrain_service.getHeightMap());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_riverbank->configure(
+          terrain_service.get_height_map()->getRiverSegments(),
+          *terrain_service.get_height_map());
     }
   }
 
   if (m_bridge != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_bridge->configure(terrain_service.getHeightMap()->getBridges(),
-                          terrain_service.getHeightMap()->getTileSize());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_bridge->configure(terrain_service.get_height_map()->getBridges(),
+                          terrain_service.get_height_map()->getTileSize());
     }
   }
 
   if (m_stone != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_stone->configure(*terrain_service.getHeightMap(),
-                         terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_stone->configure(*terrain_service.get_height_map(),
+                         terrain_service.biome_settings());
     }
   }
 
   if (m_plant != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_plant->configure(*terrain_service.getHeightMap(),
-                         terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_plant->configure(*terrain_service.get_height_map(),
+                         terrain_service.biome_settings());
     }
   }
 
   if (m_pine != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_pine->configure(*terrain_service.getHeightMap(),
-                        terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_pine->configure(*terrain_service.get_height_map(),
+                        terrain_service.biome_settings());
     }
   }
 
   if (m_olive != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_olive->configure(*terrain_service.getHeightMap(),
-                         terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_olive->configure(*terrain_service.get_height_map(),
+                         terrain_service.biome_settings());
     }
   }
 
   if (m_firecamp != nullptr) {
-    if (terrain_service.isInitialized() &&
-        (terrain_service.getHeightMap() != nullptr)) {
-      m_firecamp->configure(*terrain_service.getHeightMap(),
-                            terrain_service.biomeSettings());
+    if (terrain_service.is_initialized() &&
+        (terrain_service.get_height_map() != nullptr)) {
+      m_firecamp->configure(*terrain_service.get_height_map(),
+                            terrain_service.biome_settings());
 
       const auto &fire_camps = terrain_service.fire_camps();
       if (!fire_camps.empty()) {
@@ -404,7 +405,7 @@ auto SkirmishLoader::start(const QString &map_path,
         std::vector<float> intensities;
         std::vector<float> radii;
 
-        const auto *height_map = terrain_service.getHeightMap();
+        const auto *height_map = terrain_service.get_height_map();
         const float tile_size = height_map->getTileSize();
         const int width = height_map->getWidth();
         const int height = height_map->getHeight();
@@ -416,7 +417,7 @@ auto SkirmishLoader::start(const QString &map_path,
           float const world_x = (fc.x - half_width) * tile_size;
           float const world_z = (fc.z - half_height) * tile_size;
           float const world_y =
-              terrain_service.getTerrainHeight(world_x, world_z);
+              terrain_service.get_terrain_height(world_x, world_z);
 
           positions.emplace_back(world_x, world_y, world_z);
           intensities.push_back(fc.intensity);
@@ -439,8 +440,8 @@ auto SkirmishLoader::start(const QString &map_path,
   visibility_service.initialize(map_width, map_height, level_result.tile_size);
   visibility_service.computeImmediate(m_world, player_owner_id);
 
-  if ((m_fog != nullptr) && visibility_service.isInitialized()) {
-    m_fog->updateMask(
+  if ((m_fog != nullptr) && visibility_service.is_initialized()) {
+    m_fog->update_mask(
         visibility_service.getWidth(), visibility_service.getHeight(),
         visibility_service.getTileSize(), visibility_service.snapshotCells());
 
@@ -483,7 +484,7 @@ auto SkirmishLoader::start(const QString &map_path,
             focus_entity->get_component<Engine::Core::TransformComponent>()) {
       result.focusPosition = QVector3D(
           transform->position.x, transform->position.y, transform->position.z);
-      result.hasFocusPosition = true;
+      result.has_focus_position = true;
     }
   }
 

+ 19 - 19
game/map/skirmish_loader.h

@@ -47,7 +47,7 @@ struct SkirmishLoadResult {
   int max_troops_per_player = 500;
   VictoryConfig victoryConfig;
   QVector3D focusPosition;
-  bool hasFocusPosition = false;
+  bool has_focus_position = false;
 };
 
 class SkirmishLoader {
@@ -58,44 +58,44 @@ public:
   SkirmishLoader(Engine::Core::World &world, Render::GL::Renderer &renderer,
                  Render::GL::Camera &camera);
 
-  void setGroundRenderer(Render::GL::GroundRenderer *ground) {
+  void set_ground_renderer(Render::GL::GroundRenderer *ground) {
     m_ground = ground;
   }
-  void setTerrainRenderer(Render::GL::TerrainRenderer *terrain) {
+  void set_terrain_renderer(Render::GL::TerrainRenderer *terrain) {
     m_terrain = terrain;
   }
-  void setBiomeRenderer(Render::GL::BiomeRenderer *biome) { m_biome = biome; }
-  void setRiverRenderer(Render::GL::RiverRenderer *river) { m_river = river; }
-  void setRoadRenderer(Render::GL::RoadRenderer *road) { m_road = road; }
-  void setRiverbankRenderer(Render::GL::RiverbankRenderer *riverbank) {
+  void set_biome_renderer(Render::GL::BiomeRenderer *biome) { m_biome = biome; }
+  void set_river_renderer(Render::GL::RiverRenderer *river) { m_river = river; }
+  void set_road_renderer(Render::GL::RoadRenderer *road) { m_road = road; }
+  void set_riverbank_renderer(Render::GL::RiverbankRenderer *riverbank) {
     m_riverbank = riverbank;
   }
-  void setBridgeRenderer(Render::GL::BridgeRenderer *bridge) {
+  void set_bridge_renderer(Render::GL::BridgeRenderer *bridge) {
     m_bridge = bridge;
   }
-  void setFogRenderer(Render::GL::FogRenderer *fog) { m_fog = fog; }
-  void setStoneRenderer(Render::GL::StoneRenderer *stone) { m_stone = stone; }
-  void setPlantRenderer(Render::GL::PlantRenderer *plant) { m_plant = plant; }
-  void setPineRenderer(Render::GL::PineRenderer *pine) { m_pine = pine; }
-  void setOliveRenderer(Render::GL::OliveRenderer *olive) { m_olive = olive; }
-  void setFireCampRenderer(Render::GL::FireCampRenderer *firecamp) {
+  void set_fog_renderer(Render::GL::FogRenderer *fog) { m_fog = fog; }
+  void set_stone_renderer(Render::GL::StoneRenderer *stone) { m_stone = stone; }
+  void set_plant_renderer(Render::GL::PlantRenderer *plant) { m_plant = plant; }
+  void set_pine_renderer(Render::GL::PineRenderer *pine) { m_pine = pine; }
+  void set_olive_renderer(Render::GL::OliveRenderer *olive) { m_olive = olive; }
+  void set_fire_camp_renderer(Render::GL::FireCampRenderer *firecamp) {
     m_firecamp = firecamp;
   }
 
-  void setOnOwnersUpdated(OwnersUpdatedCallback callback) {
+  void set_on_owners_updated(OwnersUpdatedCallback callback) {
     m_onOwnersUpdated = std::move(callback);
   }
 
-  void setOnVisibilityMaskReady(VisibilityMaskReadyCallback callback) {
+  void set_on_visibility_mask_ready(VisibilityMaskReadyCallback callback) {
     m_onVisibilityMaskReady = std::move(callback);
   }
 
   auto start(const QString &map_path, const QVariantList &playerConfigs,
-             int selectedPlayerId,
-             int &outSelectedPlayerId) -> SkirmishLoadResult;
+             int selected_player_id,
+             int &out_selected_player_id) -> SkirmishLoadResult;
 
 private:
-  void resetGameState();
+  void reset_game_state();
   Engine::Core::World &m_world;
   Render::GL::Renderer &m_renderer;
   Render::GL::Camera &m_camera;

+ 17 - 17
game/map/terrain_service.cpp

@@ -23,43 +23,43 @@ void TerrainService::initialize(const MapDefinition &mapDef) {
   m_height_map->buildFromFeatures(mapDef.terrain);
   m_height_map->addRiverSegments(mapDef.rivers);
   m_height_map->addBridges(mapDef.bridges);
-  m_biomeSettings = mapDef.biome;
-  m_height_map->applyBiomeVariation(m_biomeSettings);
+  m_biome_settings = mapDef.biome;
+  m_height_map->applyBiomeVariation(m_biome_settings);
   m_fire_camps = mapDef.firecamps;
   m_road_segments = mapDef.roads;
 }
 
 void TerrainService::clear() {
   m_height_map.reset();
-  m_biomeSettings = BiomeSettings();
+  m_biome_settings = BiomeSettings();
   m_fire_camps.clear();
   m_road_segments.clear();
 }
 
-auto TerrainService::getTerrainHeight(float world_x,
-                                      float world_z) const -> float {
+auto TerrainService::get_terrain_height(float world_x,
+                                        float world_z) const -> float {
   if (!m_height_map) {
     return 0.0F;
   }
   return m_height_map->getHeightAt(world_x, world_z);
 }
 
-auto TerrainService::getTerrainHeightGrid(int grid_x,
-                                          int grid_z) const -> float {
+auto TerrainService::get_terrain_height_grid(int grid_x,
+                                             int grid_z) const -> float {
   if (!m_height_map) {
     return 0.0F;
   }
   return m_height_map->getHeightAtGrid(grid_x, grid_z);
 }
 
-auto TerrainService::isWalkable(int grid_x, int grid_z) const -> bool {
+auto TerrainService::is_walkable(int grid_x, int grid_z) const -> bool {
   if (!m_height_map) {
     return true;
   }
   return m_height_map->isWalkable(grid_x, grid_z);
 }
 
-auto TerrainService::isForbidden(int grid_x, int grid_z) const -> bool {
+auto TerrainService::is_forbidden(int grid_x, int grid_z) const -> bool {
   if (!m_height_map) {
     return false;
   }
@@ -85,8 +85,8 @@ auto TerrainService::isForbidden(int grid_x, int grid_z) const -> bool {
   return registry.isPointInBuilding(world_x, world_z);
 }
 
-auto TerrainService::isForbiddenWorld(float world_x,
-                                      float world_z) const -> bool {
+auto TerrainService::is_forbidden_world(float world_x,
+                                        float world_z) const -> bool {
   if (!m_height_map) {
     return false;
   }
@@ -106,25 +106,25 @@ auto TerrainService::isForbiddenWorld(float world_x,
   const int grid_x_int = static_cast<int>(std::round(grid_x));
   const int grid_z_int = static_cast<int>(std::round(grid_z));
 
-  return isForbidden(grid_x_int, grid_z_int);
+  return is_forbidden(grid_x_int, grid_z_int);
 }
 
-auto TerrainService::isHillEntrance(int grid_x, int grid_z) const -> bool {
+auto TerrainService::is_hill_entrance(int grid_x, int grid_z) const -> bool {
   if (!m_height_map) {
     return false;
   }
   return m_height_map->isHillEntrance(grid_x, grid_z);
 }
 
-auto TerrainService::getTerrainType(int grid_x,
-                                    int grid_z) const -> TerrainType {
+auto TerrainService::get_terrain_type(int grid_x,
+                                      int grid_z) const -> TerrainType {
   if (!m_height_map) {
     return TerrainType::Flat;
   }
   return m_height_map->getTerrainType(grid_x, grid_z);
 }
 
-void TerrainService::restoreFromSerialized(
+void TerrainService::restore_from_serialized(
     int width, int height, float tile_size, const std::vector<float> &heights,
     const std::vector<TerrainType> &terrain_types,
     const std::vector<RiverSegment> &rivers,
@@ -132,7 +132,7 @@ void TerrainService::restoreFromSerialized(
     const BiomeSettings &biome) {
   m_height_map = std::make_unique<TerrainHeightMap>(width, height, tile_size);
   m_height_map->restoreFromData(heights, terrain_types, rivers, bridges);
-  m_biomeSettings = biome;
+  m_biome_settings = biome;
   m_road_segments = roads;
 }
 

+ 23 - 23
game/map/terrain_service.h

@@ -17,30 +17,30 @@ public:
 
   void clear();
 
-  [[nodiscard]] auto getTerrainHeight(float world_x,
-                                      float world_z) const -> float;
+  [[nodiscard]] auto get_terrain_height(float world_x,
+                                        float world_z) const -> float;
 
-  [[nodiscard]] auto getTerrainHeightGrid(int grid_x,
-                                          int grid_z) const -> float;
+  [[nodiscard]] auto get_terrain_height_grid(int grid_x,
+                                             int grid_z) const -> float;
 
-  [[nodiscard]] auto isWalkable(int grid_x, int grid_z) const -> bool;
+  [[nodiscard]] auto is_walkable(int grid_x, int grid_z) const -> bool;
 
-  [[nodiscard]] auto isHillEntrance(int grid_x, int grid_z) const -> bool;
+  [[nodiscard]] auto is_hill_entrance(int grid_x, int grid_z) const -> bool;
 
-  [[nodiscard]] auto isForbidden(int grid_x, int grid_z) const -> bool;
+  [[nodiscard]] auto is_forbidden(int grid_x, int grid_z) const -> bool;
 
-  [[nodiscard]] auto isForbiddenWorld(float world_x,
-                                      float world_z) const -> bool;
+  [[nodiscard]] auto is_forbidden_world(float world_x,
+                                        float world_z) const -> bool;
 
-  [[nodiscard]] auto getTerrainType(int grid_x,
-                                    int grid_z) const -> TerrainType;
+  [[nodiscard]] auto get_terrain_type(int grid_x,
+                                      int grid_z) const -> TerrainType;
 
-  [[nodiscard]] auto getHeightMap() const -> const TerrainHeightMap * {
+  [[nodiscard]] auto get_height_map() const -> const TerrainHeightMap * {
     return m_height_map.get();
   }
 
-  [[nodiscard]] auto biomeSettings() const -> const BiomeSettings & {
-    return m_biomeSettings;
+  [[nodiscard]] auto biome_settings() const -> const BiomeSettings & {
+    return m_biome_settings;
   }
 
   [[nodiscard]] auto fire_camps() const -> const std::vector<FireCamp> & {
@@ -54,17 +54,17 @@ public:
   [[nodiscard]] auto is_point_on_road(float world_x,
                                       float world_z) const -> bool;
 
-  [[nodiscard]] auto isInitialized() const -> bool {
+  [[nodiscard]] auto is_initialized() const -> bool {
     return m_height_map != nullptr;
   }
 
-  void restoreFromSerialized(int width, int height, float tile_size,
-                             const std::vector<float> &heights,
-                             const std::vector<TerrainType> &terrain_types,
-                             const std::vector<RiverSegment> &rivers,
-                             const std::vector<RoadSegment> &roads,
-                             const std::vector<Bridge> &bridges,
-                             const BiomeSettings &biome);
+  void restore_from_serialized(int width, int height, float tile_size,
+                               const std::vector<float> &heights,
+                               const std::vector<TerrainType> &terrain_types,
+                               const std::vector<RiverSegment> &rivers,
+                               const std::vector<RoadSegment> &roads,
+                               const std::vector<Bridge> &bridges,
+                               const BiomeSettings &biome);
 
 private:
   TerrainService() = default;
@@ -74,7 +74,7 @@ private:
   auto operator=(const TerrainService &) -> TerrainService & = delete;
 
   std::unique_ptr<TerrainHeightMap> m_height_map;
-  BiomeSettings m_biomeSettings;
+  BiomeSettings m_biome_settings;
   std::vector<FireCamp> m_fire_camps;
   std::vector<RoadSegment> m_road_segments;
 };

+ 1 - 1
game/map/visibility_service.cpp

@@ -127,7 +127,7 @@ auto VisibilityService::gatherVisionSources(Engine::Core::World &world,
     }
 
     if (unit->owner_id != player_id &&
-        !owner_registry.areAllies(player_id, unit->owner_id)) {
+        !owner_registry.are_allies(player_id, unit->owner_id)) {
       continue;
     }
 

+ 1 - 1
game/map/visibility_service.h

@@ -28,7 +28,7 @@ public:
   auto update(Engine::Core::World &world, int player_id) -> bool;
   void computeImmediate(Engine::Core::World &world, int player_id);
 
-  auto isInitialized() const -> bool { return m_initialized; }
+  auto is_initialized() const -> bool { return m_initialized; }
 
   auto getWidth() const -> int { return m_width; }
   auto getHeight() const -> int { return m_height; }

+ 1 - 1
game/map/world_bootstrap.cpp

@@ -25,7 +25,7 @@ auto WorldBootstrap::initialize(Render::GL::Renderer &renderer,
   }
 
   if (ground != nullptr) {
-    ground->configureExtent(50.0F);
+    ground->configure_extent(50.0F);
   }
 
   return true;

+ 12 - 12
game/systems/ai_system.cpp

@@ -34,7 +34,7 @@ AISystem::AISystem() {
   m_buildingAttackedSubscription = Engine::Core::ScopedEventSubscription<
       Engine::Core::BuildingAttackedEvent>(
       [this](const Engine::Core::BuildingAttackedEvent &event) {
-        this->onBuildingAttacked(event);
+        this->on_building_attacked(event);
       });
 
   initializeAIPlayers();
@@ -48,14 +48,14 @@ void AISystem::reinitialize() {
 
 void AISystem::initializeAIPlayers() {
   auto &registry = OwnerRegistry::instance();
-  const auto &ai_owner_ids = registry.getAIOwnerIds();
+  const auto &ai_owner_ids = registry.get_ai_owner_ids();
 
   if (ai_owner_ids.empty()) {
     return;
   }
 
   for (uint32_t const player_id : ai_owner_ids) {
-    int const team_id = registry.getOwnerTeam(player_id);
+    int const team_id = registry.get_owner_team(player_id);
     AIInstance instance;
     instance.context.player_id = player_id;
     instance.context.state = AI::AIState::Idle;
@@ -73,11 +73,11 @@ void AISystem::update(Engine::Core::World *world, float delta_time) {
     return;
   }
 
-  m_totalGameTime += delta_time;
+  m_total_game_time += delta_time;
 
-  m_commandFilter.update(m_totalGameTime);
+  m_commandFilter.update(m_total_game_time);
 
-  processResults(*world);
+  process_results(*world);
 
   for (auto &ai : m_aiInstances) {
 
@@ -93,7 +93,7 @@ void AISystem::update(Engine::Core::World *world, float delta_time) {
 
     AI::AISnapshot snapshot = Game::Systems::AI::AISnapshotBuilder::build(
         *world, ai.context.player_id);
-    snapshot.gameTime = m_totalGameTime;
+    snapshot.game_time = m_total_game_time;
 
     AI::AIJob job;
     job.snapshot = std::move(snapshot);
@@ -106,7 +106,7 @@ void AISystem::update(Engine::Core::World *world, float delta_time) {
   }
 }
 
-void AISystem::processResults(Engine::Core::World &world) {
+void AISystem::process_results(Engine::Core::World &world) {
 
   for (auto &ai : m_aiInstances) {
 
@@ -119,7 +119,7 @@ void AISystem::processResults(Engine::Core::World &world) {
       ai.context = result.context;
 
       auto filtered_commands =
-          m_commandFilter.filter(result.commands, m_totalGameTime);
+          m_commandFilter.filter(result.commands, m_total_game_time);
 
       Game::Systems::AI::AICommandApplier::apply(world, ai.context.player_id,
                                                  filtered_commands);
@@ -129,14 +129,14 @@ void AISystem::processResults(Engine::Core::World &world) {
   }
 }
 
-void AISystem::onBuildingAttacked(
+void AISystem::on_building_attacked(
     const Engine::Core::BuildingAttackedEvent &event) {
   for (auto &ai : m_aiInstances) {
     if (ai.context.player_id == event.owner_id) {
-      ai.context.buildingsUnderAttack[event.buildingId] = m_totalGameTime;
+      ai.context.buildingsUnderAttack[event.buildingId] = m_total_game_time;
 
       if (event.buildingId == ai.context.primaryBarracks) {
-        ai.context.barracksUnderThreat = true;
+        ai.context.barracks_under_threat = true;
       }
       break;
     }

+ 3 - 3
game/systems/ai_system.h

@@ -45,16 +45,16 @@ private:
   AI::AICommandApplier m_applier;
   AI::AICommandFilter m_commandFilter;
 
-  float m_totalGameTime = 0.0F;
+  float m_total_game_time = 0.0F;
 
   Engine::Core::ScopedEventSubscription<Engine::Core::BuildingAttackedEvent>
       m_buildingAttackedSubscription;
 
   void initializeAIPlayers();
 
-  void processResults(Engine::Core::World &world);
+  void process_results(Engine::Core::World &world);
 
-  void onBuildingAttacked(const Engine::Core::BuildingAttackedEvent &event);
+  void on_building_attacked(const Engine::Core::BuildingAttackedEvent &event);
 };
 
 } // namespace Game::Systems

+ 1 - 1
game/systems/ai_system/ai_command_applier.cpp

@@ -133,7 +133,7 @@ void AICommandApplier::apply(Engine::Core::World &world, int aiOwnerId,
       int const current_troops =
           Engine::Core::World::count_troops_for_player(aiOwnerId);
       int const max_troops =
-          Game::GameConfig::instance().getMaxTroopsPerPlayer();
+          Game::GameConfig::instance().get_max_troops_per_player();
       Game::Units::TroopType const product_type = production->product_type;
       int const individuals_per_unit =
           Game::Units::TroopConfig::instance().getIndividualsPerUnit(

+ 53 - 50
game/systems/ai_system/ai_reasoner.cpp

@@ -13,8 +13,9 @@ namespace Game::Systems::AI {
 void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
 
   if (ctx.nation == nullptr) {
-    ctx.nation = Game::Systems::NationRegistry::instance().getNationForPlayer(
-        ctx.player_id);
+    ctx.nation =
+        Game::Systems::NationRegistry::instance().get_nation_for_player(
+            ctx.player_id);
   }
 
   cleanupDeadUnits(snapshot, ctx);
@@ -23,39 +24,39 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
   ctx.buildings.clear();
   ctx.primaryBarracks = 0;
   ctx.total_units = 0;
-  ctx.idleUnits = 0;
-  ctx.combatUnits = 0;
-  ctx.meleeCount = 0;
+  ctx.idle_units = 0;
+  ctx.combat_units = 0;
+  ctx.melee_count = 0;
   ctx.rangedCount = 0;
   ctx.damagedUnitsCount = 0;
-  ctx.averageHealth = 1.0F;
+  ctx.average_health = 1.0F;
   ctx.rally_x = 0.0F;
   ctx.rally_z = 0.0F;
-  ctx.barracksUnderThreat = false;
-  ctx.nearbyThreatCount = 0;
-  ctx.closest_threatDistance = std::numeric_limits<float>::infinity();
-  ctx.basePosX = 0.0F;
-  ctx.basePosY = 0.0F;
-  ctx.basePosZ = 0.0F;
+  ctx.barracks_under_threat = false;
+  ctx.nearby_threat_count = 0;
+  ctx.closest_threat_distance = std::numeric_limits<float>::infinity();
+  ctx.base_pos_x = 0.0F;
+  ctx.base_pos_y = 0.0F;
+  ctx.base_pos_z = 0.0F;
   ctx.visibleEnemyCount = 0;
   ctx.enemyBuildingsCount = 0;
   ctx.averageEnemyDistance = 0.0F;
   ctx.max_troops_per_player =
-      Game::GameConfig::instance().getMaxTroopsPerPlayer();
+      Game::GameConfig::instance().get_max_troops_per_player();
 
   constexpr float attack_record_timeout = 10.0F;
   auto it = ctx.buildingsUnderAttack.begin();
   while (it != ctx.buildingsUnderAttack.end()) {
     bool still_exists = false;
     for (const auto &entity : snapshot.friendlies) {
-      if (entity.id == it->first && entity.isBuilding) {
+      if (entity.id == it->first && entity.is_building) {
         still_exists = true;
         break;
       }
     }
 
     if (!still_exists ||
-        (snapshot.gameTime - it->second) > attack_record_timeout) {
+        (snapshot.game_time - it->second) > attack_record_timeout) {
       it = ctx.buildingsUnderAttack.erase(it);
     } else {
       ++it;
@@ -65,7 +66,7 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
   float total_health_ratio = 0.0F;
 
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       ctx.buildings.push_back(entity.id);
 
       if (entity.spawn_type == Game::Units::SpawnType::Barracks &&
@@ -73,9 +74,9 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
         ctx.primaryBarracks = entity.id;
         ctx.rally_x = entity.posX - 5.0F;
         ctx.rally_z = entity.posZ;
-        ctx.basePosX = entity.posX;
-        ctx.basePosY = entity.posY;
-        ctx.basePosZ = entity.posZ;
+        ctx.base_pos_x = entity.posX;
+        ctx.base_pos_y = entity.posY;
+        ctx.base_pos_z = entity.posZ;
       }
       continue;
     }
@@ -91,15 +92,15 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
         if (ctx.nation->is_ranged_unit(troop_type)) {
           ctx.rangedCount++;
         } else if (ctx.nation->isMeleeUnit(troop_type)) {
-          ctx.meleeCount++;
+          ctx.melee_count++;
         }
       }
     }
 
     if (!entity.movement.has_component || !entity.movement.has_target) {
-      ctx.idleUnits++;
+      ctx.idle_units++;
     } else {
-      ctx.combatUnits++;
+      ctx.combat_units++;
     }
 
     if (entity.max_health > 0) {
@@ -113,7 +114,7 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
     }
   }
 
-  ctx.averageHealth =
+  ctx.average_health =
       (ctx.total_units > 0)
           ? (total_health_ratio / static_cast<float>(ctx.total_units))
           : 1.0F;
@@ -122,13 +123,14 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
   float total_enemy_dist = 0.0F;
 
   for (const auto &enemy : snapshot.visibleEnemies) {
-    if (enemy.isBuilding) {
+    if (enemy.is_building) {
       ctx.enemyBuildingsCount++;
     }
 
     if (ctx.primaryBarracks != 0) {
-      float const dist = distance(enemy.posX, enemy.posY, enemy.posZ,
-                                  ctx.basePosX, ctx.basePosY, ctx.basePosZ);
+      float const dist =
+          distance(enemy.posX, enemy.posY, enemy.posZ, ctx.base_pos_x,
+                   ctx.base_pos_y, ctx.base_pos_z);
       total_enemy_dist += dist;
     }
   }
@@ -147,32 +149,33 @@ void AIReasoner::updateContext(const AISnapshot &snapshot, AIContext &ctx) {
 
     for (const auto &enemy : snapshot.visibleEnemies) {
       float const dist_sq =
-          distance_squared(enemy.posX, enemy.posY, enemy.posZ, ctx.basePosX,
-                           ctx.basePosY, ctx.basePosZ);
+          distance_squared(enemy.posX, enemy.posY, enemy.posZ, ctx.base_pos_x,
+                           ctx.base_pos_y, ctx.base_pos_z);
 
       if (dist_sq <= defend_radius_sq) {
-        ctx.barracksUnderThreat = true;
-        ctx.nearbyThreatCount++;
+        ctx.barracks_under_threat = true;
+        ctx.nearby_threat_count++;
 
         float const dist = std::sqrt(std::max(dist_sq, 0.0F));
-        ctx.closest_threatDistance = std::min(ctx.closest_threatDistance, dist);
+        ctx.closest_threat_distance =
+            std::min(ctx.closest_threat_distance, dist);
       }
     }
 
-    if (!ctx.barracksUnderThreat) {
-      ctx.closest_threatDistance = std::numeric_limits<float>::infinity();
+    if (!ctx.barracks_under_threat) {
+      ctx.closest_threat_distance = std::numeric_limits<float>::infinity();
     }
   }
 }
 
 void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
-  ctx.stateTimer += delta_time;
-  ctx.decisionTimer += delta_time;
+  ctx.state_timer += delta_time;
+  ctx.decision_timer += delta_time;
 
   constexpr float min_state_duration = 3.0F;
 
   AIState previous_state = ctx.state;
-  if ((ctx.barracksUnderThreat || !ctx.buildingsUnderAttack.empty()) &&
+  if ((ctx.barracks_under_threat || !ctx.buildingsUnderAttack.empty()) &&
       ctx.state != AIState::Defending) {
 
     ctx.state = AIState::Defending;
@@ -183,27 +186,27 @@ void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
     ctx.state = AIState::Defending;
   }
 
-  if (ctx.decisionTimer < 2.0F) {
+  if (ctx.decision_timer < 2.0F) {
     if (ctx.state != previous_state) {
-      ctx.stateTimer = 0.0F;
+      ctx.state_timer = 0.0F;
     }
     return;
   }
 
-  ctx.decisionTimer = 0.0F;
+  ctx.decision_timer = 0.0F;
   previous_state = ctx.state;
 
-  if (ctx.stateTimer < min_state_duration &&
-      ((!ctx.barracksUnderThreat && ctx.buildingsUnderAttack.empty()) ||
+  if (ctx.state_timer < min_state_duration &&
+      ((!ctx.barracks_under_threat && ctx.buildingsUnderAttack.empty()) ||
        ctx.state == AIState::Defending)) {
     return;
   }
 
   switch (ctx.state) {
   case AIState::Idle:
-    if (ctx.idleUnits >= 2) {
+    if (ctx.idle_units >= 2) {
       ctx.state = AIState::Gathering;
-    } else if (ctx.averageHealth < 0.40F && ctx.total_units > 0) {
+    } else if (ctx.average_health < 0.40F && ctx.total_units > 0) {
 
       ctx.state = AIState::Defending;
     } else if (ctx.total_units >= 1 && ctx.visibleEnemyCount > 0) {
@@ -218,7 +221,7 @@ void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
       ctx.state = AIState::Attacking;
     } else if (ctx.total_units < 2) {
       ctx.state = AIState::Idle;
-    } else if (ctx.averageHealth < 0.40F) {
+    } else if (ctx.average_health < 0.40F) {
 
       ctx.state = AIState::Defending;
     } else if (ctx.visibleEnemyCount > 0 && ctx.total_units >= 2) {
@@ -228,7 +231,7 @@ void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
     break;
 
   case AIState::Attacking:
-    if (ctx.averageHealth < 0.25F) {
+    if (ctx.average_health < 0.25F) {
 
       ctx.state = AIState::Retreating;
     } else if (ctx.total_units == 0) {
@@ -240,12 +243,12 @@ void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
 
   case AIState::Defending:
 
-    if (ctx.barracksUnderThreat || !ctx.buildingsUnderAttack.empty()) {
+    if (ctx.barracks_under_threat || !ctx.buildingsUnderAttack.empty()) {
 
-    } else if (ctx.total_units >= 4 && ctx.averageHealth > 0.65F) {
+    } else if (ctx.total_units >= 4 && ctx.average_health > 0.65F) {
 
       ctx.state = AIState::Attacking;
-    } else if (ctx.averageHealth > 0.80F) {
+    } else if (ctx.average_health > 0.80F) {
 
       ctx.state = AIState::Idle;
     }
@@ -253,7 +256,7 @@ void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
 
   case AIState::Retreating:
 
-    if (ctx.stateTimer > 6.0F && ctx.averageHealth > 0.55F) {
+    if (ctx.state_timer > 6.0F && ctx.average_health > 0.55F) {
 
       ctx.state = AIState::Defending;
     }
@@ -266,7 +269,7 @@ void AIReasoner::updateStateMachine(AIContext &ctx, float delta_time) {
   }
 
   if (ctx.state != previous_state) {
-    ctx.stateTimer = 0.0F;
+    ctx.state_timer = 0.0F;
   }
 }
 

+ 2 - 2
game/systems/ai_system/ai_snapshot_builder.cpp

@@ -42,7 +42,7 @@ auto AISnapshotBuilder::build(const Engine::Core::World &world,
     data.owner_id = unit->owner_id;
     data.health = unit->health;
     data.max_health = unit->max_health;
-    data.isBuilding = entity->has_component<Engine::Core::BuildingComponent>();
+    data.is_building = entity->has_component<Engine::Core::BuildingComponent>();
 
     if (auto *transform =
             entity->get_component<Engine::Core::TransformComponent>()) {
@@ -93,7 +93,7 @@ auto AISnapshotBuilder::build(const Engine::Core::World &world,
 
     ContactSnapshot contact;
     contact.id = entity->get_id();
-    contact.isBuilding =
+    contact.is_building =
         entity->has_component<Engine::Core::BuildingComponent>();
     contact.posX = transform->position.x;
     contact.posY = 0.0F;

+ 23 - 23
game/systems/ai_system/ai_tactical.cpp

@@ -15,17 +15,17 @@ namespace Game::Systems::AI {
 auto TacticalUtils::assessEngagement(
     const std::vector<const EntitySnapshot *> &friendlies,
     const std::vector<const ContactSnapshot *> &enemies,
-    float minForceRatio) -> TacticalUtils::EngagementAssessment {
+    float min_force_ratio) -> TacticalUtils::EngagementAssessment {
 
   EngagementAssessment result;
 
   if (friendlies.empty() || enemies.empty()) {
-    result.shouldEngage = false;
+    result.should_engage = false;
     return result;
   }
 
-  result.friendlyCount = static_cast<int>(friendlies.size());
-  result.enemyCount = static_cast<int>(enemies.size());
+  result.friendly_count = static_cast<int>(friendlies.size());
+  result.enemy_count = static_cast<int>(enemies.size());
 
   float total_friendly_health = 0.0F;
   float total_enemy_health = 0.0F;
@@ -48,26 +48,26 @@ auto TacticalUtils::assessEngagement(
     }
   }
 
-  result.avgFriendlyHealth =
+  result.avg_friendly_health =
       valid_friendlies > 0 ? (total_friendly_health / valid_friendlies) : 1.0F;
-  result.avgEnemyHealth =
+  result.avg_enemy_health =
       valid_enemies > 0 ? (total_enemy_health / valid_enemies) : 1.0F;
 
   float const friendly_strength =
-      static_cast<float>(result.friendlyCount) * result.avgFriendlyHealth;
+      static_cast<float>(result.friendly_count) * result.avg_friendly_health;
   float const enemy_strength =
-      static_cast<float>(result.enemyCount) * result.avgEnemyHealth;
+      static_cast<float>(result.enemy_count) * result.avg_enemy_health;
 
   if (enemy_strength < 0.01F) {
-    result.forceRatio = 10.0F;
+    result.force_ratio = 10.0F;
   } else {
-    result.forceRatio = friendly_strength / enemy_strength;
+    result.force_ratio = friendly_strength / enemy_strength;
   }
 
-  result.confidenceLevel =
-      std::clamp((result.forceRatio - 0.5F) / 1.5F, 0.0F, 1.0F);
+  result.confidence_level =
+      std::clamp((result.force_ratio - 0.5F) / 1.5F, 0.0F, 1.0F);
 
-  result.shouldEngage = (result.forceRatio >= minForceRatio);
+  result.should_engage = (result.force_ratio >= min_force_ratio);
 
   return result;
 }
@@ -109,7 +109,7 @@ auto TacticalUtils::selectFocusFireTarget(
         Game::Units::spawn_typeToString(enemy->spawn_type), context.nation);
     score += type_priority * 3.0F;
 
-    if (!enemy->isBuilding) {
+    if (!enemy->is_building) {
       score += 5.0F;
     }
 
@@ -124,25 +124,25 @@ auto TacticalUtils::selectFocusFireTarget(
 
     if (context.primaryBarracks != 0) {
       float const dist_to_base =
-          distance(enemy->posX, enemy->posY, enemy->posZ, context.basePosX,
-                   context.basePosY, context.basePosZ);
+          distance(enemy->posX, enemy->posY, enemy->posZ, context.base_pos_x,
+                   context.base_pos_y, context.base_pos_z);
 
       if (dist_to_base < 16.0F) {
         score += (16.0F - dist_to_base) * 0.8F;
       }
     }
 
-    if (context.state == AIState::Attacking && !enemy->isBuilding) {
+    if (context.state == AIState::Attacking && !enemy->is_building) {
       score += 3.0F;
     }
 
     if (score > best_target.score) {
       best_target.target_id = enemy->id;
       best_target.score = score;
-      best_target.distanceToGroup = dist;
-      best_target.isLowHealth =
+      best_target.distance_to_group = dist;
+      best_target.is_low_health =
           (enemy->max_health > 0 && enemy->health < enemy->max_health / 2);
-      best_target.isIsolated = isolated;
+      best_target.is_isolated = isolated;
     }
   }
 
@@ -184,9 +184,9 @@ auto TacticalUtils::calculateForceStrength(
 auto TacticalUtils::isTargetIsolated(
     const ContactSnapshot &target,
     const std::vector<const ContactSnapshot *> &allEnemies,
-    float isolationRadius) -> bool {
+    float isolation_radius) -> bool {
 
-  const float isolation_radius_sq = isolationRadius * isolationRadius;
+  const float isolation_radius_sq = isolation_radius * isolation_radius;
   int nearby_allies = 0;
 
   for (const auto *enemy : allEnemies) {
@@ -226,7 +226,7 @@ auto TacticalUtils::getUnitTypePriority(const std::string &unit_type,
   }
 
   auto spawn_type = Game::Units::spawn_typeFromString(unit_type);
-  if (spawn_type && Game::Units::isBuildingSpawn(*spawn_type)) {
+  if (spawn_type && Game::Units::is_building_spawn(*spawn_type)) {
     return 0.5F;
   }
 

+ 12 - 12
game/systems/ai_system/ai_tactical.h

@@ -8,27 +8,27 @@ namespace Game::Systems::AI {
 class TacticalUtils {
 public:
   struct EngagementAssessment {
-    bool shouldEngage = false;
-    float forceRatio = 0.0F;
-    float confidenceLevel = 0.0F;
-    int friendlyCount = 0;
-    int enemyCount = 0;
-    float avgFriendlyHealth = 1.0F;
-    float avgEnemyHealth = 1.0F;
+    bool should_engage = false;
+    float force_ratio = 0.0F;
+    float confidence_level = 0.0F;
+    int friendly_count = 0;
+    int enemy_count = 0;
+    float avg_friendly_health = 1.0F;
+    float avg_enemy_health = 1.0F;
   };
 
   struct TargetScore {
     Engine::Core::EntityID target_id = 0;
     float score = 0.0F;
-    float distanceToGroup = 0.0F;
-    bool isLowHealth = false;
-    bool isIsolated = false;
+    float distance_to_group = 0.0F;
+    bool is_low_health = false;
+    bool is_isolated = false;
   };
 
   static auto
   assessEngagement(const std::vector<const EntitySnapshot *> &friendlies,
                    const std::vector<const ContactSnapshot *> &enemies,
-                   float minForceRatio = 0.8F) -> EngagementAssessment;
+                   float min_force_ratio = 0.8F) -> EngagementAssessment;
 
   static auto selectFocusFireTarget(
       const std::vector<const EntitySnapshot *> &attackers,
@@ -45,7 +45,7 @@ public:
   static auto
   isTargetIsolated(const ContactSnapshot &target,
                    const std::vector<const ContactSnapshot *> &allEnemies,
-                   float isolationRadius = 8.0F) -> bool;
+                   float isolation_radius = 8.0F) -> bool;
 
   static auto
   getUnitTypePriority(const std::string &unit_type,

+ 17 - 17
game/systems/ai_system/ai_types.h

@@ -60,7 +60,7 @@ struct EntitySnapshot {
   int owner_id = 0;
   int health = 0;
   int max_health = 0;
-  bool isBuilding = false;
+  bool is_building = false;
 
   float posX = 0.0F;
   float posY = 0.0F;
@@ -72,7 +72,7 @@ struct EntitySnapshot {
 
 struct ContactSnapshot {
   Engine::Core::EntityID id = 0;
-  bool isBuilding = false;
+  bool is_building = false;
 
   float posX = 0.0F;
   float posY = 0.0F;
@@ -88,14 +88,14 @@ struct AISnapshot {
   std::vector<EntitySnapshot> friendlies;
   std::vector<ContactSnapshot> visibleEnemies;
 
-  float gameTime = 0.0F;
+  float game_time = 0.0F;
 };
 
 struct AIContext {
   int player_id = 0;
   AIState state = AIState::Idle;
-  float stateTimer = 0.0F;
-  float decisionTimer = 0.0F;
+  float state_timer = 0.0F;
+  float decision_timer = 0.0F;
 
   const Game::Systems::Nation *nation = nullptr;
 
@@ -105,28 +105,28 @@ struct AIContext {
 
   float rally_x = 0.0F;
   float rally_z = 0.0F;
-  int targetPriority = 0;
+  int target_priority = 0;
 
   int total_units = 0;
-  int idleUnits = 0;
-  int combatUnits = 0;
-  float averageHealth = 1.0F;
-  bool barracksUnderThreat = false;
-  int nearbyThreatCount = 0;
-  float closest_threatDistance = 0.0F;
+  int idle_units = 0;
+  int combat_units = 0;
+  float average_health = 1.0F;
+  bool barracks_under_threat = false;
+  int nearby_threat_count = 0;
+  float closest_threat_distance = 0.0F;
 
-  float basePosX = 0.0F;
-  float basePosY = 0.0F;
-  float basePosZ = 0.0F;
+  float base_pos_x = 0.0F;
+  float base_pos_y = 0.0F;
+  float base_pos_z = 0.0F;
 
   struct UnitAssignment {
     BehaviorPriority ownerPriority = BehaviorPriority::Normal;
-    float assignmentTime = 0.0F;
+    float assignment_time = 0.0F;
     std::string assignedTask;
   };
   std::unordered_map<Engine::Core::EntityID, UnitAssignment> assignedUnits;
 
-  int meleeCount = 0;
+  int melee_count = 0;
   int rangedCount = 0;
   int damagedUnitsCount = 0;
 

+ 4 - 4
game/systems/ai_system/ai_utils.h

@@ -92,7 +92,7 @@ inline auto claimUnits(
 
       AIContext::UnitAssignment assignment;
       assignment.ownerPriority = priority;
-      assignment.assignmentTime = currentTime;
+      assignment.assignment_time = currentTime;
       assignment.assignedTask = taskName;
       context.assignedUnits[unit_id] = assignment;
       claimed.push_back(unit_id);
@@ -100,7 +100,7 @@ inline auto claimUnits(
     } else {
 
       const auto &existing = it->second;
-      float const assignmentAge = currentTime - existing.assignmentTime;
+      float const assignmentAge = currentTime - existing.assignment_time;
 
       bool const canSteal = (priority > existing.ownerPriority) &&
                             (assignmentAge > minLockDuration);
@@ -109,7 +109,7 @@ inline auto claimUnits(
 
         AIContext::UnitAssignment assignment;
         assignment.ownerPriority = priority;
-        assignment.assignmentTime = currentTime;
+        assignment.assignment_time = currentTime;
         assignment.assignedTask = taskName;
         context.assignedUnits[unit_id] = assignment;
         claimed.push_back(unit_id);
@@ -131,7 +131,7 @@ inline void cleanupDeadUnits(const AISnapshot &snapshot, AIContext &context) {
 
   std::unordered_set<Engine::Core::EntityID> aliveUnits;
   for (const auto &entity : snapshot.friendlies) {
-    if (!entity.isBuilding) {
+    if (!entity.is_building) {
       aliveUnits.insert(entity.id);
     }
   }

+ 7 - 7
game/systems/ai_system/behaviors/attack_behavior.cpp

@@ -35,7 +35,7 @@ void AttackBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   float group_center_z = 0.0F;
 
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       continue;
     }
 
@@ -90,7 +90,7 @@ void AttackBehavior::execute(const AISnapshot &snapshot, AIContext &context,
       float closest_barracks_dist_sq = std::numeric_limits<float>::max();
 
       for (const auto &enemy : snapshot.visibleEnemies) {
-        if (enemy.isBuilding) {
+        if (enemy.is_building) {
           float const dist_sq =
               distance_squared(enemy.posX, enemy.posY, enemy.posZ,
                                group_center_x, group_center_y, group_center_z);
@@ -190,7 +190,7 @@ void AttackBehavior::execute(const AISnapshot &snapshot, AIContext &context,
 
   bool const being_attacked = context.damagedUnitsCount > 0;
 
-  if (!assessment.shouldEngage && !context.barracksUnderThreat &&
+  if (!assessment.should_engage && !context.barracks_under_threat &&
       !being_attacked) {
 
     m_lastTarget = 0;
@@ -245,8 +245,8 @@ void AttackBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   command.target_id = target_info.target_id;
 
   bool const should_chase_aggressive =
-      (context.state == AIState::Attacking || context.barracksUnderThreat) &&
-      assessment.forceRatio >= 0.8F;
+      (context.state == AIState::Attacking || context.barracks_under_threat) &&
+      assessment.force_ratio >= 0.8F;
 
   command.should_chase = should_chase_aggressive;
 
@@ -261,7 +261,7 @@ auto AttackBehavior::should_execute(const AISnapshot &snapshot,
 
   int ready_units = 0;
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       continue;
     }
 
@@ -285,7 +285,7 @@ auto AttackBehavior::should_execute(const AISnapshot &snapshot,
   }
 
   if (context.state == AIState::Defending) {
-    return context.barracksUnderThreat && ready_units >= 2;
+    return context.barracks_under_threat && ready_units >= 2;
   }
 
   return ready_units >= 1;

+ 10 - 10
game/systems/ai_system/behaviors/defend_behavior.cpp

@@ -21,7 +21,7 @@ void DefendBehavior::execute(const AISnapshot &snapshot, AIContext &context,
                              std::vector<AICommand> &outCommands) {
   m_defendTimer += delta_time;
 
-  float const update_interval = context.barracksUnderThreat ? 0.5F : 1.5F;
+  float const update_interval = context.barracks_under_threat ? 0.5F : 1.5F;
 
   if (m_defendTimer < update_interval) {
     return;
@@ -57,7 +57,7 @@ void DefendBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   engaged_defenders.reserve(snapshot.friendlies.size());
 
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       continue;
     }
 
@@ -92,7 +92,7 @@ void DefendBehavior::execute(const AISnapshot &snapshot, AIContext &context,
       ready_defenders.size() + engaged_defenders.size();
   std::size_t desired_count = total_available;
 
-  if (context.barracksUnderThreat || !context.buildingsUnderAttack.empty()) {
+  if (context.barracks_under_threat || !context.buildingsUnderAttack.empty()) {
 
     desired_count = total_available;
   } else {
@@ -109,7 +109,7 @@ void DefendBehavior::execute(const AISnapshot &snapshot, AIContext &context,
     return;
   }
 
-  if (context.barracksUnderThreat || !context.buildingsUnderAttack.empty()) {
+  if (context.barracks_under_threat || !context.buildingsUnderAttack.empty()) {
 
     std::vector<const ContactSnapshot *> nearby_threats;
     nearby_threats.reserve(snapshot.visibleEnemies.size());
@@ -154,7 +154,7 @@ void DefendBehavior::execute(const AISnapshot &snapshot, AIContext &context,
           return;
         }
       }
-    } else if (context.barracksUnderThreat ||
+    } else if (context.barracks_under_threat ||
                !context.buildingsUnderAttack.empty()) {
 
       const ContactSnapshot *closest_threat = nullptr;
@@ -228,14 +228,14 @@ void DefendBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   }
 
   const Nation *nation =
-      NationRegistry::instance().getNationForPlayer(context.player_id);
+      NationRegistry::instance().get_nation_for_player(context.player_id);
   FormationType formation_type = FormationType::Roman;
   if (nation != nullptr) {
     formation_type = nation->formation_type;
   }
 
   QVector3D const defend_pos(defend_pos_x, defend_pos_y, defend_pos_z);
-  auto targets = FormationSystem::instance().getFormationPositions(
+  auto targets = FormationSystem::instance().get_formation_positions(
       formation_type, static_cast<int>(unclaimed_defenders.size()), defend_pos,
       3.0F);
 
@@ -307,15 +307,15 @@ auto DefendBehavior::should_execute(const AISnapshot &snapshot,
     return false;
   }
 
-  if (context.barracksUnderThreat || !context.buildingsUnderAttack.empty()) {
+  if (context.barracks_under_threat || !context.buildingsUnderAttack.empty()) {
     return true;
   }
 
-  if (context.state == AIState::Defending && context.idleUnits > 0) {
+  if (context.state == AIState::Defending && context.idle_units > 0) {
     return true;
   }
 
-  if (context.averageHealth < 0.6F && context.total_units > 0) {
+  if (context.average_health < 0.6F && context.total_units > 0) {
     return true;
   }
 

+ 4 - 4
game/systems/ai_system/behaviors/gather_behavior.cpp

@@ -33,7 +33,7 @@ void GatherBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   units_to_gather.reserve(snapshot.friendlies.size());
 
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       continue;
     }
 
@@ -55,13 +55,13 @@ void GatherBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   }
 
   const Nation *nation =
-      NationRegistry::instance().getNationForPlayer(context.player_id);
+      NationRegistry::instance().get_nation_for_player(context.player_id);
   FormationType formation_type = FormationType::Roman;
   if (nation != nullptr) {
     formation_type = nation->formation_type;
   }
 
-  auto formation_targets = FormationSystem::instance().getFormationPositions(
+  auto formation_targets = FormationSystem::instance().get_formation_positions(
       formation_type, static_cast<int>(units_to_gather.size()), rally_point,
       1.4F);
 
@@ -135,7 +135,7 @@ auto GatherBehavior::should_execute(const AISnapshot &snapshot,
 
     QVector3D const rally_point(context.rally_x, 0.0F, context.rally_z);
     for (const auto &entity : snapshot.friendlies) {
-      if (entity.isBuilding) {
+      if (entity.is_building) {
         continue;
       }
 

+ 4 - 4
game/systems/ai_system/behaviors/production_behavior.cpp

@@ -20,7 +20,7 @@ void ProductionBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   static int const exec_counter = 0;
 
   auto &nation_registry = Game::Systems::NationRegistry::instance();
-  const auto *nation = nation_registry.getNationForPlayer(context.player_id);
+  const auto *nation = nation_registry.get_nation_for_player(context.player_id);
 
   if (nation == nullptr) {
 
@@ -30,8 +30,8 @@ void ProductionBehavior::execute(const AISnapshot &snapshot, AIContext &context,
 
   bool produce_ranged = true;
 
-  if (context.barracksUnderThreat || context.state == AIState::Defending) {
-    produce_ranged = (context.meleeCount > context.rangedCount);
+  if (context.barracks_under_threat || context.state == AIState::Defending) {
+    produce_ranged = (context.melee_count > context.rangedCount);
   } else {
 
     float const ranged_ratio =
@@ -56,7 +56,7 @@ void ProductionBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   }
 
   for (const auto &entity : snapshot.friendlies) {
-    if (!entity.isBuilding ||
+    if (!entity.is_building ||
         entity.spawn_type != Game::Units::SpawnType::Barracks) {
       continue;
     }

+ 4 - 3
game/systems/ai_system/behaviors/retreat_behavior.cpp

@@ -32,7 +32,7 @@ void RetreatBehavior::execute(const AISnapshot &snapshot, AIContext &context,
   constexpr float low_health = 0.50F;
 
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       continue;
     }
 
@@ -57,7 +57,8 @@ void RetreatBehavior::execute(const AISnapshot &snapshot, AIContext &context,
     return;
   }
 
-  QVector3D retreat_pos(context.basePosX, context.basePosY, context.basePosZ);
+  QVector3D retreat_pos(context.base_pos_x, context.base_pos_y,
+                        context.base_pos_z);
 
   retreat_pos.setX(retreat_pos.x() - 8.0F);
 
@@ -121,7 +122,7 @@ auto RetreatBehavior::should_execute(const AISnapshot &snapshot,
   constexpr float critical_health = 0.35F;
 
   for (const auto &entity : snapshot.friendlies) {
-    if (entity.isBuilding) {
+    if (entity.is_building) {
       continue;
     }
 

+ 2 - 2
game/systems/arrow_system.cpp

@@ -19,8 +19,8 @@ void ArrowSystem::spawnArrow(const QVector3D &start, const QVector3D &end,
   a.active = true;
   QVector3D const delta = end - start;
   float const dist = delta.length();
-  a.arc_height = std::clamp(m_config.arcHeightMultiplier * dist,
-                            m_config.arcHeightMin, m_config.arcHeightMax);
+  a.arc_height = std::clamp(m_config.arc_height_multiplier * dist,
+                            m_config.arc_height_min, m_config.arc_height_max);
 
   a.inv_dist = (dist > 0.001F) ? (1.0F / dist) : 1.0F;
   m_arrows.push_back(a);

+ 8 - 8
game/systems/building_collision_registry.cpp

@@ -35,13 +35,13 @@ auto BuildingCollisionRegistry::getBuildingSize(const std::string &buildingType)
   return {2.0F, 2.0F};
 }
 
-void BuildingCollisionRegistry::registerBuilding(
+void BuildingCollisionRegistry::register_building(
     unsigned int entity_id, const std::string &buildingType, float center_x,
     float center_z, int owner_id) {
 
   if (m_entityToIndex.find(entity_id) != m_entityToIndex.end()) {
 
-    updateBuildingPosition(entity_id, center_x, center_z);
+    update_building_position(entity_id, center_x, center_z);
     return;
   }
 
@@ -57,7 +57,7 @@ void BuildingCollisionRegistry::registerBuilding(
   }
 }
 
-void BuildingCollisionRegistry::unregisterBuilding(unsigned int entity_id) {
+void BuildingCollisionRegistry::unregister_building(unsigned int entity_id) {
   auto it = m_entityToIndex.find(entity_id);
   if (it == m_entityToIndex.end()) {
     return;
@@ -79,9 +79,9 @@ void BuildingCollisionRegistry::unregisterBuilding(unsigned int entity_id) {
   }
 }
 
-void BuildingCollisionRegistry::updateBuildingPosition(unsigned int entity_id,
-                                                       float center_x,
-                                                       float center_z) {
+void BuildingCollisionRegistry::update_building_position(unsigned int entity_id,
+                                                         float center_x,
+                                                         float center_z) {
   auto it = m_entityToIndex.find(entity_id);
   if (it == m_entityToIndex.end()) {
     return;
@@ -96,8 +96,8 @@ void BuildingCollisionRegistry::updateBuildingPosition(unsigned int entity_id,
   }
 }
 
-void BuildingCollisionRegistry::updateBuildingOwner(unsigned int entity_id,
-                                                    int owner_id) {
+void BuildingCollisionRegistry::update_building_owner(unsigned int entity_id,
+                                                      int owner_id) {
   auto it = m_entityToIndex.find(entity_id);
   if (it == m_entityToIndex.end()) {
     return;

+ 7 - 6
game/systems/building_collision_registry.h

@@ -32,15 +32,16 @@ public:
 
   static auto getBuildingSize(const std::string &buildingType) -> BuildingSize;
 
-  void registerBuilding(unsigned int entity_id, const std::string &buildingType,
-                        float center_x, float center_z, int owner_id);
+  void register_building(unsigned int entity_id,
+                         const std::string &buildingType, float center_x,
+                         float center_z, int owner_id);
 
-  void unregisterBuilding(unsigned int entity_id);
+  void unregister_building(unsigned int entity_id);
 
-  void updateBuildingPosition(unsigned int entity_id, float center_x,
-                              float center_z);
+  void update_building_position(unsigned int entity_id, float center_x,
+                                float center_z);
 
-  void updateBuildingOwner(unsigned int entity_id, int owner_id);
+  void update_building_owner(unsigned int entity_id, int owner_id);
 
   [[nodiscard]] auto
   getAllBuildings() const -> const std::vector<BuildingFootprint> & {

+ 22 - 21
game/systems/camera_controller.cpp

@@ -5,54 +5,55 @@ namespace Game::Systems {
 
 void CameraController::move(Render::GL::Camera &camera, float dx, float dz) {
   camera.pan(dx, dz);
-  if (camera.isFollowEnabled()) {
-    camera.captureFollowOffset();
+  if (camera.is_follow_enabled()) {
+    camera.capture_follow_offset();
   }
 }
 
 void CameraController::elevate(Render::GL::Camera &camera, float dy) {
   camera.elevate(dy);
-  if (camera.isFollowEnabled()) {
-    camera.captureFollowOffset();
+  if (camera.is_follow_enabled()) {
+    camera.capture_follow_offset();
   }
 }
 
-void CameraController::moveUp(Render::GL::Camera &camera, float dy) {
-  camera.moveUp(dy);
-  if (camera.isFollowEnabled()) {
-    camera.captureFollowOffset();
+void CameraController::move_up(Render::GL::Camera &camera, float dy) {
+  camera.move_up(dy);
+  if (camera.is_follow_enabled()) {
+    camera.capture_follow_offset();
   }
 }
 
 void CameraController::yaw(Render::GL::Camera &camera, float degrees) {
   camera.yaw(degrees);
-  if (camera.isFollowEnabled()) {
-    camera.captureFollowOffset();
+  if (camera.is_follow_enabled()) {
+    camera.capture_follow_offset();
   }
 }
 
 void CameraController::orbit(Render::GL::Camera &camera, float yaw_deg,
                              float pitch_deg) {
   camera.orbit(yaw_deg, pitch_deg);
-  if (camera.isFollowEnabled()) {
-    camera.captureFollowOffset();
+  if (camera.is_follow_enabled()) {
+    camera.capture_follow_offset();
   }
 }
 
-void CameraController::zoomDistance(Render::GL::Camera &camera, float delta) {
-  camera.zoomDistance(delta);
-  if (camera.isFollowEnabled()) {
-    camera.captureFollowOffset();
+void CameraController::zoom_distance(Render::GL::Camera &camera, float delta) {
+  camera.zoom_distance(delta);
+  if (camera.is_follow_enabled()) {
+    camera.capture_follow_offset();
   }
 }
 
-void CameraController::setFollowEnabled(Render::GL::Camera &camera,
-                                        bool enable) {
-  camera.setFollowEnabled(enable);
+void CameraController::set_follow_enabled(Render::GL::Camera &camera,
+                                          bool enable) {
+  camera.set_follow_enabled(enable);
 }
 
-void CameraController::setFollowLerp(Render::GL::Camera &camera, float alpha) {
-  camera.setFollowLerp(alpha);
+void CameraController::set_follow_lerp(Render::GL::Camera &camera,
+                                       float alpha) {
+  camera.set_follow_lerp(alpha);
 }
 
 } // namespace Game::Systems

+ 4 - 4
game/systems/camera_controller.h

@@ -10,12 +10,12 @@ class CameraController {
 public:
   static void move(Render::GL::Camera &camera, float dx, float dz);
   static void elevate(Render::GL::Camera &camera, float dy);
-  static void moveUp(Render::GL::Camera &camera, float dy);
+  static void move_up(Render::GL::Camera &camera, float dy);
   static void yaw(Render::GL::Camera &camera, float degrees);
   static void orbit(Render::GL::Camera &camera, float yaw_deg, float pitch_deg);
-  static void zoomDistance(Render::GL::Camera &camera, float delta);
-  static void setFollowEnabled(Render::GL::Camera &camera, bool enable);
-  static void setFollowLerp(Render::GL::Camera &camera, float alpha);
+  static void zoom_distance(Render::GL::Camera &camera, float delta);
+  static void set_follow_enabled(Render::GL::Camera &camera, bool enable);
+  static void set_follow_lerp(Render::GL::Camera &camera, float alpha);
 };
 
 } // namespace Game::Systems

+ 2 - 2
game/systems/camera_follow_system.cpp

@@ -27,7 +27,7 @@ void CameraFollowSystem::update(Engine::Core::World &world,
   if (count > 0) {
     QVector3D const center = sum / float(count);
     camera.setTarget(center);
-    camera.updateFollow(center);
+    camera.update_follow(center);
   }
 }
 
@@ -51,7 +51,7 @@ void CameraFollowSystem::snapToSelection(Engine::Core::World &world,
   if (count > 0) {
     QVector3D const target = sum / float(count);
     camera.setTarget(target);
-    camera.captureFollowOffset();
+    camera.capture_follow_offset();
   }
 }
 

+ 8 - 8
game/systems/camera_service.cpp

@@ -30,11 +30,11 @@ void CameraService::move(Render::GL::Camera &camera, float dx, float dz) {
 void CameraService::elevate(Render::GL::Camera &camera, float dy) {
   float const distance = camera.get_distance();
   float const scale = std::clamp(distance * 0.05F, 0.1F, 5.0F);
-  m_controller->moveUp(camera, dy * scale);
+  m_controller->move_up(camera, dy * scale);
 }
 
 void CameraService::zoom(Render::GL::Camera &camera, float delta) {
-  m_controller->zoomDistance(camera, delta);
+  m_controller->zoom_distance(camera, delta);
 }
 
 auto CameraService::get_distance(const Render::GL::Camera &camera) -> float {
@@ -57,14 +57,14 @@ void CameraService::orbit_direction(Render::GL::Camera &camera, int direction,
                                     bool shift) {
   const auto &cam_config = Game::GameConfig::instance().camera();
   float const step =
-      shift ? cam_config.orbitStepShift : cam_config.orbitStepNormal;
+      shift ? cam_config.orbit_step_shift : cam_config.orbit_step_normal;
   float const pitch = step * float(direction);
   orbit(camera, 0.0F, pitch);
 }
 
 void CameraService::follow_selection(Render::GL::Camera &camera,
                                      Engine::Core::World &world, bool enable) {
-  m_controller->setFollowEnabled(camera, enable);
+  m_controller->set_follow_enabled(camera, enable);
 
   if (enable) {
     if (auto *selection_system = world.get_system<SelectionSystem>()) {
@@ -77,9 +77,9 @@ void CameraService::follow_selection(Render::GL::Camera &camera,
   }
 }
 
-void CameraService::setFollowLerp(Render::GL::Camera &camera, float alpha) {
+void CameraService::set_follow_lerp(Render::GL::Camera &camera, float alpha) {
   float const a = std::clamp(alpha, 0.0F, 1.0F);
-  m_controller->setFollowLerp(camera, a);
+  m_controller->set_follow_lerp(camera, a);
 }
 
 void CameraService::resetCamera(Render::GL::Camera &camera,
@@ -114,8 +114,8 @@ void CameraService::snapToEntity(Render::GL::Camera &camera,
   if (auto *t = entity.get_component<Engine::Core::TransformComponent>()) {
     QVector3D const center(t->position.x, t->position.y, t->position.z);
     const auto &cam_config = Game::GameConfig::instance().camera();
-    camera.setRTSView(center, cam_config.defaultDistance,
-                      cam_config.defaultPitch, cam_config.defaultYaw);
+    camera.set_rts_view(center, cam_config.default_distance,
+                        cam_config.default_pitch, cam_config.default_yaw);
   }
 }
 

+ 1 - 1
game/systems/camera_service.h

@@ -30,7 +30,7 @@ public:
   void orbit_direction(Render::GL::Camera &camera, int direction, bool shift);
   void follow_selection(Render::GL::Camera &camera, Engine::Core::World &world,
                         bool enable);
-  void setFollowLerp(Render::GL::Camera &camera, float alpha);
+  void set_follow_lerp(Render::GL::Camera &camera, float alpha);
   [[nodiscard]] static auto
   get_distance(const Render::GL::Camera &camera) -> float;
   static void resetCamera(Render::GL::Camera &camera,

+ 3 - 2
game/systems/capture_system.cpp

@@ -76,7 +76,8 @@ void CaptureSystem::transferBarrackOwnership(Engine::Core::World *,
 
   auto &nation_registry = NationRegistry::instance();
   if (!Game::Core::isNeutralOwner(new_owner_id)) {
-    if (const auto *nation = nation_registry.getNationForPlayer(new_owner_id)) {
+    if (const auto *nation =
+            nation_registry.get_nation_for_player(new_owner_id)) {
       unit->nation_id = nation->id;
     } else {
       unit->nation_id = nation_registry.default_nation_id();
@@ -90,7 +91,7 @@ void CaptureSystem::transferBarrackOwnership(Engine::Core::World *,
   renderable->color[1] = tc.y();
   renderable->color[2] = tc.z();
 
-  Game::Systems::BuildingCollisionRegistry::instance().updateBuildingOwner(
+  Game::Systems::BuildingCollisionRegistry::instance().update_building_owner(
       barrack->get_id(), new_owner_id);
 
   if (!Game::Core::isNeutralOwner(new_owner_id) && (prod == nullptr)) {

+ 16 - 16
game/systems/combat_system.cpp

@@ -20,12 +20,12 @@
 namespace Game::Systems {
 
 void CombatSystem::update(Engine::Core::World *world, float delta_time) {
-  processAttacks(world, delta_time);
-  processAutoEngagement(world, delta_time);
+  process_attacks(world, delta_time);
+  process_auto_engagement(world, delta_time);
 }
 
-void CombatSystem::processAttacks(Engine::Core::World *world,
-                                  float delta_time) {
+void CombatSystem::process_attacks(Engine::Core::World *world,
+                                   float delta_time) {
   auto units = world->get_entities_with<Engine::Core::UnitComponent>();
 
   auto *arrow_sys = world->get_system<ArrowSystem>();
@@ -166,8 +166,8 @@ void CombatSystem::processAttacks(Engine::Core::World *world,
             target->get_component<Engine::Core::UnitComponent>();
 
         auto &owner_registry = Game::Systems::OwnerRegistry::instance();
-        bool const is_ally = owner_registry.areAllies(attacker_unit->owner_id,
-                                                      target_unit->owner_id);
+        bool const is_ally = owner_registry.are_allies(attacker_unit->owner_id,
+                                                       target_unit->owner_id);
 
         if ((target_unit != nullptr) && target_unit->health > 0 &&
             target_unit->owner_id != attacker_unit->owner_id && !is_ally) {
@@ -395,8 +395,8 @@ void CombatSystem::processAttacks(Engine::Core::World *world,
           continue;
         }
 
-        if (owner_registry.areAllies(attacker_unit->owner_id,
-                                     target_unit->owner_id)) {
+        if (owner_registry.are_allies(attacker_unit->owner_id,
+                                      target_unit->owner_id)) {
           continue;
         }
 
@@ -696,7 +696,7 @@ void CombatSystem::dealDamage(Engine::Core::World *world,
       }
 
       if (target->has_component<Engine::Core::BuildingComponent>()) {
-        BuildingCollisionRegistry::instance().unregisterBuilding(
+        BuildingCollisionRegistry::instance().unregister_building(
             target->get_id());
       }
 
@@ -759,8 +759,8 @@ void CombatSystem::updateCombatMode(
       continue;
     }
 
-    if (owner_registry.areAllies(attacker_unit->owner_id,
-                                 target_unit->owner_id)) {
+    if (owner_registry.are_allies(attacker_unit->owner_id,
+                                  target_unit->owner_id)) {
       continue;
     }
 
@@ -816,8 +816,8 @@ void CombatSystem::updateCombatMode(
   }
 }
 
-void CombatSystem::processAutoEngagement(Engine::Core::World *world,
-                                         float delta_time) {
+void CombatSystem::process_auto_engagement(Engine::Core::World *world,
+                                           float delta_time) {
   auto units = world->get_entities_with<Engine::Core::UnitComponent>();
 
   for (auto it = m_engagementCooldowns.begin();
@@ -861,7 +861,7 @@ void CombatSystem::processAutoEngagement(Engine::Core::World *world,
       continue;
     }
 
-    if (!isUnitIdle(unit)) {
+    if (!is_unit_idle(unit)) {
       continue;
     }
 
@@ -886,7 +886,7 @@ void CombatSystem::processAutoEngagement(Engine::Core::World *world,
   }
 }
 
-auto CombatSystem::isUnitIdle(Engine::Core::Entity *unit) -> bool {
+auto CombatSystem::is_unit_idle(Engine::Core::Entity *unit) -> bool {
 
   auto *hold_mode = unit->get_component<Engine::Core::HoldModeComponent>();
   if ((hold_mode != nullptr) && hold_mode->active) {
@@ -946,7 +946,7 @@ auto CombatSystem::findNearestEnemy(Engine::Core::Entity *unit,
     if (target_unit->owner_id == unit_comp->owner_id) {
       continue;
     }
-    if (owner_registry.areAllies(unit_comp->owner_id, target_unit->owner_id)) {
+    if (owner_registry.are_allies(unit_comp->owner_id, target_unit->owner_id)) {
       continue;
     }
 

+ 4 - 4
game/systems/combat_system.h

@@ -15,7 +15,7 @@ public:
   void update(Engine::Core::World *world, float delta_time) override;
 
 private:
-  static void processAttacks(Engine::Core::World *world, float delta_time);
+  static void process_attacks(Engine::Core::World *world, float delta_time);
   static void updateCombatMode(Engine::Core::Entity *attacker,
                                Engine::Core::World *world,
                                Engine::Core::AttackComponent *attack_comp);
@@ -24,8 +24,8 @@ private:
   static void dealDamage(Engine::Core::World *world,
                          Engine::Core::Entity *target, int damage,
                          Engine::Core::EntityID attackerId = 0);
-  void processAutoEngagement(Engine::Core::World *world, float delta_time);
-  static auto isUnitIdle(Engine::Core::Entity *unit) -> bool;
+  void process_auto_engagement(Engine::Core::World *world, float delta_time);
+  static auto is_unit_idle(Engine::Core::Entity *unit) -> bool;
   static auto findNearestEnemy(Engine::Core::Entity *unit,
                                Engine::Core::World *world,
                                float max_range) -> Engine::Core::Entity *;
@@ -34,4 +34,4 @@ private:
   static constexpr float ENGAGEMENT_COOLDOWN = 0.5F;
 };
 
-} // namespace Game::Systems
+} // namespace Game::Systems

+ 2 - 2
game/systems/formation_system.cpp

@@ -99,7 +99,7 @@ void FormationSystem::initializeDefaults() {
                     std::make_unique<BarbarianFormation>());
 }
 
-auto FormationSystem::getFormationPositions(
+auto FormationSystem::get_formation_positions(
     FormationType type, int unit_count, const QVector3D &center,
     float base_spacing) -> std::vector<QVector3D> {
   auto it = m_formations.find(type);
@@ -117,7 +117,7 @@ void FormationSystem::registerFormation(FormationType type,
   m_formations[type] = std::move(formation);
 }
 
-auto FormationSystem::getFormation(FormationType type) const
+auto FormationSystem::get_formation(FormationType type) const
     -> const IFormation * {
   auto it = m_formations.find(type);
   if (it == m_formations.end()) {

+ 4 - 4
game/systems/formation_system.h

@@ -60,14 +60,14 @@ public:
   static auto instance() -> FormationSystem &;
 
   auto
-  getFormationPositions(FormationType type, int unit_count,
-                        const QVector3D &center,
-                        float base_spacing = 1.0F) -> std::vector<QVector3D>;
+  get_formation_positions(FormationType type, int unit_count,
+                          const QVector3D &center,
+                          float base_spacing = 1.0F) -> std::vector<QVector3D>;
 
   void registerFormation(FormationType type,
                          std::unique_ptr<IFormation> formation);
 
-  auto getFormation(FormationType type) const -> const IFormation *;
+  auto get_formation(FormationType type) const -> const IFormation *;
 
 private:
   FormationSystem();

+ 13 - 13
game/systems/game_state_serializer.cpp

@@ -23,10 +23,10 @@ auto GameStateSerializer::buildMetadata(
   metadata["player_unit_id"] = static_cast<qint64>(level.player_unit_id);
 
   metadata["gameMaxTroopsPerPlayer"] =
-      Game::GameConfig::instance().getMaxTroopsPerPlayer();
+      Game::GameConfig::instance().get_max_troops_per_player();
 
   const auto &terrain_service = Game::Map::TerrainService::instance();
-  if (const auto *height_map = terrain_service.getHeightMap()) {
+  if (const auto *height_map = terrain_service.get_height_map()) {
     metadata["grid_width"] = height_map->getWidth();
     metadata["grid_height"] = height_map->getHeight();
     metadata["tile_size"] = height_map->getTileSize();
@@ -39,10 +39,10 @@ auto GameStateSerializer::buildMetadata(
     camera_obj["target"] =
         App::JsonUtils::vec3ToJsonArray(camera->get_target());
     camera_obj["distance"] = camera->get_distance();
-    camera_obj["pitch_deg"] = camera->getPitchDeg();
-    camera_obj["fov"] = camera->getFOV();
-    camera_obj["near"] = camera->getNear();
-    camera_obj["far"] = camera->getFar();
+    camera_obj["pitch_deg"] = camera->get_pitch_deg();
+    camera_obj["fov"] = camera->get_fov();
+    camera_obj["near"] = camera->get_near();
+    camera_obj["far"] = camera->get_far();
     metadata["camera"] = camera_obj;
   }
 
@@ -74,17 +74,17 @@ void GameStateSerializer::restoreCameraFromMetadata(const QJsonObject &metadata,
   camera->lookAt(position, target, QVector3D(0.0F, 1.0F, 0.0F));
 
   const float near_plane =
-      static_cast<float>(camera_obj.value("near").toDouble(camera->getNear()));
+      static_cast<float>(camera_obj.value("near").toDouble(camera->get_near()));
   const float far_plane =
-      static_cast<float>(camera_obj.value("far").toDouble(camera->getFar()));
+      static_cast<float>(camera_obj.value("far").toDouble(camera->get_far()));
   const float fov =
-      static_cast<float>(camera_obj.value("fov").toDouble(camera->getFOV()));
+      static_cast<float>(camera_obj.value("fov").toDouble(camera->get_fov()));
 
-  float aspect = camera->getAspect();
+  float aspect = camera->get_aspect();
   if (viewport_height > 0) {
     aspect = float(viewport_width) / float(std::max(1, viewport_height));
   }
-  camera->setPerspective(fov, aspect, near_plane, far_plane);
+  camera->set_perspective(fov, aspect, near_plane, far_plane);
 }
 
 void GameStateSerializer::restoreRuntimeFromMetadata(
@@ -145,10 +145,10 @@ void GameStateSerializer::restoreLevelFromMetadata(const QJsonObject &metadata,
   int max_troops = metadata.value("max_troops_per_player")
                        .toInt(level.max_troops_per_player);
   if (max_troops <= 0) {
-    max_troops = Game::GameConfig::instance().getMaxTroopsPerPlayer();
+    max_troops = Game::GameConfig::instance().get_max_troops_per_player();
   }
   level.max_troops_per_player = max_troops;
-  Game::GameConfig::instance().setMaxTroopsPerPlayer(max_troops);
+  Game::GameConfig::instance().set_max_troops_per_player(max_troops);
 }
 
 } // namespace Game::Systems

+ 1 - 1
game/systems/global_stats_registry.cpp

@@ -102,7 +102,7 @@ void GlobalStatsRegistry::on_unit_died(
 
     auto &owner_registry = OwnerRegistry::instance();
 
-    if (owner_registry.areEnemies(event.killer_owner_id, event.owner_id)) {
+    if (owner_registry.are_enemies(event.killer_owner_id, event.owner_id)) {
       auto &stats = m_player_stats[event.killer_owner_id];
 
       if (event.spawn_type != Game::Units::SpawnType::Barracks) {

+ 40 - 0
game/systems/healing_beam.cpp

@@ -0,0 +1,40 @@
+#include "healing_beam.h"
+#include <cmath>
+
+namespace Game::Systems {
+
+HealingBeam::HealingBeam(const QVector3D &healer_pos,
+                         const QVector3D &target_pos, const QVector3D &color,
+                         float duration)
+    : m_healer_pos(healer_pos), m_target_pos(target_pos), m_color(color),
+      m_duration(duration) {
+
+  float dist = (target_pos - healer_pos).length();
+  m_beam_width = 0.1F + dist * 0.02F;
+}
+
+auto HealingBeam::get_arc_height() const -> float {
+
+  float dist = (m_target_pos - m_healer_pos).length();
+  return dist * 0.25F;
+}
+
+void HealingBeam::update(float delta_time) {
+  if (!m_active) {
+    return;
+  }
+
+  m_progress += delta_time / m_duration;
+
+  if (m_progress >= 1.3F) {
+    m_active = false;
+  }
+
+  float fade = 1.0F;
+  if (m_progress > 1.0F) {
+    fade = 1.0F - (m_progress - 1.0F) / 0.3F;
+  }
+  m_intensity = fade * (0.8F + 0.2F * std::sin(m_progress * 20.0F));
+}
+
+} // namespace Game::Systems

+ 51 - 0
game/systems/healing_beam.h

@@ -0,0 +1,51 @@
+#pragma once
+
+#include "projectile.h"
+#include <QVector3D>
+
+namespace Game::Systems {
+
+class HealingBeam : public Projectile {
+public:
+  HealingBeam(const QVector3D &healer_pos, const QVector3D &target_pos,
+              const QVector3D &color, float duration = 0.8F);
+
+  auto get_start() const -> QVector3D override { return m_healer_pos; }
+  auto get_end() const -> QVector3D override { return m_target_pos; }
+  auto get_color() const -> QVector3D override { return m_color; }
+  auto get_speed() const -> float override { return 1.0F / m_duration; }
+  auto get_arc_height() const -> float override;
+  auto get_progress() const -> float override { return m_progress; }
+  auto get_scale() const -> float override { return m_beam_width; }
+  auto is_active() const -> bool override { return m_active; }
+
+  auto should_apply_damage() const -> bool override { return false; }
+  auto get_damage() const -> int override { return 0; }
+  auto get_target_id() const -> Engine::Core::EntityID override { return 0; }
+  auto get_attacker_id() const -> Engine::Core::EntityID override { return 0; }
+  auto get_target_locked_position() const -> QVector3D override {
+    return m_target_pos;
+  }
+
+  void update(float delta_time) override;
+  void deactivate() override { m_active = false; }
+
+  auto get_duration() const -> float { return m_duration; }
+  auto get_beam_width() const -> float { return m_beam_width; }
+  auto get_intensity() const -> float { return m_intensity; }
+
+  void set_beam_width(float width) { m_beam_width = width; }
+  void set_intensity(float intensity) { m_intensity = intensity; }
+
+private:
+  QVector3D m_healer_pos;
+  QVector3D m_target_pos;
+  QVector3D m_color;
+  float m_duration;
+  float m_progress{0.0F};
+  float m_beam_width{0.15F};
+  float m_intensity{1.0F};
+  bool m_active{true};
+};
+
+} // namespace Game::Systems

+ 30 - 0
game/systems/healing_beam_system.cpp

@@ -0,0 +1,30 @@
+#include "healing_beam_system.h"
+#include "../core/world.h"
+#include <QDebug>
+#include <algorithm>
+
+namespace Game::Systems {
+
+void HealingBeamSystem::update(Engine::Core::World *, float delta_time) {
+
+  for (auto &beam : m_beams) {
+    if (beam && beam->is_active()) {
+      beam->update(delta_time);
+    }
+  }
+
+  m_beams.erase(std::remove_if(m_beams.begin(), m_beams.end(),
+                               [](const std::unique_ptr<HealingBeam> &beam) {
+                                 return !beam || !beam->is_active();
+                               }),
+                m_beams.end());
+}
+
+void HealingBeamSystem::spawn_beam(const QVector3D &healer_pos,
+                                   const QVector3D &target_pos,
+                                   const QVector3D &color, float duration) {
+  m_beams.push_back(
+      std::make_unique<HealingBeam>(healer_pos, target_pos, color, duration));
+}
+
+} // namespace Game::Systems

+ 38 - 0
game/systems/healing_beam_system.h

@@ -0,0 +1,38 @@
+#pragma once
+
+#include "../core/system.h"
+#include "healing_beam.h"
+#include <QVector3D>
+#include <memory>
+#include <vector>
+
+namespace Engine::Core {
+class World;
+}
+
+namespace Game::Systems {
+
+class HealingBeamSystem : public Engine::Core::System {
+public:
+  HealingBeamSystem() = default;
+  ~HealingBeamSystem() override = default;
+
+  void update(Engine::Core::World *world, float delta_time) override;
+
+  void spawn_beam(const QVector3D &healer_pos, const QVector3D &target_pos,
+                  const QVector3D &color = QVector3D(0.3F, 1.0F, 0.5F),
+                  float duration = 0.8F);
+
+  auto get_beams() const -> const std::vector<std::unique_ptr<HealingBeam>> & {
+    return m_beams;
+  }
+
+  auto get_beam_count() const -> size_t { return m_beams.size(); }
+
+  void clear() { m_beams.clear(); }
+
+private:
+  std::vector<std::unique_ptr<HealingBeam>> m_beams;
+};
+
+} // namespace Game::Systems

+ 17 - 10
game/systems/healing_system.cpp

@@ -1,7 +1,8 @@
 #include "healing_system.h"
 #include "../core/component.h"
 #include "../core/world.h"
-#include "arrow_system.h"
+#include "healing_beam_system.h"
+#include <QDebug>
 #include <cmath>
 #include <qvectornd.h>
 #include <vector>
@@ -9,13 +10,13 @@
 namespace Game::Systems {
 
 void HealingSystem::update(Engine::Core::World *world, float delta_time) {
-  processHealing(world, delta_time);
+  process_healing(world, delta_time);
 }
 
-void HealingSystem::processHealing(Engine::Core::World *world,
-                                   float delta_time) {
+void HealingSystem::process_healing(Engine::Core::World *world,
+                                    float delta_time) {
   auto healers = world->get_entities_with<Engine::Core::HealerComponent>();
-  auto *arrow_system = world->get_system<ArrowSystem>();
+  auto *healing_beam_system = world->get_system<HealingBeamSystem>();
 
   for (auto *healer : healers) {
     if (healer->has_component<Engine::Core::PendingRemovalComponent>()) {
@@ -78,15 +79,17 @@ void HealingSystem::processHealing(Engine::Core::World *world,
           target_unit->health = target_unit->max_health;
         }
 
-        if (arrow_system != nullptr) {
+        if (healing_beam_system != nullptr) {
           QVector3D const healer_pos(healer_transform->position.x,
-                                     healer_transform->position.y + 1.0F,
+                                     healer_transform->position.y + 1.2F,
                                      healer_transform->position.z);
           QVector3D const target_pos(target_transform->position.x,
-                                     target_transform->position.y + 1.0F,
+                                     target_transform->position.y + 0.8F,
                                      target_transform->position.z);
-          arrow_system->spawnArrow(healer_pos, target_pos,
-                                   QVector3D(0.2F, 1.0F, 0.4F), 6.0F);
+
+          QVector3D const heal_color(0.4F, 1.0F, 0.5F);
+          healing_beam_system->spawn_beam(healer_pos, target_pos, heal_color,
+                                          0.7F);
         }
 
         healed_any = true;
@@ -95,6 +98,10 @@ void HealingSystem::processHealing(Engine::Core::World *world,
 
     if (healed_any) {
       healer_comp->time_since_last_heal = 0.0F;
+
+      healer_comp->is_healing_active = true;
+    } else {
+      healer_comp->is_healing_active = false;
     }
   }
 }

+ 1 - 1
game/systems/healing_system.h

@@ -13,7 +13,7 @@ public:
   void update(Engine::Core::World *world, float delta_time) override;
 
 private:
-  void processHealing(Engine::Core::World *world, float delta_time);
+  void process_healing(Engine::Core::World *world, float delta_time);
 };
 
 } // namespace Game::Systems

+ 4 - 4
game/systems/movement_system.cpp

@@ -37,10 +37,10 @@ auto isPointAllowed(const QVector3D &pos,
     if (!pathfinder->isWalkable(grid_x, grid_z)) {
       return false;
     }
-  } else if (terrain_service.isInitialized()) {
+  } else if (terrain_service.is_initialized()) {
     int const grid_x = static_cast<int>(std::round(pos.x()));
     int const grid_z = static_cast<int>(std::round(pos.z()));
-    if (!terrain_service.isWalkable(grid_x, grid_z)) {
+    if (!terrain_service.is_walkable(grid_x, grid_z)) {
       return false;
     }
   }
@@ -349,8 +349,8 @@ void MovementSystem::move_unit(Engine::Core::Entity *entity,
   transform->position.z += movement->vz * delta_time;
 
   auto &terrain = Game::Map::TerrainService::instance();
-  if (terrain.isInitialized()) {
-    const Game::Map::TerrainHeightMap *hm = terrain.getHeightMap();
+  if (terrain.is_initialized()) {
+    const Game::Map::TerrainHeightMap *hm = terrain.get_height_map();
     if (hm != nullptr) {
       const float tile = hm->getTileSize();
       const int w = hm->getWidth();

+ 8 - 7
game/systems/nation_registry.cpp

@@ -87,7 +87,7 @@ auto NationRegistry::instance() -> NationRegistry & {
   return inst;
 }
 
-void NationRegistry::registerNation(Nation nation) {
+void NationRegistry::register_nation(Nation nation) {
 
   auto it = m_nationIndex.find(nation.id);
   if (it != m_nationIndex.end()) {
@@ -109,7 +109,8 @@ auto NationRegistry::getNation(NationID nationId) const -> const Nation * {
   return &m_nations[it->second];
 }
 
-auto NationRegistry::getNationForPlayer(int player_id) const -> const Nation * {
+auto NationRegistry::get_nation_for_player(int player_id) const
+    -> const Nation * {
 
   auto it = m_playerNations.find(player_id);
   if (it != m_playerNations.end()) {
@@ -123,11 +124,11 @@ auto NationRegistry::getNationForPlayer(int player_id) const -> const Nation * {
   return nation;
 }
 
-void NationRegistry::setPlayerNation(int player_id, NationID nationId) {
+void NationRegistry::set_player_nation(int player_id, NationID nationId) {
   m_playerNations[player_id] = nationId;
 }
 
-void NationRegistry::initializeDefaults() {
+void NationRegistry::initialize_defaults() {
   if (m_initialized) {
     return;
   }
@@ -163,12 +164,12 @@ void NationRegistry::initializeDefaults() {
     appendTroop(Game::Units::TroopType::Spearman);
     appendTroop(Game::Units::TroopType::MountedKnight);
 
-    registerNation(std::move(roman));
+    register_nation(std::move(roman));
     m_defaultNation = NationID::RomanRepublic;
   } else {
     NationID fallback_default = nations.front().id;
     for (auto &nation : nations) {
-      registerNation(std::move(nation));
+      register_nation(std::move(nation));
     }
     m_defaultNation = fallback_default;
   }
@@ -184,6 +185,6 @@ void NationRegistry::clear() {
   m_initialized = false;
 }
 
-void NationRegistry::clearPlayerAssignments() { m_playerNations.clear(); }
+void NationRegistry::clear_player_assignments() { m_playerNations.clear(); }
 
 } // namespace Game::Systems

+ 6 - 6
game/systems/nation_registry.h

@@ -75,23 +75,23 @@ class NationRegistry {
 public:
   static auto instance() -> NationRegistry &;
 
-  void registerNation(Nation nation);
+  void register_nation(Nation nation);
 
   auto getNation(NationID nationId) const -> const Nation *;
 
-  auto getNationForPlayer(int player_id) const -> const Nation *;
+  auto get_nation_for_player(int player_id) const -> const Nation *;
 
-  void setPlayerNation(int player_id, NationID nationId);
+  void set_player_nation(int player_id, NationID nationId);
 
-  auto getAllNations() const -> const std::vector<Nation> & {
+  auto get_all_nations() const -> const std::vector<Nation> & {
     return m_nations;
   }
 
-  void initializeDefaults();
+  void initialize_defaults();
 
   void clear();
 
-  void clearPlayerAssignments();
+  void clear_player_assignments();
 
   auto default_nation_id() const -> NationID { return m_defaultNation; }
 

+ 64 - 61
game/systems/owner_registry.cpp

@@ -65,14 +65,14 @@ auto OwnerRegistry::instance() -> OwnerRegistry & {
 
 void OwnerRegistry::clear() {
   m_owners.clear();
-  m_owner_idToIndex.clear();
-  m_nextOwnerId = 1;
-  m_localPlayerId = 1;
+  m_owner_id_to_index.clear();
+  m_next_owner_id = 1;
+  m_local_player_id = 1;
 }
 
-auto OwnerRegistry::registerOwner(OwnerType type,
-                                  const std::string &name) -> int {
-  int const owner_id = m_nextOwnerId++;
+auto OwnerRegistry::register_owner(OwnerType type,
+                                   const std::string &name) -> int {
+  int const owner_id = m_next_owner_id++;
   OwnerInfo info;
   info.owner_id = owner_id;
   info.type = type;
@@ -98,14 +98,14 @@ auto OwnerRegistry::registerOwner(OwnerType type,
 
   size_t const index = m_owners.size();
   m_owners.push_back(info);
-  m_owner_idToIndex[owner_id] = index;
+  m_owner_id_to_index[owner_id] = index;
 
   return owner_id;
 }
 
-void OwnerRegistry::registerOwnerWithId(int owner_id, OwnerType type,
-                                        const std::string &name) {
-  if (m_owner_idToIndex.find(owner_id) != m_owner_idToIndex.end()) {
+void OwnerRegistry::register_owner_with_id(int owner_id, OwnerType type,
+                                           const std::string &name) {
+  if (m_owner_id_to_index.find(owner_id) != m_owner_id_to_index.end()) {
     return;
   }
 
@@ -134,56 +134,58 @@ void OwnerRegistry::registerOwnerWithId(int owner_id, OwnerType type,
 
   size_t const index = m_owners.size();
   m_owners.push_back(info);
-  m_owner_idToIndex[owner_id] = index;
+  m_owner_id_to_index[owner_id] = index;
 
-  if (owner_id >= m_nextOwnerId) {
-    m_nextOwnerId = owner_id + 1;
+  if (owner_id >= m_next_owner_id) {
+    m_next_owner_id = owner_id + 1;
   }
 }
 
-void OwnerRegistry::setLocalPlayerId(int player_id) {
-  m_localPlayerId = player_id;
+void OwnerRegistry::set_local_player_id(int player_id) {
+  m_local_player_id = player_id;
 }
 
-auto OwnerRegistry::getLocalPlayerId() const -> int { return m_localPlayerId; }
+auto OwnerRegistry::get_local_player_id() const -> int {
+  return m_local_player_id;
+}
 
-auto OwnerRegistry::isPlayer(int owner_id) const -> bool {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it == m_owner_idToIndex.end()) {
+auto OwnerRegistry::is_player(int owner_id) const -> bool {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it == m_owner_id_to_index.end()) {
     return false;
   }
   return m_owners[it->second].type == OwnerType::Player;
 }
 
-auto OwnerRegistry::isAI(int owner_id) const -> bool {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it == m_owner_idToIndex.end()) {
+auto OwnerRegistry::is_ai(int owner_id) const -> bool {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it == m_owner_id_to_index.end()) {
     return false;
   }
   return m_owners[it->second].type == OwnerType::AI;
 }
 
-auto OwnerRegistry::getOwnerType(int owner_id) const -> OwnerType {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it == m_owner_idToIndex.end()) {
+auto OwnerRegistry::get_owner_type(int owner_id) const -> OwnerType {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it == m_owner_id_to_index.end()) {
     return OwnerType::Neutral;
   }
   return m_owners[it->second].type;
 }
 
-auto OwnerRegistry::getOwnerName(int owner_id) const -> std::string {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it == m_owner_idToIndex.end()) {
+auto OwnerRegistry::get_owner_name(int owner_id) const -> std::string {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it == m_owner_id_to_index.end()) {
     return "Unknown";
   }
   return m_owners[it->second].name;
 }
 
-auto OwnerRegistry::getAllOwners() const -> const std::vector<OwnerInfo> & {
+auto OwnerRegistry::get_all_owners() const -> const std::vector<OwnerInfo> & {
   return m_owners;
 }
 
-auto OwnerRegistry::getPlayerOwnerIds() const -> std::vector<int> {
+auto OwnerRegistry::get_player_owner_ids() const -> std::vector<int> {
   std::vector<int> result;
   for (const auto &owner : m_owners) {
     if (owner.type == OwnerType::Player) {
@@ -193,7 +195,7 @@ auto OwnerRegistry::getPlayerOwnerIds() const -> std::vector<int> {
   return result;
 }
 
-auto OwnerRegistry::getAIOwnerIds() const -> std::vector<int> {
+auto OwnerRegistry::get_ai_owner_ids() const -> std::vector<int> {
   std::vector<int> result;
   for (const auto &owner : m_owners) {
     if (owner.type == OwnerType::AI) {
@@ -203,51 +205,51 @@ auto OwnerRegistry::getAIOwnerIds() const -> std::vector<int> {
   return result;
 }
 
-void OwnerRegistry::setOwnerTeam(int owner_id, int team_id) {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it != m_owner_idToIndex.end()) {
+void OwnerRegistry::set_owner_team(int owner_id, int team_id) {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it != m_owner_id_to_index.end()) {
     m_owners[it->second].team_id = team_id;
   }
 }
 
-auto OwnerRegistry::getOwnerTeam(int owner_id) const -> int {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it == m_owner_idToIndex.end()) {
+auto OwnerRegistry::get_owner_team(int owner_id) const -> int {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it == m_owner_id_to_index.end()) {
     return 0;
   }
   return m_owners[it->second].team_id;
 }
 
-auto OwnerRegistry::areAllies(int owner_id1, int owner_id2) const -> bool {
+auto OwnerRegistry::are_allies(int owner_id1, int owner_id2) const -> bool {
 
   if (owner_id1 == owner_id2) {
     return true;
   }
 
-  int const team1 = getOwnerTeam(owner_id1);
-  int const team2 = getOwnerTeam(owner_id2);
+  int const team1 = get_owner_team(owner_id1);
+  int const team2 = get_owner_team(owner_id2);
 
   bool const result = (team1 == team2);
 
   return result;
 }
 
-auto OwnerRegistry::areEnemies(int owner_id1, int owner_id2) const -> bool {
+auto OwnerRegistry::are_enemies(int owner_id1, int owner_id2) const -> bool {
 
   if (owner_id1 == owner_id2) {
     return false;
   }
 
-  if (areAllies(owner_id1, owner_id2)) {
+  if (are_allies(owner_id1, owner_id2)) {
     return false;
   }
 
   return true;
 }
 
-auto OwnerRegistry::getAlliesOf(int owner_id) const -> std::vector<int> {
+auto OwnerRegistry::get_allies_of(int owner_id) const -> std::vector<int> {
   std::vector<int> result;
-  int const my_team = getOwnerTeam(owner_id);
+  int const my_team = get_owner_team(owner_id);
 
   if (my_team == 0) {
     return result;
@@ -261,37 +263,38 @@ auto OwnerRegistry::getAlliesOf(int owner_id) const -> std::vector<int> {
   return result;
 }
 
-auto OwnerRegistry::getEnemiesOf(int owner_id) const -> std::vector<int> {
+auto OwnerRegistry::get_enemies_of(int owner_id) const -> std::vector<int> {
   std::vector<int> result;
 
   for (const auto &owner : m_owners) {
-    if (areEnemies(owner_id, owner.owner_id)) {
+    if (are_enemies(owner_id, owner.owner_id)) {
       result.push_back(owner.owner_id);
     }
   }
   return result;
 }
 
-void OwnerRegistry::setOwnerColor(int owner_id, float r, float g, float b) {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it != m_owner_idToIndex.end()) {
+void OwnerRegistry::set_owner_color(int owner_id, float r, float g, float b) {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it != m_owner_id_to_index.end()) {
     m_owners[it->second].color = {r, g, b};
   }
 }
 
-auto OwnerRegistry::getOwnerColor(int owner_id) const -> std::array<float, 3> {
-  auto it = m_owner_idToIndex.find(owner_id);
-  if (it != m_owner_idToIndex.end()) {
+auto OwnerRegistry::get_owner_color(int owner_id) const
+    -> std::array<float, 3> {
+  auto it = m_owner_id_to_index.find(owner_id);
+  if (it != m_owner_id_to_index.end()) {
     return m_owners[it->second].color;
   }
 
   return {0.8F, 0.9F, 1.0F};
 }
 
-auto OwnerRegistry::toJson() const -> QJsonObject {
+auto OwnerRegistry::to_json() const -> QJsonObject {
   QJsonObject root;
-  root["nextOwnerId"] = m_nextOwnerId;
-  root["localPlayerId"] = m_localPlayerId;
+  root["nextOwnerId"] = m_next_owner_id;
+  root["localPlayerId"] = m_local_player_id;
 
   QJsonArray owners_array;
   for (const auto &owner : m_owners) {
@@ -308,11 +311,11 @@ auto OwnerRegistry::toJson() const -> QJsonObject {
   return root;
 }
 
-void OwnerRegistry::fromJson(const QJsonObject &json) {
+void OwnerRegistry::from_json(const QJsonObject &json) {
   clear();
 
-  m_nextOwnerId = json["nextOwnerId"].toInt(1);
-  m_localPlayerId = json["localPlayerId"].toInt(1);
+  m_next_owner_id = json["nextOwnerId"].toInt(1);
+  m_local_player_id = json["localPlayerId"].toInt(1);
 
   const auto owners_array = json["owners"].toArray();
   m_owners.reserve(owners_array.size());
@@ -329,12 +332,12 @@ void OwnerRegistry::fromJson(const QJsonObject &json) {
 
     const size_t index = m_owners.size();
     m_owners.push_back(info);
-    m_owner_idToIndex[info.owner_id] = index;
+    m_owner_id_to_index[info.owner_id] = index;
   }
 
   for (const auto &owner : m_owners) {
-    if (owner.owner_id >= m_nextOwnerId) {
-      m_nextOwnerId = owner.owner_id + 1;
+    if (owner.owner_id >= m_next_owner_id) {
+      m_next_owner_id = owner.owner_id + 1;
     }
   }
 }

+ 25 - 25
game/systems/owner_registry.h

@@ -29,41 +29,41 @@ public:
 
   void clear();
 
-  auto registerOwner(OwnerType type, const std::string &name = "") -> int;
+  auto register_owner(OwnerType type, const std::string &name = "") -> int;
 
-  void registerOwnerWithId(int owner_id, OwnerType type,
-                           const std::string &name = "");
+  void register_owner_with_id(int owner_id, OwnerType type,
+                              const std::string &name = "");
 
-  void setLocalPlayerId(int player_id);
+  void set_local_player_id(int player_id);
 
-  auto getLocalPlayerId() const -> int;
+  auto get_local_player_id() const -> int;
 
-  auto isPlayer(int owner_id) const -> bool;
+  auto is_player(int owner_id) const -> bool;
 
-  auto isAI(int owner_id) const -> bool;
+  auto is_ai(int owner_id) const -> bool;
 
-  auto getOwnerType(int owner_id) const -> OwnerType;
+  auto get_owner_type(int owner_id) const -> OwnerType;
 
-  auto getOwnerName(int owner_id) const -> std::string;
+  auto get_owner_name(int owner_id) const -> std::string;
 
-  auto getAllOwners() const -> const std::vector<OwnerInfo> &;
+  auto get_all_owners() const -> const std::vector<OwnerInfo> &;
 
-  auto getPlayerOwnerIds() const -> std::vector<int>;
+  auto get_player_owner_ids() const -> std::vector<int>;
 
-  auto getAIOwnerIds() const -> std::vector<int>;
+  auto get_ai_owner_ids() const -> std::vector<int>;
 
-  void setOwnerTeam(int owner_id, int team_id);
-  auto getOwnerTeam(int owner_id) const -> int;
-  auto areAllies(int owner_id1, int owner_id2) const -> bool;
-  auto areEnemies(int owner_id1, int owner_id2) const -> bool;
-  auto getAlliesOf(int owner_id) const -> std::vector<int>;
-  auto getEnemiesOf(int owner_id) const -> std::vector<int>;
+  void set_owner_team(int owner_id, int team_id);
+  auto get_owner_team(int owner_id) const -> int;
+  auto are_allies(int owner_id1, int owner_id2) const -> bool;
+  auto are_enemies(int owner_id1, int owner_id2) const -> bool;
+  auto get_allies_of(int owner_id) const -> std::vector<int>;
+  auto get_enemies_of(int owner_id) const -> std::vector<int>;
 
-  void setOwnerColor(int owner_id, float r, float g, float b);
-  auto getOwnerColor(int owner_id) const -> std::array<float, 3>;
+  void set_owner_color(int owner_id, float r, float g, float b);
+  auto get_owner_color(int owner_id) const -> std::array<float, 3>;
 
-  auto toJson() const -> QJsonObject;
-  void fromJson(const QJsonObject &json);
+  auto to_json() const -> QJsonObject;
+  void from_json(const QJsonObject &json);
 
 private:
   OwnerRegistry() = default;
@@ -71,10 +71,10 @@ private:
   OwnerRegistry(const OwnerRegistry &) = delete;
   auto operator=(const OwnerRegistry &) -> OwnerRegistry & = delete;
 
-  int m_nextOwnerId = 1;
-  int m_localPlayerId = 1;
+  int m_next_owner_id = 1;
+  int m_local_player_id = 1;
   std::vector<OwnerInfo> m_owners;
-  std::unordered_map<int, size_t> m_owner_idToIndex;
+  std::unordered_map<int, size_t> m_owner_id_to_index;
 };
 
 } // namespace Game::Systems

+ 3 - 3
game/systems/pathfinding.cpp

@@ -71,9 +71,9 @@ void Pathfinding::updateBuildingObstacles() {
   }
 
   auto &terrain_service = Game::Map::TerrainService::instance();
-  if (terrain_service.isInitialized()) {
+  if (terrain_service.is_initialized()) {
     const Game::Map::TerrainHeightMap *height_map =
-        terrain_service.getHeightMap();
+        terrain_service.get_height_map();
     const int terrain_width =
         (height_map != nullptr) ? height_map->getWidth() : 0;
     const int terrain_height =
@@ -83,7 +83,7 @@ void Pathfinding::updateBuildingObstacles() {
       for (int x = 0; x < m_width; ++x) {
         bool blocked = false;
         if (x < terrain_width && z < terrain_height) {
-          blocked = !terrain_service.isWalkable(x, z);
+          blocked = !terrain_service.is_walkable(x, z);
         } else {
           blocked = true;
         }

+ 4 - 4
game/systems/picking_service.cpp

@@ -70,17 +70,17 @@ auto PickingService::update_hover(float sx, float sy,
       pick_single(sx, sy, world, camera, view_w, view_h, 0, false);
 
   if (picked != 0 && picked != prev_hover) {
-    m_hoverGraceTicks = 6;
+    m_hover_grace_ticks = 6;
   }
 
   Engine::Core::EntityID current_hover = picked;
-  if (current_hover == 0 && prev_hover != 0 && m_hoverGraceTicks > 0) {
+  if (current_hover == 0 && prev_hover != 0 && m_hover_grace_ticks > 0) {
 
     current_hover = prev_hover;
   }
 
-  if (m_hoverGraceTicks > 0) {
-    --m_hoverGraceTicks;
+  if (m_hover_grace_ticks > 0) {
+    --m_hover_grace_ticks;
   }
   m_prev_hoverId = current_hover;
   return current_hover;

+ 1 - 1
game/systems/picking_service.h

@@ -56,7 +56,7 @@ public:
 
 private:
   Engine::Core::EntityID m_prev_hoverId = 0;
-  int m_hoverGraceTicks = 0;
+  int m_hover_grace_ticks = 0;
   static auto project_bounds(const Render::GL::Camera &cam,
                              const QVector3D &center, float hx, float hz,
                              int view_w, int view_h, QRectF &out) -> bool;

+ 3 - 2
game/systems/production_service.cpp

@@ -35,7 +35,7 @@ namespace {
 auto resolve_nation_id(const Engine::Core::UnitComponent *unit,
                        int owner_id) -> Game::Systems::NationID {
   auto &registry = NationRegistry::instance();
-  if (const auto *nation = registry.getNationForPlayer(owner_id)) {
+  if (const auto *nation = registry.get_nation_for_player(owner_id)) {
     return nation->id;
   }
   return registry.default_nation_id();
@@ -85,7 +85,8 @@ auto ProductionService::startProductionForFirstSelectedBarracks(
 
   int const current_troops =
       Engine::Core::World::count_troops_for_player(owner_id);
-  int const max_troops = Game::GameConfig::instance().getMaxTroopsPerPlayer();
+  int const max_troops =
+      Game::GameConfig::instance().get_max_troops_per_player();
   if (current_troops + production_cost > max_troops) {
     return ProductionResult::GlobalTroopLimitReached;
   }

+ 3 - 3
game/systems/production_system.cpp

@@ -32,7 +32,7 @@ void apply_production_profile(Engine::Core::ProductionComponent *prod,
 auto resolve_nation_id(const Engine::Core::UnitComponent *unit,
                        int owner_id) -> Game::Systems::NationID {
   auto &registry = NationRegistry::instance();
-  if (const auto *nation = registry.getNationForPlayer(owner_id)) {
+  if (const auto *nation = registry.get_nation_for_player(owner_id)) {
     return nation->id;
   }
   return registry.default_nation_id();
@@ -82,7 +82,7 @@ void ProductionSystem::update(Engine::Core::World *world, float delta_time) {
         int const current_troops =
             Engine::Core::World::count_troops_for_player(u->owner_id);
         int const max_troops =
-            Game::GameConfig::instance().getMaxTroopsPerPlayer();
+            Game::GameConfig::instance().get_max_troops_per_player();
         if (current_troops + production_cost > max_troops) {
           prod->in_progress = false;
           prod->time_remaining = 0.0F;
@@ -108,7 +108,7 @@ void ProductionSystem::update(Engine::Core::World *world, float delta_time) {
           auto unit = reg->create(sp.spawn_type, *world, sp);
 
           if (unit && prod->rally_set) {
-            unit->moveTo(prod->rally_x, prod->rally_z);
+            unit->move_to(prod->rally_x, prod->rally_z);
           }
         }
 

+ 6 - 6
game/systems/projectile_system.cpp

@@ -22,14 +22,14 @@ void ProjectileSystem::spawn_arrow(const QVector3D &start, const QVector3D &end,
   float arc_height;
   if (is_ballista_bolt) {
 
-    arc_height = std::clamp(m_arrow_config.arcHeightMultiplier * dist * 0.4F,
-                            m_arrow_config.arcHeightMin * 0.5F,
-                            m_arrow_config.arcHeightMax * 0.6F);
+    arc_height = std::clamp(m_arrow_config.arc_height_multiplier * dist * 0.4F,
+                            m_arrow_config.arc_height_min * 0.5F,
+                            m_arrow_config.arc_height_max * 0.6F);
   } else {
 
-    arc_height =
-        std::clamp(m_arrow_config.arcHeightMultiplier * dist,
-                   m_arrow_config.arcHeightMin, m_arrow_config.arcHeightMax);
+    arc_height = std::clamp(m_arrow_config.arc_height_multiplier * dist,
+                            m_arrow_config.arc_height_min,
+                            m_arrow_config.arc_height_max);
   }
   float inv_dist = (dist > 0.001F) ? (1.0F / dist) : 1.0F;
 

+ 7 - 7
game/systems/save_load_service.cpp

@@ -95,9 +95,9 @@ auto SaveLoadService::saveGameToSlot(Engine::Core::World &world,
       return false;
     }
 
-    m_lastMetadata = combined_metadata;
+    m_last_metadata = combined_metadata;
     m_lastTitle = title;
-    m_lastScreenshot = screenshot;
+    m_last_screenshot = screenshot;
     m_last_error.clear();
     return true;
   } catch (const std::exception &e) {
@@ -108,8 +108,8 @@ auto SaveLoadService::saveGameToSlot(Engine::Core::World &world,
   }
 }
 
-auto SaveLoadService::loadGameFromSlot(Engine::Core::World &world,
-                                       const QString &slotName) -> bool {
+auto SaveLoadService::load_game_from_slot(Engine::Core::World &world,
+                                          const QString &slotName) -> bool {
   qInfo() << "Loading game from slot:" << slotName;
 
   try {
@@ -145,9 +145,9 @@ auto SaveLoadService::loadGameFromSlot(Engine::Core::World &world,
     world.clear();
     Engine::Core::Serialization::deserializeWorld(&world, doc);
 
-    m_lastMetadata = metadata;
+    m_last_metadata = metadata;
     m_lastTitle = title;
-    m_lastScreenshot = screenshot;
+    m_last_screenshot = screenshot;
     m_last_error.clear();
     return true;
   } catch (const std::exception &e) {
@@ -158,7 +158,7 @@ auto SaveLoadService::loadGameFromSlot(Engine::Core::World &world,
   }
 }
 
-auto SaveLoadService::getSaveSlots() const -> QVariantList {
+auto SaveLoadService::get_save_slots() const -> QVariantList {
   if (!m_storage) {
     return {};
   }

+ 7 - 7
game/systems/save_load_service.h

@@ -25,10 +25,10 @@ public:
                       const QJsonObject &metadata = {},
                       const QByteArray &screenshot = QByteArray()) -> bool;
 
-  auto loadGameFromSlot(Engine::Core::World &world,
-                        const QString &slotName) -> bool;
+  auto load_game_from_slot(Engine::Core::World &world,
+                           const QString &slotName) -> bool;
 
-  auto getSaveSlots() const -> QVariantList;
+  auto get_save_slots() const -> QVariantList;
 
   auto deleteSaveSlot(const QString &slotName) -> bool;
 
@@ -36,9 +36,9 @@ public:
 
   void clearError() { m_last_error.clear(); }
 
-  auto getLastMetadata() const -> QJsonObject { return m_lastMetadata; }
+  auto getLastMetadata() const -> QJsonObject { return m_last_metadata; }
   auto getLastTitle() const -> QString { return m_lastTitle; }
-  auto getLastScreenshot() const -> QByteArray { return m_lastScreenshot; }
+  auto getLastScreenshot() const -> QByteArray { return m_last_screenshot; }
 
   auto list_campaigns(QString *out_error = nullptr) const -> QVariantList;
   auto get_campaign_progress(const QString &campaign_id,
@@ -56,9 +56,9 @@ private:
   static void ensureSavesDirectoryExists();
 
   mutable QString m_last_error;
-  QJsonObject m_lastMetadata;
+  QJsonObject m_last_metadata;
   QString m_lastTitle;
-  QByteArray m_lastScreenshot;
+  QByteArray m_last_screenshot;
   std::unique_ptr<SaveStorage> m_storage;
 };
 

+ 6 - 6
game/systems/save_storage.cpp

@@ -480,12 +480,12 @@ auto SaveStorage::ensureSchema(QString *out_error) const -> bool {
     return false;
   }
 
-  if (!migrateSchema(current_version, out_error)) {
+  if (!migrate_schema(current_version, out_error)) {
     transaction.rollback();
     return false;
   }
 
-  if (!setSchemaVersion(k_current_schema_version, out_error)) {
+  if (!set_schema_version(k_current_schema_version, out_error)) {
     transaction.rollback();
     return false;
   }
@@ -514,8 +514,8 @@ auto SaveStorage::schemaVersion(QString *out_error) const -> int {
   return 0;
 }
 
-auto SaveStorage::setSchemaVersion(int version,
-                                   QString *out_error) const -> bool {
+auto SaveStorage::set_schema_version(int version,
+                                     QString *out_error) const -> bool {
   QSqlQuery pragma_query(m_database);
   if (!pragma_query.exec(
           QStringLiteral("PRAGMA user_version = %1").arg(version))) {
@@ -566,8 +566,8 @@ auto SaveStorage::createBaseSchema(QString *out_error) const -> bool {
   return true;
 }
 
-auto SaveStorage::migrateSchema(int fromVersion,
-                                QString *out_error) const -> bool {
+auto SaveStorage::migrate_schema(int fromVersion,
+                                 QString *out_error) const -> bool {
   int version = fromVersion;
 
   while (version < k_current_schema_version) {

+ 4 - 4
game/systems/save_storage.h

@@ -41,11 +41,11 @@ private:
   auto open(QString *out_error = nullptr) const -> bool;
   auto ensureSchema(QString *out_error = nullptr) const -> bool;
   auto createBaseSchema(QString *out_error = nullptr) const -> bool;
-  auto migrateSchema(int fromVersion,
-                     QString *out_error = nullptr) const -> bool;
+  auto migrate_schema(int fromVersion,
+                      QString *out_error = nullptr) const -> bool;
   auto schemaVersion(QString *out_error = nullptr) const -> int;
-  auto setSchemaVersion(int version,
-                        QString *out_error = nullptr) const -> bool;
+  auto set_schema_version(int version,
+                          QString *out_error = nullptr) const -> bool;
   auto migrate_to_2(QString *out_error = nullptr) const -> bool;
 
   QString m_database_path;

+ 2 - 2
game/systems/terrain_alignment_system.cpp

@@ -10,7 +10,7 @@ namespace Game::Systems {
 void TerrainAlignmentSystem::update(Engine::Core::World *world, float) {
   auto &terrain_service = Game::Map::TerrainService::instance();
 
-  if (!terrain_service.isInitialized()) {
+  if (!terrain_service.is_initialized()) {
     return;
   }
 
@@ -29,7 +29,7 @@ void TerrainAlignmentSystem::alignEntityToTerrain(
 
   auto &terrain_service = Game::Map::TerrainService::instance();
 
-  float const terrain_height = terrain_service.getTerrainHeight(
+  float const terrain_height = terrain_service.get_terrain_height(
       transform->position.x, transform->position.z);
 
   float entity_base_offset = 0.0F;

+ 1 - 1
game/systems/troop_count_registry.cpp

@@ -26,7 +26,7 @@ void TroopCountRegistry::initialize() {
 
 void TroopCountRegistry::clear() { m_troop_counts.clear(); }
 
-auto TroopCountRegistry::getTroopCount(int owner_id) const -> int {
+auto TroopCountRegistry::get_troop_count(int owner_id) const -> int {
   auto it = m_troop_counts.find(owner_id);
   if (it != m_troop_counts.end()) {
     return it->second;

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