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

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

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 :

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

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

"cargo": self.cargo,
"gathering": self.gathering,
"returning": self.returning

APRÈS :

"cargo": self.cargo,
"gathering": self.gathering,
"returning": self.returning,
"manual_control": self.manual_control

3. Skip IA si contrôle manuel (Ligne 427)

AVANT :

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

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

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 :

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 :

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

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

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 :

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

# Dans handle_command() ligne 633
print(f"[CMD] move_unit: unit_ids={unit_ids}, target={target}")

2. Vérifier manual_control activé

# 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

# 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

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