Chessistics/PLAN_missions.md
Samuel Bouchet 2d1aea0a7a Snapshot campaign system progress before automation harness
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.
2026-04-16 21:22:49 +02:00

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 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

{
  "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 VictoryCheckerMissionChecker : 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