Bundles in-flight work on the campaign/missions system (CampaignDef, MissionDef, TerrainPatch, TransformerDef, MissionChecker, CampaignLoader, FlavorBanner, transformer rules), plan files, and matching tests. Baseline commit so the upcoming automation testing harness lands on a clean tree.
18 KiB
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
missionIndexsur chaqueDemandStatepour savoir à quelle mission appartient une demande. - Les demandes complétées restent dans le state (marquées
Satisfied), les nouvelles s'ajoutent. ProductionDefreçoit un champamount(1-4) au lieu deinterval.
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
Victoryséparé :MissionCompletepour 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
amountcible 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
VictoryChecker→ renomméMissionChecker.- Vérifie les demandes de la mission courante uniquement.
- Si toutes satisfaites →
MissionCompleteEvent. - La sim continue de tourner (les pièces continuent de produire/livrer).
- Le joueur déclenche
AdvanceMissionCommandquand il est prêt. - 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
PieceReturnedToStockEventau lieu dePieceDestroyedEvent. - Le stock dans
BoardStateest 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
{
"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)
- Créer
CampaignDef,MissionDef,TerrainPatch,PatchCell,PieceUpgradedanschessistics-engine/Model/. - Créer
CampaignStatedanschessistics-engine/Model/. - Modifier
SimPhase: supprimerEdit,Victory,Defeat. AjouterMissionComplete. - Ajouter
missionIndexàDemandState. - Ajouter
amountàProductionDef(remplace le concept d'intervalle). Buffer max = amount. - Ajouter
levelàPieceStocket aux modèles de pièces pour supporter les niveaux I/II/III.
Phase 2 — Refactor SimPhase et édition en temps réel
- Supprimer les guards
phase == EditdansPlacePieceCommandetRemovePieceCommand. - Supprimer
StopSimulationCommand,StartSimulationCommand,ResetLevelCommand. - Modifier
PauseSimulationCommandpour terminer le tour en cours avant de figer. - La sim démarre automatiquement au chargement (état initial =
Runningen pause).
Phase 3 — Collisions → retour au stock + pause auto
- Modifier
CollisionResolver: la pièce détruite retourne au stock. - Créer
PieceReturnedToStockEvent(avec coordonnées de la collision pour le pan caméra). - Incrémenter le stock dans
BoardStatequand une pièce est détruite. - L'engine émet un event de pause automatique après une collision.
- 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
- Créer
LoadCampaignCommand: initialiseBoardStatedepuis mission 0, appliqueunlockedPieces. - Créer
AdvanceMissionCommand: appliqueTerrainPatch, ajoute stock/demandes/pièces débloquées. - Créer
MissionCompleteEvent,MissionStartedEvent,TerrainExpandedEvent,PieceUnlockedEvent. - Renommer
VictoryChecker→MissionChecker: ne vérifie que les demandes de la mission courante.
Phase 5 — Drag & drop engine
- Créer
MovePieceCommand: valide le nouveau placement, met à jour start/end. - Créer
PieceMovedByPlayerEvent. - Tests : déplacer une pièce en Running, vérifier qu'elle reprend sa trajectoire.
Phase 6 — Production variable
- Modifier la production pour utiliser
amount(1-4) au lieu de toujours produire 1. - Le buffer max =
amount. La surproduction écrase le buffer. - Tests : production amount=3 remplit 3 slots, non récupérés → écrasés au tour suivant.
Phase 7 — CampaignLoader
- Créer
CampaignLoaderdanschessistics-engine/Loading/: parse le JSON campagne. - Valider la cohérence : pas de régression de taille,
unlockedPiecescohérent. - Les anciens JSON de niveaux restent compatibles (mode puzzle legacy optionnel).
Phase 8 — Tests engine intégration
- Test : charger une campagne, compléter mission 1, avancer, vérifier terrain étendu.
- Test : les pièces de la mission 1 restent en place après
AdvanceMissionCommand. - Test :
MissionCompleteEventémis au bon moment. - Test : placer une pièce pendant Running fonctionne.
- Test : collision retourne la pièce au stock.
- Test : pièces non débloquées ne sont pas plaçables.
- Test : niveaux de pièces affectent portée et priorité.
Phase 9 — GameSim facade
GameSimaccepte unCampaignDef(mode principal) ou unLevelDef(legacy).- Exposer
CampaignSnapshotaveccurrentMissionIndex,completedMissions,availablePieces. - Plus de
StopSimulationCommanddans la facade.
Phase 10 — Présentation Godot
Main.cs: charger une campagne, supprimer le flow Stop/Start.- Contrôles : Espace = pause/resume, plus de bouton Stop. Barre de contrôle :
[⏸/▶] [x1] [x2] [x4] Tour: N Mission: M/T. - 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).
- 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.
- 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.
- Sur
- 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.
ObjectivePanel: objectifs de la mission courante + badge ✓ pour les missions complétées.PieceStockPanel: stock cumulatif, n'affiche que les pièces débloquées.- Notification
PieceUnlockedEvent: animation de déblocage d'une nouvelle pièce. - Drag & drop visuel : le joueur glisse une pièce, les destinations légales s'affichent.
- 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
- Créer
Data/campaigns/campaign_01.json— "La Quête du Roi". - Concevoir 6-8 missions avec progression de pièces (Pion → Tour → Fou → Cavalier → Dame).
- Chaque mission étend le terrain sans casser les solutions précédentes.
- Équilibrer les stocks cumulatifs et les
amountde 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 |