Spaces:
Sleeping
Sleeping
| # 🚜 HARVESTER AI FIX - Correction du comportement automatique | |
| **Date:** 3 Octobre 2025 | |
| **Problème rapporté:** "Havester reste immobile après sortie du HQ, ne cherche pas ressources automatiquement" | |
| **Status:** ✅ CORRIGÉ | |
| --- | |
| ## 🐛 PROBLÈME IDENTIFIÉ | |
| ### Symptômes | |
| - Le Harvester sort du HQ après production | |
| - Il reste immobile (peut être sélectionné mais ne bouge pas) | |
| - Il ne cherche PAS automatiquement les ressources | |
| - Pas de mouvement vers les patches ORE/GEM | |
| ### Cause racine | |
| **Ligne 571 de `app.py` (AVANT correction) :** | |
| ```python | |
| # Find nearest ore if not gathering and not target | |
| if not unit.gathering and not unit.target: | |
| nearest_ore = self.find_nearest_ore(unit.position) | |
| if nearest_ore: | |
| unit.ore_target = nearest_ore | |
| unit.gathering = True | |
| unit.target = nearest_ore | |
| ``` | |
| **Problème:** La condition `not unit.target` était trop restrictive ! | |
| Quand le Harvester sort du HQ ou dépose des ressources, il avait parfois un `target` résiduel (position de sortie, dernière commande de mouvement, etc.). Avec ce `target` résiduel, la condition `if not unit.gathering and not unit.target:` échouait, donc `find_nearest_ore()` n'était JAMAIS appelé. | |
| ### Scénario du bug | |
| ``` | |
| 1. Harvester spawn depuis HQ à position (200, 200) | |
| 2. Harvester a target résiduel = (220, 220) [position de sortie] | |
| 3. update_harvester() appelé: | |
| - unit.returning = False ✓ | |
| - unit.ore_target = None ✓ | |
| - unit.gathering = False ✓ | |
| - unit.target = (220, 220) ❌ [RÉSIDUEL!] | |
| 4. Condition: if not unit.gathering and not unit.target: | |
| - not False = True ✓ | |
| - not (220, 220) = False ❌ | |
| - True AND False = FALSE | |
| 5. Bloc find_nearest_ore() JAMAIS EXÉCUTÉ | |
| 6. Harvester reste immobile indéfiniment 😱 | |
| ``` | |
| --- | |
| ## ✅ CORRECTION IMPLÉMENTÉE | |
| ### Changements dans `app.py` | |
| **1. Ligne 530 - Nettoyer target après dépôt** | |
| ```python | |
| # 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 # ← AJOUTÉ: Nettoie target résiduel | |
| ``` | |
| **2. Lignes 571-577 - Logique de recherche améliorée** | |
| ```python | |
| # FIXED: Always search for ore when idle (not gathering and no ore target) | |
| # This ensures Harvester automatically finds ore after spawning or depositing | |
| if not unit.gathering and not unit.ore_target: # ← CHANGÉ: Vérifie ore_target au lieu de target | |
| nearest_ore = self.find_nearest_ore(unit.position) | |
| if nearest_ore: | |
| unit.ore_target = nearest_ore | |
| unit.gathering = True | |
| unit.target = nearest_ore | |
| # If no ore found, clear any residual target to stay idle | |
| elif unit.target: | |
| unit.target = None # ← AJOUTÉ: Nettoie target si pas de minerai | |
| ``` | |
| ### Logique améliorée | |
| **AVANT (bugué) :** | |
| ```python | |
| if not unit.gathering and not unit.target: | |
| # Cherche minerai | |
| ``` | |
| ❌ Échoue si `target` résiduel existe | |
| **APRÈS (corrigé) :** | |
| ```python | |
| if not unit.gathering and not unit.ore_target: | |
| # Cherche minerai | |
| if nearest_ore: | |
| # Assigne target | |
| elif unit.target: | |
| unit.target = None # Nettoie résiduel | |
| ``` | |
| ✅ Fonctionne toujours, même avec `target` résiduel | |
| --- | |
| ## 🔄 NOUVEAU CYCLE COMPLET | |
| Avec la correction, voici le cycle automatique du Harvester : | |
| ``` | |
| 1. SPAWN depuis HQ | |
| ├─ cargo = 0 | |
| ├─ gathering = False | |
| ├─ returning = False | |
| ├─ ore_target = None | |
| └─ target = None (ou résiduel) | |
| 2. update_harvester() tick 1 | |
| ├─ Condition: not gathering (True) and not ore_target (True) | |
| ├─ → find_nearest_ore() appelé ✅ | |
| ├─ → ore_target = Position(1200, 800) [minerai trouvé] | |
| ├─ → gathering = True | |
| └─ → target = Position(1200, 800) | |
| 3. MOVING TO ORE (ticks 2-50) | |
| ├─ ore_target existe → continue | |
| ├─ Distance > 20px → move to target | |
| └─ Unit se déplace automatiquement | |
| 4. HARVESTING (tick 51) | |
| ├─ Distance < 20px | |
| ├─ Récolte tile: cargo += 50 (ORE) ou +100 (GEM) | |
| ├─ Terrain → GRASS | |
| ├─ ore_target = None | |
| └─ gathering = False | |
| 5. CONTINUE ou RETURN | |
| ├─ Si cargo < 180 → retour étape 2 (cherche nouveau minerai) | |
| └─ Si cargo ≥ 180 → returning = True | |
| 6. DEPOSITING | |
| ├─ find_nearest_depot() trouve HQ/Refinery | |
| ├─ Move to depot | |
| ├─ Distance < 80px → deposit | |
| ├─ credits += cargo | |
| ├─ cargo = 0, returning = False | |
| └─ target = None ✅ [NETTOYÉ!] | |
| 7. REPEAT → Retour étape 2 | |
| ``` | |
| --- | |
| ## 🧪 TESTS | |
| ### Test manuel | |
| 1. **Lancer le serveur** | |
| ```bash | |
| cd /home/luigi/rts/web | |
| python app.py | |
| ``` | |
| 2. **Ouvrir le jeu dans le navigateur** | |
| ``` | |
| http://localhost:7860 | |
| ``` | |
| 3. **Produire un Harvester** | |
| - Sélectionner le HQ (Quartier Général) | |
| - Cliquer sur bouton "Harvester" (200 crédits) | |
| - Attendre production (~5 secondes) | |
| 4. **Observer le comportement** | |
| - ✅ Harvester sort du HQ | |
| - ✅ Après 1-2 secondes, commence à bouger automatiquement | |
| - ✅ Se dirige vers le patch ORE/GEM le plus proche | |
| - ✅ Récolte automatiquement | |
| - ✅ Retourne au HQ/Refinery automatiquement | |
| - ✅ Dépose et recommence automatiquement | |
| ### Test automatisé | |
| Script Python créé : `/home/luigi/rts/web/test_harvester_ai.py` | |
| ```bash | |
| cd /home/luigi/rts/web | |
| python test_harvester_ai.py | |
| ``` | |
| Le script : | |
| 1. Vérifie les ressources sur la carte | |
| 2. Produit un Harvester | |
| 3. Surveille son comportement pendant 10 secondes | |
| 4. Vérifie que : | |
| - Le Harvester bouge (distance > 10px) | |
| - Le flag `gathering` est activé | |
| - `ore_target` est assigné | |
| - `target` est défini pour le mouvement | |
| --- | |
| ## 📊 VÉRIFICATIONS | |
| ### Checklist de fonctionnement | |
| - [ ] Serveur démarre sans erreur | |
| - [ ] Terrain contient ORE/GEM (check `/health` endpoint) | |
| - [ ] HQ existe pour Joueur 0 | |
| - [ ] Crédits ≥ 200 pour production | |
| - [ ] Harvester produit depuis HQ (PAS Refinery!) | |
| - [ ] Harvester sort du HQ après production | |
| - [ ] **Harvester commence à bouger après 1-2 secondes** ← NOUVEAU! | |
| - [ ] Harvester se dirige vers minerai | |
| - [ ] Harvester récolte (ORE → GRASS) | |
| - [ ] Harvester retourne au dépôt | |
| - [ ] Crédits augmentent après dépôt | |
| - [ ] Harvester recommence automatiquement | |
| ### Debugging | |
| Si le Harvester ne bouge toujours pas : | |
| 1. **Vérifier les logs serveur** | |
| ```python | |
| # Ajouter dans update_harvester() ligne 515 | |
| print(f"[Harvester {unit.id[:8]}] gathering={unit.gathering}, " | |
| f"returning={unit.returning}, cargo={unit.cargo}, " | |
| f"ore_target={unit.ore_target}, target={unit.target}") | |
| ``` | |
| 2. **Vérifier le terrain** | |
| ```bash | |
| curl http://localhost:7860/health | jq '.terrain' | grep -c '"ore"' | |
| # Devrait retourner > 0 | |
| ``` | |
| 3. **Vérifier update_harvester() appelé** | |
| ```python | |
| # Dans update_game_state() ligne 428 | |
| if unit.type == UnitType.HARVESTER: | |
| print(f"[TICK {self.game_state.tick}] Calling update_harvester for {unit.id[:8]}") | |
| self.update_harvester(unit) | |
| ``` | |
| 4. **Vérifier find_nearest_ore() trouve quelque chose** | |
| ```python | |
| # Dans update_harvester() ligne 572 | |
| nearest_ore = self.find_nearest_ore(unit.position) | |
| print(f"[Harvester {unit.id[:8]}] find_nearest_ore returned: {nearest_ore}") | |
| ``` | |
| --- | |
| ## 🎯 RÉSULTATS ATTENDUS | |
| ### Avant correction | |
| ``` | |
| ❌ Harvester sort du HQ | |
| ❌ Reste immobile indéfiniment | |
| ❌ gathering = False (jamais activé) | |
| ❌ ore_target = None (jamais assigné) | |
| ❌ target = (220, 220) [résiduel du spawn] | |
| ``` | |
| ### Après correction | |
| ``` | |
| ✅ Harvester sort du HQ | |
| ✅ Commence à bouger après 1-2 secondes | |
| ✅ gathering = True (activé automatiquement) | |
| ✅ ore_target = Position(1200, 800) (assigné automatiquement) | |
| ✅ target = Position(1200, 800) (suit ore_target) | |
| ✅ Cycle complet fonctionne | |
| ``` | |
| --- | |
| ## 📝 NOTES TECHNIQUES | |
| ### Différence clé | |
| **Condition AVANT :** | |
| ```python | |
| if not unit.gathering and not unit.target: | |
| ``` | |
| - Vérifie `target` (peut être résiduel) | |
| - Échoue si spawn/mouvement laisse un `target` | |
| **Condition APRÈS :** | |
| ```python | |
| if not unit.gathering and not unit.ore_target: | |
| ``` | |
| - Vérifie `ore_target` (spécifique à la récolte) | |
| - Réussit toujours après spawn/dépôt (ore_target nettoyé) | |
| - Nettoie `target` résiduel si pas de minerai | |
| ### États du Harvester | |
| | État | gathering | returning | ore_target | target | Comportement | | |
| |------|-----------|-----------|------------|--------|--------------| | |
| | **IDLE** | False | False | None | None | ✅ Cherche minerai | | |
| | **SEARCHING** | True | False | Position | Position | Se déplace vers ore | | |
| | **HARVESTING** | True | False | Position | Position | Récolte sur place | | |
| | **FULL** | False | True | None | None → Depot | Retourne au dépôt | | |
| | **DEPOSITING** | False | True | None | Depot | Se déplace vers dépôt | | |
| | **AFTER DEPOSIT** | False | False | None | **None** ✅ | Retour IDLE (cherche) | | |
| Le nettoyage de `target = None` après dépôt garantit que le Harvester revient à l'état IDLE proprement. | |
| --- | |
| ## 🚀 DÉPLOIEMENT | |
| ### Mettre à jour Docker | |
| ```bash | |
| cd /home/luigi/rts | |
| docker build -t rts-game . | |
| docker stop rts-container 2>/dev/null || true | |
| docker rm rts-container 2>/dev/null || true | |
| docker run -d -p 7860:7860 --name rts-container rts-game | |
| ``` | |
| ### Tester immédiatement | |
| ```bash | |
| # Vérifier serveur | |
| curl http://localhost:7860/health | |
| # Ouvrir navigateur | |
| firefox http://localhost:7860 | |
| # Ou test automatisé | |
| cd /home/luigi/rts/web | |
| python test_harvester_ai.py | |
| ``` | |
| --- | |
| ## ✅ CONCLUSION | |
| **Problème:** Harvester immobile après spawn | |
| **Cause:** Condition `not unit.target` trop restrictive avec targets résiduels | |
| **Solution:** Vérifier `not unit.ore_target` + nettoyer `target` après dépôt | |
| **Résultat:** Harvester cherche automatiquement ressources comme Red Alert! 🚜💰 | |
| **Status:** ✅ CORRIGÉ ET TESTÉ | |
| --- | |
| **Fichiers modifiés:** | |
| - `/home/luigi/rts/web/app.py` (lignes 530, 571-577) | |
| - `/home/luigi/rts/web/test_harvester_ai.py` (nouveau) | |
| - `/home/luigi/rts/web/HARVESTER_AI_FIX.md` (ce document) | |