2026-04-10 21:44:12 +02:00
# 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.
Juice pass: procedural SFX, particles, polished visuals
Sound (SfxManager.cs):
- Procedural audio synthesis via AudioStreamWav — no external files
- Distinct tones for place, produce, transfer, deliver, move, destroy, victory
- Simple ADSR envelope, sine/triangle waveforms, filtered noise for swooshes
Pieces (PieceView.cs):
- Warm earthy palette: sage green, deep teal, dusty rose, burnt sienna
- Drop shadow under each piece for depth
- 3-stop radial gradient (bright center → main → dark rim)
- Scale bounce on placement (0 → 1.15 → 1.0 with back-out easing)
- Cargo indicator pulses gently when carrying
Trajectories (TrajectView.cs):
- Arrowhead at endpoint showing movement direction
- Antialiased lines with piece-matched colors
Cells (CellView.cs):
- Warmer palette: parchment/walnut board, deep forest production, aged gold demand
- Production flash uses warm golden glow instead of white
- Subtle inner shadow for visual depth
Animations (EventAnimator.cs):
- Production: golden particles burst from production cells
- Transfer: cargo slides with 2-particle trail + back-out whip easing
- Destruction: pieces shrink + spin + red particle explosion
- Victory: 40 confetti particles rain across the screen
- All phases trigger appropriate SFX
UI polish:
- ControlBar: styled buttons with rounded corners, disabled states
- MetricsOverlay: fade-in + scale animation, sequential metric reveals
- ObjectivePanel: animated progress bars, styled fills, green flash on completion
- Main: fade-in/out transitions between level select and gameplay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:05:55 +02:00
## Conventions Claude
### Plans
Les fichiers de plan doivent etre rediges a la racine du workspace (ex: `/workspace/PLAN_juice.md` ), **pas** dans `.claude/plans/` car ce dossier a une taille limitee.
2026-04-16 22:37:20 +02:00
## 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
2026-04-17 21:13:05 +02:00
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).
2026-04-16 22:37:20 +02:00
### 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).
2026-04-17 21:13:05 +02:00
## 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.