Delete autonomous_plan.md (fully shipped), PLAN_playtest.md (all P1-P7 done), PLAN_missions.md and PLAN_leveldesign.md (partial — engine done, UI polish + 3 final missions + recurring demands + transformer visuals remain). The surviving TODO list lives in docs/PLAN.md. Also add the 6-step dev loop to CLAUDE.md (take next topic → implement → tests → UI test → docs → commit).
207 lines
8.9 KiB
Markdown
207 lines
8.9 KiB
Markdown
# Chessistics
|
|
|
|
Jeu de logistique sur echiquier en Godot 4 / C#. Le joueur place des pieces d'echecs sur un plateau ; elles se deplacent automatiquement et transportent des ressources entre des productions et des demandes.
|
|
|
|
## Architecture : Black-Box Simulation
|
|
|
|
Ref: https://samuel-bouchet.fr/posts/2026-04-08-black-box-sim/
|
|
|
|
Le moteur de jeu (`chessistics-engine/`) est une boite noire sans aucune dependance vers Godot. Il recoit des **Commands**, mute son etat interne, et retourne des **Events**. Le code Godot (`Scripts/`) ne fait que traduire l'input en commands et les events en visuels/animations.
|
|
|
|
```
|
|
Input → Command → GameSim (state + rules) → Events → Presentation
|
|
```
|
|
|
|
- **Commands** (`PlacePieceCommand`, `StartSimulationCommand`, …) : seul moyen de modifier l'etat.
|
|
- **Events** (`PiecePlacedEvent`, `CargoDeliveredEvent`, …) : seul output du moteur. Le presenteur les consomme pour animer.
|
|
- **GameSim** : point d'entree unique. `ProcessCommand()` retourne la liste d'events.
|
|
- **Tests** : `chessistics-tests/` teste le moteur en headless, sans Godot.
|
|
|
|
## Pieges Godot a eviter
|
|
|
|
### MouseFilter sur les Controls enfants de Node2D
|
|
|
|
Tout `Control` (ColorRect, Label…) a `MouseFilter = Stop` par defaut. Quand un Control est enfant d'un Node2D (ex: les ColorRect dans CellView, les Labels dans PieceView), **il participe quand meme au systeme GUI et consomme les clics**, empechant `_UnhandledInput` de recevoir l'evenement.
|
|
|
|
**Regle** : toujours mettre `MouseFilter = Control.MouseFilterEnum.Ignore` sur les Controls purement visuels enfants de Node2D.
|
|
|
|
## Conventions Claude
|
|
|
|
### Plans
|
|
|
|
Les plans vivants (travail restant, features a faire) vont dans
|
|
[`docs/PLAN.md`](docs/PLAN.md). Pour un brouillon ad-hoc, ecrire a la racine
|
|
du workspace (ex: `/workspace/PLAN_<sujet>.md`), **pas** dans
|
|
`.claude/plans/` (taille limitee). Une fois implemente, supprimer le
|
|
fichier ; si partiel, consolider le restant dans `docs/PLAN.md`.
|
|
|
|
### Boucle de developpement
|
|
|
|
Pour chaque sujet pris dans `docs/PLAN.md` :
|
|
|
|
1. **Prendre le sujet suivant** dans le plan (ordre de priorite).
|
|
2. **Implementer** (moteur + presentation selon le cas).
|
|
3. **Ajouter des tests unitaires** si applicable (`chessistics-tests/`).
|
|
4. **Tester l'UI/UX** de la fonctionnalite dans le jeu si applicable
|
|
(harness + quick save/load pour reprendre un checkpoint).
|
|
5. **Mettre a jour la documentation** (README, CLAUDE.md, GDD) si
|
|
necessaire et **retirer le sujet du plan** (ou annoter ce qui reste).
|
|
6. **Commit** (un commit par sujet, message en anglais, sans co-author
|
|
Claude).
|
|
|
|
## Harnais d'automatisation (Claude peut jouer tout seul)
|
|
|
|
Le jeu peut etre pilote de maniere autonome via le flag `--automation=<dir>`. Un
|
|
`AutomationHarness` (`Scripts/Automation/`) s'active alors comme noeud au root de la
|
|
scene, lit des commandes JSON dans `<dir>/inbox/`, ecrit les resultats dans
|
|
`<dir>/outbox/`, et place les captures d'ecran dans `<dir>/screens/`. Sans le flag,
|
|
comportement normal — overhead zero.
|
|
|
|
Cote agent, un wrapper Python stdlib (`tools/automation/harness.py`) expose une API
|
|
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
|
|
|
|
```bash
|
|
dotnet build Chessistics.csproj # compiler avant tout lancement
|
|
python tools/automation/smoke.py # smoke test end-to-end
|
|
python tools/automation/run_game.py # REPL interactif
|
|
```
|
|
|
|
### API Python
|
|
|
|
```python
|
|
from tools.automation.harness import Harness
|
|
|
|
with Harness.launch() as h:
|
|
h.load_mission("campaign_01", 0) # charge la campagne + mission 0
|
|
state = h.state() # snapshot complet (dict)
|
|
h.screenshot("before") # -> .automation_runs/<ts>/screens/before.png
|
|
h.place("Pawn", (0, 0), (0, 1)) # pose une piece
|
|
h.step() # un tour (auto-wait animation)
|
|
h.screenshot("after")
|
|
h.set_speed(0.1); h.play() # auto-play rapide
|
|
```
|
|
|
|
Methodes : `screenshot`, `state`, `select`, `place`, `click_cell`, `key`, `play`,
|
|
`pause`, `step`, `wait_idle`, `set_speed`, `load_mission`, `back_to_menu`, `quit`.
|
|
|
|
Toutes les commandes non-query attendent `EventAnimator.IsAnimating == false` avant
|
|
de retourner -> appels en serie toujours vus par le prochain `state()`.
|
|
|
|
### Validation visuelle par Claude
|
|
|
|
Les PNG 1280x720 ecrites dans `.automation_runs/<run>/screens/` peuvent etre lues
|
|
directement par l'outil `Read` de Claude. Workflow type pour valider l'UI :
|
|
|
|
1. `h.load_mission("campaign_01", N)` + `h.screenshot("mission_N_start")`
|
|
2. Lire le PNG -> verifier titre, flavor banner, board, panneau objectifs, stock
|
|
3. Placer des pieces via `h.place(...)` et re-screenshot
|
|
4. `h.step()` en boucle + screenshot a chaque etape
|
|
5. Attendre `phase == "MissionComplete"` dans le snapshot
|
|
|
|
Cette boucle permet de valider que :
|
|
- Les demandes affichent les bons compteurs
|
|
- Les pieces bougent comme prevu
|
|
- Le stock se met a jour
|
|
- L'ecran `MissionComplete` apparait quand attendu
|
|
|
|
### Details importants
|
|
|
|
- `Place` passe par le signal `PlacementRequested` (meme chemin qu'un vrai clic) --
|
|
ne pas appeler `GameSim.ProcessCommand(PlacePieceCommand)` directement dans le
|
|
dispatcher, ca mute deux fois.
|
|
- Les captures d'ecran sont prises apres `RenderingServer.frame_post_draw` -> le
|
|
frame reflete l'etat final, animations incluses.
|
|
- La facade (`AutomationFacade`) est la **seule** surface exposee au dispatcher.
|
|
Elle ne touche que des methodes/signals publics de `GameSim`, `InputMapper`,
|
|
`EventAnimator`, `ControlBar`, `PieceStockPanel`. La separation black-box tient.
|
|
- 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
|
|
> 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.
|