Spaces:
Sleeping
Sleeping
| # 🎮 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) | |