# Scénario 13: Cross-System Integration (IO + DataNode)

**Priorité**: ⭐⭐ SHOULD HAVE
**Phase**: 2 (SHOULD HAVE)
**Durée estimée**: ~6 minutes
**Effort implémentation**: ~6-8 heures

---

## 🎯 Objectif

Valider que les systèmes IO (IntraIO) et DataNode (IDataTree) fonctionnent correctement **ensemble** dans des cas d'usage réels:
- Config hot-reload → republish via IO
- State persistence via DataNode + message routing via IO
- Multi-module coordination (Module A publie state → Module B lit via DataNode)
- Concurrent access (IO threads + DataNode threads)
- Integration avec hot-reload de modules
- Performance du système complet

**Note**: Ce test valide l'intégration complète du moteur, pas les composants isolés.

---

## 📋 Description

### Setup Initial
1. Créer IDataTree avec structure complète:
   - **config/** - Configuration modules (units, gameplay, network)
   - **data/** - State persistence (player, world, economy)
   - **runtime/** - State temporaire (fps, metrics, active_entities)

2. Créer 4 modules avec IO + DataNode:
   - **ConfigWatcherModule** - Surveille config/, publie changements via IO
   - **PlayerModule** - Gère state joueur, persiste via data/, publie events
   - **EconomyModule** - Souscrit à player events, met à jour economy data/
   - **MetricsModule** - Collecte metrics dans runtime/, publie stats

3. Total: 4 modules communicant via IO et partageant data via DataNode

### Test Séquence

#### Test 1: Config Hot-Reload → IO Broadcast (60s)
1. ConfigWatcherModule souscrit à hot-reload callbacks
2. Modifier `config/gameplay.json` (changer difficulty)
3. Quand callback déclenché:
   - ConfigWatcherModule publie "config:gameplay:changed" via IO
   - PlayerModule souscrit et reçoit notification
   - PlayerModule lit nouvelle config via DataNode
   - PlayerModule ajuste son comportement
4. Vérifier:
   - Callback → publish → subscribe → read chain fonctionne
   - Nouvelle config appliquée dans PlayerModule
   - Latence totale < 100ms

#### Test 2: State Persistence + Event Publishing (60s)
1. PlayerModule crée state:
   - `data/player/profile` - {name, level, gold}
   - `data/player/inventory` - {items[]}
2. PlayerModule sauvegarde via `tree->saveNode()`
3. PlayerModule publie "player:level_up" via IO
4. EconomyModule souscrit à "player:*"
5. EconomyModule reçoit event, lit player data via DataNode
6. EconomyModule calcule bonus, met à jour `data/economy/bonuses`
7. EconomyModule sauvegarde via `tree->saveNode()`
8. Vérifier:
   - Save → publish → subscribe → read → save chain fonctionne
   - Data persistence correcte
   - Pas de race conditions

#### Test 3: Multi-Module State Synchronization (90s)
1. PlayerModule met à jour `data/player/gold` = 1000
2. PlayerModule publie "player:gold:updated" avec {gold: 1000}
3. EconomyModule reçoit event via IO
4. EconomyModule lit `data/player/gold` via DataNode
5. Vérifier cohérence:
   - Valeur dans message IO = valeur dans DataNode
   - Pas de désynchronisation
   - Order des events préservé
6. Répéter 100 fois avec updates rapides
7. Vérifier consistency finale

#### Test 4: Runtime Metrics Collection (60s)
1. MetricsModule collecte metrics toutes les 100ms:
   - `runtime/fps` - FPS actuel
   - `runtime/memory` - Memory usage
   - `runtime/message_count` - Messages IO
2. MetricsModule publie "metrics:snapshot" toutes les secondes
3. ConfigWatcherModule souscrit et log metrics
4. Vérifier:
   - Runtime data pas persisté (pas de fichiers)
   - Metrics publishing fonctionne
   - Low-frequency batching optimise (pas 10 msg/s mais 1 msg/s)

#### Test 5: Concurrent Access (IO + DataNode) (90s)
1. Lancer 4 threads:
   - Thread 1: PlayerModule publie events à 100 Hz
   - Thread 2: EconomyModule lit data/ à 50 Hz
   - Thread 3: MetricsModule écrit runtime/ à 100 Hz
   - Thread 4: ConfigWatcherModule lit config/ à 10 Hz
2. Exécuter pendant 60 secondes
3. Vérifier:
   - Aucun crash
   - Aucune corruption de data
   - Aucun deadlock
   - Performance acceptable (< 10% overhead)

#### Test 6: Hot-Reload Module + Preserve State (90s)
1. PlayerModule a state actif:
   - 50 entities dans `runtime/entities`
   - Gold = 5000 dans `data/player/gold`
   - Active quest dans `runtime/quest`
2. Déclencher hot-reload de PlayerModule:
   - `getState()` extrait tout (data/ + runtime/)
   - Recompile module
   - `setState()` restaure
3. Pendant reload:
   - EconomyModule continue de publier via IO
   - Messages accumulés dans queue PlayerModule
4. Après reload:
   - PlayerModule pull messages accumulés
   - Vérifie state préservé (50 entities, 5000 gold, quest)
   - Continue processing normalement
5. Vérifier:
   - State complet préservé (DataNode + runtime)
   - Messages pas perdus (IO queue)
   - Pas de corruption

#### Test 7: Config Change Cascades (60s)
1. Modifier `config/gameplay.json` → difficulty = "hard"
2. ConfigWatcherModule détecte → publie "config:gameplay:changed"
3. PlayerModule reçoit → reload config → ajuste HP multiplier
4. PlayerModule publie "player:config:updated"
5. EconomyModule reçoit → reload config → ajuste prices
6. EconomyModule publie "economy:config:updated"
7. MetricsModule reçoit → log cascade
8. Vérifier:
   - Cascade complète en < 500ms
   - Tous modules synchronisés
   - Ordre des events correct

#### Test 8: Large State + High-Frequency IO (60s)
1. Créer large DataNode tree (1000 nodes)
2. Publier 10k messages/s via IO
3. Modules lisent DataNode pendant IO flood
4. Mesurer:
   - Latence IO: < 10ms p99
   - Latence DataNode read: < 5ms p99
   - Memory growth: < 20MB
   - CPU usage: < 80%
5. Vérifier:
   - Systèmes restent performants
   - Pas de dégradation mutuelle

---

## 🏗️ Implémentation

### ConfigWatcherModule Structure

```cpp
// ConfigWatcherModule.h
class ConfigWatcherModule : public IModule {
public:
    void initialize(std::shared_ptr<IDataNode> config) override;
    void process(float deltaTime) override;
    std::shared_ptr<IDataNode> getState() const override;
    void setState(std::shared_ptr<IDataNode> state) override;
    bool isIdle() const override { return true; }

private:
    std::shared_ptr<IIO> io;
    std::shared_ptr<IDataTree> tree;

    void onConfigReloaded();
    void publishConfigChange(const std::string& configName);
};
```

### PlayerModule Structure

```cpp
// PlayerModule.h
class PlayerModule : public IModule {
public:
    void initialize(std::shared_ptr<IDataNode> config) override;
    void process(float deltaTime) override;
    std::shared_ptr<IDataNode> getState() const override;
    void setState(std::shared_ptr<IDataNode> state) override;
    bool isIdle() const override { return true; }

private:
    std::shared_ptr<IIO> io;
    std::shared_ptr<IDataTree> tree;

    int gold = 0;
    int level = 1;
    std::vector<std::string> inventory;

    void handleConfigChange();
    void savePlayerData();
    void publishLevelUp();
};
```

### Test Principal

```cpp
// test_13_cross_system.cpp
#include "helpers/TestMetrics.h"
#include "helpers/TestAssertions.h"
#include "helpers/TestReporter.h"
#include <atomic>
#include <thread>

int main() {
    TestReporter reporter("Cross-System Integration");
    TestMetrics metrics;

    // === SETUP ===
    std::filesystem::create_directories("test_cross/config");
    std::filesystem::create_directories("test_cross/data");

    auto tree = std::make_shared<JsonDataTree>("test_cross");

    DebugEngine engine;
    engine.setDataTree(tree);

    // Charger modules
    engine.loadModule("ConfigWatcherModule", "build/modules/libConfigWatcherModule.so");
    engine.loadModule("PlayerModule", "build/modules/libPlayerModule.so");
    engine.loadModule("EconomyModule", "build/modules/libEconomyModule.so");
    engine.loadModule("MetricsModule", "build/modules/libMetricsModule.so");

    auto config = createJsonConfig({
        {"transport", "intra"},
        {"instanceId", "test_cross"}
    });

    engine.initializeModule("ConfigWatcherModule", config);
    engine.initializeModule("PlayerModule", config);
    engine.initializeModule("EconomyModule", config);
    engine.initializeModule("MetricsModule", config);

    // ========================================================================
    // TEST 1: Config Hot-Reload → IO Broadcast
    // ========================================================================
    std::cout << "\n=== TEST 1: Config Hot-Reload → IO Broadcast ===\n";

    // Créer config initial
    nlohmann::json gameplayConfig = {
        {"difficulty", "normal"},
        {"hpMultiplier", 1.0}
    };

    std::ofstream configFile("test_cross/config/gameplay.json");
    configFile << gameplayConfig.dump(2);
    configFile.close();

    tree->loadConfigFile("gameplay.json");

    // Setup reload callback
    std::atomic<int> configChangedEvents{0};
    auto playerIO = engine.getModuleIO("PlayerModule");

    playerIO->subscribe("config:gameplay:changed", {});

    // ConfigWatcherModule setup callback
    tree->onTreeReloaded([&]() {
        std::cout << "  → Config reloaded, publishing event...\n";
        auto watcherIO = engine.getModuleIO("ConfigWatcherModule");
        auto data = std::make_unique<JsonDataNode>(nlohmann::json{
            {"config", "gameplay"},
            {"timestamp", std::time(nullptr)}
        });
        watcherIO->publish("config:gameplay:changed", std::move(data));
    });

    // Modifier config
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    gameplayConfig["difficulty"] = "hard";
    gameplayConfig["hpMultiplier"] = 1.5;

    std::ofstream configFile2("test_cross/config/gameplay.json");
    configFile2 << gameplayConfig.dump(2);
    configFile2.close();

    auto reloadStart = std::chrono::high_resolution_clock::now();

    // Trigger reload
    tree->reloadIfChanged();

    // Process pour permettre IO routing
    engine.update(1.0f/60.0f);

    // PlayerModule vérifie message
    if (playerIO->hasMessages() > 0) {
        auto msg = playerIO->pullMessage();
        configChangedEvents++;

        // PlayerModule lit nouvelle config
        auto gameplay = tree->getConfigRoot()->getChild("gameplay");
        std::string difficulty = gameplay->getString("difficulty");
        double hpMult = gameplay->getDouble("hpMultiplier");

        std::cout << "  PlayerModule received config change: difficulty=" << difficulty
                  << ", hpMult=" << hpMult << "\n";

        ASSERT_EQ(difficulty, "hard", "Difficulty should be updated");
        ASSERT_EQ(hpMult, 1.5, "HP multiplier should be updated");
    }

    auto reloadEnd = std::chrono::high_resolution_clock::now();
    float reloadLatency = std::chrono::duration<float, std::milli>(reloadEnd - reloadStart).count();

    std::cout << "Total latency (reload + publish + subscribe + read): " << reloadLatency << "ms\n";
    ASSERT_LT(reloadLatency, 100.0f, "Total latency should be < 100ms");
    ASSERT_EQ(configChangedEvents, 1, "Should receive exactly 1 config change event");

    reporter.addMetric("config_reload_latency_ms", reloadLatency);
    reporter.addAssertion("config_hotreload_chain", true);
    std::cout << "✓ TEST 1 PASSED\n";

    // ========================================================================
    // TEST 2: State Persistence + Event Publishing
    // ========================================================================
    std::cout << "\n=== TEST 2: State Persistence + Event Publishing ===\n";

    auto dataRoot = tree->getDataRoot();

    // PlayerModule crée state
    auto player = std::make_shared<JsonDataNode>("player", nlohmann::json::object());
    auto profile = std::make_shared<JsonDataNode>("profile", nlohmann::json{
        {"name", "TestPlayer"},
        {"level", 5},
        {"gold", 1000}
    });
    player->setChild("profile", profile);
    dataRoot->setChild("player", player);

    // Save
    tree->saveNode("data/player");

    // Verify file
    ASSERT_TRUE(std::filesystem::exists("test_cross/data/player/profile.json"),
                "Profile should be saved");

    // PlayerModule publie level up
    auto levelUpData = std::make_unique<JsonDataNode>(nlohmann::json{
        {"event", "level_up"},
        {"newLevel", 6},
        {"goldBonus", 500}
    });
    playerIO->publish("player:level_up", std::move(levelUpData));

    // EconomyModule souscrit
    auto economyIO = engine.getModuleIO("EconomyModule");
    economyIO->subscribe("player:*", {});

    engine.update(1.0f/60.0f);

    // EconomyModule reçoit et traite
    if (economyIO->hasMessages() > 0) {
        auto msg = economyIO->pullMessage();
        std::cout << "  EconomyModule received: " << msg.topic << "\n";

        // EconomyModule lit player data
        auto playerData = tree->getDataRoot()->getChild("player")->getChild("profile");
        int gold = playerData->getInt("gold");
        std::cout << "  Player gold: " << gold << "\n";

        // EconomyModule calcule bonus
        int goldBonus = 500;
        int newGold = gold + goldBonus;

        // Update data
        playerData->setInt("gold", newGold);

        // Create economy bonuses
        auto economy = std::make_shared<JsonDataNode>("economy", nlohmann::json::object());
        auto bonuses = std::make_shared<JsonDataNode>("bonuses", nlohmann::json{
            {"levelUpBonus", goldBonus},
            {"appliedAt", std::time(nullptr)}
        });
        economy->setChild("bonuses", bonuses);
        dataRoot->setChild("economy", economy);

        // Save economy data
        tree->saveNode("data/economy");

        std::cout << "  EconomyModule updated bonuses and saved\n";
    }

    // Verify full chain
    ASSERT_TRUE(std::filesystem::exists("test_cross/data/economy/bonuses.json"),
                "Economy bonuses should be saved");

    reporter.addAssertion("state_persistence_chain", true);
    std::cout << "✓ TEST 2 PASSED\n";

    // ========================================================================
    // TEST 3: Multi-Module State Synchronization
    // ========================================================================
    std::cout << "\n=== TEST 3: Multi-Module State Synchronization ===\n";

    int syncErrors = 0;

    for (int i = 0; i < 100; i++) {
        // PlayerModule met à jour gold
        int goldValue = 1000 + i * 10;
        auto playerProfile = tree->getDataRoot()->getChild("player")->getChild("profile");
        playerProfile->setInt("gold", goldValue);

        // PlayerModule publie event avec valeur
        auto goldUpdate = std::make_unique<JsonDataNode>(nlohmann::json{
            {"event", "gold_updated"},
            {"gold", goldValue}
        });
        playerIO->publish("player:gold:updated", std::move(goldUpdate));

        engine.update(1.0f/60.0f);

        // EconomyModule reçoit et vérifie cohérence
        if (economyIO->hasMessages() > 0) {
            auto msg = economyIO->pullMessage();
            auto* msgData = dynamic_cast<JsonDataNode*>(msg.data.get());
            int msgGold = msgData->getJsonData()["gold"];

            // Lire DataNode
            auto playerData = tree->getDataRoot()->getChild("player")->getChild("profile");
            int dataGold = playerData->getInt("gold");

            if (msgGold != dataGold) {
                std::cerr << "  SYNC ERROR: msg=" << msgGold << " data=" << dataGold << "\n";
                syncErrors++;
            }
        }
    }

    std::cout << "Synchronization errors: " << syncErrors << " / 100\n";
    ASSERT_EQ(syncErrors, 0, "Should have zero synchronization errors");

    reporter.addMetric("sync_errors", syncErrors);
    reporter.addAssertion("state_synchronization", syncErrors == 0);
    std::cout << "✓ TEST 3 PASSED\n";

    // ========================================================================
    // TEST 4: Runtime Metrics Collection
    // ========================================================================
    std::cout << "\n=== TEST 4: Runtime Metrics Collection ===\n";

    auto runtimeRoot = tree->getRuntimeRoot();
    auto metricsIO = engine.getModuleIO("MetricsModule");

    // MetricsModule publie metrics avec low-freq batching
    IIO::SubscriptionConfig metricsConfig;
    metricsConfig.replaceable = true;
    metricsConfig.batchInterval = 1000; // 1 second

    playerIO->subscribeLowFreq("metrics:*", metricsConfig);

    // Simulate 3 seconds de metrics collection
    for (int sec = 0; sec < 3; sec++) {
        for (int i = 0; i < 10; i++) {
            // MetricsModule collecte metrics
            auto metrics = std::make_shared<JsonDataNode>("metrics", nlohmann::json{
                {"fps", 60.0},
                {"memory", 125000000 + i * 1000},
                {"messageCount", i * 100}
            });
            runtimeRoot->setChild("metrics", metrics);

            // Publie snapshot
            auto snapshot = std::make_unique<JsonDataNode>(nlohmann::json{
                {"fps", 60.0},
                {"memory", 125000000 + i * 1000},
                {"timestamp", std::time(nullptr)}
            });
            metricsIO->publish("metrics:snapshot", std::move(snapshot));

            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            engine.update(1.0f/60.0f);
        }
    }

    // Vérifier batching
    int snapshotsReceived = 0;
    while (playerIO->hasMessages() > 0) {
        playerIO->pullMessage();
        snapshotsReceived++;
    }

    std::cout << "Snapshots received: " << snapshotsReceived << " (expected ~3 due to batching)\n";
    ASSERT_TRUE(snapshotsReceived >= 2 && snapshotsReceived <= 4,
                "Should receive ~3 batched snapshots");

    // Vérifier runtime pas persisté
    ASSERT_FALSE(std::filesystem::exists("test_cross/runtime"),
                 "Runtime data should not be persisted");

    reporter.addMetric("batched_snapshots", snapshotsReceived);
    reporter.addAssertion("runtime_metrics", true);
    std::cout << "✓ TEST 4 PASSED\n";

    // ========================================================================
    // TEST 5: Concurrent Access (IO + DataNode)
    // ========================================================================
    std::cout << "\n=== TEST 5: Concurrent Access ===\n";

    std::atomic<bool> running{true};
    std::atomic<int> publishCount{0};
    std::atomic<int> readCount{0};
    std::atomic<int> writeCount{0};
    std::atomic<int> errors{0};

    // Thread 1: PlayerModule publie events
    std::thread pubThread([&]() {
        while (running) {
            try {
                auto data = std::make_unique<JsonDataNode>(nlohmann::json{{"id", publishCount++}});
                playerIO->publish("concurrent:test", std::move(data));
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            } catch (...) {
                errors++;
            }
        }
    });

    // Thread 2: EconomyModule lit data/
    std::thread readThread([&]() {
        while (running) {
            try {
                auto playerData = tree->getDataRoot()->getChild("player");
                if (playerData) {
                    auto profile = playerData->getChild("profile");
                    if (profile) {
                        int gold = profile->getInt("gold", 0);
                        readCount++;
                    }
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(20));
            } catch (...) {
                errors++;
            }
        }
    });

    // Thread 3: MetricsModule écrit runtime/
    std::thread writeThread([&]() {
        while (running) {
            try {
                auto metrics = std::make_shared<JsonDataNode>("metrics", nlohmann::json{
                    {"counter", writeCount++}
                });
                runtimeRoot->setChild("metrics", metrics);
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            } catch (...) {
                errors++;
            }
        }
    });

    // Thread 4: ConfigWatcherModule lit config/
    std::thread configThread([&]() {
        while (running) {
            try {
                auto gameplay = tree->getConfigRoot()->getChild("gameplay");
                if (gameplay) {
                    std::string diff = gameplay->getString("difficulty", "normal");
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            } catch (...) {
                errors++;
            }
        }
    });

    // Run for 5 seconds
    auto concurrentStart = std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for(std::chrono::seconds(5));
    running = false;
    auto concurrentEnd = std::chrono::high_resolution_clock::now();

    pubThread.join();
    readThread.join();
    writeThread.join();
    configThread.join();

    float duration = std::chrono::duration<float>(concurrentEnd - concurrentStart).count();

    std::cout << "Concurrent test ran for " << duration << "s\n";
    std::cout << "  Publishes: " << publishCount << "\n";
    std::cout << "  Reads: " << readCount << "\n";
    std::cout << "  Writes: " << writeCount << "\n";
    std::cout << "  Errors: " << errors << "\n";

    ASSERT_EQ(errors, 0, "Should have zero errors during concurrent access");
    ASSERT_GT(publishCount, 0, "Should have published messages");
    ASSERT_GT(readCount, 0, "Should have read data");
    ASSERT_GT(writeCount, 0, "Should have written data");

    reporter.addMetric("concurrent_publishes", publishCount);
    reporter.addMetric("concurrent_reads", readCount);
    reporter.addMetric("concurrent_writes", writeCount);
    reporter.addMetric("concurrent_errors", errors);
    reporter.addAssertion("concurrent_access", errors == 0);
    std::cout << "✓ TEST 5 PASSED\n";

    // ========================================================================
    // TEST 6: Hot-Reload Module + Preserve State
    // ========================================================================
    std::cout << "\n=== TEST 6: Hot-Reload Module + Preserve State ===\n";

    // PlayerModule crée state complexe
    auto entities = std::make_shared<JsonDataNode>("entities", nlohmann::json::array());
    for (int i = 0; i < 50; i++) {
        entities->getJsonData().push_back({{"id", i}, {"hp", 100}});
    }
    runtimeRoot->setChild("entities", entities);

    auto playerGold = tree->getDataRoot()->getChild("player")->getChild("profile");
    playerGold->setInt("gold", 5000);
    tree->saveNode("data/player/profile");

    auto quest = std::make_shared<JsonDataNode>("quest", nlohmann::json{
        {"active", true},
        {"questId", 42}
    });
    runtimeRoot->setChild("quest", quest);

    std::cout << "State before reload: 50 entities, 5000 gold, quest #42 active\n";

    // EconomyModule publie messages pendant reload
    std::thread spamThread([&]() {
        for (int i = 0; i < 100; i++) {
            auto data = std::make_unique<JsonDataNode>(nlohmann::json{{"spam", i}});
            economyIO->publish("player:spam", std::move(data));
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    });

    // Trigger hot-reload de PlayerModule
    std::this_thread::sleep_for(std::chrono::milliseconds(200));

    auto stateBefore = engine.getModuleState("PlayerModule");

    modifySourceFile("tests/modules/PlayerModule.cpp", "v1.0", "v2.0");
    system("cmake --build build --target PlayerModule 2>&1 > /dev/null");

    engine.reloadModule("PlayerModule");

    spamThread.join();

    // Vérifier state après reload
    auto stateAfter = engine.getModuleState("PlayerModule");

    auto entitiesAfter = runtimeRoot->getChild("entities");
    int entityCount = entitiesAfter->getJsonData().size();
    std::cout << "Entities after reload: " << entityCount << "\n";
    ASSERT_EQ(entityCount, 50, "Should preserve 50 entities");

    auto goldAfter = tree->getDataRoot()->getChild("player")->getChild("profile");
    int goldValue = goldAfter->getInt("gold");
    std::cout << "Gold after reload: " << goldValue << "\n";
    ASSERT_EQ(goldValue, 5000, "Should preserve 5000 gold");

    auto questAfter = runtimeRoot->getChild("quest");
    bool questActive = questAfter->getBool("active");
    int questId = questAfter->getInt("questId");
    std::cout << "Quest after reload: active=" << questActive << ", id=" << questId << "\n";
    ASSERT_EQ(questActive, true, "Quest should still be active");
    ASSERT_EQ(questId, 42, "Quest ID should be preserved");

    // Vérifier messages pas perdus
    int spamReceived = 0;
    while (playerIO->hasMessages() > 0) {
        playerIO->pullMessage();
        spamReceived++;
    }
    std::cout << "Spam messages received after reload: " << spamReceived << "\n";
    ASSERT_GT(spamReceived, 0, "Should receive queued messages after reload");

    reporter.addAssertion("hotreload_preserve_state", true);
    reporter.addMetric("spam_messages_queued", spamReceived);
    std::cout << "✓ TEST 6 PASSED\n";

    // ========================================================================
    // TEST 7: Config Change Cascades
    // ========================================================================
    std::cout << "\n=== TEST 7: Config Change Cascades ===\n";

    // Subscribe chain
    playerIO->subscribe("config:*", {});
    economyIO->subscribe("player:*", {});
    metricsIO->subscribe("economy:*", {});

    auto cascadeStart = std::chrono::high_resolution_clock::now();

    // 1. Modifier config
    gameplayConfig["difficulty"] = "extreme";
    std::ofstream configFile3("test_cross/config/gameplay.json");
    configFile3 << gameplayConfig.dump(2);
    configFile3.close();

    // 2. Trigger reload
    tree->reloadIfChanged();
    auto watcherIO = engine.getModuleIO("ConfigWatcherModule");
    watcherIO->publish("config:gameplay:changed", std::make_unique<JsonDataNode>(nlohmann::json{{"config", "gameplay"}}));

    engine.update(1.0f/60.0f);

    // 3. PlayerModule reçoit et publie
    if (playerIO->hasMessages() > 0) {
        playerIO->pullMessage();
        playerIO->publish("player:config:updated", std::make_unique<JsonDataNode>(nlohmann::json{{"hpMult", 2.0}}));
    }

    engine.update(1.0f/60.0f);

    // 4. EconomyModule reçoit et publie
    if (economyIO->hasMessages() > 0) {
        economyIO->pullMessage();
        economyIO->publish("economy:config:updated", std::make_unique<JsonDataNode>(nlohmann::json{{"pricesMult", 1.5}}));
    }

    engine.update(1.0f/60.0f);

    // 5. MetricsModule reçoit et log
    if (metricsIO->hasMessages() > 0) {
        metricsIO->pullMessage();
        std::cout << "  → Cascade complete!\n";
    }

    auto cascadeEnd = std::chrono::high_resolution_clock::now();
    float cascadeTime = std::chrono::duration<float, std::milli>(cascadeEnd - cascadeStart).count();

    std::cout << "Cascade latency: " << cascadeTime << "ms\n";
    ASSERT_LT(cascadeTime, 500.0f, "Cascade should complete in < 500ms");

    reporter.addMetric("cascade_latency_ms", cascadeTime);
    reporter.addAssertion("config_cascade", true);
    std::cout << "✓ TEST 7 PASSED\n";

    // ========================================================================
    // TEST 8: Large State + High-Frequency IO
    // ========================================================================
    std::cout << "\n=== TEST 8: Large State + High-Frequency IO ===\n";

    // Créer large tree (1000 nodes)
    auto largeRoot = tree->getDataRoot();
    for (int i = 0; i < 100; i++) {
        auto category = std::make_shared<JsonDataNode>("cat_" + std::to_string(i), nlohmann::json::object());
        for (int j = 0; j < 10; j++) {
            auto item = std::make_shared<JsonDataNode>("item_" + std::to_string(j), nlohmann::json{
                {"id", i * 10 + j},
                {"value", (i * 10 + j) * 100}
            });
            category->setChild("item_" + std::to_string(j), item);
        }
        largeRoot->setChild("cat_" + std::to_string(i), category);
    }

    std::cout << "Created large DataNode tree (1000 nodes)\n";

    // High-frequency IO + concurrent DataNode reads
    std::atomic<int> ioPublished{0};
    std::atomic<int> dataReads{0};
    std::vector<float> ioLatencies;
    std::vector<float> dataLatencies;

    running = true;

    std::thread ioThread([&]() {
        while (running) {
            auto start = std::chrono::high_resolution_clock::now();
            auto data = std::make_unique<JsonDataNode>(nlohmann::json{{"id", ioPublished++}});
            playerIO->publish("stress:test", std::move(data));
            auto end = std::chrono::high_resolution_clock::now();

            float latency = std::chrono::duration<float, std::milli>(end - start).count();
            ioLatencies.push_back(latency);

            // Target: 10k msg/s = 0.1ms interval
            std::this_thread::sleep_for(std::chrono::microseconds(100));
        }
    });

    std::thread dataThread([&]() {
        while (running) {
            auto start = std::chrono::high_resolution_clock::now();
            auto cat = largeRoot->getChild("cat_50");
            if (cat) {
                auto item = cat->getChild("item_5");
                if (item) {
                    int value = item->getInt("value", 0);
                    dataReads++;
                }
            }
            auto end = std::chrono::high_resolution_clock::now();

            float latency = std::chrono::duration<float, std::milli>(end - start).count();
            dataLatencies.push_back(latency);

            std::this_thread::sleep_for(std::chrono::microseconds(500));
        }
    });

    auto memBefore = getCurrentMemoryUsage();

    std::this_thread::sleep_for(std::chrono::seconds(5));
    running = false;

    ioThread.join();
    dataThread.join();

    auto memAfter = getCurrentMemoryUsage();
    long memGrowth = static_cast<long>(memAfter) - static_cast<long>(memBefore);

    // Calculate p99 latencies
    std::sort(ioLatencies.begin(), ioLatencies.end());
    std::sort(dataLatencies.begin(), dataLatencies.end());

    float ioP99 = ioLatencies[static_cast<size_t>(ioLatencies.size() * 0.99)];
    float dataP99 = dataLatencies[static_cast<size_t>(dataLatencies.size() * 0.99)];

    std::cout << "Performance results:\n";
    std::cout << "  IO published: " << ioPublished << " messages\n";
    std::cout << "  IO p99 latency: " << ioP99 << "ms\n";
    std::cout << "  DataNode reads: " << dataReads << "\n";
    std::cout << "  DataNode p99 latency: " << dataP99 << "ms\n";
    std::cout << "  Memory growth: " << (memGrowth / 1024.0 / 1024.0) << "MB\n";

    ASSERT_LT(ioP99, 10.0f, "IO p99 latency should be < 10ms");
    ASSERT_LT(dataP99, 5.0f, "DataNode p99 latency should be < 5ms");
    ASSERT_LT(memGrowth, 20 * 1024 * 1024, "Memory growth should be < 20MB");

    reporter.addMetric("io_p99_latency_ms", ioP99);
    reporter.addMetric("datanode_p99_latency_ms", dataP99);
    reporter.addMetric("memory_growth_mb", memGrowth / 1024.0 / 1024.0);
    reporter.addAssertion("performance_under_load", true);
    std::cout << "✓ TEST 8 PASSED\n";

    // ========================================================================
    // CLEANUP
    // ========================================================================
    std::filesystem::remove_all("test_cross");

    // ========================================================================
    // RAPPORT FINAL
    // ========================================================================

    metrics.printReport();
    reporter.printFinalReport();

    return reporter.getExitCode();
}
```

---

## 📊 Métriques Collectées

| Métrique | Description | Seuil |
|----------|-------------|-------|
| **config_reload_latency_ms** | Latence reload→publish→subscribe→read | < 100ms |
| **sync_errors** | Erreurs synchronisation IO/DataNode | 0 |
| **batched_snapshots** | Snapshots reçus avec batching | 2-4 |
| **concurrent_publishes** | Messages publiés en concurrence | > 0 |
| **concurrent_reads** | Lectures DataNode concurrentes | > 0 |
| **concurrent_writes** | Écritures DataNode concurrentes | > 0 |
| **concurrent_errors** | Erreurs pendant concurrence | 0 |
| **spam_messages_queued** | Messages queued pendant reload | > 0 |
| **cascade_latency_ms** | Latence cascade config changes | < 500ms |
| **io_p99_latency_ms** | P99 latence IO sous charge | < 10ms |
| **datanode_p99_latency_ms** | P99 latence DataNode sous charge | < 5ms |
| **memory_growth_mb** | Croissance mémoire sous charge | < 20MB |

---

## ✅ Critères de Succès

### MUST PASS
1. ✅ Config hot-reload chain fonctionne (< 100ms)
2. ✅ State persistence + event publishing chain fonctionne
3. ✅ Synchronization IO/DataNode sans erreurs
4. ✅ Runtime metrics avec batching
5. ✅ Concurrent access sans crashes/corruption
6. ✅ Hot-reload préserve state complet
7. ✅ Messages IO pas perdus pendant reload
8. ✅ Config cascades propagent correctement
9. ✅ Performance acceptable sous charge

### NICE TO HAVE
1. ✅ Config reload latency < 50ms (optimal)
2. ✅ Cascade latency < 200ms (optimal)
3. ✅ IO p99 < 5ms (optimal)
4. ✅ DataNode p99 < 2ms (optimal)

---

## 🐛 Cas d'Erreur Attendus

| Erreur | Cause | Action |
|--------|-------|--------|
| Config change pas propagé | Callback pas déclenché | FAIL - fix onTreeReloaded |
| Sync errors > 0 | Race condition IO/DataNode | FAIL - add locking |
| Messages perdus | Queue overflow pendant reload | WARN - increase queue size |
| Concurrent crashes | Missing mutex | FAIL - add thread safety |
| State corrompu après reload | setState() bug | FAIL - fix state restoration |
| Cascade timeout | Deadlock dans chain | FAIL - fix event routing |
| Performance degradation | O(n²) algorithm | FAIL - optimize |
| Memory leak | Resources not freed | FAIL - fix destructors |

---

## 📝 Output Attendu

```
================================================================================
TEST: Cross-System Integration (IO + DataNode)
================================================================================

=== TEST 1: Config Hot-Reload → IO Broadcast ===
  → Config reloaded, publishing event...
  PlayerModule received config change: difficulty=hard, hpMult=1.5
Total latency (reload + publish + subscribe + read): 87ms
✓ TEST 1 PASSED

=== TEST 2: State Persistence + Event Publishing ===
  EconomyModule received: player:level_up
  Player gold: 1000
  EconomyModule updated bonuses and saved
✓ TEST 2 PASSED

=== TEST 3: Multi-Module State Synchronization ===
Synchronization errors: 0 / 100
✓ TEST 3 PASSED

=== TEST 4: Runtime Metrics Collection ===
Snapshots received: 3 (expected ~3 due to batching)
✓ TEST 4 PASSED

=== TEST 5: Concurrent Access ===
Concurrent test ran for 5.001s
  Publishes: 487
  Reads: 243
  Writes: 489
  Errors: 0
✓ TEST 5 PASSED

=== TEST 6: Hot-Reload Module + Preserve State ===
State before reload: 50 entities, 5000 gold, quest #42 active
Entities after reload: 50
Gold after reload: 5000
Quest after reload: active=true, id=42
Spam messages received after reload: 94
✓ TEST 6 PASSED

=== TEST 7: Config Change Cascades ===
  → Cascade complete!
Cascade latency: 234ms
✓ TEST 7 PASSED

=== TEST 8: Large State + High-Frequency IO ===
Created large DataNode tree (1000 nodes)
Performance results:
  IO published: 48723 messages
  IO p99 latency: 8.3ms
  DataNode reads: 9745
  DataNode p99 latency: 3.2ms
  Memory growth: 14.7MB
✓ TEST 8 PASSED

================================================================================
METRICS
================================================================================
  Config reload latency:     87ms           (threshold: < 100ms) ✓
  Sync errors:               0              (threshold: 0)       ✓
  Batched snapshots:         3
  Concurrent publishes:      487
  Concurrent reads:          243
  Concurrent writes:         489
  Concurrent errors:         0              (threshold: 0)       ✓
  Spam messages queued:      94
  Cascade latency:           234ms          (threshold: < 500ms) ✓
  IO p99 latency:            8.3ms          (threshold: < 10ms)  ✓
  DataNode p99 latency:      3.2ms          (threshold: < 5ms)   ✓
  Memory growth:             14.7MB         (threshold: < 20MB)  ✓

================================================================================
ASSERTIONS
================================================================================
  ✓ config_hotreload_chain
  ✓ state_persistence_chain
  ✓ state_synchronization
  ✓ runtime_metrics
  ✓ concurrent_access
  ✓ hotreload_preserve_state
  ✓ config_cascade
  ✓ performance_under_load

Result: ✅ PASSED (8/8 tests)

================================================================================
```

---

## 📅 Planning

**Jour 1 (4h):**
- Implémenter ConfigWatcherModule, PlayerModule, EconomyModule, MetricsModule
- Setup IDataTree avec structure config/data/runtime
- Tests 1-3 (config reload, persistence, sync)

**Jour 2 (4h):**
- Tests 4-6 (metrics, concurrent, hot-reload)
- Tests 7-8 (cascades, performance)
- Debug + validation

---

**Conclusion**: Ces 3 nouveaux scénarios (11, 12, 13) complètent la suite de tests d'intégration en couvrant les systèmes IO et DataNode, ainsi que leur intégration.
