rts-commander / docs /HARVESTER_AI_FIX.md
Luigi's picture
deploy(web): full clean snapshot with app code and assets
12d64f8

🚜 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) :

# 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

# 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

# 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é) :

if not unit.gathering and not unit.target:
    # Cherche minerai

❌ Échoue si target résiduel existe

APRÈS (corrigé) :

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

    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

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

    # 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

    curl http://localhost:7860/health | jq '.terrain' | grep -c '"ore"'
    # Devrait retourner > 0
    
  3. Vérifier update_harvester() appelé

    # 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

    # 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 :

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 :

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

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

# 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)