Snapshots de simulation (NSAP)

Les NSAP (Numbered Situation Snapshots) permettent de sauvegarder l’état complet d’une simulation et d’y revenir à tout moment.

C’est une pile LIFO (Last In, First Out) : le dernier état sauvegardé est le premier restauré.

Simulation  →  push  →  push  →  push
               [S0]      [S1]      [S2]   ← sommet de pile
                                   │
                            pull ──┘   → restaure S2, retire de la pile
                     pull ──┘          → restaure S1
               pull ──┘                → restaure S0
               pull                    → pile vide, retourne -1

Cas d’usage

Cas

Description

Backtracking de planification

Essaie un plan, si échec pull_nsap() et essaie un autre

Débogage de simulation

Reviens à l’instant T où le comportement est devenu anormal

Exploration de branches

Depuis un état S, explore plusieurs scénarios en repartant de S

Replay pas-à-pas

Enregistre chaque étape, rejoue en avant/arrière

Démonstration pédagogique

Montre une simulation étape par étape avec retour en arrière

API

#include <gagent/env/Environnement.hpp>

Environnement env;

// Sauvegarde l'état courant
// Retourne le numéro de séquence du snapshot (= taille de la pile)
int seq = env.push_nsap();

// Restaure le snapshot le plus récent (LIFO)
// Retourne la taille restante, ou -1 si la pile était vide
int remaining = env.pull_nsap();

// Liste tous les snapshots disponibles : { seq → timestamp ISO }
std::map<int, std::string>* index = env.get_nsaps();

// Vide toute la pile (l'état courant n'est pas modifié)
env.clear_nsap();

Note

push_nsap() et pull_nsap() sont thread-safe : ils peuvent être appelés depuis un behaviour d’agent pendant que l’environnement reçoit des mises à jour d’autres agents.

Exemple 1 — Retour arrière simple

// t0 : agent1 à (10, 20)
// ... agents bougent, l'environnement se met à jour via MQ ...

env.push_nsap();    // sauvegarde t0

// t1 : agent1 à (50, 80), agent2 est apparu
// ...

env.push_nsap();    // sauvegarde t1

// On revient à t1
env.pull_nsap();    // agent1 à (50,80), agent2 présent

// On revient à t0
env.pull_nsap();    // agent1 à (10,20), agent2 absent

// Pile vide
int r = env.pull_nsap();  // r == -1

Exemple 2 — Backtracking d’un planificateur

Scénario : un agent planificateur tente deux stratégies depuis le même état.

class PlannerBehaviour : public Behaviour {
public:
    PlannerBehaviour(Agent* ag, Environnement* env)
        : Behaviour(ag), env_(env) {}

    void action() override
    {
        // Sauvegarde l'état avant de tenter le plan A
        env_->push_nsap();
        std::cout << "Tentative plan A...\n";

        bool success = try_plan_A();

        if (!success) {
            std::cout << "Plan A échoué → retour arrière\n";
            env_->pull_nsap();   // restaure l'état initial

            std::cout << "Tentative plan B...\n";
            try_plan_B();
        }
    }

    bool done() override { return true; }

private:
    Environnement* env_;

    bool try_plan_A() {
        // ... envoie des ACL REQUEST aux agents ...
        // ... retourne false si l'objectif n'est pas atteint
        return false;
    }
    void try_plan_B() {
        // ... stratégie alternative ...
    }
};

Exemple 3 — Exploration de scénarios (branches)

Depuis un état pivot, explore N scénarios indépendants.

// Sauvegarde l'état pivot
env.push_nsap();

for (int scenario = 0; scenario < 3; scenario++) {
    std::cout << "=== Scénario " << scenario << " ===\n";

    // Simule le scénario
    run_scenario(scenario, env);

    // Affiche les résultats
    env.make_agent();
    for (auto* va : env.list_visual_agents)
        std::cout << va->id << " → (" << va->pos_x << ", " << va->pos_y << ")\n";

    // Retour à l'état pivot pour le prochain scénario
    env.pull_nsap();   // restaure le pivot
    env.push_nsap();   // re-sauvegarde pour la prochaine itération
}

env.pull_nsap();  // libère le dernier pivot

Exemple 4 — Replay pas-à-pas

Enregistre chaque étape d’une simulation pour la rejouer.

// Phase 1 : enregistrement
std::vector<int> steps;
for (int t = 0; t < 10; t++) {
    simulate_one_step(env);
    steps.push_back(env.push_nsap());
    std::cout << "Step " << t << " sauvegardé\n";
}

// Affiche l'index des snapshots
auto* idx = env.get_nsaps();
for (auto& [seq, ts] : *idx)
    std::cout << "snap #" << seq << " @ " << ts << "\n";

// Phase 2 : retour arrière étape par étape
std::cout << "\n--- Replay en arrière ---\n";
while (env.pull_nsap() >= 0) {
    env.make_agent();
    std::cout << env.list_visual_agents.size() << " agents\n";
}

Inspecter la pile

auto* idx = env.get_nsaps();
if (idx->empty()) {
    std::cout << "Pile vide\n";
} else {
    std::cout << idx->size() << " snapshot(s) :\n";
    for (auto& [seq, ts] : *idx)
        std::cout << "  #" << seq << " → " << ts << "\n";
}

// Exemple de sortie :
//   3 snapshot(s) :
//   #0 → 2026-03-23T21:18:16.058
//   #1 → 2026-03-23T21:18:16.139
//   #2 → 2026-03-23T21:18:17.042

Intégration avec la planification neuro-symbolique

Les NSAP sont le mécanisme de backtracking naturel pour un planificateur HTN ou PDDL embarqué dans gAgent :

Planificateur HTN
│
├─ decompose(tâche)  →  génère sous-tâches
│       │
│       ├─ push_nsap()           # point de choix
│       ├─ essaie méthode M1
│       │       │
│       │       ├─ succès  →  continue
│       │       └─ échec   →  pull_nsap()  →  essaie M2
│       │
│       └─ toutes méthodes échouées  →  pull_nsap()  →  échec global
│
└─ solution trouvée  →  clear_nsap()  →  exécution