rts-commander / docs /HARVESTER_MANUAL_CONTROL_FIX.md
Luigi's picture
deploy(web): full clean snapshot with app code and assets
12d64f8
|
raw
history blame
17 kB
# 🎮 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)