File size: 10,076 Bytes
12d64f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# 🚜 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)