Teach Claude the autonomous devcontainer test recipe in CLAUDE.md

Covers toolchain sanity check, the root-owned .godot permissions pitfall,
build -> smoke -> Harness loop, Python snippet, visual validation loop,
and non-obvious state shape details. Also fixes the stale Windows-only
Godot path mention.
This commit is contained in:
Samuel Bouchet 2026-04-17 21:13:05 +02:00
parent 5146798f5c
commit 3077b2d669

View file

@ -40,7 +40,10 @@ scene, lit des commandes JSON dans `<dir>/inbox/`, ecrit les resultats dans
comportement normal — overhead zero. comportement normal — overhead zero.
Cote agent, un wrapper Python stdlib (`tools/automation/harness.py`) expose une API Cote agent, un wrapper Python stdlib (`tools/automation/harness.py`) expose une API
simple. Godot attendu a `C:\Apps\godot\Godot_v4.6.2-stable_mono_win64_console.exe`. simple. Le binaire Godot est detecte via `GODOT_BIN` (fallback Windows
`C:\Apps\godot\Godot_v4.6.2-stable_mono_win64_console.exe`, Linux
`/opt/godot/godot`). Sous Linux sans `DISPLAY`, la commande Godot est auto-wrappee
dans `xvfb-run` (framebuffer virtuel 1280x720).
### Build + utilisation ### Build + utilisation
@ -101,3 +104,86 @@ Cette boucle permet de valider que :
- Les fichiers IPC sont ecrits `.tmp` puis renommes (atomique sur Windows). - Les fichiers IPC sont ecrits `.tmp` puis renommes (atomique sur Windows).
- La campagne se charge via `load_mission("campaign_01", 0)`. Passer a une mission - La campagne se charge via `load_mission("campaign_01", 0)`. Passer a une mission
> 0 n'est pas supporte directement (il faut passer par `MissionComplete` reel). > 0 n'est pas supporte directement (il faut passer par `MissionComplete` reel).
## Mode autonome dans le devcontainer
Claude tourne dans un devcontainer Linux (`.devcontainer/`) qui embarque deja
`.NET 9 SDK`, `Godot 4.6.2 mono Linux`, `Xvfb`, `Python 3`. Tout le workflow
ci-dessous est executable sans sortir du container, sans display physique.
### Sanity check toolchain (une fois par session)
```bash
dotnet --version # 9.0.x
godot --version # 4.6.2.stable.mono.official.*
which godot-xvfb # /usr/local/bin/godot-xvfb (auto xvfb-run wrapper)
```
### Piege permissions `.godot/`
Si une etape precedente (build Docker, editeur…) a laisse `.godot/` detenu par
`root`, le build dotnet echoue avec `MSB3374: Access to the path '...Up2Date'
is denied`. Les perms sont a 777 -> **supprimer le cache suffit**, pas besoin de
`sudo` :
```bash
rm -rf /workspace/.godot
dotnet build Chessistics.csproj
```
### Recette pour verifier que Claude peut jouer tout seul
```bash
dotnet build Chessistics.csproj # doit etre vert
python3 tools/automation/smoke.py # charge mission 1, screenshots, determinisme
ls .automation_runs/smoke/screens/ # PNG non-noirs
```
Si `smoke.py` passe, tout le pipeline marche : Godot boot -> IPC inbox/outbox
-> screenshots lisibles via l'outil `Read`.
### Driver le jeu en Python depuis Claude
```python
import sys, time
sys.path.insert(0, '/workspace')
from tools.automation.harness import Harness
with Harness.launch(run_name="claude_drive") as h:
h.load_mission("campaign_01", 0)
s = h.state() # dict: phase, turn, width, height,
# grid, productions, demands,
# transformers, pieces, remainingStock
h.screenshot("01_loaded")
h.place("Pawn", (0, 0), (0, 1))
h.screenshot("02_placed")
h.set_speed(0.05); h.play()
time.sleep(2); h.pause()
h.screenshot("03_after_play")
```
Les PNG atterrissent dans `/workspace/.automation_runs/<run_name>/screens/`.
Claude les lit directement via `Read` (multimodal).
### Boucle typique de validation visuelle
1. `h.load_mission("campaign_01", N)` -> `h.screenshot(f"m{N}_start")` -> `Read`
2. Verifier sur le PNG : titre, bandeau flavor, board, panneau OBJECTIFS,
compteurs PIECES.
3. Poser des pieces via `h.place(...)`, relire l'etat (`remainingStock`
diminue, `pieces` grandit).
4. `h.play()` + `sleep` + `h.pause()` (ou `h.step()` en boucle), screenshot
a chaque palier.
5. Boucler jusqu'a `state()["phase"] == "MissionComplete"` ou un objectif
`demands[i].satisfied == True`.
### Details qui surprennent
- Le snapshot `state()` expose `width`/`height` au niveau racine (pas
`board.width`).
- `remainingStock` est un dict `{"Pawn": 4, ...}` ; verifier qu'il decremente
apres un `place()` confirme que la commande a bien ete appliquee.
- Les logs Godot affichent des warnings ALSA (pas de carte son) et V-Sync —
inoffensifs en headless, les filtrer avant d'afficher a l'utilisateur.
- Le firewall du container bloque tout sauf l'allowlist ; le runtime Godot
n'a besoin d'aucun reseau, donc aucun probleme.