From bd1763f3728db32ec94809a83b612ecfa842e4cf Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Fri, 17 Apr 2026 22:09:53 +0200 Subject: [PATCH] Consolidate plans into docs/PLAN.md and document dev loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- CLAUDE.md | 20 ++- PLAN_leveldesign.md | 157 ------------------- PLAN_missions.md | 371 -------------------------------------------- PLAN_playtest.md | 41 ----- README.md | 2 - autonomous_plan.md | 149 ------------------ docs/PLAN.md | 87 +++++++++++ 7 files changed, 106 insertions(+), 721 deletions(-) delete mode 100644 PLAN_leveldesign.md delete mode 100644 PLAN_missions.md delete mode 100644 PLAN_playtest.md delete mode 100644 autonomous_plan.md create mode 100644 docs/PLAN.md diff --git a/CLAUDE.md b/CLAUDE.md index cdefcf9..b80c52a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,25 @@ Tout `Control` (ColorRect, Label…) a `MouseFilter = Stop` par defaut. Quand un ### 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. +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_.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) diff --git a/PLAN_leveldesign.md b/PLAN_leveldesign.md deleted file mode 100644 index 1f30dbb..0000000 --- a/PLAN_leveldesign.md +++ /dev/null @@ -1,157 +0,0 @@ -# Plan : Level Design - Batiments de Transformation - -## Concept - -Les batiments de **transformation** consomment une ressource en entree et produisent une ressource differente en sortie. Le joueur doit construire des chaines logistiques multi-etapes : extraction → transformation → livraison. - -Exemple : Scierie (produit bois) → Forge (consomme bois, produit outils) → Caserne (demande outils). - -## Modele Engine - -### Nouveau type de cellule : Transformer - -``` -CellType.Transformer -``` - -Un `TransformerDef` combine une demande (input) et une production (output) : - -```csharp -public record TransformerDef( - Coords Position, - string Name, - CargoType InputCargo, // ce qu'il consomme - int InputRequired, // nb d'unites avant conversion - CargoType OutputCargo, // ce qu'il produit - int OutputAmount // nb d'unites produites par cycle -); -``` - -### Logique de production - -Chaque tour, le `TransferResolver` livre du cargo au transformateur comme a une demande normale. Quand le buffer d'entree atteint `InputRequired`, le transformateur : -1. Vide son buffer d'entree -2. Remplit son buffer de sortie avec `OutputAmount` unites de `OutputCargo` -3. Le buffer de sortie est distribue aux pieces adjacentes (comme une production classique) - -Cela cree un rythme : accumulation → conversion → distribution. - -### Changements au TerrainPatch - -Ajouter le type `"transformer"` dans le JSON : - -```json -{ - "col": 3, "row": 2, - "type": "transformer", - "transformer": { - "name": "Forge", - "inputCargo": "wood", - "inputRequired": 2, - "outputCargo": "tools", - "outputAmount": 1 - } -} -``` - -### Nouveaux types de cargo - -Ajouter au `CargoType` enum : -- `Tools` (outils) — bois transforme -- `Arms` (armes) — pierre transformee -- `Gold` (or) — ressource rare de fin de campagne - -## Evolution de la campagne (10 missions) - -### Mission 1 : Premier Convoi (4x4) -- **Deverrouille** : Pion -- **Batiments** : Scierie → Depot Royal -- **Objectif** : livrer 3 bois — apprendre le placement de base - -### Mission 2 : Forger les Tours (6x6) -- **Deverrouille** : Tour -- **Batiments** : +Carriere, +Caserne (bois), +Forge (pierre) -- **Objectif** : livrer bois a la caserne ET pierre a la forge -- **Defi** : gerer deux routes independantes - -### Mission 3 : Le Col (6x6) -- **Deverrouille** : Cavalier -- **Batiments** : mur bloquant + Depot (bois) -- **Objectif** : traverser les murs avec des cavaliers -- **Les demands precedentes restent actives** - -### Mission 4 : Le Carrefour (8x8) -- **Deverrouille** : Fou -- **Batiments** : +Chateau (bois), +Forge Royale (pierre), murs -- **Objectif** : routes diagonales avec les fous - -### Mission 5 : La Forge (8x8) -- TRANSFORMATION -- **Deverrouille** : aucun (nouveau type de cargo : Outils) -- **Batiments** : Forge *transformee* en transformateur (bois → outils) -- **Objectif** : livrer des outils a un nouveau batiment (Armurerie) -- **Defi** : la route bois existante doit continuer, PLUS une branche vers la forge qui produit des outils - -### Mission 6 : La Dame Blanche (10x10) -- **Deverrouille** : Dame -- **Batiments** : +Scierie Nord, +Grand Chantier (bois), +Arsenal (pierre) -- **Objectif** : routes longue distance avec la dame - -### Mission 7 : L'Armurerie (10x10) -- TRANSFORMATION -- **Nouveau cargo** : Armes -- **Batiments** : Armurerie (transforme pierre → armes) -- **Objectif** : livrer des armes a une Garnison - -### Mission 8 : Le Comptoir (12x12) -- **Batiments** : Comptoir (transforme outils → or) -- **Objectif** : livrer de l'or au Tresor Royal -- **Defi** : chaine a 3 etapes : bois → outils → or - -### Mission 9 : L'Expansion Finale (14x14) -- **Batiments** : multiples transformateurs, murs complexes -- **Objectif** : maintenir toutes les chaines tout en s'etendant -- **Defi** : gestion de la congestion (risque de collisions) - -### Mission 10 : Le Couronnement (14x14) -- **Batiments** : Cathedrale (demande or + armes + outils) -- **Objectif** : livrer les 3 types de cargo transforme -- **Defi** : orchestrer l'ensemble des chaines logistiques simultanement - -## Demands recurrentes (futur) - -Pour que le joueur doive "preserver ses automatisations", les demands pourraient devenir recurrentes : -- Un batiment de demande **consomme** N unites par tour -- S'il n'est plus approvisionne, il passe en etat "en penurie" -- Condition de mission : **aucun** batiment en penurie pendant X tours consecutifs - -Cela force le joueur a maintenir ses routes existantes quand le terrain s'agrandit, au lieu de tout reconstruire. - -## Implementation par phases - -### Phase 1 : Modele Transformer -- Ajouter `CellType.Transformer` et `TransformerDef` -- Ajouter `TransformerState` avec buffers input/output -- Integrer dans `BoardState` - -### Phase 2 : Logique de conversion -- Modifier `TurnExecutor` : sous-phase "transformation" entre production et transferts -- Le transformateur agit comme une demande (recoit) ET une production (emet) - -### Phase 3 : Nouveaux cargos -- Ajouter `Tools`, `Arms`, `Gold` a `CargoType` -- Couleurs visuelles pour chaque cargo -- Mise a jour du `CampaignLoader` pour parser les transformateurs - -### Phase 4 : Visuels -- Couleur de cellule pour les transformateurs (ex: orange cuivre) -- Animation de conversion (flash input → flash output) -- Icones de cargo dans les pieces - -### Phase 5 : Missions 5-10 -- Ecrire les donnees JSON des missions 5 a 10 -- Tester la solvabilite de chaque mission -- Equilibrer les quantites (input/output ratios) - -### Phase 6 (optionnel) : Demands recurrentes -- Modifier `DemandState` pour tracker la consommation par tour -- Ajouter un flag "en penurie" -- Condition de victoire : pas de penurie pendant N tours diff --git a/PLAN_missions.md b/PLAN_missions.md deleted file mode 100644 index 1e4f759..0000000 --- a/PLAN_missions.md +++ /dev/null @@ -1,371 +0,0 @@ -# Plan : Puzzle → Logistique incrémentale (Missions) - -## Vision - -Remplacer la série de niveaux indépendants par un plateau persistant où les **missions** s'enchaînent. Chaque mission complétée débloque une extension du terrain (nouvelles cases, productions, demandes, murs) sans casser la solution en place. Le jeu devient un jeu de **logistique** et non de **puzzle**. - -### Fil narratif (lore GDD §12) - -Les pions manquent d'un roi. Mission après mission, ils construisent des pièces de plus en plus puissantes pour atteindre des ressources plus lointaines, jusqu'à fabriquer le roi — qui exécute tout le monde. - -Chaque mission débloque de nouveaux **types de pièces** : on commence avec les Pions seuls, puis les Tours, les Fous, les Cavaliers, et enfin la Dame. Le lore justifie la progression mécanique. - ---- - -## Concepts clés - -| Ancien | Nouveau | -|--------|---------| -| Level (plateau isolé) | **Campaign** : un plateau persistant avec N missions | -| Victoire → charger le niveau suivant | Victoire → le plateau **s'étend**, la mission suivante se débloque | -| Stock fixe par niveau | Stock cumulé : chaque mission donne un budget additionnel | -| Reset complet entre niveaux | Les pièces posées et les productions **restent actives** | -| Stop (retour en Edit) | **Supprimé** — la simulation tourne en continu, le joueur édite en temps réel | -| Toutes pièces disponibles dès le début | Pièces **débloquées progressivement** par mission (lore) | -| Pièces à niveau fixe | **Niveaux de pièces** (I, II, III) débloqués par la progression | - -### Règle d'or : pas de régression -Les murs ne peuvent apparaître que sur les **nouvelles cases** débloquées par la mission. Le terrain existant ne change jamais de façon bloquante. - ---- - -## Changements de game design - -### Suppression du Stop — édition en temps réel - -Il n'y a plus de phase Edit séparée. La simulation tourne en continu. Le joueur peut : -- **Pause** (barre espace) : l'animation termine le tour en cours puis se fige. -- **Placer/retirer des pièces** à tout moment (en pause ou pendant que ça tourne). -- **Réoptimiser** les missions précédentes — essentiel pour un jeu de logistique. - -Conséquence engine : `PlacePieceCommand` et `RemovePieceCommand` fonctionnent quel que soit le `SimPhase` (plus de guard `phase == Edit`). - -### Auto-pause pendant le placement - -Sélectionner un type de pièce à placer met automatiquement la simulation en pause. La pause est levée quand le placement est confirmé (clic arrivée) ou annulé (Échap). Le joueur ne pense pas "pause" — il pense "placement". C'est transparent. - -### Retirer une pièce — touche Suppr - -Le clic droit est réservé au pan de caméra. Pour retirer une pièce : -- Clic gauche sur la pièce → sélection + panneau de détail -- **Touche Suppr** ou **bouton [Retirer]** dans le panneau de détail → retourne au stock - -### Collisions → retour au stock + pause auto - -Quand une pièce est détruite par collision, elle retourne dans le stock du joueur (au lieu d'être perdue). Cela évite un soft-lock où le joueur n'a plus assez de pièces pour résoudre la mission. - -La simulation se met en **pause automatique** sur collision : -- La caméra effectue un **pan + zoom** vers la zone de collision. -- Une **notification** apparaît dans un coin de l'écran pour expliciter ce qui s'est passé (ex: "Tour II détruite par Dame — retournée au stock"). -- Le joueur peut reprendre la simulation (Espace) après avoir pris connaissance de la situation. - -### Undo (Ctrl+Z) - -Annule le dernier placement ou retrait de pièce. L'architecture event-sourcing de l'engine rend l'implémentation naturelle : on conserve un historique de commandes et on les rejoue sans la dernière. Essentiel pour l'itération rapide sur un réseau en temps réel. - -### Productions — pas d'intervalle, production variable - -Chaque bâtiment de production a un champ `amount` (1 à 4) : il produit ce nombre de cargaisons **à chaque tour**. Le buffer max = `amount`. La surproduction non récupérée est écrasée au tour suivant (perdue). Pas de champ `interval`. - -### Niveaux de pièces (I, II, III) - -Chaque type de pièce a un niveau qui affecte : -- **Portée** : Tour I = 1 case, Tour II = 2 cases, Tour III = 3 cases -- **Priorité de transfert** : à statut social égal, le niveau départage -- **Collisions** : à statut égal, le niveau supérieur survit - -Les missions débloquent des niveaux supérieurs progressivement. - -### Drag & drop de pièces - -Le joueur peut glisser une pièce déjà placée pour déplacer son point de départ (les arrivées possibles se recalculent). Quality-of-life essentiel quand on itère sur un réseau en temps réel. - ---- - -## Architecture (Black-Box Sim) - -### Nouveaux modèles (engine) - -``` -CampaignDef # remplace la liste de LevelDef -├── name: string -├── initialWidth / initialHeight -├── missions: MissionDef[] - -MissionDef -├── id: int -├── name: string -├── description: string -├── terrainPatch: TerrainPatch # extension du plateau -├── stock: PieceStock[] # stock additionnel offert -├── demands: DemandDef[] # nouveaux objectifs -├── unlockedPieces: PieceKind[] # types de pièces débloqués par cette mission -├── unlockedLevels: PieceUpgrade[] # niveaux de pièces débloqués -│ -PieceUpgrade -├── kind: PieceKind -├── level: int # ex: Tour niveau 2 - -TerrainPatch -├── newWidth / newHeight # nouvelle taille du plateau (>= précédente) -├── cells: PatchCell[] # cases ajoutées/modifiées -│ ├── col, row -│ └── type: Empty | Wall | Production(def) | Demand(def) -``` - -### Nouveau state (engine) - -``` -CampaignState -├── campaignDef: CampaignDef -├── currentMissionIndex: int -├── completedMissions: int[] -├── boardState: BoardState # persistant entre missions -├── availablePieces: Set<(PieceKind, int level)> # pièces débloquées -``` - -`BoardState` évolue : -- Ajout d'un champ `missionIndex` sur chaque `DemandState` pour savoir à quelle mission appartient une demande. -- Les demandes complétées restent dans le state (marquées `Satisfied`), les nouvelles s'ajoutent. -- `ProductionDef` reçoit un champ `amount` (1-4) au lieu de `interval`. - -### Modification de SimPhase - -``` -Running ←→ Paused - ↓ ↓ - MissionComplete (la sim continue mais un overlay félicite) - ↓ - AdvanceMission → Running (terrain étendu, nouvelles demandes) -``` - -Phases supprimées : `Edit`, `Victory`, `Defeat`. -- Plus de `Edit` : le joueur place des pièces à tout moment. -- Plus de `Victory` séparé : `MissionComplete` pour chaque mission, la dernière affiche un écran de fin. -- Plus de `Defeat` : pas de deadline punitive (jeu de logistique, pas de puzzle). - -> **Note** : la suppression des deadlines est une conséquence de la suppression du Stop. Sans pouvoir reset, un deadline qui expire sans recours serait frustrant. Les demandes ont un `amount` cible mais pas de date limite. Les métriques (tours pour compléter) servent de score optionnel. - -### Nouvelles Commands - -| Command | Effet | -|---------|-------| -| `LoadCampaignCommand` | Charge la campagne, initialise le plateau avec la mission 0, démarre Running | -| `AdvanceMissionCommand` | Applique le `TerrainPatch` de la mission suivante, ajoute stock/demandes/pièces | -| `MovePieceCommand` | Drag & drop : déplace une pièce existante (nouveau start + end) | - -Commands modifiées : -| Command | Changement | -|---------|------------| -| `PlacePieceCommand` | Fonctionne en Running et Paused (plus de guard Edit) | -| `RemovePieceCommand` | Idem | -| `PauseSimulationCommand` | Termine le tour en cours avant de figer | - -Commands supprimées : -| Command | Raison | -|---------|--------| -| `StopSimulationCommand` | Plus de Stop | -| `StartSimulationCommand` | La sim démarre automatiquement au load | -| `ResetLevelCommand` | Plus de reset complet | - -### Nouveaux Events - -| Event | Données | -|-------|---------| -| `MissionCompleteEvent` | missionIndex | -| `MissionStartedEvent` | missionIndex, terrainPatch appliqué | -| `TerrainExpandedEvent` | nouvelles cases, nouvelle taille | -| `PieceUnlockedEvent` | kind, level | -| `PieceMovedByPlayerEvent` | pieceId, oldStart, oldEnd, newStart, newEnd | -| `PieceReturnedToStockEvent` | pieceId, kind, reason (collision) | - -### Modifications du flow de victoire - -1. `VictoryChecker` → renommé `MissionChecker`. -2. Vérifie les demandes **de la mission courante** uniquement. -3. Si toutes satisfaites → `MissionCompleteEvent`. -4. La sim continue de tourner (les pièces continuent de produire/livrer). -5. Le joueur déclenche `AdvanceMissionCommand` quand il est prêt. -6. Dernière mission complétée → écran de fin de campagne. - -### Collisions — retour au stock + pause auto + caméra - -`CollisionResolver` modifié : -- La pièce détruite émet `PieceReturnedToStockEvent` au lieu de `PieceDestroyedEvent`. -- Le stock dans `BoardState` est incrémenté. -- La pièce est retirée du plateau mais le joueur peut la replacer. - -Côté présentation (Godot) : -- La simulation se met en **pause automatique** à la détection d'une collision. -- La caméra effectue un **pan + zoom** vers la zone de collision. -- Une **notification** apparaît dans un coin de l'écran (ex: "Tour II détruite par Dame — retournée au stock"). -- Le joueur reprend avec Espace après avoir pris connaissance. - ---- - -## Format JSON de campagne - -```json -{ - "name": "La Quête du Roi", - "initialWidth": 4, - "initialHeight": 4, - "missions": [ - { - "id": 1, - "name": "Premier Convoi", - "description": "Les pions découvrent une scierie. Il faut acheminer le bois.", - "terrainPatch": { - "newWidth": 4, - "newHeight": 4, - "cells": [ - { "col": 0, "row": 0, "type": "production", "production": { "name": "Scierie", "cargo": "wood", "amount": 1 } }, - { "col": 3, "row": 0, "type": "demand", "demand": { "name": "Dépôt", "cargo": "wood", "amount": 3 } } - ] - }, - "unlockedPieces": ["pawn"], - "unlockedLevels": [{ "kind": "pawn", "level": 1 }], - "stock": [ - { "kind": "pawn", "count": 6 } - ] - }, - { - "id": 2, - "name": "Forger les Tours", - "description": "Les pions ont forgé des Tours. De nouveaux territoires s'ouvrent à l'est.", - "terrainPatch": { - "newWidth": 6, - "newHeight": 4, - "cells": [ - { "col": 4, "row": 0, "type": "empty" }, - { "col": 4, "row": 1, "type": "wall" }, - { "col": 4, "row": 2, "type": "empty" }, - { "col": 4, "row": 3, "type": "empty" }, - { "col": 5, "row": 0, "type": "empty" }, - { "col": 5, "row": 1, "type": "empty" }, - { "col": 5, "row": 2, "type": "production", "production": { "name": "Carrière", "cargo": "stone", "amount": 1 } }, - { "col": 5, "row": 3, "type": "demand", "demand": { "name": "Chantier", "cargo": "stone", "amount": 5 } } - ] - }, - "unlockedPieces": ["rook"], - "unlockedLevels": [{ "kind": "rook", "level": 1 }], - "stock": [ - { "kind": "pawn", "count": 2 }, - { "kind": "rook", "count": 3 } - ] - } - ] -} -``` - ---- - -## Phases d'implémentation - -### Phase 1 — Modèles engine (pas de Godot) - -1. Créer `CampaignDef`, `MissionDef`, `TerrainPatch`, `PatchCell`, `PieceUpgrade` dans `chessistics-engine/Model/`. -2. Créer `CampaignState` dans `chessistics-engine/Model/`. -3. Modifier `SimPhase` : supprimer `Edit`, `Victory`, `Defeat`. Ajouter `MissionComplete`. -4. Ajouter `missionIndex` à `DemandState`. -5. Ajouter `amount` à `ProductionDef` (remplace le concept d'intervalle). Buffer max = amount. -6. Ajouter `level` à `PieceStock` et aux modèles de pièces pour supporter les niveaux I/II/III. - -### Phase 2 — Refactor SimPhase et édition en temps réel - -1. Supprimer les guards `phase == Edit` dans `PlacePieceCommand` et `RemovePieceCommand`. -2. Supprimer `StopSimulationCommand`, `StartSimulationCommand`, `ResetLevelCommand`. -3. Modifier `PauseSimulationCommand` pour terminer le tour en cours avant de figer. -4. La sim démarre automatiquement au chargement (état initial = `Running` en pause). - -### Phase 3 — Collisions → retour au stock + pause auto - -1. Modifier `CollisionResolver` : la pièce détruite retourne au stock. -2. Créer `PieceReturnedToStockEvent` (avec coordonnées de la collision pour le pan caméra). -3. Incrémenter le stock dans `BoardState` quand une pièce est détruite. -4. L'engine émet un event de pause automatique après une collision. -5. Tests : vérifier qu'après collision la pièce réapparaît dans le stock et la sim est en pause. - -### Phase 4 — Commands & Events campagne - -1. Créer `LoadCampaignCommand` : initialise `BoardState` depuis mission 0, applique `unlockedPieces`. -2. Créer `AdvanceMissionCommand` : applique `TerrainPatch`, ajoute stock/demandes/pièces débloquées. -3. Créer `MissionCompleteEvent`, `MissionStartedEvent`, `TerrainExpandedEvent`, `PieceUnlockedEvent`. -4. Renommer `VictoryChecker` → `MissionChecker` : ne vérifie que les demandes de la mission courante. - -### Phase 5 — Drag & drop engine - -1. Créer `MovePieceCommand` : valide le nouveau placement, met à jour start/end. -2. Créer `PieceMovedByPlayerEvent`. -3. Tests : déplacer une pièce en Running, vérifier qu'elle reprend sa trajectoire. - -### Phase 6 — Production variable - -1. Modifier la production pour utiliser `amount` (1-4) au lieu de toujours produire 1. -2. Le buffer max = `amount`. La surproduction écrase le buffer. -3. Tests : production amount=3 remplit 3 slots, non récupérés → écrasés au tour suivant. - -### Phase 7 — CampaignLoader - -1. Créer `CampaignLoader` dans `chessistics-engine/Loading/` : parse le JSON campagne. -2. Valider la cohérence : pas de régression de taille, `unlockedPieces` cohérent. -3. Les anciens JSON de niveaux restent compatibles (mode puzzle legacy optionnel). - -### Phase 8 — Tests engine intégration - -1. Test : charger une campagne, compléter mission 1, avancer, vérifier terrain étendu. -2. Test : les pièces de la mission 1 restent en place après `AdvanceMissionCommand`. -3. Test : `MissionCompleteEvent` émis au bon moment. -4. Test : placer une pièce pendant Running fonctionne. -5. Test : collision retourne la pièce au stock. -6. Test : pièces non débloquées ne sont pas plaçables. -7. Test : niveaux de pièces affectent portée et priorité. - -### Phase 9 — GameSim facade - -1. `GameSim` accepte un `CampaignDef` (mode principal) ou un `LevelDef` (legacy). -2. Exposer `CampaignSnapshot` avec `currentMissionIndex`, `completedMissions`, `availablePieces`. -3. Plus de `StopSimulationCommand` dans la facade. - -### Phase 10 — Présentation Godot - -1. `Main.cs` : charger une campagne, supprimer le flow Stop/Start. -2. Contrôles : Espace = pause/resume, plus de bouton Stop. Barre de contrôle : `[⏸/▶] [x1] [x2] [x4] Tour: N Mission: M/T`. -3. **Auto-pause placement** : sélectionner un type de pièce dans le stock met la sim en pause. La pause est levée quand le placement est confirmé ou annulé (Échap). -4. **Retrait de pièce** : supprimer le clic droit pour retirer. Clic gauche sélectionne → touche Suppr ou bouton [Retirer] dans le panneau de détail. -5. **Collision — pause + caméra + notification** : - - Sur `PieceReturnedToStockEvent` : pause auto de la simulation. - - Pan + zoom caméra vers la zone de collision (tween animé). - - Notification dans un coin de l'écran (ex: "Tour II détruite par Dame — retournée au stock"). - - Le joueur reprend avec Espace. -6. **Transition de mission** (cinématique) : - - Titre "Nouvelle mission" en plein écran, fade-in. - - Lock du pan/zoom joueur — la caméra se déplace pour montrer la zone de la prochaine mission. - - Les nouvelles cases apparaissent sur le plateau avec animation d'expansion. - - Le titre de mission se déplace vers la zone d'objectif du panneau latéral avant de disparaître (guide l'œil). - - Unlock pan/zoom, la simulation reprend. -7. `ObjectivePanel` : objectifs de la mission courante + badge ✓ pour les missions complétées. -8. `PieceStockPanel` : stock cumulatif, n'affiche que les pièces débloquées. -9. Notification `PieceUnlockedEvent` : animation de déblocage d'une nouvelle pièce. -10. Drag & drop visuel : le joueur glisse une pièce, les destinations légales s'affichent. -11. **Undo (Ctrl+Z)** : annule le dernier placement ou retrait. Conserver un historique de commandes côté présentation, rejouer l'état sans la dernière. - -### Phase 11 — Données de campagne - -1. Créer `Data/campaigns/campaign_01.json` — "La Quête du Roi". -2. Concevoir 6-8 missions avec progression de pièces (Pion → Tour → Fou → Cavalier → Dame). -3. Chaque mission étend le terrain sans casser les solutions précédentes. -4. Équilibrer les stocks cumulatifs et les `amount` de production. - ---- - -## Risques et mitigations - -| Risque | Mitigation | -|--------|------------| -| Un patch de terrain casse une solution existante | Validation loader : les patches ne peuvent que **ajouter** des cases ou modifier des cases hors de la zone initiale | -| Le stock cumulé rend les missions triviales | Playtesting + stock additionnel calibré mission par mission | -| Placer des pièces en Running cause des bugs de timing | La commande s'applique **entre deux tours** (fin du tour courant → placement → tour suivant) | -| Collision = perte de pièce frustrante | Retour au stock — le joueur peut replacer immédiatement | -| Drag & drop complexe pendant la simulation | Le drag ne modifie la pièce qu'au **relâchement** (atomique), appliqué entre deux tours | -| Niveaux de pièces explosent la complexité | Introduction graduelle : mission 1-3 = niveau I seul, niveaux II/III arrivent plus tard | diff --git a/PLAN_playtest.md b/PLAN_playtest.md deleted file mode 100644 index af17f95..0000000 --- a/PLAN_playtest.md +++ /dev/null @@ -1,41 +0,0 @@ -# Plan Playtest Fixes - -## Problèmes identifiés - -### P1 — Superposition production/demand (Bug moteur) -**Cause racine**: `ApplyTerrainPatch` ne nettoie pas les bâtiments existants avant d'ajouter un nouveau (Production, Demand, Transformer). Mission 3 place un demand sur (5,5) où Mission 2 avait une production → les deux coexistent dans les dictionnaires. -**Fix**: Appeler `ClearBuildingAt(coords)` pour TOUS les types de cellules dans `ApplyTerrainPatch`. - -### P2 — Murs sur cases existantes / pièces traversent les murs -**Cause racine**: Mission 3 ajoute des murs sur des cases déjà jouables (2,2), (2,3), etc. Si des pièces y sont placées, elles restent et traversent le mur. -**Fix moteur**: Quand un mur apparaît via terrain patch, retirer les pièces dont StartCell ou EndCell est sur ce mur (retour au stock). -**Fix level design**: Redessiner mission 3 pour que les murs soient sur des cases nouvellement révélées (agrandir le plateau). - -### P3 — Noms de bâtiments dupliqués -**Cause**: Deux "Dépôt Royal" (mission 1 et mission 3). -**Fix**: Renommer dans campaign_01.json. Mission 3 demand → "Avant-Poste du Col". - -### P4 — Compteurs d'objectifs qui montent à l'infini -**Cause**: `ObjectivePanel.UpdateProgress` affiche le current réel même quand il dépasse le required. -**Fix**: Afficher `min(current, required)/required` et marquer visuellement les objectifs complétés. Les objectifs des missions précédentes complétées: afficher "✓" et ne plus mettre à jour. - -### P5 — Espace sélectionne un pion au lieu de lancer la simulation -**Cause**: Les boutons Godot ont `FocusMode = All` par défaut et capturent la touche Espace. -**Fix**: Mettre `FocusMode = None` sur les boutons du PieceStockPanel. Ajouter un handler Espace dans Main pour toggle play/pause. - -### P6 — Centrage du plateau -**Cause**: Le calcul de centrage ne prend pas en compte la barre de titre (~36px en haut). -**Fix**: Ajouter `TitleBarHeight` au calcul de l'offset caméra. - -### P7 — Pas de narration/lore -**Fix**: Ajouter un champ `flavor` dans MissionDef + campaign JSON. Afficher un encart narratif au démarrage de chaque mission. Un personnage parle en une phrase, ton léger et enjoué. - -## Ordre d'implémentation - -1. P1 + P2: Fixes moteur (ApplyTerrainPatch) + tests -2. P3: Redesign campaign_01.json (missions 2-7, noms uniques, pas de superposition, murs sur nouvelles cases) -3. P4: Test automatisé de validation du campaign (pas de superposition, niveaux finissables) -4. P5: Spacebar play/pause -5. P6: Centrage caméra -6. P4: Objectifs capped -7. P7: Narration diff --git a/README.md b/README.md index 9edd64a..5a81064 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,6 @@ Claude Code, lancé à l'intérieur, peut donc compiler le projet, exécuter les tests, **démarrer une vraie instance de Godot en headless** et lire les captures PNG produites — sans dépendance Windows. -Voir [`autonomous_plan.md`](autonomous_plan.md) pour le design détaillé. - ### Option 1 — Docker Desktop + terminal Windows (le plus simple) 1. Installer [Docker Desktop pour Windows](https://www.docker.com/products/docker-desktop/). diff --git a/autonomous_plan.md b/autonomous_plan.md deleted file mode 100644 index ee214a7..0000000 --- a/autonomous_plan.md +++ /dev/null @@ -1,149 +0,0 @@ -# Headless Linux dev container for autonomous Chessistics testing - -## Why - -Today the automation harness only runs on Windows because the Godot binary is -hardcoded to `C:\Apps\godot\Godot_v4.6.2-stable_mono_win64_console.exe`. When -Claude Code runs inside the project's dev container (Linux / `node:20`), it -can read source code and run `dotnet test`, but it **cannot launch the actual -game** — there's no Godot binary, no .NET SDK, and no display server for the -renderer. - -The goal: make the dev container a self-contained environment where Claude -Code can build the project, launch a real Godot instance in headless Linux -mode, drive it via the automation harness, and read back 1280×720 PNG -screenshots — all without any Windows dependency. - -## Design - -### Pieces required - -1. **Godot 4.6.2-stable Mono for Linux** — matches the Windows editor the - project already uses. Installed once at image build time to `/opt/godot/` - with a symlink `/opt/godot/godot`. -2. **.NET SDK 9.0** — the project targets `net9.0`. Installed via the - upstream `dot.net` install script to `/usr/local/dotnet/`. -3. **Xvfb + Mesa software GL** — a virtual framebuffer at `:99` so Godot's - GL-compatibility renderer has somewhere to draw. `xvfb-run` wraps any - command transparently. -4. **Python 3** — the automation harness is stdlib-only Python. -5. **Minimal X / audio runtime deps** — `libx11`, `libxcursor`, `libxrandr`, - `libxi`, `libgl1`, `libgles2`, `libasound2`, `libxkbcommon0`, etc. - Without these, Godot exits on startup with `libXext not found`-style errors. - -### How Godot reaches the framebuffer - -Two options considered: - -- **(A)** Run `Xvfb :99 -screen 0 1280x720x24 &` as a background process, - export `DISPLAY=:99`, launch Godot normally. Persistent display, shared by - many Godot runs. -- **(B)** Use `xvfb-run -a --server-args="-screen 0 1280x720x24"` as a prefix - on every Godot invocation. A fresh display per launch; cleans up - automatically on exit. - -**Chosen: (B)**, because the automation harness already spawns Godot once per -`Harness.launch()` and cleans up on context exit — matches the per-launch -lifecycle naturally, no daemon to keep alive, no race on the display number. - -A tiny wrapper `/usr/local/bin/godot-xvfb` wraps `xvfb-run … $GODOT_BIN -"$@"`, so the harness (or a human) only has to invoke one path. - -### Integration with the existing harness - -`tools/automation/harness.py` currently hardcodes the Windows Godot path. We -teach it two things: - -1. Read `GODOT_BIN` from the environment first; fall back to the platform - default (Windows path on Windows, `/opt/godot/godot` on Linux). -2. On Linux, auto-prepend `["xvfb-run", "-a", "--server-args=-screen 0 - 1280x720x24"]` to the Godot launch command unless `DISPLAY` is already - set (someone has a real display, skip the wrap). - -With those two tweaks, every existing Python helper (`smoke.py`, -`run_game.py`, `solve_*.py`) works unchanged inside the container. - -### Firewall considerations - -The container's `init-firewall.sh` runs at `postStartCommand`, **after** the -image is built, and drops all outbound traffic except to a small allowlist -(GitHub, npmjs, Anthropic, Sentry, Statsig). Impact on our pieces: - -- **Godot binary + .NET SDK**: downloaded during `docker build`, which runs - _before_ the firewall exists → works unconditionally. -- **`dotnet restore`** (runtime, e.g. after a `git pull`): needs - `api.nuget.org`. Added to the allowlist. -- **Godot runtime**: no outbound traffic required — the engine runs fully - offline once installed. - -### Build sequence inside the Dockerfile - -As `root`, before the existing `USER node` switch: - -``` -# 1. X/GL/audio runtime + python + xvfb -apt-get install xvfb xauth libx11-6 libxcursor1 libxinerama1 libxrandr2 \ - libxi6 libxext6 libgl1 libglx-mesa0 libgl1-mesa-dri libglu1-mesa \ - libasound2 libxkbcommon0 libxkbcommon-x11-0 libfontconfig1 libdbus-1-3 \ - python3 python3-pip - -# 2. .NET SDK 9.0 via upstream install script -curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- \ - --channel 9.0 --install-dir /usr/local/dotnet -ln -s /usr/local/dotnet/dotnet /usr/local/bin/dotnet - -# 3. Godot 4.6.2-stable mono, Linux x86_64 -wget https://github.com/godotengine/godot/releases/download/${VERSION}/\ -Godot_v${VERSION}_mono_linux_x86_64.zip -unzip … -d /opt/godot -ln -s /opt/godot/Godot_v…/Godot_v…_mono_linux.x86_64 /opt/godot/godot -``` - -Then: -- `ENV GODOT_BIN=/opt/godot/godot` -- `ENV PATH=$PATH:/opt/godot:/usr/local/dotnet` -- Drop in `/usr/local/bin/godot-xvfb` wrapper - -## File-by-file change list - -| File | Change | -|------|--------| -| `.devcontainer/Dockerfile` | Add Godot / dotnet / xvfb installs before `USER node` | -| `.devcontainer/init-firewall.sh` | Append `api.nuget.org` to the domain allowlist | -| `.devcontainer/godot-xvfb.sh` *(new)* | `exec xvfb-run -a … "$GODOT_BIN" "$@"` | -| `tools/automation/harness.py` | Env-aware Godot path + Linux xvfb auto-wrap | -| `README.md` *(new)* | Windows / WSL2 launch instructions for the dev container | - -Nothing inside `Scripts/` or `chessistics-engine/` changes. The harness -contract (inbox/outbox/screens) is platform-agnostic already. - -## Verification - -After rebuild: - -1. `docker build .devcontainer -t chessistics-dev` succeeds. -2. `devcontainer up --workspace-folder .` starts the container and - post-start firewall passes. -3. Inside: `dotnet --version` → `9.0.x`, `godot --version` → - `4.6.2.stable.mono.official.*`. -4. `dotnet build Chessistics.csproj` → green. -5. `dotnet test chessistics-tests/` → 102 / 102. -6. `python3 tools/automation/smoke.py` → loads mission 1, takes PNG - screenshots that are non-black, quits cleanly. -7. `Read` one of the PNGs — it should show the same mission 1 UI as the - Windows run (title bar, board, objectives, stock panel). - -## Out of scope (explicit non-goals) - -- **GPU acceleration**: we use Mesa software rendering. Xvfb + llvmpipe is - enough for 1280×720 at a few FPS, which is what the harness needs. -- **Real display forwarding** (X11 forwarding, VNC, noVNC): doable but - unnecessary — Claude reads PNGs, not a live video feed. -- **Multi-arch images**: we ship x86_64 only. ARM (Apple Silicon via - Docker Desktop emulation) would need `Godot_v…_mono_linux_arm64.zip` — - straightforward to add if needed, not done here. -- **Shrinking the image**: Godot + .NET SDK adds ~500 MB. Worth it; - multi-stage builds could trim later. -- **Keeping Xvfb warm across launches**: the single-launch pattern is clean - enough. If someone ever scripts dozens of rapid Godot starts and the - xvfb-run startup cost shows up, revisit approach (A). diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..2f16d8e --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,87 @@ +# Chessistics — Plan de travail (restant) + +Consolidation des sections non implementees des anciens `PLAN_missions.md` et +`PLAN_leveldesign.md`. Le moteur (black-box sim, campagne, transformateurs, +missions 1-7) est en place. Ce qui suit concerne la finition UX, les visuels +et l'extension de la campagne. + +--- + +## 1. UX / Presentation — gaps Godot + +Le moteur expose deja les commandes et events requis ; cote Godot il manque +les surfaces d'interaction et d'animation. + +### 1.1 Undo (Ctrl+Z) +Annule le dernier placement ou retrait. L'architecture event-sourcing rend +l'implementation naturelle : conserver un historique de commandes cote +presentation, rejouer l'etat sans la derniere. Essentiel pour l'iteration +rapide sur un reseau en temps reel. + +### 1.2 Drag & drop des pieces placees +`MovePieceCommand` + `PieceMovedByPlayerEvent` existent cote engine. +Cote Godot : permettre de glisser une piece placee pour deplacer son point de +depart. Les arrivees legales se recalculent pendant le drag. Application +atomique au relachement (entre deux tours). + +### 1.3 Collision — camera pan/zoom + notification +L'engine emet deja `PieceReturnedToStockEvent` + auto-pause. Il reste a : +- Animer un pan + zoom de la camera vers la case de collision. +- Afficher une notification dans un coin de l'ecran + (ex : "Tour II detruite par Dame — retournee au stock"). +- Reprise a Espace apres lecture. + +### 1.4 Touche Suppr pour retirer une piece +Le bouton `[Retirer]` du `DetailPanel` existe. Ajouter en complement : +selection + `Delete` → meme effet que le bouton. + +### 1.5 Cinematique de transition de mission +Sur `MissionStartedEvent` (hors mission 0) : +- Titre "Nouvelle mission" plein ecran en fade-in. +- Lock pan/zoom ; la camera se deplace vers la nouvelle zone. +- Animation d'expansion pour les cases debloquees par le `TerrainPatch`. +- Le titre glisse ensuite vers le panneau d'objectifs avant de disparaitre + (guide l'oeil). +- Unlock pan/zoom, la simulation reprend. + +### 1.6 Visualisation des trajets +`TrajectView` existe. Manque : +- Fleches directionnelles sur le trait. +- Pulsation legere du trait quand la piece est en mouvement. +- Couleur unique par piece pour distinguer les chaines. + +--- + +## 2. Extension de la campagne + +`campaign_01.json` compte actuellement 7 missions (Pion → Tour → Cavalier → +Fou → Dame + 2 transformateurs). La vision GDD/plan prevoit une campagne +plus longue et un final orchestrant toutes les chaines. + +### 2.1 Missions supplementaires +- **"L'Expansion Finale"** (14x14) : multiples transformateurs, murs + complexes. Defi : maintenir toutes les chaines en s'etendant ; gestion de + la congestion / collisions. +- **"Le Couronnement final"** (14x14) : Cathedrale qui demande or + armes + + outils simultanement. Orchestrer l'ensemble des chaines logistiques. + +Actuel mission 7 "Le Couronnement" = transformation outils→or seule. +La renommer ou l'inserer comme mission intermediaire et ajouter les deux +missions ci-dessus pour retrouver la progression 10 missions de la vision +initiale. + +### 2.2 Demandes recurrentes (post-campagne 1) +Pour forcer la preservation des automatisations entre missions : +- Une demande consomme N unites par tour. +- Si plus approvisionnee → etat "en penurie". +- Condition : aucune demande en penurie pendant X tours consecutifs. + +Implique : nouveau champ sur `DemandState`, flag shortage, regle de victoire +parametrable par mission. A valider via playtest avant d'ajouter au scope. + +--- + +## 3. Polish visuel des transformateurs + +Couleur de cellule dediee (orange cuivre), animation flash input → flash +output sur `CargoConvertedEvent`, icones de cargo sur les pieces porteuses.