# Architecture Game-Agnostic **Date** : 2 décembre 2025 **Version** : 0.1.0 ## Principe Fondamental Les modules **core** sont game-agnostic et réutilisables entre Mobile Command et WarFactory. La logique spécifique au jeu se fait via **pub/sub** dans GameModule. ## Structure ``` ┌──────────────────────────────────────┐ │ GameModule (MC-specific) │ │ ┌────────────────────────────────┐ │ │ │ Subscribe aux events core │ │ │ │ Applique logique MC │ │ │ │ (drones, train, expeditions) │ │ │ └────────────────────────────────┘ │ └────────────┬─────────────────────────┘ │ pub/sub via grove::IIO │ ┌────────────▼─────────────────────────┐ │ Core Modules (Game-Agnostic) │ │ ┌────────────────────────────────┐ │ │ │ ResourceModule │ │ │ │ - Inventaire │ │ │ │ - Craft │ │ │ │ - Quality │ │ │ │ │ │ │ │ Publie: resource:* │ │ │ │ PAS de logique MC/WF │ │ │ └────────────────────────────────┘ │ │ ┌────────────────────────────────┐ │ │ │ StorageModule │ │ │ │ CombatModule │ │ │ │ EventModule │ │ │ └────────────────────────────────┘ │ └──────────────────────────────────────┘ ``` ## Exemples ### ResourceModule (Core - Game-Agnostic) **Ce qu'il FAIT** : ```cpp class ResourceModule : public grove::IModule { bool addResource(string id, int quantity); bool removeResource(string id, int quantity); bool canCraft(string recipeId); void startCraft(string recipeId); void process(float dt) { updateCrafting(dt); if (craftComplete) { io->publish("resource:craft_complete", craftData); } } }; ``` **Ce qu'il NE FAIT PAS** : - ❌ Savoir ce qu'est un "drone" ou un "tank" - ❌ Connaître les expéditions ou factories - ❌ Gérer le train ou les wagons - ❌ Appliquer fame bonus - ❌ Contenir logique MC ou WF ### GameModule (MC-Specific) **Ce qu'il FAIT** : ```cpp class GameModule : public grove::IModule { void initialize() { // Subscribe aux events core io->subscribe("resource:craft_complete", [this](const IDataNode& data) { string recipe = data.getString("recipe"); // LOGIQUE MC ICI if (recipe == "drone_recon") { // MC: Ajouter aux expéditions m_availableDrones["recon"]++; io->publish("expedition:drone_available", droneData); // MC: Fame bonus si 2024+ if (m_timeline.year >= 2024) { io->publish("fame:gain", fameData); } } }); io->subscribe("resource:inventory_low", [this](const IDataNode& data) { // MC: Warning train storage showWarning("Fuel bas ! Retour au train recommandé."); }); } }; ``` ## Topics Pub/Sub ### ResourceModule - `resource:craft_complete` - {recipe, result, quantity} - `resource:craft_started` - {recipe, duration} - `resource:inventory_changed` - {resource_id, delta, total} - `resource:inventory_low` - {resource_id, threshold} - `resource:storage_full` - {} ### StorageModule - `storage:save_complete` - {filename, timestamp} - `storage:load_complete` - {filename, version} - `storage:save_failed` - {error} ### CombatModule - `combat:started` - {location, combatants} - `combat:round_complete` - {casualties, damage} - `combat:ended` - {victory, loot, casualties} ### EventModule - `event:triggered` - {event_id, conditions} - `event:choice_made` - {event_id, choice_id} - `event:outcome` - {resources, flags} ## Configuration ### Core Modules Config JSON pure, pas de hardcoded behavior. **resources.json (MC)** : ```json { "resources": { "scrap_metal": {"maxStack": 100, "weight": 1.5}, "drone_parts": {"maxStack": 50, "weight": 0.5} }, "recipes": { "drone_recon": { "inputs": {"drone_parts": 3, "electronics": 2}, "outputs": {"drone_recon": 1}, "time": 120 } } } ``` **resources.json (WF - futur)** : ```json { "resources": { "iron_ore": {"maxStack": 1000, "weight": 2.0}, "steel_plates": {"maxStack": 500, "weight": 5.0} }, "recipes": { "tank_t72": { "inputs": {"steel_plates": 50, "engine": 1}, "outputs": {"tank_t72": 1}, "time": 600 } } } ``` Même code ResourceModule, configs différentes → Comportements différents. ## Checklist Module Core Avant de commiter un module core : - [ ] ❌ Aucune mention de "Mobile Command", "WarFactory", "train", "factory", "drone", "tank" - [ ] ✅ Interface pure (fonctions publiques génériques) - [ ] ✅ Tout comportement via config JSON - [ ] ✅ Communication via pub/sub uniquement - [ ] ✅ Topics documentés en commentaires - [ ] ✅ Exemple usage MC + WF en commentaires - [ ] ✅ Hot-reload state preservation - [ ] ✅ Tests basiques passent ## Règles d'Or 1. **Core = Generic** - Si tu penses "drone" ou "train", c'est game-specific 2. **Config > Code** - Behavior via JSON, pas hardcodé 3. **Pub/Sub Only** - Modules ne se référencent jamais directement 4. **Think Both Games** - Chaque feature core doit avoir sens pour MC ET WF 5. **Test Reusability** - Si tu ne peux pas imaginer WF l'utiliser, refactor ## Bénéfices ### Court Terme (Prototype MC) - Architecture propre, modules découplés - Hot-reload rapide (modules petits) - Tests unitaires faciles ### Moyen Terme (MVP MC) - Code stable, bien testé - Pas de spaghetti code - Maintenance simplifiée ### Long Terme (WF + MC) - **Réutilisation massive** : WF bénéficie du code MC - **Bugs fixés une fois** : Profit aux deux projets - **Features partagées** : Economy, storage, combat - **Extraction library** : grove-modules/ opensource possible ## Future: grove-modules/ Library Quand modules stabilisés (Post-MVP MC) : ``` ../grove-modules/ # Bibliothèque partagée ├── core/ │ ├── ResourceModule/ │ ├── StorageModule/ │ ├── CombatModule/ │ └── EventModule/ └── README.md ../mobilecommand/ └── external/ ├── GroveEngine/ └── grove-modules/ # Symlink ../warfactoryracine/ └── external/ └── grove-modules/ # Symlink ``` **Plan complet** : Voir `plans/SHARED_MODULES_PLAN.md` --- **TL;DR** : Core modules = Generic library. Game logic = GameModule via pub/sub.