# 🎮 HARVESTER MANUAL CONTROL FIX - Contrôle Manuel vs IA Automatique **Date:** 3 Octobre 2025 **Problème rapporté:** "Havester à sa sortie de HQ reste immobile, et ne reçoit pas l'ordre du joueur de se déplacer" **Status:** ✅ CORRIGÉ --- ## 🐛 NOUVEAU PROBLÈME IDENTIFIÉ ### Symptômes Après la première correction de l'IA automatique, un **nouveau problème** est apparu : 1. ✅ L'IA automatique fonctionne (Harvester cherche ressources) 2. ❌ **MAIS** le joueur ne peut plus donner d'ordres manuels ! 3. ❌ Quand le joueur clique pour déplacer le Harvester, il ignore la commande 4. ❌ Le Harvester continue de suivre l'IA automatique même si ordre manuel donné ### Comportement observé ``` 1. Joueur produit Harvester depuis HQ 2. Harvester commence à chercher minerai automatiquement ✓ 3. Joueur clique pour déplacer Harvester manuellement 4. Harvester ignore et continue vers minerai automatiquement ✗ ``` --- ## 🔍 CAUSE RACINE ### Ordre d'exécution dans la game loop **Chaque tick (20x par seconde) :** ```python 1. handle_command() - Traite commandes joueur ├─ Reçoit "move_unit" du joueur └─ Définit unit.target = (clic joueur) ✓ 2. update_game_state() - Mise à jour simulation ├─ update_harvester() appelé pour chaque Harvester └─ ÉCRASE unit.target avec l'IA automatique ✗ ``` ### Problème de conflit **Séquence du bug :** ``` Tick N: ├─ [WebSocket] Joueur envoie: move_unit(x=800, y=600) ├─ [handle_command] unit.target = Position(800, 600) ✓ │ ├─ [update_game_state] update_harvester() appelé ├─ [update_harvester] Condition: not gathering and not ore_target ├─ [update_harvester] find_nearest_ore() trouve minerai à (1200, 800) ├─ [update_harvester] unit.target = Position(1200, 800) ✗ [ÉCRASE!] │ └─ Résultat: Harvester va vers (1200, 800) au lieu de (800, 600) ``` **Le problème** : L'IA automatique **s'exécute APRÈS** les commandes du joueur et **écrase** le `target` manuel ! --- ## ✅ SOLUTION IMPLÉMENTÉE ### Approche : Flag de contrôle manuel Ajout d'un nouveau champ `manual_control` à la classe `Unit` pour distinguer : - **manual_control = False** : IA automatique active (comportement par défaut) - **manual_control = True** : Joueur contrôle manuellement (IA désactivée temporairement) ### Architecture de la solution ``` ┌─────────────────────────────────────────────────────────────┐ │ Unit Dataclass │ ├─────────────────────────────────────────────────────────────┤ │ EXISTING FIELDS: │ │ ├─ cargo: int │ │ ├─ gathering: bool │ │ ├─ returning: bool │ │ ├─ ore_target: Optional[Position] │ │ └─ last_attacker_id: Optional[str] │ │ │ │ NEW FIELD: │ │ └─ manual_control: bool = False ← AJOUTÉ │ │ │ │ Purpose: Track when player takes manual control │ └─────────────────────────────────────────────────────────────┘ ``` ### Logique de commutation ``` ┌───────────────────────────────────────────────────────────────────┐ │ État du Harvester │ ├───────────────────────────────────────────────────────────────────┤ │ │ │ MODE AUTOMATIQUE (manual_control = False) │ │ ├─ IA active │ │ ├─ update_harvester() exécuté chaque tick │ │ ├─ Cherche minerai automatiquement │ │ ├─ Récolte automatiquement │ │ └─ Cycle complet géré par IA │ │ │ │ ↓ Joueur donne ordre "move_unit" │ │ │ │ MODE MANUEL (manual_control = True) │ │ ├─ IA désactivée │ │ ├─ update_harvester() SKIPPED │ │ ├─ Harvester obéit aux ordres du joueur │ │ ├─ gathering/returning/ore_target nettoyés │ │ └─ Se déplace vers target défini par joueur │ │ │ │ ↓ Harvester arrive à destination OU dépose cargo │ │ │ │ MODE AUTOMATIQUE (manual_control = False) │ │ └─ Reprend IA automatique │ │ │ └───────────────────────────────────────────────────────────────────┘ ``` --- ## 🔧 CHANGEMENTS DE CODE ### 1. Ajout du champ `manual_control` (Ligne 130) **AVANT :** ```python @dataclass class Unit: cargo: int = 0 gathering: bool = False returning: bool = False ore_target: Optional[Position] = None last_attacker_id: Optional[str] = None ``` **APRÈS :** ```python @dataclass class Unit: cargo: int = 0 gathering: bool = False returning: bool = False ore_target: Optional[Position] = None last_attacker_id: Optional[str] = None manual_control: bool = False # True when player gives manual orders ``` --- ### 2. Sérialisation JSON (Ligne 148) **AVANT :** ```python "cargo": self.cargo, "gathering": self.gathering, "returning": self.returning ``` **APRÈS :** ```python "cargo": self.cargo, "gathering": self.gathering, "returning": self.returning, "manual_control": self.manual_control ``` --- ### 3. Skip IA si contrôle manuel (Ligne 427) **AVANT :** ```python # Update units for unit in list(self.game_state.units.values()): # RED ALERT: Harvester AI if unit.type == UnitType.HARVESTER: self.update_harvester(unit) continue ``` **APRÈS :** ```python # Update units for unit in list(self.game_state.units.values()): # RED ALERT: Harvester AI (only if not manually controlled) if unit.type == UnitType.HARVESTER and not unit.manual_control: self.update_harvester(unit) continue ``` **Effet** : Si `manual_control = True`, `update_harvester()` n'est **pas appelé** → IA désactivée --- ### 4. Activer contrôle manuel sur ordre joueur (Ligne 633) **AVANT :** ```python if cmd_type == "move_unit": unit_ids = command.get("unit_ids", []) target = command.get("target") if target and "x" in target and "y" in target: for uid in unit_ids: if uid in self.game_state.units: self.game_state.units[uid].target = Position(target["x"], target["y"]) ``` **APRÈS :** ```python if cmd_type == "move_unit": unit_ids = command.get("unit_ids", []) target = command.get("target") if target and "x" in target and "y" in target: for uid in unit_ids: if uid in self.game_state.units: unit = self.game_state.units[uid] unit.target = Position(target["x"], target["y"]) # If it's a Harvester, enable manual control to override AI if unit.type == UnitType.HARVESTER: unit.manual_control = True # Clear AI state unit.gathering = False unit.returning = False unit.ore_target = None ``` **Effet** : - Active `manual_control = True` pour les Harvesters - Nettoie les états de l'IA (`gathering`, `returning`, `ore_target`) - Le Harvester obéit maintenant à l'ordre manuel --- ### 5. Reprendre IA quand destination atteinte (Ligne 483) **AVANT :** ```python # Movement if unit.target: # Move towards target dx = unit.target.x - unit.position.x dy = unit.target.y - unit.position.y dist = (dx*dx + dy*dy) ** 0.5 if dist > 5: unit.position.x += (dx / dist) * unit.speed unit.position.y += (dy / dist) * unit.speed else: unit.target = None ``` **APRÈS :** ```python # Movement if unit.target: # Move towards target dx = unit.target.x - unit.position.x dy = unit.target.y - unit.position.y dist = (dx*dx + dy*dy) ** 0.5 if dist > 5: unit.position.x += (dx / dist) * unit.speed unit.position.y += (dy / dist) * unit.speed else: unit.target = None # If Harvester reached manual destination, resume AI if unit.type == UnitType.HARVESTER and unit.manual_control: unit.manual_control = False ``` **Effet** : Quand le Harvester arrive à la destination manuelle, `manual_control = False` → reprend IA automatique --- ### 6. Reprendre IA après dépôt (Ligne 529) **AVANT :** ```python if distance < TILE_SIZE * 2: # Deposit cargo self.game_state.players[unit.player_id].credits += unit.cargo unit.cargo = 0 unit.returning = False unit.gathering = False unit.ore_target = None unit.target = None # Clear target after deposit ``` **APRÈS :** ```python if distance < TILE_SIZE * 2: # Deposit cargo self.game_state.players[unit.player_id].credits += unit.cargo unit.cargo = 0 unit.returning = False unit.gathering = False unit.ore_target = None unit.target = None # Clear target after deposit unit.manual_control = False # Resume AI after deposit ``` **Effet** : Après dépôt de cargo, reprend IA automatique (même si était en mode manuel) --- ## 🔄 NOUVEAU COMPORTEMENT ### Scénario 1 : IA Automatique (défaut) ``` 1. Harvester spawn depuis HQ └─ manual_control = False ✓ 2. Chaque tick: ├─ update_harvester() exécuté ✓ ├─ Cherche minerai automatiquement ├─ Récolte automatiquement └─ Cycle automatique complet ✓ ``` ### Scénario 2 : Contrôle manuel par le joueur ``` 1. Joueur clique pour déplacer Harvester vers (800, 600) ├─ handle_command("move_unit") ├─ unit.target = (800, 600) ├─ unit.manual_control = True ✓ └─ gathering/returning/ore_target nettoyés 2. Chaque tick: ├─ Condition: unit.type == HARVESTER and not manual_control ├─ False (manual_control = True) └─ update_harvester() SKIPPED ✓ 3. Harvester se déplace vers (800, 600) └─ Code de mouvement normal (lignes 470-486) 4. Harvester arrive à destination: ├─ dist < 5 ├─ unit.target = None └─ unit.manual_control = False ✓ [REPREND IA!] 5. Tick suivant: └─ update_harvester() exécuté de nouveau (IA reprend) ✓ ``` ### Scénario 3 : Contrôle manuel puis dépôt ``` 1. Joueur déplace Harvester manuellement près d'un patch ORE └─ manual_control = True 2. Harvester arrive à destination └─ manual_control = False (reprend IA) 3. IA détecte minerai proche ├─ ore_target = (1200, 800) ├─ gathering = True └─ Commence récolte automatique ✓ 4. Cargo plein, retourne au dépôt automatiquement └─ returning = True 5. Dépose au HQ ├─ credits += cargo ├─ cargo = 0 └─ manual_control = False (confirmé) ✓ 6. Reprend cycle automatique └─ Cherche nouveau minerai ✓ ``` --- ## 📊 TABLEAU COMPARATIF | Situation | AVANT (Bugué) | APRÈS (Corrigé) | |-----------|---------------|-----------------| | **Spawn du HQ** | IA fonctionne ✓ | IA fonctionne ✓ | | **Ordre manuel du joueur** | ❌ Ignoré (IA écrase) | ✅ Obéit (IA désactivée) | | **Arrivée à destination manuelle** | N/A | ✅ Reprend IA automatique | | **Dépôt de cargo** | ✅ Reprend IA | ✅ Reprend IA (forcé) | | **Récolte automatique** | ✅ Fonctionne | ✅ Fonctionne | | **Cycle complet** | ❌ Pas de contrôle manuel | ✅ Manuel ET automatique | --- ## 🎯 RÉSULTATS ### Comportement attendu (Red Alert classique) ✅ **IA Automatique par défaut** - Harvester cherche et récolte ressources automatiquement - Cycle complet sans intervention du joueur ✅ **Contrôle manuel optionnel** - Joueur peut donner ordres manuels (clic droit pour déplacer) - Harvester obéit immédiatement aux ordres manuels - IA se désactive temporairement ✅ **Retour automatique à l'IA** - Après avoir atteint destination manuelle - Après avoir déposé cargo - Le joueur n'a pas besoin de réactiver l'IA ### Flexibilité Le joueur peut maintenant : 1. **Laisser l'IA gérer** (défaut) - Harvester autonome 2. **Prendre le contrôle** - Déplacer manuellement vers un patch spécifique 3. **Mélanger les deux** - Ordres manuels ponctuels, IA reprend après --- ## 🧪 TESTS ### Test 1 : IA automatique ``` 1. Produire Harvester depuis HQ 2. Observer: Harvester cherche minerai automatiquement ✓ 3. Observer: Récolte et dépose automatiquement ✓ ``` ### Test 2 : Contrôle manuel ``` 1. Produire Harvester 2. Attendre qu'il commence à bouger (IA) 3. Cliquer pour le déplacer ailleurs 4. Observer: Harvester obéit immédiatement ✓ 5. Observer: Arrive à destination 6. Observer: Reprend IA automatique ✓ ``` ### Test 3 : Mélange manuel/automatique ``` 1. Produire Harvester 2. Déplacer manuellement près d'un patch GEM (valeur +100) 3. Attendre arrivée à destination 4. Observer: IA reprend et récolte le GEM proche ✓ 5. Observer: Retourne au dépôt automatiquement ✓ 6. Observer: Recommence cycle automatique ✓ ``` --- ## 🐛 DEBUGGING Si le Harvester ne répond toujours pas aux ordres manuels : ### 1. Vérifier WebSocket ```python # Dans handle_command() ligne 633 print(f"[CMD] move_unit: unit_ids={unit_ids}, target={target}") ``` ### 2. Vérifier manual_control activé ```python # Après unit.manual_control = True ligne 641 print(f"[Harvester {unit.id[:8]}] manual_control=True, target={unit.target}") ``` ### 3. Vérifier update_harvester() skipped ```python # Dans update_game_state() ligne 427 if unit.type == UnitType.HARVESTER: if unit.manual_control: print(f"[Harvester {unit.id[:8]}] SKIPPING update_harvester (manual control)") else: print(f"[Harvester {unit.id[:8]}] Running update_harvester (AI)") ``` ### 4. Vérifier reprise de l'IA ```python # Dans mouvement ligne 486 if unit.type == UnitType.HARVESTER and unit.manual_control: print(f"[Harvester {unit.id[:8]}] Reached destination, resuming AI") unit.manual_control = False ``` --- ## 📖 DOCUMENTATION ### Fichiers modifiés - `/home/luigi/rts/web/app.py` - Ligne 130: Ajout champ `manual_control` - Ligne 148: Sérialisation `manual_control` - Ligne 427: Skip IA si `manual_control = True` - Ligne 633-642: Activer `manual_control` sur ordre joueur - Ligne 486: Reprendre IA quand destination atteinte - Ligne 532: Reprendre IA après dépôt ### Fichiers créés - `/home/luigi/rts/web/HARVESTER_MANUAL_CONTROL_FIX.md` (ce document) --- ## ✅ CONCLUSION **Problème 1:** Harvester ne cherchait pas ressources automatiquement **Solution 1:** Correction condition `not ore_target` au lieu de `not target` **Résultat 1:** ✅ IA automatique fonctionne **Problème 2:** Harvester ignorait ordres manuels du joueur **Solution 2:** Flag `manual_control` pour désactiver temporairement IA **Résultat 2:** ✅ Contrôle manuel fonctionne **Résultat final:** 🎮 Harvester fonctionne exactement comme Red Alert ! - ✅ IA automatique par défaut - ✅ Contrôle manuel optionnel - ✅ Retour automatique à l'IA - ✅ Flexibilité totale pour le joueur --- **Date:** 3 Octobre 2025 **Status:** ✅ CORRIGÉ ET TESTÉ **Version:** 2.0 (IA automatique + contrôle manuel)