Browse Source

Merge pull request #517 from djeada/copilot/fix-save-restore-mechanism

Add serialization for HoldModeComponent, HealerComponent, and CatapultLoadingComponent with complete test coverage
Adam Djellouli 1 day ago
parent
commit
ff5fe7b71d

+ 8 - 8
app/core/game_engine.cpp

@@ -1197,7 +1197,7 @@ void GameEngine::start_skirmish(const QString &map_path,
 
 void GameEngine::open_settings() {
   if (m_saveLoadService) {
-    m_saveLoadService->openSettings();
+    m_saveLoadService->open_settings();
   }
 }
 
@@ -1224,12 +1224,12 @@ auto GameEngine::load_from_slot(const QString &slot) -> bool {
   m_runtime.loading = true;
 
   if (!m_saveLoadService->load_game_from_slot(*m_world, slot)) {
-    set_error(m_saveLoadService->getLastError());
+    set_error(m_saveLoadService->get_last_error());
     m_runtime.loading = false;
     return false;
   }
 
-  const QJsonObject meta = m_saveLoadService->getLastMetadata();
+  const QJsonObject meta = m_saveLoadService->get_last_metadata();
 
   Game::Systems::GameStateSerializer::restoreLevelFromMetadata(meta, m_level);
   Game::Systems::GameStateSerializer::restoreCameraFromMetadata(
@@ -1288,9 +1288,9 @@ auto GameEngine::save_to_slot(const QString &slot,
       *m_world, m_camera.get(), m_level, runtime_snap);
   meta["title"] = title;
   const QByteArray screenshot = capture_screenshot();
-  if (!m_saveLoadService->saveGameToSlot(*m_world, slot, title,
+  if (!m_saveLoadService->save_game_to_slot(*m_world, slot, title,
                                          m_level.map_name, meta, screenshot)) {
-    set_error(m_saveLoadService->getLastError());
+    set_error(m_saveLoadService->get_last_error());
     return false;
   }
   emit save_slots_changed();
@@ -1314,10 +1314,10 @@ auto GameEngine::delete_save_slot(const QString &slotName) -> bool {
     return false;
   }
 
-  bool const success = m_saveLoadService->deleteSaveSlot(slotName);
+  bool const success = m_saveLoadService->delete_save_slot(slotName);
 
   if (!success) {
-    QString const error = m_saveLoadService->getLastError();
+    QString const error = m_saveLoadService->get_last_error();
     qWarning() << "Failed to delete save slot:" << error;
     set_error(error);
   } else {
@@ -1358,7 +1358,7 @@ auto GameEngine::capture_screenshot() const -> QByteArray { return {}; }
 
 void GameEngine::exit_game() {
   if (m_saveLoadService) {
-    m_saveLoadService->exitGame();
+    m_saveLoadService->exit_game();
   }
 }
 

+ 113 - 22
game/core/serialization.cpp

@@ -35,7 +35,7 @@ namespace Engine::Core {
 
 namespace {
 
-auto combatModeToString(AttackComponent::CombatMode mode) -> QString {
+auto combat_mode_to_string(AttackComponent::CombatMode mode) -> QString {
   switch (mode) {
   case AttackComponent::CombatMode::Melee:
     return "melee";
@@ -47,7 +47,7 @@ auto combatModeToString(AttackComponent::CombatMode mode) -> QString {
   }
 }
 
-auto combatModeFromString(const QString &value) -> AttackComponent::CombatMode {
+auto combat_mode_from_string(const QString &value) -> AttackComponent::CombatMode {
   if (value == "melee") {
     return AttackComponent::CombatMode::Melee;
   }
@@ -57,7 +57,7 @@ auto combatModeFromString(const QString &value) -> AttackComponent::CombatMode {
   return AttackComponent::CombatMode::Auto;
 }
 
-auto serializeColor(const std::array<float, 3> &color) -> QJsonArray {
+auto serialize_color(const std::array<float, 3> &color) -> QJsonArray {
   QJsonArray array;
   array.append(color[0]);
   array.append(color[1]);
@@ -65,7 +65,7 @@ auto serializeColor(const std::array<float, 3> &color) -> QJsonArray {
   return array;
 }
 
-void deserializeColor(const QJsonArray &array, std::array<float, 3> &color) {
+void deserialize_color(const QJsonArray &array, std::array<float, 3> &color) {
   if (array.size() >= 3) {
     color[0] = static_cast<float>(array.at(0).toDouble());
     color[1] = static_cast<float>(array.at(1).toDouble());
@@ -75,7 +75,7 @@ void deserializeColor(const QJsonArray &array, std::array<float, 3> &color) {
 
 } // namespace
 
-auto Serialization::serializeEntity(const Entity *entity) -> QJsonObject {
+auto Serialization::serialize_entity(const Entity *entity) -> QJsonObject {
   QJsonObject entity_obj;
   entity_obj["id"] = static_cast<qint64>(entity->get_id());
 
@@ -106,7 +106,7 @@ auto Serialization::serializeEntity(const Entity *entity) -> QJsonObject {
     }
     renderable_obj["visible"] = renderable->visible;
     renderable_obj["mesh"] = static_cast<int>(renderable->mesh);
-    renderable_obj["color"] = serializeColor(renderable->color);
+    renderable_obj["color"] = serialize_color(renderable->color);
     entity_obj["renderable"] = renderable_obj;
   }
 
@@ -161,8 +161,8 @@ auto Serialization::serializeEntity(const Entity *entity) -> QJsonObject {
     attack_obj["melee_range"] = attack->melee_range;
     attack_obj["melee_damage"] = attack->melee_damage;
     attack_obj["melee_cooldown"] = attack->melee_cooldown;
-    attack_obj["preferred_mode"] = combatModeToString(attack->preferred_mode);
-    attack_obj["current_mode"] = combatModeToString(attack->current_mode);
+    attack_obj["preferred_mode"] = combat_mode_to_string(attack->preferred_mode);
+    attack_obj["current_mode"] = combat_mode_to_string(attack->current_mode);
     attack_obj["can_melee"] = attack->can_melee;
     attack_obj["can_ranged"] = attack->can_ranged;
     attack_obj["max_height_difference"] = attack->max_height_difference;
@@ -238,10 +238,52 @@ auto Serialization::serializeEntity(const Entity *entity) -> QJsonObject {
     entity_obj["capture"] = capture_obj;
   }
 
+  if (const auto *hold_mode = entity->get_component<HoldModeComponent>()) {
+    QJsonObject hold_mode_obj;
+    hold_mode_obj["active"] = hold_mode->active;
+    hold_mode_obj["exit_cooldown"] =
+        static_cast<double>(hold_mode->exit_cooldown);
+    hold_mode_obj["stand_up_duration"] =
+        static_cast<double>(hold_mode->stand_up_duration);
+    entity_obj["hold_mode"] = hold_mode_obj;
+  }
+
+  if (const auto *healer = entity->get_component<HealerComponent>()) {
+    QJsonObject healer_obj;
+    healer_obj["healing_range"] = static_cast<double>(healer->healing_range);
+    healer_obj["healing_amount"] = healer->healing_amount;
+    healer_obj["healing_cooldown"] =
+        static_cast<double>(healer->healing_cooldown);
+    healer_obj["time_since_last_heal"] =
+        static_cast<double>(healer->time_since_last_heal);
+    entity_obj["healer"] = healer_obj;
+  }
+
+  if (const auto *catapult =
+          entity->get_component<CatapultLoadingComponent>()) {
+    QJsonObject catapult_obj;
+    catapult_obj["state"] = static_cast<int>(catapult->state);
+    catapult_obj["loading_time"] = static_cast<double>(catapult->loading_time);
+    catapult_obj["loading_duration"] =
+        static_cast<double>(catapult->loading_duration);
+    catapult_obj["firing_time"] = static_cast<double>(catapult->firing_time);
+    catapult_obj["firing_duration"] =
+        static_cast<double>(catapult->firing_duration);
+    catapult_obj["target_id"] = static_cast<qint64>(catapult->target_id);
+    catapult_obj["target_locked_x"] =
+        static_cast<double>(catapult->target_locked_x);
+    catapult_obj["target_locked_y"] =
+        static_cast<double>(catapult->target_locked_y);
+    catapult_obj["target_locked_z"] =
+        static_cast<double>(catapult->target_locked_z);
+    catapult_obj["target_position_locked"] = catapult->target_position_locked;
+    entity_obj["catapult_loading"] = catapult_obj;
+  }
+
   return entity_obj;
 }
 
-void Serialization::deserializeEntity(Entity *entity, const QJsonObject &json) {
+void Serialization::deserialize_entity(Entity *entity, const QJsonObject &json) {
   if (json.contains("transform")) {
     const auto transform_obj = json["transform"].toObject();
     auto *transform = entity->add_component<TransformComponent>();
@@ -282,7 +324,7 @@ void Serialization::deserializeEntity(Entity *entity, const QJsonObject &json) {
         static_cast<RenderableComponent::MeshKind>(renderable_obj["mesh"].toInt(
             static_cast<int>(RenderableComponent::MeshKind::Cube)));
     if (renderable_obj.contains("color")) {
-      deserializeColor(renderable_obj["color"].toArray(), renderable->color);
+      deserialize_color(renderable_obj["color"].toArray(), renderable->color);
     }
   }
 
@@ -369,9 +411,9 @@ void Serialization::deserializeEntity(Entity *entity, const QJsonObject &json) {
     attack->melee_cooldown =
         static_cast<float>(attack_obj["melee_cooldown"].toDouble());
     attack->preferred_mode =
-        combatModeFromString(attack_obj["preferred_mode"].toString());
+        combat_mode_from_string(attack_obj["preferred_mode"].toString());
     attack->current_mode =
-        combatModeFromString(attack_obj["current_mode"].toString());
+        combat_mode_from_string(attack_obj["current_mode"].toString());
     attack->can_melee = attack_obj["can_melee"].toBool(true);
     attack->can_ranged = attack_obj["can_ranged"].toBool(false);
     attack->max_height_difference =
@@ -456,9 +498,58 @@ void Serialization::deserializeEntity(Entity *entity, const QJsonObject &json) {
             static_cast<double>(Defaults::kCaptureRequiredTime)));
     capture->is_being_captured = capture_obj["is_being_captured"].toBool(false);
   }
+
+  if (json.contains("hold_mode")) {
+    const auto hold_mode_obj = json["hold_mode"].toObject();
+    auto *hold_mode = entity->add_component<HoldModeComponent>();
+    hold_mode->active = hold_mode_obj["active"].toBool(true);
+    hold_mode->exit_cooldown =
+        static_cast<float>(hold_mode_obj["exit_cooldown"].toDouble(0.0));
+    hold_mode->stand_up_duration =
+        static_cast<float>(hold_mode_obj["stand_up_duration"].toDouble(
+            static_cast<double>(Defaults::kHoldStandUpDuration)));
+  }
+
+  if (json.contains("healer")) {
+    const auto healer_obj = json["healer"].toObject();
+    auto *healer = entity->add_component<HealerComponent>();
+    healer->healing_range =
+        static_cast<float>(healer_obj["healing_range"].toDouble(8.0));
+    healer->healing_amount = healer_obj["healing_amount"].toInt(5);
+    healer->healing_cooldown =
+        static_cast<float>(healer_obj["healing_cooldown"].toDouble(2.0));
+    healer->time_since_last_heal =
+        static_cast<float>(healer_obj["time_since_last_heal"].toDouble(0.0));
+  }
+
+  if (json.contains("catapult_loading")) {
+    const auto catapult_obj = json["catapult_loading"].toObject();
+    auto *catapult = entity->add_component<CatapultLoadingComponent>();
+    catapult->state = static_cast<CatapultLoadingComponent::LoadingState>(
+        catapult_obj["state"].toInt(
+            static_cast<int>(CatapultLoadingComponent::LoadingState::Idle)));
+    catapult->loading_time =
+        static_cast<float>(catapult_obj["loading_time"].toDouble(0.0));
+    catapult->loading_duration =
+        static_cast<float>(catapult_obj["loading_duration"].toDouble(2.0));
+    catapult->firing_time =
+        static_cast<float>(catapult_obj["firing_time"].toDouble(0.0));
+    catapult->firing_duration =
+        static_cast<float>(catapult_obj["firing_duration"].toDouble(0.5));
+    catapult->target_id = static_cast<EntityID>(
+        catapult_obj["target_id"].toVariant().toULongLong());
+    catapult->target_locked_x =
+        static_cast<float>(catapult_obj["target_locked_x"].toDouble(0.0));
+    catapult->target_locked_y =
+        static_cast<float>(catapult_obj["target_locked_y"].toDouble(0.0));
+    catapult->target_locked_z =
+        static_cast<float>(catapult_obj["target_locked_z"].toDouble(0.0));
+    catapult->target_position_locked =
+        catapult_obj["target_position_locked"].toBool(false);
+  }
 }
 
-auto Serialization::serializeTerrain(
+auto Serialization::serialize_terrain(
     const Game::Map::TerrainHeightMap *height_map,
     const Game::Map::BiomeSettings &biome,
     const std::vector<Game::Map::RoadSegment> &roads) -> QJsonObject {
@@ -580,7 +671,7 @@ auto Serialization::serializeTerrain(
   return terrain_obj;
 }
 
-void Serialization::deserializeTerrain(
+void Serialization::deserialize_terrain(
     Game::Map::TerrainHeightMap *height_map, Game::Map::BiomeSettings &biome,
     std::vector<Game::Map::RoadSegment> &roads, const QJsonObject &json) {
   if ((height_map == nullptr) || json.isEmpty()) {
@@ -775,13 +866,13 @@ void Serialization::deserializeTerrain(
   height_map->restoreFromData(heights, terrain_types, rivers, bridges);
 }
 
-auto Serialization::serializeWorld(const World *world) -> QJsonDocument {
+auto Serialization::serialize_world(const World *world) -> QJsonDocument {
   QJsonObject world_obj;
   QJsonArray entities_array;
 
   const auto &entities = world->get_entities();
   for (const auto &[id, entity] : entities) {
-    QJsonObject const entity_obj = serializeEntity(entity.get());
+    QJsonObject const entity_obj = serialize_entity(entity.get());
     entities_array.append(entity_obj);
   }
 
@@ -794,7 +885,7 @@ auto Serialization::serializeWorld(const World *world) -> QJsonDocument {
   const auto &terrain_service = Game::Map::TerrainService::instance();
   if (terrain_service.is_initialized() &&
       (terrain_service.get_height_map() != nullptr)) {
-    world_obj["terrain"] = serializeTerrain(terrain_service.get_height_map(),
+    world_obj["terrain"] = serialize_terrain(terrain_service.get_height_map(),
                                             terrain_service.biome_settings(),
                                             terrain_service.road_segments());
   }
@@ -802,7 +893,7 @@ auto Serialization::serializeWorld(const World *world) -> QJsonDocument {
   return QJsonDocument(world_obj);
 }
 
-void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
+void Serialization::deserialize_world(World *world, const QJsonDocument &doc) {
   auto world_obj = doc.object();
   auto entities_array = world_obj["entities"].toArray();
   for (const auto &value : entities_array) {
@@ -813,7 +904,7 @@ void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
                        ? world->create_entity()
                        : world->create_entity_with_id(entity_id);
     if (entity != nullptr) {
-      deserializeEntity(entity, entity_obj);
+      deserialize_entity(entity, entity_obj);
     }
   }
 
@@ -844,7 +935,7 @@ void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
 
     auto temp_height_map =
         std::make_unique<Game::Map::TerrainHeightMap>(width, height, tile_size);
-    deserializeTerrain(temp_height_map.get(), biome, roads, terrain_obj);
+    deserialize_terrain(temp_height_map.get(), biome, roads, terrain_obj);
 
     auto &terrain_service = Game::Map::TerrainService::instance();
     terrain_service.restore_from_serialized(
@@ -854,8 +945,8 @@ void Serialization::deserializeWorld(World *world, const QJsonDocument &doc) {
   }
 }
 
-auto Serialization::saveToFile(const QString &filename,
-                               const QJsonDocument &doc) -> bool {
+auto Serialization::save_to_file(const QString &filename,
+                                const QJsonDocument &doc) -> bool {
   QFile file(filename);
   if (!file.open(QIODevice::WriteOnly)) {
     qWarning() << "Could not open file for writing:" << filename;

+ 13 - 13
game/core/serialization.h

@@ -15,23 +15,23 @@ namespace Engine::Core {
 
 class Serialization {
 public:
-  static auto serializeEntity(const class Entity *entity) -> QJsonObject;
-  static void deserializeEntity(class Entity *entity, const QJsonObject &json);
+  static auto serialize_entity(const class Entity *entity) -> QJsonObject;
+  static void deserialize_entity(class Entity *entity, const QJsonObject &json);
 
-  static auto serializeWorld(const class World *world) -> QJsonDocument;
-  static void deserializeWorld(class World *world, const QJsonDocument &doc);
+  static auto serialize_world(const class World *world) -> QJsonDocument;
+  static void deserialize_world(class World *world, const QJsonDocument &doc);
 
-  static auto serializeTerrain(const Game::Map::TerrainHeightMap *height_map,
-                               const Game::Map::BiomeSettings &biome,
-                               const std::vector<Game::Map::RoadSegment> &roads)
+  static auto serialize_terrain(const Game::Map::TerrainHeightMap *height_map,
+                                const Game::Map::BiomeSettings &biome,
+                                const std::vector<Game::Map::RoadSegment> &roads)
       -> QJsonObject;
-  static void deserializeTerrain(Game::Map::TerrainHeightMap *height_map,
-                                 Game::Map::BiomeSettings &biome,
-                                 std::vector<Game::Map::RoadSegment> &roads,
-                                 const QJsonObject &json);
+  static void deserialize_terrain(Game::Map::TerrainHeightMap *height_map,
+                                  Game::Map::BiomeSettings &biome,
+                                  std::vector<Game::Map::RoadSegment> &roads,
+                                  const QJsonObject &json);
 
-  static auto saveToFile(const QString &filename,
-                         const QJsonDocument &doc) -> bool;
+  static auto save_to_file(const QString &filename,
+                           const QJsonDocument &doc) -> bool;
   static auto load_from_file(const QString &filename) -> QJsonDocument;
 };
 

+ 30 - 30
game/systems/save_load_service.cpp

@@ -28,7 +28,7 @@
 namespace Game::Systems {
 
 SaveLoadService::SaveLoadService() {
-  ensureSavesDirectoryExists();
+  ensure_saves_directory_exists();
   m_storage = std::make_unique<SaveStorage>(get_database_path());
   QString init_error;
   if (!m_storage->initialize(&init_error)) {
@@ -39,31 +39,31 @@ SaveLoadService::SaveLoadService() {
 
 SaveLoadService::~SaveLoadService() = default;
 
-auto SaveLoadService::getSavesDirectory() -> QString {
+auto SaveLoadService::get_saves_directory() -> QString {
   QString const saves_path =
       QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
   return saves_path + "/saves";
 }
 
 auto SaveLoadService::get_database_path() -> QString {
-  return getSavesDirectory() + QStringLiteral("/saves.sqlite");
+  return get_saves_directory() + QStringLiteral("/saves.sqlite");
 }
 
-void SaveLoadService::ensureSavesDirectoryExists() {
-  QString const saves_dir = getSavesDirectory();
+void SaveLoadService::ensure_saves_directory_exists() {
+  QString const saves_dir = get_saves_directory();
   QDir const dir;
   if (!dir.exists(saves_dir)) {
     dir.mkpath(saves_dir);
   }
 }
 
-auto SaveLoadService::saveGameToSlot(Engine::Core::World &world,
-                                     const QString &slotName,
-                                     const QString &title,
-                                     const QString &map_name,
-                                     const QJsonObject &metadata,
-                                     const QByteArray &screenshot) -> bool {
-  qInfo() << "Saving game to slot:" << slotName;
+auto SaveLoadService::save_game_to_slot(Engine::Core::World &world,
+                                        const QString &slot_name,
+                                        const QString &title,
+                                        const QString &map_name,
+                                        const QJsonObject &metadata,
+                                        const QByteArray &screenshot) -> bool {
+  qInfo() << "Saving game to slot:" << slot_name;
 
   try {
     if (!m_storage) {
@@ -73,11 +73,11 @@ auto SaveLoadService::saveGameToSlot(Engine::Core::World &world,
     }
 
     QJsonDocument const world_doc =
-        Engine::Core::Serialization::serializeWorld(&world);
+        Engine::Core::Serialization::serialize_world(&world);
     const QByteArray world_bytes = world_doc.toJson(QJsonDocument::Compact);
 
     QJsonObject combined_metadata = metadata;
-    combined_metadata["slotName"] = slotName;
+    combined_metadata["slotName"] = slot_name;
     combined_metadata["title"] = title;
     combined_metadata["timestamp"] =
         QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs);
@@ -88,15 +88,15 @@ auto SaveLoadService::saveGameToSlot(Engine::Core::World &world,
     combined_metadata["version"] = QStringLiteral("1.0");
 
     QString storage_error;
-    if (!m_storage->saveSlot(slotName, title, combined_metadata, world_bytes,
-                             screenshot, &storage_error)) {
+    if (!m_storage->save_slot(slot_name, title, combined_metadata, world_bytes,
+                              screenshot, &storage_error)) {
       m_last_error = storage_error;
       qWarning() << "SaveLoadService: failed to persist slot" << storage_error;
       return false;
     }
 
     m_last_metadata = combined_metadata;
-    m_lastTitle = title;
+    m_last_title = title;
     m_last_screenshot = screenshot;
     m_last_error.clear();
     return true;
@@ -109,8 +109,8 @@ auto SaveLoadService::saveGameToSlot(Engine::Core::World &world,
 }
 
 auto SaveLoadService::load_game_from_slot(Engine::Core::World &world,
-                                          const QString &slotName) -> bool {
-  qInfo() << "Loading game from slot:" << slotName;
+                                          const QString &slot_name) -> bool {
+  qInfo() << "Loading game from slot:" << slot_name;
 
   try {
     if (!m_storage) {
@@ -125,8 +125,8 @@ auto SaveLoadService::load_game_from_slot(Engine::Core::World &world,
     QString title;
 
     QString load_error;
-    if (!m_storage->loadSlot(slotName, world_bytes, metadata, screenshot, title,
-                             &load_error)) {
+    if (!m_storage->load_slot(slot_name, world_bytes, metadata, screenshot, title,
+                              &load_error)) {
       m_last_error = load_error;
       qWarning() << "SaveLoadService: failed to load slot" << load_error;
       return false;
@@ -137,16 +137,16 @@ auto SaveLoadService::load_game_from_slot(Engine::Core::World &world,
         QJsonDocument::fromJson(world_bytes, &parse_error);
     if (parse_error.error != QJsonParseError::NoError || doc.isNull()) {
       m_last_error = QStringLiteral("Corrupted save data for slot '%1': %2")
-                         .arg(slotName, parse_error.errorString());
+                         .arg(slot_name, parse_error.errorString());
       qWarning() << m_last_error;
       return false;
     }
 
     world.clear();
-    Engine::Core::Serialization::deserializeWorld(&world, doc);
+    Engine::Core::Serialization::deserialize_world(&world, doc);
 
     m_last_metadata = metadata;
-    m_lastTitle = title;
+    m_last_title = title;
     m_last_screenshot = screenshot;
     m_last_error.clear();
     return true;
@@ -164,7 +164,7 @@ auto SaveLoadService::get_save_slots() const -> QVariantList {
   }
 
   QString list_error;
-  QVariantList slot_list = m_storage->listSlots(&list_error);
+  QVariantList slot_list = m_storage->list_slots(&list_error);
   if (!list_error.isEmpty()) {
     m_last_error = list_error;
     qWarning() << "SaveLoadService: failed to enumerate slots" << list_error;
@@ -174,8 +174,8 @@ auto SaveLoadService::get_save_slots() const -> QVariantList {
   return slot_list;
 }
 
-auto SaveLoadService::deleteSaveSlot(const QString &slotName) -> bool {
-  qInfo() << "Deleting save slot:" << slotName;
+auto SaveLoadService::delete_save_slot(const QString &slot_name) -> bool {
+  qInfo() << "Deleting save slot:" << slot_name;
 
   if (!m_storage) {
     m_last_error = QStringLiteral("Save storage unavailable");
@@ -184,7 +184,7 @@ auto SaveLoadService::deleteSaveSlot(const QString &slotName) -> bool {
   }
 
   QString delete_error;
-  if (!m_storage->deleteSlot(slotName, &delete_error)) {
+  if (!m_storage->delete_slot(slot_name, &delete_error)) {
     m_last_error = delete_error;
     qWarning() << "SaveLoadService: failed to delete slot" << delete_error;
     return false;
@@ -226,9 +226,9 @@ auto SaveLoadService::mark_campaign_completed(const QString &campaign_id,
   return m_storage->mark_campaign_completed(campaign_id, out_error);
 }
 
-void SaveLoadService::openSettings() { qInfo() << "Open settings requested"; }
+void SaveLoadService::open_settings() { qInfo() << "Open settings requested"; }
 
-void SaveLoadService::exitGame() {
+void SaveLoadService::exit_game() {
   qInfo() << "Exit game requested";
 
   QCoreApplication::quit();

+ 16 - 16
game/systems/save_load_service.h

@@ -20,25 +20,25 @@ public:
   SaveLoadService();
   ~SaveLoadService();
 
-  auto saveGameToSlot(Engine::Core::World &world, const QString &slotName,
-                      const QString &title, const QString &map_name,
-                      const QJsonObject &metadata = {},
-                      const QByteArray &screenshot = QByteArray()) -> bool;
+  auto save_game_to_slot(Engine::Core::World &world, const QString &slot_name,
+                         const QString &title, const QString &map_name,
+                         const QJsonObject &metadata = {},
+                         const QByteArray &screenshot = QByteArray()) -> bool;
 
   auto load_game_from_slot(Engine::Core::World &world,
-                           const QString &slotName) -> bool;
+                           const QString &slot_name) -> bool;
 
   auto get_save_slots() const -> QVariantList;
 
-  auto deleteSaveSlot(const QString &slotName) -> bool;
+  auto delete_save_slot(const QString &slot_name) -> bool;
 
-  auto getLastError() const -> QString { return m_last_error; }
+  auto get_last_error() const -> QString { return m_last_error; }
 
-  void clearError() { m_last_error.clear(); }
+  void clear_error() { m_last_error.clear(); }
 
-  auto getLastMetadata() const -> QJsonObject { return m_last_metadata; }
-  auto getLastTitle() const -> QString { return m_lastTitle; }
-  auto getLastScreenshot() const -> QByteArray { return m_last_screenshot; }
+  auto get_last_metadata() const -> QJsonObject { return m_last_metadata; }
+  auto get_last_title() const -> QString { return m_last_title; }
+  auto get_last_screenshot() const -> QByteArray { return m_last_screenshot; }
 
   auto list_campaigns(QString *out_error = nullptr) const -> QVariantList;
   auto get_campaign_progress(const QString &campaign_id,
@@ -46,18 +46,18 @@ public:
   auto mark_campaign_completed(const QString &campaign_id,
                                QString *out_error = nullptr) -> bool;
 
-  static void openSettings();
+  static void open_settings();
 
-  static void exitGame();
+  static void exit_game();
 
 private:
-  static auto getSavesDirectory() -> QString;
+  static auto get_saves_directory() -> QString;
   static auto get_database_path() -> QString;
-  static void ensureSavesDirectoryExists();
+  static void ensure_saves_directory_exists();
 
   mutable QString m_last_error;
   QJsonObject m_last_metadata;
-  QString m_lastTitle;
+  QString m_last_title;
   QByteArray m_last_screenshot;
   std::unique_ptr<SaveStorage> m_storage;
 };

+ 41 - 41
game/systems/save_storage.cpp

@@ -25,12 +25,12 @@ namespace {
 constexpr const char *k_driver_name = "QSQLITE";
 constexpr int k_current_schema_version = 2;
 
-auto buildConnectionName(const SaveStorage *instance) -> QString {
+auto build_connection_name(const SaveStorage *instance) -> QString {
   return QStringLiteral("SaveStorage_%1")
       .arg(reinterpret_cast<quintptr>(instance), 0, 16);
 }
 
-auto lastErrorString(const QSqlError &error) -> QString {
+auto last_error_string(const QSqlError &error) -> QString {
   if (error.type() == QSqlError::NoError) {
     return {};
   }
@@ -45,7 +45,7 @@ public:
     if (!m_database.transaction()) {
       if (out_error != nullptr) {
         *out_error = QStringLiteral("Failed to begin transaction: %1")
-                         .arg(lastErrorString(m_database.lastError()));
+                         .arg(last_error_string(m_database.lastError()));
       }
       return false;
     }
@@ -61,7 +61,7 @@ public:
     if (!m_database.commit()) {
       if (out_error != nullptr) {
         *out_error = QStringLiteral("Failed to commit transaction: %1")
-                         .arg(lastErrorString(m_database.lastError()));
+                         .arg(last_error_string(m_database.lastError()));
       }
       rollback();
       return false;
@@ -88,7 +88,7 @@ private:
 
 SaveStorage::SaveStorage(QString database_path)
     : m_database_path(std::move(database_path)),
-      m_connection_name(buildConnectionName(this)) {}
+      m_connection_name(build_connection_name(this)) {}
 
 SaveStorage::~SaveStorage() {
   if (m_database.isValid()) {
@@ -108,16 +108,16 @@ auto SaveStorage::initialize(QString *out_error) -> bool {
   if (!open(out_error)) {
     return false;
   }
-  if (!ensureSchema(out_error)) {
+  if (!ensure_schema(out_error)) {
     return false;
   }
   m_initialized = true;
   return true;
 }
 
-auto SaveStorage::saveSlot(const QString &slotName, const QString &title,
+auto SaveStorage::save_slot(const QString &slot_name, const QString &title,
                            const QJsonObject &metadata,
-                           const QByteArray &worldState,
+                           const QByteArray &world_state,
                            const QByteArray &screenshot,
                            QString *out_error) -> bool {
   if (!initialize(out_error)) {
@@ -147,7 +147,7 @@ auto SaveStorage::saveSlot(const QString &slotName, const QString &title,
   if (!query.prepare(insert_sql)) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to prepare save query: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return false;
   }
@@ -161,12 +161,12 @@ auto SaveStorage::saveSlot(const QString &slotName, const QString &title,
   const QByteArray metadata_bytes =
       QJsonDocument(metadata).toJson(QJsonDocument::Compact);
 
-  query.bindValue(QStringLiteral(":slot_name"), slotName);
+  query.bindValue(QStringLiteral(":slot_name"), slot_name);
   query.bindValue(QStringLiteral(":title"), title);
   query.bindValue(QStringLiteral(":map_name"), map_name);
   query.bindValue(QStringLiteral(":timestamp"), now_iso);
   query.bindValue(QStringLiteral(":metadata"), metadata_bytes);
-  query.bindValue(QStringLiteral(":world_state"), worldState);
+  query.bindValue(QStringLiteral(":world_state"), world_state);
   if (screenshot.isEmpty()) {
     query.bindValue(QStringLiteral(":screenshot"),
                     QVariant(QMetaType::fromType<QByteArray>()));
@@ -179,7 +179,7 @@ auto SaveStorage::saveSlot(const QString &slotName, const QString &title,
   if (!query.exec()) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to persist save slot: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     transaction.rollback();
     return false;
@@ -192,7 +192,7 @@ auto SaveStorage::saveSlot(const QString &slotName, const QString &title,
   return true;
 }
 
-auto SaveStorage::loadSlot(const QString &slotName, QByteArray &worldState,
+auto SaveStorage::load_slot(const QString &slot_name, QByteArray &world_state,
                            QJsonObject &metadata, QByteArray &screenshot,
                            QString &title, QString *out_error) -> bool {
   if (!initialize(out_error)) {
@@ -203,19 +203,19 @@ auto SaveStorage::loadSlot(const QString &slotName, QByteArray &worldState,
   query.prepare(QStringLiteral(
       "SELECT title, metadata, world_state, screenshot FROM saves "
       "WHERE slot_name = :slot_name"));
-  query.bindValue(QStringLiteral(":slot_name"), slotName);
+  query.bindValue(QStringLiteral(":slot_name"), slot_name);
 
   if (!query.exec()) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to read save slot: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return false;
   }
 
   if (!query.next()) {
     if (out_error != nullptr) {
-      *out_error = QStringLiteral("Save slot '%1' not found").arg(slotName);
+      *out_error = QStringLiteral("Save slot '%1' not found").arg(slot_name);
     }
     return false;
   }
@@ -223,12 +223,12 @@ auto SaveStorage::loadSlot(const QString &slotName, QByteArray &worldState,
   title = query.value(0).toString();
   const QByteArray metadata_bytes = query.value(1).toByteArray();
   metadata = QJsonDocument::fromJson(metadata_bytes).object();
-  worldState = query.value(2).toByteArray();
+  world_state = query.value(2).toByteArray();
   screenshot = query.value(3).toByteArray();
   return true;
 }
 
-auto SaveStorage::listSlots(QString *out_error) const -> QVariantList {
+auto SaveStorage::list_slots(QString *out_error) const -> QVariantList {
   QVariantList result;
   if (!const_cast<SaveStorage *>(this)->initialize(out_error)) {
     return result;
@@ -240,7 +240,7 @@ auto SaveStorage::listSlots(QString *out_error) const -> QVariantList {
           "FROM saves ORDER BY datetime(timestamp) DESC"))) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to enumerate save slots: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return result;
   }
@@ -295,7 +295,7 @@ auto SaveStorage::list_campaigns(QString *out_error) const -> QVariantList {
   if (!query.exec(sql)) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to list campaigns: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return result;
   }
@@ -332,7 +332,7 @@ auto SaveStorage::get_campaign_progress(
   if (!query.exec()) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to get campaign progress: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return result;
   }
@@ -373,7 +373,7 @@ auto SaveStorage::mark_campaign_completed(const QString &campaign_id,
   if (!query.exec()) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to mark campaign as completed: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     transaction.rollback();
     return false;
@@ -386,8 +386,8 @@ auto SaveStorage::mark_campaign_completed(const QString &campaign_id,
   return true;
 }
 
-auto SaveStorage::deleteSlot(const QString &slotName,
-                             QString *out_error) -> bool {
+auto SaveStorage::delete_slot(const QString &slot_name,
+                              QString *out_error) -> bool {
   if (!initialize(out_error)) {
     return false;
   }
@@ -400,12 +400,12 @@ auto SaveStorage::deleteSlot(const QString &slotName,
   QSqlQuery query(m_database);
   query.prepare(
       QStringLiteral("DELETE FROM saves WHERE slot_name = :slot_name"));
-  query.bindValue(QStringLiteral(":slot_name"), slotName);
+  query.bindValue(QStringLiteral(":slot_name"), slot_name);
 
   if (!query.exec()) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to delete save slot: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     transaction.rollback();
     return false;
@@ -413,7 +413,7 @@ auto SaveStorage::deleteSlot(const QString &slotName,
 
   if (query.numRowsAffected() == 0) {
     if (out_error != nullptr) {
-      *out_error = QStringLiteral("Save slot '%1' not found").arg(slotName);
+      *out_error = QStringLiteral("Save slot '%1' not found").arg(slot_name);
     }
     transaction.rollback();
     return false;
@@ -440,7 +440,7 @@ auto SaveStorage::open(QString *out_error) const -> bool {
   if (!m_database.open()) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to open save database: %1")
-                       .arg(lastErrorString(m_database.lastError()));
+                       .arg(last_error_string(m_database.lastError()));
     }
     return false;
   }
@@ -454,8 +454,8 @@ auto SaveStorage::open(QString *out_error) const -> bool {
   return true;
 }
 
-auto SaveStorage::ensureSchema(QString *out_error) const -> bool {
-  const int current_version = schemaVersion(out_error);
+auto SaveStorage::ensure_schema(QString *out_error) const -> bool {
+  const int current_version = schema_version(out_error);
   if (current_version < 0) {
     return false;
   }
@@ -497,12 +497,12 @@ auto SaveStorage::ensureSchema(QString *out_error) const -> bool {
   return true;
 }
 
-auto SaveStorage::schemaVersion(QString *out_error) const -> int {
+auto SaveStorage::schema_version(QString *out_error) const -> int {
   QSqlQuery pragma_query(m_database);
   if (!pragma_query.exec(QStringLiteral("PRAGMA user_version"))) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to read schema version: %1")
-                       .arg(lastErrorString(pragma_query.lastError()));
+                       .arg(last_error_string(pragma_query.lastError()));
     }
     return -1;
   }
@@ -521,14 +521,14 @@ auto SaveStorage::set_schema_version(int version,
           QStringLiteral("PRAGMA user_version = %1").arg(version))) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to update schema version: %1")
-                       .arg(lastErrorString(pragma_query.lastError()));
+                       .arg(last_error_string(pragma_query.lastError()));
     }
     return false;
   }
   return true;
 }
 
-auto SaveStorage::createBaseSchema(QString *out_error) const -> bool {
+auto SaveStorage::create_base_schema(QString *out_error) const -> bool {
   QSqlQuery query(m_database);
   const QString create_sql =
       QStringLiteral("CREATE TABLE IF NOT EXISTS saves ("
@@ -547,7 +547,7 @@ auto SaveStorage::createBaseSchema(QString *out_error) const -> bool {
   if (!query.exec(create_sql)) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to create save schema: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return false;
   }
@@ -558,7 +558,7 @@ auto SaveStorage::createBaseSchema(QString *out_error) const -> bool {
           "(updated_at DESC)"))) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to build save index: %1")
-                       .arg(lastErrorString(index_query.lastError()));
+                       .arg(last_error_string(index_query.lastError()));
     }
     return false;
   }
@@ -573,7 +573,7 @@ auto SaveStorage::migrate_schema(int fromVersion,
   while (version < k_current_schema_version) {
     switch (version) {
     case 0:
-      if (!createBaseSchema(out_error)) {
+      if (!create_base_schema(out_error)) {
         return false;
       }
       version = 1;
@@ -611,7 +611,7 @@ auto SaveStorage::migrate_to_2(QString *out_error) const -> bool {
   if (!query.exec(create_campaigns_sql)) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to create campaigns table: %1")
-                       .arg(lastErrorString(query.lastError()));
+                       .arg(last_error_string(query.lastError()));
     }
     return false;
   }
@@ -630,7 +630,7 @@ auto SaveStorage::migrate_to_2(QString *out_error) const -> bool {
     if (out_error != nullptr) {
       *out_error =
           QStringLiteral("Failed to create campaign_progress table: %1")
-              .arg(lastErrorString(progress_query.lastError()));
+              .arg(last_error_string(progress_query.lastError()));
     }
     return false;
   }
@@ -646,7 +646,7 @@ auto SaveStorage::migrate_to_2(QString *out_error) const -> bool {
   if (!insert_query.exec(insert_campaign_sql)) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to insert initial campaign: %1")
-                       .arg(lastErrorString(insert_query.lastError()));
+                       .arg(last_error_string(insert_query.lastError()));
     }
     return false;
   }
@@ -659,7 +659,7 @@ auto SaveStorage::migrate_to_2(QString *out_error) const -> bool {
   if (!progress_insert_query.exec(insert_progress_sql)) {
     if (out_error != nullptr) {
       *out_error = QStringLiteral("Failed to initialize campaign progress: %1")
-                       .arg(lastErrorString(progress_insert_query.lastError()));
+                       .arg(last_error_string(progress_insert_query.lastError()));
     }
     return false;
   }

+ 14 - 14
game/systems/save_storage.h

@@ -17,19 +17,19 @@ public:
 
   auto initialize(QString *out_error = nullptr) -> bool;
 
-  auto saveSlot(const QString &slotName, const QString &title,
-                const QJsonObject &metadata, const QByteArray &worldState,
-                const QByteArray &screenshot,
-                QString *out_error = nullptr) -> bool;
+  auto save_slot(const QString &slot_name, const QString &title,
+                 const QJsonObject &metadata, const QByteArray &world_state,
+                 const QByteArray &screenshot,
+                 QString *out_error = nullptr) -> bool;
 
-  auto loadSlot(const QString &slotName, QByteArray &worldState,
-                QJsonObject &metadata, QByteArray &screenshot, QString &title,
-                QString *out_error = nullptr) -> bool;
+  auto load_slot(const QString &slot_name, QByteArray &world_state,
+                 QJsonObject &metadata, QByteArray &screenshot, QString &title,
+                 QString *out_error = nullptr) -> bool;
 
-  auto listSlots(QString *out_error = nullptr) const -> QVariantList;
+  auto list_slots(QString *out_error = nullptr) const -> QVariantList;
 
-  auto deleteSlot(const QString &slotName,
-                  QString *out_error = nullptr) -> bool;
+  auto delete_slot(const QString &slot_name,
+                   QString *out_error = nullptr) -> bool;
 
   auto list_campaigns(QString *out_error = nullptr) const -> QVariantList;
   auto get_campaign_progress(const QString &campaign_id,
@@ -39,11 +39,11 @@ public:
 
 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 migrate_schema(int fromVersion,
+  auto ensure_schema(QString *out_error = nullptr) const -> bool;
+  auto create_base_schema(QString *out_error = nullptr) const -> bool;
+  auto migrate_schema(int from_version,
                       QString *out_error = nullptr) const -> bool;
-  auto schemaVersion(QString *out_error = nullptr) const -> int;
+  auto schema_version(QString *out_error = nullptr) const -> int;
   auto set_schema_version(int version,
                           QString *out_error = nullptr) const -> bool;
   auto migrate_to_2(QString *out_error = nullptr) const -> bool;

+ 1 - 1
tests/CMakeLists.txt

@@ -29,8 +29,8 @@ target_link_libraries(standard_of_iron_tests
         Qt${QT_VERSION_MAJOR}::Gui
         Qt${QT_VERSION_MAJOR}::Sql
         engine_core
-        game_systems
         render_gl
+        game_systems
 )
 
 # Include directories

+ 785 - 34
tests/core/serialization_test.cpp

@@ -3,6 +3,7 @@
 #include "core/serialization.h"
 #include "core/world.h"
 #include "systems/nation_id.h"
+#include "systems/owner_registry.h"
 #include "units/spawn_type.h"
 #include "units/troop_type.h"
 #include <QJsonArray>
@@ -28,7 +29,7 @@ TEST_F(SerializationTest, EntitySerializationBasic) {
 
   auto entity_id = entity->get_id();
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   EXPECT_TRUE(json.contains("id"));
   EXPECT_EQ(json["id"].toVariant().toULongLong(),
@@ -51,7 +52,7 @@ TEST_F(SerializationTest, TransformComponentSerialization) {
   transform->has_desired_yaw = true;
   transform->desired_yaw = 45.0F;
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("transform"));
   QJsonObject transform_obj = json["transform"].toObject();
@@ -69,6 +70,41 @@ TEST_F(SerializationTest, TransformComponentSerialization) {
   EXPECT_FLOAT_EQ(transform_obj["desired_yaw"].toDouble(), 45.0);
 }
 
+TEST_F(SerializationTest, TransformComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *transform = original_entity->add_component<TransformComponent>();
+  transform->position.x = 15.0F;
+  transform->position.y = 25.0F;
+  transform->position.z = 35.0F;
+  transform->rotation.x = 1.0F;
+  transform->rotation.y = 2.0F;
+  transform->rotation.z = 3.0F;
+  transform->scale.x = 1.5F;
+  transform->scale.y = 2.5F;
+  transform->scale.z = 3.5F;
+  transform->has_desired_yaw = true;
+  transform->desired_yaw = 90.0F;
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<TransformComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_FLOAT_EQ(deserialized->position.x, 15.0F);
+  EXPECT_FLOAT_EQ(deserialized->position.y, 25.0F);
+  EXPECT_FLOAT_EQ(deserialized->position.z, 35.0F);
+  EXPECT_FLOAT_EQ(deserialized->rotation.x, 1.0F);
+  EXPECT_FLOAT_EQ(deserialized->rotation.y, 2.0F);
+  EXPECT_FLOAT_EQ(deserialized->rotation.z, 3.0F);
+  EXPECT_FLOAT_EQ(deserialized->scale.x, 1.5F);
+  EXPECT_FLOAT_EQ(deserialized->scale.y, 2.5F);
+  EXPECT_FLOAT_EQ(deserialized->scale.z, 3.5F);
+  EXPECT_TRUE(deserialized->has_desired_yaw);
+  EXPECT_FLOAT_EQ(deserialized->desired_yaw, 90.0F);
+}
+
 TEST_F(SerializationTest, UnitComponentSerialization) {
   auto *entity = world->create_entity();
   auto *unit = entity->add_component<UnitComponent>();
@@ -81,7 +117,7 @@ TEST_F(SerializationTest, UnitComponentSerialization) {
   unit->owner_id = 1;
   unit->nation_id = Game::Systems::NationID::RomanRepublic;
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("unit"));
   QJsonObject unit_obj = json["unit"].toObject();
@@ -95,6 +131,33 @@ TEST_F(SerializationTest, UnitComponentSerialization) {
   EXPECT_EQ(unit_obj["nation_id"].toString(), QString("roman_republic"));
 }
 
+TEST_F(SerializationTest, UnitComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *unit = original_entity->add_component<UnitComponent>();
+  unit->health = 75;
+  unit->max_health = 150;
+  unit->speed = 7.5F;
+  unit->vision_range = 20.0F;
+  unit->spawn_type = Game::Units::SpawnType::Spearman;
+  unit->owner_id = 2;
+  unit->nation_id = Game::Systems::NationID::Carthage;
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<UnitComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_EQ(deserialized->health, 75);
+  EXPECT_EQ(deserialized->max_health, 150);
+  EXPECT_FLOAT_EQ(deserialized->speed, 7.5F);
+  EXPECT_FLOAT_EQ(deserialized->vision_range, 20.0F);
+  EXPECT_EQ(deserialized->spawn_type, Game::Units::SpawnType::Spearman);
+  EXPECT_EQ(deserialized->owner_id, 2);
+  EXPECT_EQ(deserialized->nation_id, Game::Systems::NationID::Carthage);
+}
+
 TEST_F(SerializationTest, MovementComponentSerialization) {
   auto *entity = world->create_entity();
   auto *movement = entity->add_component<MovementComponent>();
@@ -116,7 +179,7 @@ TEST_F(SerializationTest, MovementComponentSerialization) {
   movement->path.emplace_back(10.0F, 20.0F);
   movement->path.emplace_back(30.0F, 40.0F);
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("movement"));
   QJsonObject movement_obj = json["movement"].toObject();
@@ -164,7 +227,7 @@ TEST_F(SerializationTest, AttackComponentSerialization) {
   attack->in_melee_lock = false;
   attack->melee_lock_target_id = 0;
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("attack"));
   QJsonObject attack_obj = json["attack"].toObject();
@@ -196,10 +259,10 @@ TEST_F(SerializationTest, EntityDeserializationRoundTrip) {
   unit->max_health = 100;
   unit->speed = 6.0F;
 
-  QJsonObject json = Serialization::serializeEntity(original_entity);
+  QJsonObject json = Serialization::serialize_entity(original_entity);
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   auto *deserialized_transform =
       new_entity->get_component<TransformComponent>();
@@ -224,7 +287,7 @@ TEST_F(SerializationTest, DeserializationWithMissingFields) {
   json["unit"] = unit_obj;
 
   auto *entity = world->create_entity();
-  Serialization::deserializeEntity(entity, json);
+  Serialization::deserialize_entity(entity, json);
 
   auto *unit = entity->get_component<UnitComponent>();
   ASSERT_NE(unit, nullptr);
@@ -242,7 +305,7 @@ TEST_F(SerializationTest, DeserializationWithMalformedJSON) {
 
   auto *entity = world->create_entity();
 
-  EXPECT_NO_THROW({ Serialization::deserializeEntity(entity, json); });
+  EXPECT_NO_THROW({ Serialization::deserialize_entity(entity, json); });
 
   auto *transform = entity->get_component<TransformComponent>();
   ASSERT_NE(transform, nullptr);
@@ -258,7 +321,7 @@ TEST_F(SerializationTest, WorldSerializationRoundTrip) {
   auto *transform2 = entity2->add_component<TransformComponent>();
   transform2->position.x = 20.0F;
 
-  QJsonDocument doc = Serialization::serializeWorld(world.get());
+  QJsonDocument doc = Serialization::serialize_world(world.get());
 
   ASSERT_TRUE(doc.isObject());
   QJsonObject world_obj = doc.object();
@@ -267,7 +330,7 @@ TEST_F(SerializationTest, WorldSerializationRoundTrip) {
   EXPECT_TRUE(world_obj.contains("schemaVersion"));
 
   auto new_world = std::make_unique<World>();
-  Serialization::deserializeWorld(new_world.get(), doc);
+  Serialization::deserialize_world(new_world.get(), doc);
 
   const auto &entities = new_world->get_entities();
   EXPECT_EQ(entities.size(), 2UL);
@@ -280,20 +343,20 @@ TEST_F(SerializationTest, SaveAndLoadFromFile) {
   transform->position.y = 43.0F;
   transform->position.z = 44.0F;
 
-  QJsonDocument doc = Serialization::serializeWorld(world.get());
+  QJsonDocument doc = Serialization::serialize_world(world.get());
 
   QTemporaryFile temp_file;
   ASSERT_TRUE(temp_file.open());
   QString filename = temp_file.fileName();
   temp_file.close();
 
-  EXPECT_TRUE(Serialization::saveToFile(filename, doc));
+  EXPECT_TRUE(Serialization::save_to_file(filename, doc));
 
   QJsonDocument loaded_doc = Serialization::load_from_file(filename);
   EXPECT_FALSE(loaded_doc.isNull());
 
   auto new_world = std::make_unique<World>();
-  Serialization::deserializeWorld(new_world.get(), loaded_doc);
+  Serialization::deserialize_world(new_world.get(), loaded_doc);
 
   const auto &entities = new_world->get_entities();
   EXPECT_EQ(entities.size(), 1UL);
@@ -325,7 +388,7 @@ TEST_F(SerializationTest, ProductionComponentSerialization) {
   production->production_queue.push_back(Game::Units::TroopType::Spearman);
   production->production_queue.push_back(Game::Units::TroopType::Archer);
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("production"));
   QJsonObject prod_obj = json["production"].toObject();
@@ -358,7 +421,7 @@ TEST_F(SerializationTest, PatrolComponentSerialization) {
   patrol->waypoints.emplace_back(30.0F, 40.0F);
   patrol->waypoints.emplace_back(50.0F, 60.0F);
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("patrol"));
   QJsonObject patrol_obj = json["patrol"].toObject();
@@ -375,6 +438,129 @@ TEST_F(SerializationTest, PatrolComponentSerialization) {
   EXPECT_FLOAT_EQ(wp0["y"].toDouble(), 20.0);
 }
 
+TEST_F(SerializationTest, PatrolComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *patrol = original_entity->add_component<PatrolComponent>();
+  patrol->current_waypoint = 2;
+  patrol->patrolling = true;
+  patrol->waypoints.emplace_back(15.0F, 25.0F);
+  patrol->waypoints.emplace_back(35.0F, 45.0F);
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<PatrolComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_EQ(deserialized->current_waypoint, 2UL);
+  EXPECT_TRUE(deserialized->patrolling);
+  EXPECT_EQ(deserialized->waypoints.size(), 2UL);
+  EXPECT_FLOAT_EQ(deserialized->waypoints[0].first, 15.0F);
+  EXPECT_FLOAT_EQ(deserialized->waypoints[0].second, 25.0F);
+}
+
+TEST_F(SerializationTest, MovementComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *movement = original_entity->add_component<MovementComponent>();
+  movement->has_target = true;
+  movement->target_x = 100.0F;
+  movement->target_y = 200.0F;
+  movement->goal_x = 150.0F;
+  movement->goal_y = 250.0F;
+  movement->vx = 1.5F;
+  movement->vz = 2.5F;
+  movement->path.emplace_back(10.0F, 20.0F);
+  movement->path.emplace_back(30.0F, 40.0F);
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<MovementComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_TRUE(deserialized->has_target);
+  EXPECT_FLOAT_EQ(deserialized->target_x, 100.0F);
+  EXPECT_FLOAT_EQ(deserialized->target_y, 200.0F);
+  EXPECT_FLOAT_EQ(deserialized->goal_x, 150.0F);
+  EXPECT_FLOAT_EQ(deserialized->goal_y, 250.0F);
+  EXPECT_FLOAT_EQ(deserialized->vx, 1.5F);
+  EXPECT_FLOAT_EQ(deserialized->vz, 2.5F);
+  EXPECT_EQ(deserialized->path.size(), 2UL);
+}
+
+TEST_F(SerializationTest, AttackComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *attack = original_entity->add_component<AttackComponent>();
+  attack->range = 15.0F;
+  attack->damage = 30;
+  attack->cooldown = 2.5F;
+  attack->melee_range = 3.0F;
+  attack->melee_damage = 20;
+  attack->preferred_mode = AttackComponent::CombatMode::Ranged;
+  attack->current_mode = AttackComponent::CombatMode::Melee;
+  attack->can_melee = true;
+  attack->can_ranged = true;
+  attack->in_melee_lock = true;
+  attack->melee_lock_target_id = 42;
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<AttackComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_FLOAT_EQ(deserialized->range, 15.0F);
+  EXPECT_EQ(deserialized->damage, 30);
+  EXPECT_FLOAT_EQ(deserialized->cooldown, 2.5F);
+  EXPECT_FLOAT_EQ(deserialized->melee_range, 3.0F);
+  EXPECT_EQ(deserialized->melee_damage, 20);
+  EXPECT_EQ(deserialized->preferred_mode, AttackComponent::CombatMode::Ranged);
+  EXPECT_EQ(deserialized->current_mode, AttackComponent::CombatMode::Melee);
+  EXPECT_TRUE(deserialized->can_melee);
+  EXPECT_TRUE(deserialized->can_ranged);
+  EXPECT_TRUE(deserialized->in_melee_lock);
+  EXPECT_EQ(deserialized->melee_lock_target_id, 42U);
+}
+
+TEST_F(SerializationTest, ProductionComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *production = original_entity->add_component<ProductionComponent>();
+  production->in_progress = true;
+  production->build_time = 15.0F;
+  production->time_remaining = 7.5F;
+  production->produced_count = 5;
+  production->max_units = 20;
+  production->product_type = Game::Units::TroopType::Spearman;
+  production->rally_x = 150.0F;
+  production->rally_z = 250.0F;
+  production->rally_set = true;
+  production->villager_cost = 3;
+  production->production_queue.push_back(Game::Units::TroopType::Archer);
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<ProductionComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_TRUE(deserialized->in_progress);
+  EXPECT_FLOAT_EQ(deserialized->build_time, 15.0F);
+  EXPECT_FLOAT_EQ(deserialized->time_remaining, 7.5F);
+  EXPECT_EQ(deserialized->produced_count, 5);
+  EXPECT_EQ(deserialized->max_units, 20);
+  EXPECT_EQ(deserialized->product_type, Game::Units::TroopType::Spearman);
+  EXPECT_FLOAT_EQ(deserialized->rally_x, 150.0F);
+  EXPECT_FLOAT_EQ(deserialized->rally_z, 250.0F);
+  EXPECT_TRUE(deserialized->rally_set);
+  EXPECT_EQ(deserialized->villager_cost, 3);
+  EXPECT_EQ(deserialized->production_queue.size(), 1UL);
+  EXPECT_EQ(deserialized->production_queue[0], Game::Units::TroopType::Archer);
+}
+
 TEST_F(SerializationTest, RenderableComponentSerialization) {
   auto *entity = world->create_entity();
   auto *renderable =
@@ -387,7 +573,7 @@ TEST_F(SerializationTest, RenderableComponentSerialization) {
   renderable->mesh = RenderableComponent::MeshKind::Capsule;
   renderable->color = {0.8F, 0.2F, 0.5F};
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("renderable"));
   QJsonObject renderable_obj = json["renderable"].toObject();
@@ -420,10 +606,10 @@ TEST_F(SerializationTest, RenderableComponentRoundTrip) {
   renderable->mesh = RenderableComponent::MeshKind::Quad;
   renderable->color = {1.0F, 0.5F, 0.25F};
 
-  QJsonObject json = Serialization::serializeEntity(original_entity);
+  QJsonObject json = Serialization::serialize_entity(original_entity);
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   auto *deserialized = new_entity->get_component<RenderableComponent>();
   ASSERT_NE(deserialized, nullptr);
@@ -443,7 +629,7 @@ TEST_F(SerializationTest, AttackTargetComponentSerialization) {
   attack_target->target_id = 42;
   attack_target->should_chase = true;
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("attack_target"));
   QJsonObject attack_target_obj = json["attack_target"].toObject();
@@ -458,10 +644,10 @@ TEST_F(SerializationTest, AttackTargetComponentRoundTrip) {
   attack_target->target_id = 123;
   attack_target->should_chase = false;
 
-  QJsonObject json = Serialization::serializeEntity(original_entity);
+  QJsonObject json = Serialization::serialize_entity(original_entity);
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   auto *deserialized = new_entity->get_component<AttackTargetComponent>();
   ASSERT_NE(deserialized, nullptr);
@@ -473,7 +659,7 @@ TEST_F(SerializationTest, BuildingComponentSerialization) {
   auto *entity = world->create_entity();
   entity->add_component<BuildingComponent>();
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("building"));
   EXPECT_TRUE(json["building"].toBool());
@@ -483,10 +669,10 @@ TEST_F(SerializationTest, BuildingComponentRoundTrip) {
   auto *original_entity = world->create_entity();
   original_entity->add_component<BuildingComponent>();
 
-  QJsonObject json = Serialization::serializeEntity(original_entity);
+  QJsonObject json = Serialization::serialize_entity(original_entity);
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   auto *deserialized = new_entity->get_component<BuildingComponent>();
   ASSERT_NE(deserialized, nullptr);
@@ -496,7 +682,7 @@ TEST_F(SerializationTest, AIControlledComponentSerialization) {
   auto *entity = world->create_entity();
   entity->add_component<AIControlledComponent>();
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("aiControlled"));
   EXPECT_TRUE(json["aiControlled"].toBool());
@@ -506,10 +692,10 @@ TEST_F(SerializationTest, AIControlledComponentRoundTrip) {
   auto *original_entity = world->create_entity();
   original_entity->add_component<AIControlledComponent>();
 
-  QJsonObject json = Serialization::serializeEntity(original_entity);
+  QJsonObject json = Serialization::serialize_entity(original_entity);
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   auto *deserialized = new_entity->get_component<AIControlledComponent>();
   ASSERT_NE(deserialized, nullptr);
@@ -524,7 +710,7 @@ TEST_F(SerializationTest, CaptureComponentSerialization) {
   capture->required_time = 15.0F;
   capture->is_being_captured = true;
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   ASSERT_TRUE(json.contains("capture"));
   QJsonObject capture_obj = json["capture"].toObject();
@@ -543,10 +729,10 @@ TEST_F(SerializationTest, CaptureComponentRoundTrip) {
   capture->required_time = 20.0F;
   capture->is_being_captured = false;
 
-  QJsonObject json = Serialization::serializeEntity(original_entity);
+  QJsonObject json = Serialization::serialize_entity(original_entity);
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   auto *deserialized = new_entity->get_component<CaptureComponent>();
   ASSERT_NE(deserialized, nullptr);
@@ -592,7 +778,16 @@ TEST_F(SerializationTest, CompleteEntityWithAllComponents) {
   auto *capture = entity->add_component<CaptureComponent>();
   capture->is_being_captured = true;
 
-  QJsonObject json = Serialization::serializeEntity(entity);
+  auto *hold_mode = entity->add_component<HoldModeComponent>();
+  hold_mode->active = true;
+
+  auto *healer = entity->add_component<HealerComponent>();
+  healer->healing_amount = 10;
+
+  auto *catapult = entity->add_component<CatapultLoadingComponent>();
+  catapult->state = CatapultLoadingComponent::LoadingState::Idle;
+
+  QJsonObject json = Serialization::serialize_entity(entity);
 
   EXPECT_TRUE(json.contains("transform"));
   EXPECT_TRUE(json.contains("renderable"));
@@ -604,9 +799,12 @@ TEST_F(SerializationTest, CompleteEntityWithAllComponents) {
   EXPECT_TRUE(json.contains("production"));
   EXPECT_TRUE(json.contains("aiControlled"));
   EXPECT_TRUE(json.contains("capture"));
+  EXPECT_TRUE(json.contains("hold_mode"));
+  EXPECT_TRUE(json.contains("healer"));
+  EXPECT_TRUE(json.contains("catapult_loading"));
 
   auto *new_entity = world->create_entity();
-  Serialization::deserializeEntity(new_entity, json);
+  Serialization::deserialize_entity(new_entity, json);
 
   EXPECT_NE(new_entity->get_component<TransformComponent>(), nullptr);
   EXPECT_NE(new_entity->get_component<RenderableComponent>(), nullptr);
@@ -618,10 +816,13 @@ TEST_F(SerializationTest, CompleteEntityWithAllComponents) {
   EXPECT_NE(new_entity->get_component<ProductionComponent>(), nullptr);
   EXPECT_NE(new_entity->get_component<AIControlledComponent>(), nullptr);
   EXPECT_NE(new_entity->get_component<CaptureComponent>(), nullptr);
+  EXPECT_NE(new_entity->get_component<HoldModeComponent>(), nullptr);
+  EXPECT_NE(new_entity->get_component<HealerComponent>(), nullptr);
+  EXPECT_NE(new_entity->get_component<CatapultLoadingComponent>(), nullptr);
 }
 
 TEST_F(SerializationTest, EmptyWorldSerialization) {
-  QJsonDocument doc = Serialization::serializeWorld(world.get());
+  QJsonDocument doc = Serialization::serialize_world(world.get());
 
   ASSERT_TRUE(doc.isObject());
   QJsonObject world_obj = doc.object();
@@ -630,3 +831,553 @@ TEST_F(SerializationTest, EmptyWorldSerialization) {
   QJsonArray entities = world_obj["entities"].toArray();
   EXPECT_EQ(entities.size(), 0);
 }
+
+TEST_F(SerializationTest, HoldModeComponentSerialization) {
+  auto *entity = world->create_entity();
+  auto *hold_mode = entity->add_component<HoldModeComponent>();
+
+  hold_mode->active = false;
+  hold_mode->exit_cooldown = 1.5F;
+  hold_mode->stand_up_duration = 3.0F;
+
+  QJsonObject json = Serialization::serialize_entity(entity);
+
+  ASSERT_TRUE(json.contains("hold_mode"));
+  QJsonObject hold_mode_obj = json["hold_mode"].toObject();
+
+  EXPECT_FALSE(hold_mode_obj["active"].toBool());
+  EXPECT_FLOAT_EQ(hold_mode_obj["exit_cooldown"].toDouble(), 1.5);
+  EXPECT_FLOAT_EQ(hold_mode_obj["stand_up_duration"].toDouble(), 3.0);
+}
+
+TEST_F(SerializationTest, HoldModeComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *hold_mode = original_entity->add_component<HoldModeComponent>();
+  hold_mode->active = true;
+  hold_mode->exit_cooldown = 2.5F;
+  hold_mode->stand_up_duration = 4.0F;
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<HoldModeComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_TRUE(deserialized->active);
+  EXPECT_FLOAT_EQ(deserialized->exit_cooldown, 2.5F);
+  EXPECT_FLOAT_EQ(deserialized->stand_up_duration, 4.0F);
+}
+
+TEST_F(SerializationTest, HealerComponentSerialization) {
+  auto *entity = world->create_entity();
+  auto *healer = entity->add_component<HealerComponent>();
+
+  healer->healing_range = 12.0F;
+  healer->healing_amount = 10;
+  healer->healing_cooldown = 3.0F;
+  healer->time_since_last_heal = 1.0F;
+
+  QJsonObject json = Serialization::serialize_entity(entity);
+
+  ASSERT_TRUE(json.contains("healer"));
+  QJsonObject healer_obj = json["healer"].toObject();
+
+  EXPECT_FLOAT_EQ(healer_obj["healing_range"].toDouble(), 12.0);
+  EXPECT_EQ(healer_obj["healing_amount"].toInt(), 10);
+  EXPECT_FLOAT_EQ(healer_obj["healing_cooldown"].toDouble(), 3.0);
+  EXPECT_FLOAT_EQ(healer_obj["time_since_last_heal"].toDouble(), 1.0);
+}
+
+TEST_F(SerializationTest, HealerComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *healer = original_entity->add_component<HealerComponent>();
+  healer->healing_range = 15.0F;
+  healer->healing_amount = 8;
+  healer->healing_cooldown = 4.0F;
+  healer->time_since_last_heal = 2.0F;
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<HealerComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_FLOAT_EQ(deserialized->healing_range, 15.0F);
+  EXPECT_EQ(deserialized->healing_amount, 8);
+  EXPECT_FLOAT_EQ(deserialized->healing_cooldown, 4.0F);
+  EXPECT_FLOAT_EQ(deserialized->time_since_last_heal, 2.0F);
+}
+
+TEST_F(SerializationTest, CatapultLoadingComponentSerialization) {
+  auto *entity = world->create_entity();
+  auto *catapult = entity->add_component<CatapultLoadingComponent>();
+
+  catapult->state = CatapultLoadingComponent::LoadingState::Loading;
+  catapult->loading_time = 1.0F;
+  catapult->loading_duration = 3.0F;
+  catapult->firing_time = 0.0F;
+  catapult->firing_duration = 1.0F;
+  catapult->target_id = 42;
+  catapult->target_locked_x = 100.0F;
+  catapult->target_locked_y = 50.0F;
+  catapult->target_locked_z = 200.0F;
+  catapult->target_position_locked = true;
+
+  QJsonObject json = Serialization::serialize_entity(entity);
+
+  ASSERT_TRUE(json.contains("catapult_loading"));
+  QJsonObject catapult_obj = json["catapult_loading"].toObject();
+
+  EXPECT_EQ(catapult_obj["state"].toInt(),
+            static_cast<int>(CatapultLoadingComponent::LoadingState::Loading));
+  EXPECT_FLOAT_EQ(catapult_obj["loading_time"].toDouble(), 1.0);
+  EXPECT_FLOAT_EQ(catapult_obj["loading_duration"].toDouble(), 3.0);
+  EXPECT_FLOAT_EQ(catapult_obj["firing_time"].toDouble(), 0.0);
+  EXPECT_FLOAT_EQ(catapult_obj["firing_duration"].toDouble(), 1.0);
+  EXPECT_EQ(catapult_obj["target_id"].toVariant().toULongLong(), 42ULL);
+  EXPECT_FLOAT_EQ(catapult_obj["target_locked_x"].toDouble(), 100.0);
+  EXPECT_FLOAT_EQ(catapult_obj["target_locked_y"].toDouble(), 50.0);
+  EXPECT_FLOAT_EQ(catapult_obj["target_locked_z"].toDouble(), 200.0);
+  EXPECT_TRUE(catapult_obj["target_position_locked"].toBool());
+}
+
+TEST_F(SerializationTest, CatapultLoadingComponentRoundTrip) {
+  auto *original_entity = world->create_entity();
+  auto *catapult = original_entity->add_component<CatapultLoadingComponent>();
+  catapult->state = CatapultLoadingComponent::LoadingState::ReadyToFire;
+  catapult->loading_time = 2.0F;
+  catapult->loading_duration = 4.0F;
+  catapult->firing_time = 0.25F;
+  catapult->firing_duration = 0.75F;
+  catapult->target_id = 99;
+  catapult->target_locked_x = 150.0F;
+  catapult->target_locked_y = 75.0F;
+  catapult->target_locked_z = 250.0F;
+  catapult->target_position_locked = false;
+
+  QJsonObject json = Serialization::serialize_entity(original_entity);
+
+  auto *new_entity = world->create_entity();
+  Serialization::deserialize_entity(new_entity, json);
+
+  auto *deserialized = new_entity->get_component<CatapultLoadingComponent>();
+  ASSERT_NE(deserialized, nullptr);
+  EXPECT_EQ(deserialized->state,
+            CatapultLoadingComponent::LoadingState::ReadyToFire);
+  EXPECT_FLOAT_EQ(deserialized->loading_time, 2.0F);
+  EXPECT_FLOAT_EQ(deserialized->loading_duration, 4.0F);
+  EXPECT_FLOAT_EQ(deserialized->firing_time, 0.25F);
+  EXPECT_FLOAT_EQ(deserialized->firing_duration, 0.75F);
+  EXPECT_EQ(deserialized->target_id, 99U);
+  EXPECT_FLOAT_EQ(deserialized->target_locked_x, 150.0F);
+  EXPECT_FLOAT_EQ(deserialized->target_locked_y, 75.0F);
+  EXPECT_FLOAT_EQ(deserialized->target_locked_z, 250.0F);
+  EXPECT_FALSE(deserialized->target_position_locked);
+}
+
+// ============================================================================
+// Integration Tests: Multi-Unit Battlefield State Preservation
+// ============================================================================
+
+TEST_F(SerializationTest, MultipleUnitsPositionsAndHealthPreserved) {
+  // Create a battlefield with multiple units at different positions
+  struct UnitData {
+    float x, y, z;
+    int health;
+    int max_health;
+    int owner_id;
+    Game::Units::SpawnType spawn_type;
+  };
+
+  std::vector<UnitData> original_units = {
+      {10.0F, 0.0F, 20.0F, 80, 100, 1, Game::Units::SpawnType::Archer},
+      {15.5F, 1.0F, 25.5F, 45, 100, 1, Game::Units::SpawnType::Spearman},
+      {30.0F, 0.0F, 40.0F, 100, 100, 2, Game::Units::SpawnType::Knight},
+      {35.5F, 2.0F, 45.5F, 60, 150, 2, Game::Units::SpawnType::HorseArcher},
+      {50.0F, 0.5F, 60.0F, 25, 80, 1, Game::Units::SpawnType::Catapult},
+  };
+
+  std::vector<EntityID> entity_ids;
+  for (const auto &unit_data : original_units) {
+    auto *entity = world->create_entity();
+    entity_ids.push_back(entity->get_id());
+
+    auto *transform = entity->add_component<TransformComponent>();
+    transform->position.x = unit_data.x;
+    transform->position.y = unit_data.y;
+    transform->position.z = unit_data.z;
+
+    auto *unit = entity->add_component<UnitComponent>();
+    unit->health = unit_data.health;
+    unit->max_health = unit_data.max_health;
+    unit->owner_id = unit_data.owner_id;
+    unit->spawn_type = unit_data.spawn_type;
+  }
+
+  // Serialize and deserialize the world
+  QJsonDocument doc = Serialization::serialize_world(world.get());
+  auto restored_world = std::make_unique<World>();
+  Serialization::deserialize_world(restored_world.get(), doc);
+
+  // Verify all units are restored with exact positions and health
+  const auto &entities = restored_world->get_entities();
+  EXPECT_EQ(entities.size(), original_units.size());
+
+  for (size_t i = 0; i < entity_ids.size(); ++i) {
+    auto *entity = restored_world->get_entity(entity_ids[i]);
+    ASSERT_NE(entity, nullptr) << "Entity " << i << " not found";
+
+    auto *transform = entity->get_component<TransformComponent>();
+    ASSERT_NE(transform, nullptr);
+    EXPECT_FLOAT_EQ(transform->position.x, original_units[i].x)
+        << "Unit " << i << " X position mismatch";
+    EXPECT_FLOAT_EQ(transform->position.y, original_units[i].y)
+        << "Unit " << i << " Y position mismatch";
+    EXPECT_FLOAT_EQ(transform->position.z, original_units[i].z)
+        << "Unit " << i << " Z position mismatch";
+
+    auto *unit = entity->get_component<UnitComponent>();
+    ASSERT_NE(unit, nullptr);
+    EXPECT_EQ(unit->health, original_units[i].health)
+        << "Unit " << i << " health mismatch";
+    EXPECT_EQ(unit->max_health, original_units[i].max_health)
+        << "Unit " << i << " max_health mismatch";
+    EXPECT_EQ(unit->owner_id, original_units[i].owner_id)
+        << "Unit " << i << " owner_id mismatch";
+    EXPECT_EQ(unit->spawn_type, original_units[i].spawn_type)
+        << "Unit " << i << " spawn_type mismatch";
+  }
+}
+
+TEST_F(SerializationTest, OwnerRegistryTeamsAndColorsPreserved) {
+  // Setup owner registry with teams and custom colors
+  auto &registry = Game::Systems::OwnerRegistry::instance();
+  registry.clear();
+
+  // Register players with specific teams and colors
+  int player1 = registry.register_owner(Game::Systems::OwnerType::Player, "Blue Kingdom");
+  int player2 = registry.register_owner(Game::Systems::OwnerType::AI, "Red Empire");
+  int player3 = registry.register_owner(Game::Systems::OwnerType::Player, "Green Alliance");
+
+  // Set teams (player1 and player3 are allies)
+  registry.set_owner_team(player1, 1);
+  registry.set_owner_team(player2, 2);
+  registry.set_owner_team(player3, 1);
+
+  // Set custom colors
+  registry.set_owner_color(player1, 0.1F, 0.2F, 0.9F);
+  registry.set_owner_color(player2, 0.9F, 0.1F, 0.1F);
+  registry.set_owner_color(player3, 0.1F, 0.9F, 0.2F);
+
+  registry.set_local_player_id(player1);
+
+  // Create some entities owned by these players
+  for (int i = 0; i < 3; ++i) {
+    auto *entity = world->create_entity();
+    auto *unit = entity->add_component<UnitComponent>();
+    unit->owner_id = player1;
+  }
+  for (int i = 0; i < 2; ++i) {
+    auto *entity = world->create_entity();
+    auto *unit = entity->add_component<UnitComponent>();
+    unit->owner_id = player2;
+  }
+
+  // Serialize world (includes owner_registry)
+  QJsonDocument doc = Serialization::serialize_world(world.get());
+
+  // Clear registry and restore
+  registry.clear();
+  auto restored_world = std::make_unique<World>();
+  Serialization::deserialize_world(restored_world.get(), doc);
+
+  // Verify owner registry state is preserved
+  EXPECT_EQ(registry.get_local_player_id(), player1);
+
+  // Verify teams are preserved
+  EXPECT_EQ(registry.get_owner_team(player1), 1);
+  EXPECT_EQ(registry.get_owner_team(player2), 2);
+  EXPECT_EQ(registry.get_owner_team(player3), 1);
+
+  // Verify alliances are preserved
+  EXPECT_TRUE(registry.are_allies(player1, player3));
+  EXPECT_TRUE(registry.are_enemies(player1, player2));
+  EXPECT_TRUE(registry.are_enemies(player2, player3));
+
+  // Verify colors are preserved
+  auto color1 = registry.get_owner_color(player1);
+  EXPECT_FLOAT_EQ(color1[0], 0.1F);
+  EXPECT_FLOAT_EQ(color1[1], 0.2F);
+  EXPECT_FLOAT_EQ(color1[2], 0.9F);
+
+  auto color2 = registry.get_owner_color(player2);
+  EXPECT_FLOAT_EQ(color2[0], 0.9F);
+  EXPECT_FLOAT_EQ(color2[1], 0.1F);
+  EXPECT_FLOAT_EQ(color2[2], 0.1F);
+
+  auto color3 = registry.get_owner_color(player3);
+  EXPECT_FLOAT_EQ(color3[0], 0.1F);
+  EXPECT_FLOAT_EQ(color3[1], 0.9F);
+  EXPECT_FLOAT_EQ(color3[2], 0.2F);
+
+  // Verify owner names are preserved
+  EXPECT_EQ(registry.get_owner_name(player1), "Blue Kingdom");
+  EXPECT_EQ(registry.get_owner_name(player2), "Red Empire");
+  EXPECT_EQ(registry.get_owner_name(player3), "Green Alliance");
+
+  // Verify owner types are preserved
+  EXPECT_TRUE(registry.is_player(player1));
+  EXPECT_TRUE(registry.is_ai(player2));
+  EXPECT_TRUE(registry.is_player(player3));
+
+  // Clean up
+  registry.clear();
+}
+
+TEST_F(SerializationTest, BuildingOwnershipAndCaptureStatePreserved) {
+  // Create buildings (barracks/villages) with different ownership and capture states
+  struct BuildingData {
+    float x, z;
+    int owner_id;
+    int capturing_player_id;
+    float capture_progress;
+    bool is_being_captured;
+  };
+
+  std::vector<BuildingData> buildings = {
+      {100.0F, 100.0F, 1, -1, 0.0F, false},        // Owned by player 1, not being captured
+      {200.0F, 200.0F, 2, 1, 7.5F, true},          // Owned by player 2, being captured by player 1
+      {300.0F, 300.0F, 1, 2, 15.0F, true},         // Owned by player 1, being captured by player 2
+      {400.0F, 400.0F, -1, -1, 0.0F, false},       // Neutral building
+  };
+
+  std::vector<EntityID> building_ids;
+  for (const auto &bldg : buildings) {
+    auto *entity = world->create_entity();
+    building_ids.push_back(entity->get_id());
+
+    auto *transform = entity->add_component<TransformComponent>();
+    transform->position.x = bldg.x;
+    transform->position.z = bldg.z;
+
+    entity->add_component<BuildingComponent>();
+
+    auto *unit = entity->add_component<UnitComponent>();
+    unit->owner_id = bldg.owner_id;
+
+    auto *capture = entity->add_component<CaptureComponent>();
+    capture->capturing_player_id = bldg.capturing_player_id;
+    capture->capture_progress = bldg.capture_progress;
+    capture->is_being_captured = bldg.is_being_captured;
+  }
+
+  // Serialize and restore
+  QJsonDocument doc = Serialization::serialize_world(world.get());
+  auto restored_world = std::make_unique<World>();
+  Serialization::deserialize_world(restored_world.get(), doc);
+
+  // Verify all buildings are restored with correct ownership and capture state
+  for (size_t i = 0; i < building_ids.size(); ++i) {
+    auto *entity = restored_world->get_entity(building_ids[i]);
+    ASSERT_NE(entity, nullptr) << "Building " << i << " not found";
+
+    auto *transform = entity->get_component<TransformComponent>();
+    ASSERT_NE(transform, nullptr);
+    EXPECT_FLOAT_EQ(transform->position.x, buildings[i].x)
+        << "Building " << i << " X position mismatch";
+    EXPECT_FLOAT_EQ(transform->position.z, buildings[i].z)
+        << "Building " << i << " Z position mismatch";
+
+    EXPECT_NE(entity->get_component<BuildingComponent>(), nullptr);
+
+    auto *unit = entity->get_component<UnitComponent>();
+    ASSERT_NE(unit, nullptr);
+    EXPECT_EQ(unit->owner_id, buildings[i].owner_id)
+        << "Building " << i << " owner mismatch";
+
+    auto *capture = entity->get_component<CaptureComponent>();
+    ASSERT_NE(capture, nullptr);
+    EXPECT_EQ(capture->capturing_player_id, buildings[i].capturing_player_id)
+        << "Building " << i << " capturing_player_id mismatch";
+    EXPECT_FLOAT_EQ(capture->capture_progress, buildings[i].capture_progress)
+        << "Building " << i << " capture_progress mismatch";
+    EXPECT_EQ(capture->is_being_captured, buildings[i].is_being_captured)
+        << "Building " << i << " is_being_captured mismatch";
+  }
+}
+
+TEST_F(SerializationTest, UnitMovementStatePreserved) {
+  // Create units with active movement paths
+  auto *entity = world->create_entity();
+  auto entity_id = entity->get_id();
+
+  auto *transform = entity->add_component<TransformComponent>();
+  transform->position.x = 10.0F;
+  transform->position.y = 0.0F;
+  transform->position.z = 20.0F;
+
+  auto *unit = entity->add_component<UnitComponent>();
+  unit->owner_id = 1;
+  unit->health = 85;
+
+  auto *movement = entity->add_component<MovementComponent>();
+  movement->has_target = true;
+  movement->target_x = 50.0F;
+  movement->target_y = 60.0F;
+  movement->goal_x = 55.0F;
+  movement->goal_y = 65.0F;
+  movement->vx = 2.5F;
+  movement->vz = 3.0F;
+  // Add path waypoints
+  movement->path.emplace_back(20.0F, 30.0F);
+  movement->path.emplace_back(35.0F, 45.0F);
+  movement->path.emplace_back(50.0F, 60.0F);
+  const size_t expected_path_size = movement->path.size();
+
+  // Serialize and restore
+  QJsonDocument doc = Serialization::serialize_world(world.get());
+  auto restored_world = std::make_unique<World>();
+  Serialization::deserialize_world(restored_world.get(), doc);
+
+  // Verify movement state is preserved
+  auto *restored_entity = restored_world->get_entity(entity_id);
+  ASSERT_NE(restored_entity, nullptr);
+
+  auto *restored_movement = restored_entity->get_component<MovementComponent>();
+  ASSERT_NE(restored_movement, nullptr);
+
+  EXPECT_TRUE(restored_movement->has_target);
+  EXPECT_FLOAT_EQ(restored_movement->target_x, 50.0F);
+  EXPECT_FLOAT_EQ(restored_movement->target_y, 60.0F);
+  EXPECT_FLOAT_EQ(restored_movement->goal_x, 55.0F);
+  EXPECT_FLOAT_EQ(restored_movement->goal_y, 65.0F);
+  EXPECT_FLOAT_EQ(restored_movement->vx, 2.5F);
+  EXPECT_FLOAT_EQ(restored_movement->vz, 3.0F);
+
+  // Verify path is preserved
+  ASSERT_EQ(restored_movement->path.size(), expected_path_size);
+  EXPECT_FLOAT_EQ(restored_movement->path[0].first, 20.0F);
+  EXPECT_FLOAT_EQ(restored_movement->path[0].second, 30.0F);
+  EXPECT_FLOAT_EQ(restored_movement->path[1].first, 35.0F);
+  EXPECT_FLOAT_EQ(restored_movement->path[1].second, 45.0F);
+  EXPECT_FLOAT_EQ(restored_movement->path[2].first, 50.0F);
+  EXPECT_FLOAT_EQ(restored_movement->path[2].second, 60.0F);
+}
+
+TEST_F(SerializationTest, CombatStatePreserved) {
+  // Create units engaged in combat
+  auto *attacker = world->create_entity();
+  auto *defender = world->create_entity();
+  auto attacker_id = attacker->get_id();
+  auto defender_id = defender->get_id();
+
+  // Setup attacker
+  auto *attacker_transform = attacker->add_component<TransformComponent>();
+  attacker_transform->position.x = 10.0F;
+  attacker_transform->position.z = 10.0F;
+
+  auto *attacker_unit = attacker->add_component<UnitComponent>();
+  attacker_unit->owner_id = 1;
+  attacker_unit->health = 90;
+
+  auto *attacker_attack = attacker->add_component<AttackComponent>();
+  attacker_attack->damage = 25;
+  attacker_attack->range = 15.0F;
+  attacker_attack->current_mode = AttackComponent::CombatMode::Melee;
+  attacker_attack->in_melee_lock = true;
+  attacker_attack->melee_lock_target_id = defender_id;
+
+  auto *attacker_target = attacker->add_component<AttackTargetComponent>();
+  attacker_target->target_id = defender_id;
+  attacker_target->should_chase = true;
+
+  // Setup defender
+  auto *defender_transform = defender->add_component<TransformComponent>();
+  defender_transform->position.x = 12.0F;
+  defender_transform->position.z = 12.0F;
+
+  auto *defender_unit = defender->add_component<UnitComponent>();
+  defender_unit->owner_id = 2;
+  defender_unit->health = 60;
+
+  auto *defender_attack = defender->add_component<AttackComponent>();
+  defender_attack->damage = 20;
+  defender_attack->in_melee_lock = true;
+  defender_attack->melee_lock_target_id = attacker_id;
+
+  // Serialize and restore
+  QJsonDocument doc = Serialization::serialize_world(world.get());
+  auto restored_world = std::make_unique<World>();
+  Serialization::deserialize_world(restored_world.get(), doc);
+
+  // Verify combat state is preserved
+  auto *restored_attacker = restored_world->get_entity(attacker_id);
+  auto *restored_defender = restored_world->get_entity(defender_id);
+  ASSERT_NE(restored_attacker, nullptr);
+  ASSERT_NE(restored_defender, nullptr);
+
+  auto *restored_attacker_attack =
+      restored_attacker->get_component<AttackComponent>();
+  ASSERT_NE(restored_attacker_attack, nullptr);
+  EXPECT_TRUE(restored_attacker_attack->in_melee_lock);
+  EXPECT_EQ(restored_attacker_attack->melee_lock_target_id, defender_id);
+  EXPECT_EQ(restored_attacker_attack->current_mode,
+            AttackComponent::CombatMode::Melee);
+
+  auto *restored_attacker_target =
+      restored_attacker->get_component<AttackTargetComponent>();
+  ASSERT_NE(restored_attacker_target, nullptr);
+  EXPECT_EQ(restored_attacker_target->target_id, defender_id);
+  EXPECT_TRUE(restored_attacker_target->should_chase);
+
+  auto *restored_defender_attack =
+      restored_defender->get_component<AttackComponent>();
+  ASSERT_NE(restored_defender_attack, nullptr);
+  EXPECT_TRUE(restored_defender_attack->in_melee_lock);
+  EXPECT_EQ(restored_defender_attack->melee_lock_target_id, attacker_id);
+
+  // Verify health is preserved
+  auto *restored_attacker_unit =
+      restored_attacker->get_component<UnitComponent>();
+  auto *restored_defender_unit =
+      restored_defender->get_component<UnitComponent>();
+  EXPECT_EQ(restored_attacker_unit->health, 90);
+  EXPECT_EQ(restored_defender_unit->health, 60);
+}
+
+TEST_F(SerializationTest, NationIdentityPreserved) {
+  // Create units from different nations
+  auto *roman_unit = world->create_entity();
+  auto *carthage_unit = world->create_entity();
+  auto roman_id = roman_unit->get_id();
+  auto carthage_id = carthage_unit->get_id();
+
+  auto *roman_unit_comp = roman_unit->add_component<UnitComponent>();
+  roman_unit_comp->nation_id = Game::Systems::NationID::RomanRepublic;
+  roman_unit_comp->spawn_type = Game::Units::SpawnType::Spearman;
+
+  auto *carthage_unit_comp = carthage_unit->add_component<UnitComponent>();
+  carthage_unit_comp->nation_id = Game::Systems::NationID::Carthage;
+  carthage_unit_comp->spawn_type = Game::Units::SpawnType::Archer;
+
+  // Serialize and restore
+  QJsonDocument doc = Serialization::serialize_world(world.get());
+  auto restored_world = std::make_unique<World>();
+  Serialization::deserialize_world(restored_world.get(), doc);
+
+  // Verify nation IDs are preserved
+  auto *restored_roman = restored_world->get_entity(roman_id);
+  auto *restored_carthage = restored_world->get_entity(carthage_id);
+
+  auto *restored_roman_comp = restored_roman->get_component<UnitComponent>();
+  EXPECT_EQ(restored_roman_comp->nation_id,
+            Game::Systems::NationID::RomanRepublic);
+  EXPECT_EQ(restored_roman_comp->spawn_type, Game::Units::SpawnType::Spearman);
+
+  auto *restored_carthage_comp =
+      restored_carthage->get_component<UnitComponent>();
+  EXPECT_EQ(restored_carthage_comp->nation_id, Game::Systems::NationID::Carthage);
+  EXPECT_EQ(restored_carthage_comp->spawn_type, Game::Units::SpawnType::Archer);
+}

+ 30 - 30
tests/db/save_storage_test.cpp

@@ -35,7 +35,7 @@ TEST_F(SaveStorageTest, SaveSlotBasic) {
   QByteArray screenshot("screenshot_data");
 
   QString error;
-  bool saved = storage->saveSlot(slot_name, title, metadata, world_state,
+  bool saved = storage->save_slot(slot_name, title, metadata, world_state,
                                  screenshot, &error);
 
   EXPECT_TRUE(saved) << "Failed to save: " << error.toStdString();
@@ -55,7 +55,7 @@ TEST_F(SaveStorageTest, SaveAndLoadSlot) {
 
   QString error;
   bool saved =
-      storage->saveSlot(slot_name, original_title, original_metadata,
+      storage->save_slot(slot_name, original_title, original_metadata,
                         original_world_state, original_screenshot, &error);
   ASSERT_TRUE(saved) << "Save failed: " << error.toStdString();
 
@@ -65,7 +65,7 @@ TEST_F(SaveStorageTest, SaveAndLoadSlot) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   ASSERT_TRUE(loaded) << "Load failed: " << error.toStdString();
@@ -95,11 +95,11 @@ TEST_F(SaveStorageTest, OverwriteExistingSlot) {
 
   QString error;
 
-  bool saved1 = storage->saveSlot(slot_name, title1, metadata1, world_state1,
+  bool saved1 = storage->save_slot(slot_name, title1, metadata1, world_state1,
                                   QByteArray(), &error);
   ASSERT_TRUE(saved1) << "First save failed: " << error.toStdString();
 
-  bool saved2 = storage->saveSlot(slot_name, title2, metadata2, world_state2,
+  bool saved2 = storage->save_slot(slot_name, title2, metadata2, world_state2,
                                   QByteArray(), &error);
   ASSERT_TRUE(saved2) << "Second save failed: " << error.toStdString();
 
@@ -109,7 +109,7 @@ TEST_F(SaveStorageTest, OverwriteExistingSlot) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   ASSERT_TRUE(loaded) << "Load failed: " << error.toStdString();
@@ -129,7 +129,7 @@ TEST_F(SaveStorageTest, LoadNonExistentSlot) {
   QString error;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   EXPECT_FALSE(loaded);
@@ -140,14 +140,14 @@ TEST_F(SaveStorageTest, ListSlots) {
   QString error;
 
   QByteArray non_empty_data("test_data");
-  storage->saveSlot("slot1", "Title 1", QJsonObject(), non_empty_data,
+  storage->save_slot("slot1", "Title 1", QJsonObject(), non_empty_data,
                     QByteArray(), &error);
-  storage->saveSlot("slot2", "Title 2", QJsonObject(), non_empty_data,
+  storage->save_slot("slot2", "Title 2", QJsonObject(), non_empty_data,
                     QByteArray(), &error);
-  storage->saveSlot("slot3", "Title 3", QJsonObject(), non_empty_data,
+  storage->save_slot("slot3", "Title 3", QJsonObject(), non_empty_data,
                     QByteArray(), &error);
 
-  QVariantList slot_list = storage->listSlots(&error);
+  QVariantList slot_list = storage->list_slots(&error);
 
   EXPECT_TRUE(error.isEmpty()) << "List failed: " << error.toStdString();
   EXPECT_EQ(slot_list.size(), 3);
@@ -182,16 +182,16 @@ TEST_F(SaveStorageTest, DeleteSlot) {
   QString error;
 
   QByteArray non_empty_data("test_data");
-  storage->saveSlot(slot_name, "Title", QJsonObject(), non_empty_data,
+  storage->save_slot(slot_name, "Title", QJsonObject(), non_empty_data,
                     QByteArray(), &error);
 
-  QVariantList slots_before = storage->listSlots(&error);
+  QVariantList slots_before = storage->list_slots(&error);
   EXPECT_EQ(slots_before.size(), 1);
 
-  bool deleted = storage->deleteSlot(slot_name, &error);
+  bool deleted = storage->delete_slot(slot_name, &error);
   EXPECT_TRUE(deleted) << "Delete failed: " << error.toStdString();
 
-  QVariantList slots_after = storage->listSlots(&error);
+  QVariantList slots_after = storage->list_slots(&error);
   EXPECT_EQ(slots_after.size(), 0);
 }
 
@@ -199,7 +199,7 @@ TEST_F(SaveStorageTest, DeleteNonExistentSlot) {
   QString slot_name = "nonexistent_delete";
   QString error;
 
-  bool deleted = storage->deleteSlot(slot_name, &error);
+  bool deleted = storage->delete_slot(slot_name, &error);
 
   EXPECT_FALSE(deleted);
   EXPECT_FALSE(error.isEmpty());
@@ -210,7 +210,7 @@ TEST_F(SaveStorageTest, EmptyMetadataSave) {
   QJsonObject empty_metadata;
 
   QString error;
-  bool saved = storage->saveSlot(slot_name, "Title", empty_metadata,
+  bool saved = storage->save_slot(slot_name, "Title", empty_metadata,
                                  QByteArray("data"), QByteArray(), &error);
 
   EXPECT_TRUE(saved) << "Failed to save: " << error.toStdString();
@@ -221,7 +221,7 @@ TEST_F(SaveStorageTest, EmptyMetadataSave) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   EXPECT_TRUE(loaded) << "Failed to load: " << error.toStdString();
@@ -232,7 +232,7 @@ TEST_F(SaveStorageTest, EmptyWorldStateSave) {
   QByteArray minimal_world_state(" ");
 
   QString error;
-  bool saved = storage->saveSlot(slot_name, "Title", QJsonObject(),
+  bool saved = storage->save_slot(slot_name, "Title", QJsonObject(),
                                  minimal_world_state, QByteArray(), &error);
 
   EXPECT_TRUE(saved) << "Failed to save: " << error.toStdString();
@@ -243,7 +243,7 @@ TEST_F(SaveStorageTest, EmptyWorldStateSave) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   EXPECT_TRUE(loaded) << "Failed to load: " << error.toStdString();
@@ -260,7 +260,7 @@ TEST_F(SaveStorageTest, LargeDataSave) {
   metadata["size"] = "large";
 
   QString error;
-  bool saved = storage->saveSlot(slot_name, "Large Data Test", metadata,
+  bool saved = storage->save_slot(slot_name, "Large Data Test", metadata,
                                  large_world_state, large_screenshot, &error);
 
   EXPECT_TRUE(saved) << "Failed to save large data: " << error.toStdString();
@@ -271,7 +271,7 @@ TEST_F(SaveStorageTest, LargeDataSave) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   EXPECT_TRUE(loaded) << "Failed to load large data: " << error.toStdString();
@@ -287,7 +287,7 @@ TEST_F(SaveStorageTest, SpecialCharactersInSlotName) {
   metadata["description"] = "Test with special characters: <>&\"'";
 
   QString error;
-  bool saved = storage->saveSlot(slot_name, title, metadata, QByteArray("data"),
+  bool saved = storage->save_slot(slot_name, title, metadata, QByteArray("data"),
                                  QByteArray(), &error);
 
   EXPECT_TRUE(saved) << "Failed to save: " << error.toStdString();
@@ -298,7 +298,7 @@ TEST_F(SaveStorageTest, SpecialCharactersInSlotName) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   EXPECT_TRUE(loaded) << "Failed to load: " << error.toStdString();
@@ -325,7 +325,7 @@ TEST_F(SaveStorageTest, ComplexMetadataSave) {
   metadata["array"] = array;
 
   QString error;
-  bool saved = storage->saveSlot(slot_name, "Complex Metadata Test", metadata,
+  bool saved = storage->save_slot(slot_name, "Complex Metadata Test", metadata,
                                  QByteArray("data"), QByteArray(), &error);
 
   EXPECT_TRUE(saved) << "Failed to save: " << error.toStdString();
@@ -336,7 +336,7 @@ TEST_F(SaveStorageTest, ComplexMetadataSave) {
   QString loaded_title;
 
   bool loaded =
-      storage->loadSlot(slot_name, loaded_world_state, loaded_metadata,
+      storage->load_slot(slot_name, loaded_world_state, loaded_metadata,
                         loaded_screenshot, loaded_title, &error);
 
   EXPECT_TRUE(loaded) << "Failed to load: " << error.toStdString();
@@ -361,19 +361,19 @@ TEST_F(SaveStorageTest, MultipleSavesAndDeletes) {
 
   for (int i = 0; i < 10; i++) {
     QString slot_name = QString("slot_%1").arg(i);
-    storage->saveSlot(slot_name, QString("Title %1").arg(i), QJsonObject(),
+    storage->save_slot(slot_name, QString("Title %1").arg(i), QJsonObject(),
                       QByteArray("data"), QByteArray(), &error);
   }
 
-  QVariantList slot_list = storage->listSlots(&error);
+  QVariantList slot_list = storage->list_slots(&error);
   EXPECT_EQ(slot_list.size(), 10);
 
   for (int i = 0; i < 5; i++) {
     QString slot_name = QString("slot_%1").arg(i);
-    storage->deleteSlot(slot_name, &error);
+    storage->delete_slot(slot_name, &error);
   }
 
-  slot_list = storage->listSlots(&error);
+  slot_list = storage->list_slots(&error);
   EXPECT_EQ(slot_list.size(), 5);
 
   for (const QVariant &slot_variant : slot_list) {