Add comprehensive AI, diplomacy, and save system documentation

- ai-framework.md: Unified decision framework with scoring system, RL integration, doctrines
- systeme-diplomatique.md: Relations (shared), intentions (bilateral), threat, reliability systems
- calcul-menace.md: Contextual threat calculation with sword & shield mechanics
- systeme-sauvegarde.md: V1 save system with JSON, metachunks, module autonomy
- module-versioning.md: Automatic MAJOR.MINOR.PATCH.BUILD versioning via CMake

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
StillHammer 2025-10-06 21:01:38 +08:00
parent 076acd4812
commit 5e4235889a
5 changed files with 4648 additions and 0 deletions

View File

@ -0,0 +1,677 @@
# Module Versioning System
## Vue d'Ensemble
Le système de versioning des modules utilise un format **MAJOR.MINOR.PATCH.BUILD** automatiquement généré par CMake lors de la compilation. Ce système permet de :
- **Tracer les versions** : Identifier précisément quelle version du code a créé une save
- **Détecter incompatibilités** : Alerter si une save utilise une version différente
- **Debugging** : Reproduire bugs avec la version exacte du module
- **Développement** : Versions incrémentales automatiques sans maintenance manuelle
## Format de Version
### Structure : MAJOR.MINOR.PATCH.BUILD
```
0.1.15.3847
│ │ │ │
│ │ │ └─── BUILD : Auto-increment à chaque build (hash sources)
│ │ └────── PATCH : Auto-increment à chaque commit
│ └──────── MINOR : Manuel (fonctionnalités nouvelles)
└────────── MAJOR : Manuel (breaking changes)
```
### Composantes
#### MAJOR (Manuel)
- **Incrémenté** : Breaking changes, incompatibilités majeures
- **Exemples** : Refonte complète architecture, changement format save incompatible
- **Gestion** : Modifié manuellement dans fichier `VERSION`
- **Valeur initiale** : `0` (pré-release), `1` (première version stable)
#### MINOR (Manuel)
- **Incrémenté** : Nouvelles fonctionnalités, ajouts non-breaking
- **Exemples** : Nouveau type de tank, nouveau système économique
- **Gestion** : Modifié manuellement dans fichier `VERSION`
- **Valeur initiale** : `1`
#### PATCH (Automatique)
- **Incrémenté** : À chaque commit Git
- **Calcul** : Nombre de commits depuis le début du projet
- **Exemples** : Bugfixes, optimisations, refactoring mineur
- **Gestion** : Calculé automatiquement par CMake via `git rev-list --count HEAD`
#### BUILD (Automatique)
- **Incrémenté** : À chaque rebuild (si sources modifiées)
- **Calcul** : Hash MD5 des fichiers sources (`.cpp`, `.h`)
- **But** : Distinguer builds de dev avec code non-commité
- **Gestion** : Calculé automatiquement par CMake
### Exemples de Versions
```
0.1.0.0 → Première version dev (MAJOR.MINOR manuel, PATCH/BUILD = 0)
0.1.5.1234 → Après 5 commits, build 1234
0.1.5.1235 → Même commit, sources modifiées → nouveau build
0.1.6.1235 → Nouveau commit, sources identiques → même build (théorique)
0.2.10.3847 → MINOR bump (nouvelle feature) + 10 commits
1.0.0.0 → Release stable, reset PATCH/BUILD
```
## Implémentation CMake
### Fichier VERSION
Chaque module contient un fichier `VERSION` à la racine :
```
modules/tank/VERSION
```
Contenu du fichier :
```
0.1
```
Ce fichier contient uniquement `MAJOR.MINOR`. Les composantes `PATCH.BUILD` sont calculées automatiquement.
### CMakeLists.txt du Module
```cmake
cmake_minimum_required(VERSION 3.20)
project(TankModule)
# ============================================================================
# MODULE VERSIONING
# ============================================================================
# 1. Lire MAJOR.MINOR depuis fichier VERSION
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" BASE_VERSION)
string(STRIP "${BASE_VERSION}" BASE_VERSION)
else()
set(BASE_VERSION "0.1")
message(WARNING "No VERSION file found, using default: ${BASE_VERSION}")
endif()
# 2. Calculer PATCH : nombre de commits Git
execute_process(
COMMAND git rev-list --count HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE PATCH_COUNT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE GIT_RESULT
)
if(NOT GIT_RESULT EQUAL 0)
set(PATCH_COUNT "0")
message(WARNING "Git not available, PATCH set to 0")
endif()
# 3. Calculer BUILD : hash des fichiers sources
file(GLOB_RECURSE MODULE_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/*.h"
)
# Concaténer contenu de tous les fichiers
set(SOURCES_CONTENT "")
foreach(SOURCE_FILE ${MODULE_SOURCES})
file(READ ${SOURCE_FILE} FILE_CONTENT)
string(APPEND SOURCES_CONTENT ${FILE_CONTENT})
endforeach()
# Hash MD5 du contenu total
string(MD5 SOURCES_HASH "${SOURCES_CONTENT}")
# Convertir hash hex en nombre décimal (4 premiers caractères)
string(SUBSTRING ${SOURCES_HASH} 0 4 BUILD_HEX)
# Calculer valeur numérique (modulo 10000 pour limiter à 4 chiffres)
math(EXPR BUILD_NUM "0x${BUILD_HEX} % 10000")
# 4. Assembler version complète
set(MODULE_VERSION "${BASE_VERSION}.${PATCH_COUNT}.${BUILD_NUM}")
message(STATUS "Module version: ${MODULE_VERSION}")
message(STATUS " BASE: ${BASE_VERSION} (from VERSION file)")
message(STATUS " PATCH: ${PATCH_COUNT} (git commits)")
message(STATUS " BUILD: ${BUILD_NUM} (sources hash)")
# 5. Définir macro C++ pour accès au runtime
add_definitions(-DMODULE_VERSION="${MODULE_VERSION}")
# ============================================================================
# MODULE BUILD
# ============================================================================
# Inclure sources
file(GLOB_RECURSE TANK_SOURCES src/*.cpp)
# Créer bibliothèque partagée (.so)
add_library(tank-module SHARED ${TANK_SOURCES})
# Configuration compilation
target_include_directories(tank-module PRIVATE include)
target_compile_features(tank-module PRIVATE cxx_std_20)
# Sortie
set_target_properties(tank-module PROPERTIES
OUTPUT_NAME "tank"
PREFIX ""
SUFFIX ".so"
)
message(STATUS "Building TankModule v${MODULE_VERSION}")
```
### Utilisation dans le Code C++
```cpp
// src/TankModule.h
#ifndef MODULE_VERSION
#define MODULE_VERSION "unknown"
#endif
class TankModule : public IModule, public IModuleSave {
public:
std::string getVersion() const override {
return MODULE_VERSION; // Défini par CMake
}
nlohmann::json serialize() override {
json data;
data["module_name"] = "tank";
data["module_version"] = getVersion(); // "0.1.15.3847"
// ... reste de la sérialisation
return data;
}
};
```
### Affichage au Runtime
```cpp
void TankModule::initialize() {
LOG_INFO("TankModule v{} initializing...", getVersion());
}
```
Sortie console :
```
[INFO] TankModule v0.1.15.3847 initializing...
```
## Workflow de Développement
### 1. Création Nouveau Module
```bash
# Créer structure module
mkdir -p modules/new_module/src
cd modules/new_module
# Créer fichier VERSION
echo "0.1" > VERSION
# Créer CMakeLists.txt avec système versioning
# (copier template ci-dessus)
```
**Première compilation** :
```bash
cmake . && make
```
Sortie :
```
-- Module version: 0.1.0.0
-- BASE: 0.1 (from VERSION file)
-- PATCH: 0 (git commits)
-- BUILD: 0 (sources hash)
-- Building NewModule v0.1.0.0
```
### 2. Développement Itératif
**Scénario 1 : Modifier code sans commit**
```bash
# Éditer src/NewModule.cpp
nano src/NewModule.cpp
# Rebuild
make
```
Sortie :
```
-- Module version: 0.1.0.4582
-- PATCH: 0 (aucun commit)
-- BUILD: 4582 (hash sources a changé)
```
**Scénario 2 : Commit modifications**
```bash
git add .
git commit -m "Add feature X"
# Rebuild
make
```
Sortie :
```
-- Module version: 0.1.1.4582
-- PATCH: 1 (nouveau commit)
-- BUILD: 4582 (sources identiques au commit)
```
**Scénario 3 : Rebuild sans modification**
```bash
# Rebuild immédiat
make
```
Sortie :
```
-- Module version: 0.1.1.4582
(version identique, aucun changement)
```
### 3. Release Mineure (Nouvelle Feature)
```bash
# Bump MINOR version
echo "0.2" > VERSION
# Commit
git add VERSION
git commit -m "Bump version to 0.2 (new feature: advanced tactics)"
# Rebuild
cmake . && make
```
Sortie :
```
-- Module version: 0.2.2.5123
-- BASE: 0.2 (nouvelle version)
-- PATCH: 2 (commits depuis début projet)
-- BUILD: 5123 (hash actuel)
```
### 4. Release Majeure (Breaking Change)
```bash
# Bump MAJOR version
echo "1.0" > VERSION
git add VERSION
git commit -m "Release 1.0.0 - Stable API"
cmake . && make
```
Sortie :
```
-- Module version: 1.0.3.5123
-- BASE: 1.0 (version stable)
-- PATCH: 3 (total commits)
-- BUILD: 5123
```
## Comparaison de Versions
### Dans le Système de Save
Lors du chargement d'une save, comparaison des versions :
```cpp
bool SaveSystem::loadModule(const std::string& moduleName) {
auto module = moduleSystem->getModule(moduleName);
json moduleData = readJsonFile(modulePath);
std::string savedVersion = moduleData["module_version"];
std::string currentVersion = module->getVersion();
if (savedVersion != currentVersion) {
LOG_WARN("Module {} version mismatch:", moduleName);
LOG_WARN(" Save version: {}", savedVersion);
LOG_WARN(" Current version: {}", currentVersion);
// Analyser différences
VersionInfo saved = parseVersion(savedVersion);
VersionInfo current = parseVersion(currentVersion);
if (saved.major != current.major) {
LOG_ERROR(" MAJOR version mismatch - likely incompatible!");
return false; // Incompatibilité critique
}
else if (saved.minor != current.minor) {
LOG_WARN(" MINOR version mismatch - some features may differ");
// Continuer avec warning
}
else if (saved.patch != current.patch) {
LOG_INFO(" PATCH version mismatch - minor changes only");
// Sûr, juste bugfixes
}
else {
LOG_DEBUG(" BUILD version mismatch - dev builds differ");
// Totalement sûr
}
}
return module->deserialize(moduleData);
}
```
### Structure VersionInfo
```cpp
struct VersionInfo {
int major;
int minor;
int patch;
int build;
static VersionInfo parse(const std::string& versionStr) {
VersionInfo info;
std::sscanf(versionStr.c_str(), "%d.%d.%d.%d",
&info.major, &info.minor, &info.patch, &info.build);
return info;
}
bool isCompatibleWith(const VersionInfo& other) const {
// Compatible si MAJOR identique
return major == other.major;
}
bool hasBreakingChanges(const VersionInfo& other) const {
return major != other.major;
}
};
```
## Gestion Multi-Modules
### Script CMake Global
Pour centraliser la logique de versioning, créer un fichier CMake réutilisable :
```cmake
# cmake/ModuleVersioning.cmake
function(setup_module_versioning)
# Lire VERSION
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" BASE_VERSION)
string(STRIP "${BASE_VERSION}" BASE_VERSION)
else()
set(BASE_VERSION "0.1")
endif()
# Calculer PATCH
execute_process(
COMMAND git rev-list --count HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE PATCH_COUNT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE GIT_RESULT
)
if(NOT GIT_RESULT EQUAL 0)
set(PATCH_COUNT "0")
endif()
# Calculer BUILD
file(GLOB_RECURSE MODULE_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/*.h"
)
set(SOURCES_CONTENT "")
foreach(SOURCE_FILE ${MODULE_SOURCES})
file(READ ${SOURCE_FILE} FILE_CONTENT)
string(APPEND SOURCES_CONTENT ${FILE_CONTENT})
endforeach()
string(MD5 SOURCES_HASH "${SOURCES_CONTENT}")
string(SUBSTRING ${SOURCES_HASH} 0 4 BUILD_HEX)
math(EXPR BUILD_NUM "0x${BUILD_HEX} % 10000")
# Assembler version
set(MODULE_VERSION "${BASE_VERSION}.${PATCH_COUNT}.${BUILD_NUM}" PARENT_SCOPE)
# Log
message(STATUS "Module version: ${BASE_VERSION}.${PATCH_COUNT}.${BUILD_NUM}")
endfunction()
```
### Utilisation dans Module
```cmake
# modules/tank/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(TankModule)
# Importer fonction versioning
include(${CMAKE_SOURCE_DIR}/cmake/ModuleVersioning.cmake)
# Setup version automatique
setup_module_versioning()
# Définir macro
add_definitions(-DMODULE_VERSION="${MODULE_VERSION}")
# ... reste du CMakeLists
```
## Best Practices
### 1. Quand Bumper MAJOR ?
**Situations justifiant MAJOR bump** :
- Changement format save incompatible
- Refonte API du module (signatures fonctions changent)
- Suppression de fonctionnalités existantes
- Changement architecture fondamentale
**Exemple** :
```bash
# Avant : TankModule v0.8.x utilise grille 10x10
# Après : TankModule v1.0.0 utilise grille 16x16 (incompatible)
echo "1.0" > VERSION
```
### 2. Quand Bumper MINOR ?
**Situations justifiant MINOR bump** :
- Ajout nouveau type de tank/composant
- Nouvelle fonctionnalité (mode formation, tactiques AI)
- Amélioration significative sans breaking change
**Exemple** :
```bash
# Ajout système de camouflage → 0.1 → 0.2
echo "0.2" > VERSION
```
### 3. PATCH et BUILD sont Automatiques
**Ne jamais modifier manuellement** :
- PATCH suit automatiquement les commits
- BUILD suit automatiquement les modifications code
### 4. Versioning pour Tests
En environnement de test, fixer une version stable :
```cmake
if(DEFINED ENV{CI_BUILD})
# Build CI : version fixe pour reproductibilité
set(MODULE_VERSION "0.0.0.0")
else()
# Build normal : versioning automatique
setup_module_versioning()
endif()
```
### 5. Affichage Version au Démarrage
```cpp
void logModuleVersions() {
LOG_INFO("=== Module Versions ===");
for (auto& module : moduleSystem->getAllModules()) {
LOG_INFO(" {}: v{}", module->getName(), module->getVersion());
}
LOG_INFO("=======================");
}
```
Sortie :
```
[INFO] === Module Versions ===
[INFO] tank: v0.1.15.3847
[INFO] economy: v0.2.8.1203
[INFO] factory: v0.1.20.4512
[INFO] transport: v0.1.5.982
[INFO] =======================
```
## Debugging avec Versions
### Reproduire un Bug
Si un bug est reporté avec une save :
1. **Lire version du module dans la save** :
```bash
cat saves/abc123/server_1/tank.json | grep module_version
# "module_version": "0.1.15.3847"
```
2. **Retrouver commit correspondant** :
```bash
# PATCH = 15 commits
git log --oneline | head -n 15 | tail -n 1
```
3. **Vérifier hash sources** :
```bash
# BUILD = 3847 correspond à un certain hash
# Comparer avec hashes de commits proches
```
4. **Checkout version exacte pour debugging** :
```bash
git checkout <commit_hash>
cmake . && make
# Version devrait matcher ou être proche
```
### Logs de Version
```cpp
void TankModule::initialize() {
LOG_INFO("TankModule v{} initializing", getVersion());
LOG_DEBUG(" Compiled: {} {}", __DATE__, __TIME__);
LOG_DEBUG(" Compiler: {}", __VERSION__);
}
```
## Alternatives et Limitations
### Alternative 1 : Semantic Versioning Manuel
**Avantage** : Contrôle total sur versions
**Inconvénient** : Maintenance manuelle, oublis fréquents
### Alternative 2 : Git Tags Only
```cmake
execute_process(
COMMAND git describe --tags --always
OUTPUT_VARIABLE MODULE_VERSION
)
```
**Avantage** : Simple, suit git
**Inconvénient** : Nécessite tags, pas de granularité build
### Limitation Actuelle : Hash Non-Déterministe
Le hash des sources peut varier selon :
- Ordre de lecture des fichiers (filesystem dépendant)
- Encodage des fichiers
- Whitespace differences
**Solution future** : Hash déterministe avec tri des fichiers et normalisation.
## Migration depuis Système Existant
Si des modules existants utilisent un autre système :
```cmake
# Détecter ancien système
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/version.txt")
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" OLD_VERSION)
message(WARNING "Found old version.txt, migrating to VERSION file")
# Extraire MAJOR.MINOR
string(REGEX MATCH "^[0-9]+\\.[0-9]+" BASE_VERSION ${OLD_VERSION})
file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" "${BASE_VERSION}")
message(STATUS "Migration complete: ${OLD_VERSION} -> ${BASE_VERSION}.x.x")
endif()
```
## Références Croisées
- `systeme-sauvegarde.md` : Utilisation des versions dans le système de save
- `architecture-modulaire.md` : Interface IModule, contraintes modules
- `claude-code-integration.md` : Optimisation builds pour développement AI
## Exemples Complets
### Module Complet avec Versioning
Voir structure complète dans :
```
modules/tank/
├── VERSION # "0.1"
├── CMakeLists.txt # Setup versioning + build
├── src/
│ ├── TankModule.cpp # Utilise MODULE_VERSION
│ └── Tank.cpp
└── include/
└── TankModule.h # Déclare getVersion()
```
### Test Versioning
```cpp
#ifdef TESTING
#include <cassert>
void testVersioning() {
TankModule module;
std::string version = module.getVersion();
assert(!version.empty());
assert(version != "unknown");
// Parse version
VersionInfo info = VersionInfo::parse(version);
assert(info.major >= 0);
assert(info.minor >= 0);
assert(info.patch >= 0);
assert(info.build >= 0);
std::cout << "Versioning test passed: " << version << std::endl;
}
#endif
```

1414
docs/ai-framework.md Normal file

File diff suppressed because it is too large Load Diff

752
docs/calcul-menace.md Normal file
View File

@ -0,0 +1,752 @@
# Calcul de Menace Contextuelle
## Vue d'Ensemble
Le système de calcul de menace évalue la **dangerosité qu'une entité représente pour une autre** en analysant non seulement les forces brutes, mais aussi les capacités défensives, la qualité des équipements, et la production industrielle.
### Principes Fondamentaux
- **Contextuel** : Menace calculée selon capacités **des deux parties**
- **Asymétrique** : A menace B ≠ B menace A
- **Sword & Shield** : Qualité défenses peut annuler supériorité numérique
- **Production** : Capacité industrielle = menace future
- **Multi-domaines** : Terre, air, mer évalués séparément puis agrégés
## Échelle de Menace
**Valeurs** : `0` à `2 000 000+` (pas de maximum strict, type `int32_t`)
**Ordres de grandeur** :
- `0` : Aucune menace
- `100 - 1 000` : PMC petit
- `5 000 - 50 000` : Company moyenne
- `100 000` : État petit
- `500 000` : État moyen (France, UK, Allemagne)
- `900 000` : Superpower (USA, Chine, Russie)
- `1 000 000+` : Menace existentielle
## Architecture Calcul
### Formule Globale
```cpp
int calculateThreat(Entity attacker, Entity defender) {
auto params = attacker.getThreatParams();
// Composantes
int current_military = evaluateCurrentForces(attacker, defender);
int production_capacity = evaluateProduction(attacker, defender, params.projection_months);
// Pondération configurable par entité
return current_military * params.current_weight +
production_capacity * params.production_weight;
}
struct ThreatCalculationParams {
int projection_months = 12; // Horizon projection (défaut 12 mois)
float current_weight = 0.6f; // Poids forces actuelles (60%)
float production_weight = 0.4f; // Poids production (40%)
};
```
**Personnalisation par entité** :
- Company **agressive** : `current: 0.8, production: 0.2` (focus court-terme)
- State **prudent** : `current: 0.4, production: 0.6` (focus long-terme)
- PMC **réactif** : `current: 0.9, production: 0.1` (menace immédiate prioritaire)
## Évaluation Forces Actuelles
### Principe : Sword & Shield
Pour chaque domaine (terre, air, mer), on évalue :
1. **Sword** (attaquant) : Capacités offensives
2. **Shield** (défenseur) : Capacités défensives
3. **Effectiveness** : Dans quelle mesure le shield arrête le sword
```cpp
int evaluateCurrentForces(Entity attacker, Entity defender) {
int land_threat = evaluateLandThreat(attacker, defender);
int air_threat = evaluateAirThreat(attacker, defender);
int naval_threat = evaluateNavalThreat(attacker, defender);
return land_threat + air_threat + naval_threat;
}
```
### Évaluation Domaine Terrestre
#### Inventaire Forces
**Attaquant** :
```cpp
struct LandForces {
std::vector<Tank> tanks;
std::vector<IFV> ifvs;
std::vector<APC> apcs;
std::vector<Artillery> artillery;
std::vector<Infantry> infantry;
};
```
**Défenseur** :
```cpp
struct LandDefenses {
std::vector<ATSystem> anti_tank; // Systèmes anti-char
std::vector<Tank> counter_tanks; // Tanks défensifs
std::vector<Infantry> anti_tank_inf; // Infanterie AT
std::vector<Fortification> fortifications;
};
```
#### Couples Équipement ↔ Contre-mesures
**Principe** : Chaque type d'équipement offensif a des contre-mesures spécifiques.
```cpp
struct EquipmentCounters {
std::map<EquipmentType, std::vector<CounterType>> counters = {
{TANK, {AT_MISSILE, AT_GUN, COUNTER_TANK, AT_INFANTRY}},
{IFV, {AT_MISSILE, AT_GUN, AUTOCANNON}},
{APC, {AUTOCANNON, MACHINE_GUN, RPG}},
{INFANTRY, {MACHINE_GUN, ARTILLERY, AUTOCANNON}},
{ARTILLERY, {COUNTER_BATTERY, AIR_STRIKE}}
};
};
```
#### Évaluation Sword vs Shield
**Étape 1 : Match-making**
Pour chaque équipement offensif, identifier contre-mesures défensives applicables :
```cpp
std::vector<Defense> findApplicableDefenses(
Equipment sword,
std::vector<Defense> available_defenses
) {
std::vector<Defense> applicable;
auto counters = EquipmentCounters::counters[sword.type];
for (auto& defense : available_defenses) {
// Vérifier si défense peut contrer cet équipement
if (std::find(counters.begin(), counters.end(), defense.type) != counters.end()) {
// Vérifier compatibilité génération/qualité
if (canCounter(defense, sword)) {
applicable.push_back(defense);
}
}
}
return applicable;
}
bool canCounter(Defense defense, Equipment sword) {
// Exemple : AT missile Gen2 ne peut pas contrer tank Gen4 avec armure avancée
if (defense.generation < sword.generation - 1) {
return false; // Trop ancien
}
// Vérifier spécificités
if (sword.has_reactive_armor && defense.type == AT_MISSILE_OLD) {
return false; // ERA bloque missiles anciens
}
return true;
}
```
**Étape 2 : Calcul Defensive Effectiveness**
```cpp
float calculateDefensiveEffectiveness(
std::vector<Equipment> swords,
std::vector<Defense> shields
) {
float total_sword_value = 0;
float neutralized_value = 0;
for (auto& sword : swords) {
float sword_value = sword.quantity * sword.quality;
total_sword_value += sword_value;
// Trouver défenses applicables
auto applicable_shields = findApplicableDefenses(sword, shields);
// Calculer valeur défensive totale contre ce sword
float shield_value = 0;
for (auto& shield : applicable_shields) {
float shield_effectiveness = calculateShieldEffectiveness(shield, sword);
shield_value += shield.quantity * shield.quality * shield_effectiveness;
}
// Neutralisation proportionnelle
float neutralization = min(1.0f, shield_value / sword_value);
neutralized_value += sword_value * neutralization;
}
return total_sword_value > 0 ? (neutralized_value / total_sword_value) : 0.0f;
}
```
**Étape 3 : Score Final**
```cpp
int evaluateLandThreat(Entity attacker, Entity defender) {
auto swords = attacker.getLandForces();
auto shields = defender.getLandDefenses();
// Menace brute (sans défenses)
int raw_threat = 0;
for (auto& sword : swords) {
raw_threat += sword.quantity * sword.combat_value;
}
// Réduction par défenses
float defensive_effectiveness = calculateDefensiveEffectiveness(swords, shields);
// Menace finale
int final_threat = raw_threat * (1.0f - defensive_effectiveness);
return final_threat;
}
```
### Exemples Concrets Terre
#### Exemple 1 : Qualité Domine Quantité
**Attaquant** :
- 20 tanks Gen4 (Leopard 2A7)
- Armure composite avancée
- Génération 4
- Combat value : 1000/tank
- Menace brute : 20 × 1000 = 20 000
**Défenseur** :
- 100 000 AT missiles Gen2
- Anciens, inefficaces contre armure Gen4
- `canCounter()` retourne `false`
- Défense applicable : 0
**Résultat** :
```
defensive_effectiveness = 0 / 20000 = 0%
final_threat = 20000 × (1 - 0) = 20 000
```
**Menace élevée** : Défenses obsolètes inefficaces
#### Exemple 2 : Quantité + Qualité Écrasent
**Attaquant** :
- 1000 tanks Gen2
- Armure standard
- Combat value : 400/tank
- Menace brute : 1000 × 400 = 400 000
**Défenseur** :
- 500 AT missiles Gen4 (Javelin)
- Top-attack, très efficaces
- Combat value : 800/missile
- `canCounter()` retourne `true`
- Shield effectiveness : 0.9 (très efficace)
- Shield value : 500 × 800 × 0.9 = 360 000
**Résultat** :
```
defensive_effectiveness = 360000 / 400000 = 90%
final_threat = 400000 × (1 - 0.9) = 40 000
```
**Menace faible** : Défenses qualitatives réduisent massavement
#### Exemple 3 : Masse Insuffisante
**Attaquant** :
- 1000 tanks Gen2
- Combat value : 400/tank
- Menace brute : 400 000
**Défenseur** :
- 20 AT missiles Gen4
- Très efficaces mais **pas assez nombreux**
- Shield value : 20 × 800 × 0.9 = 14 400
**Résultat** :
```
defensive_effectiveness = 14400 / 400000 = 3.6%
final_threat = 400000 × (1 - 0.036) = 385 440
```
**Menace reste élevée** : Pas assez de défenses pour couvrir la masse
### Évaluation Domaine Aérien
#### Spécificités Air
**Complexité supplémentaire** :
- **Qualité prime** : Quelques jets furtifs Gen4 dominent des centaines AA Gen2
- **ECM/ECCM** : Guerre électronique critique
- **Compatibilité stricte** : AA anti-hélicoptère ne touche pas jets
```cpp
struct AirForces {
std::vector<Fighter> fighters;
std::vector<Bomber> bombers;
std::vector<Helicopter> helicopters;
std::vector<Drone> drones;
// Capacités spéciales
bool has_stealth;
bool has_ecm; // Electronic Counter-Measures
int electronic_warfare_level;
};
struct AirDefenses {
std::vector<AAMissile> aa_missiles;
std::vector<AAGun> aa_guns;
std::vector<Fighter> interceptors;
// Capacités
bool has_eccm; // Electronic Counter-Counter-Measures
int radar_quality;
std::map<AASystem, TargetCapability> capabilities; // Quoi peut toucher quoi
};
enum TargetCapability {
HELICOPTER_ONLY, // Seulement hélicoptères
LOW_ALTITUDE, // Avions basse altitude
HIGH_ALTITUDE, // Avions haute altitude
ALL_AIRCRAFT // Tous types
};
```
#### Compatibilité Systèmes
```cpp
bool canEngageAircraft(AASystem aa, Aircraft aircraft) {
// Vérifier compatibilité type
switch (aa.capability) {
case HELICOPTER_ONLY:
return aircraft.type == HELICOPTER;
case LOW_ALTITUDE:
return aircraft.altitude < 5000; // mètres
case HIGH_ALTITUDE:
return aircraft.altitude > 5000;
case ALL_AIRCRAFT:
return true;
}
// Vérifier guerre électronique
if (aircraft.has_ecm && !aa.has_eccm) {
// ECM peut brouiller AA sans ECCM
float jam_probability = aircraft.ecm_power / (aa.radar_quality + 1);
if (randomFloat() < jam_probability) {
return false; // Brouillé
}
}
// Vérifier furtivité
if (aircraft.has_stealth) {
float detection_range = aa.radar_range * (1.0f - aircraft.stealth_rating);
if (distance(aa, aircraft) > detection_range) {
return false; // Pas détecté
}
}
return true;
}
```
#### Exemple Air : Furtivité Domine
**Attaquant** :
- 20 jets furtifs Gen4 (F-35)
- Stealth rating : 0.9 (réduit détection 90%)
- ECM avancé
- Combat value : 2000/jet
- Menace brute : 40 000
**Défenseur** :
- 100 000 AA missiles Gen2
- Radar standard
- Pas ECCM
- Seulement 5% peuvent détecter/engager les furtifs
- Shield value effectif : 100000 × 0.05 × 300 = 1 500 000... mais avec ECM :
- Shield value final : 1 500 000 × 0.1 (jam rate) = 150 000
```
defensive_effectiveness = 150000 / 40000 = ... wait, > 1.0 !
→ Plafonné à min(1.0, value)
defensive_effectiveness = min(1.0, 150000/40000) = 1.0...
ERREUR dans mon calcul !
```
**Correction** : Le shield value doit être calculé par aircraft, pas globalement.
```cpp
// Pour chaque jet
for (auto& jet : jets) {
float jet_value = jet.combat_value;
// Combien de AA peuvent l'engager ?
int applicable_aa = 0;
for (auto& aa : aa_systems) {
if (canEngageAircraft(aa, jet)) {
applicable_aa++;
}
}
// Shield value pour ce jet spécifique
float shield_value = applicable_aa * aa_combat_value;
float neutralization = min(1.0f, shield_value / jet_value);
threat_reduced += jet_value * neutralization;
}
```
**Résultat corrigé** :
- 5000 AA peuvent engager (sur 100k)
- Pour 1 jet : shield = 5000 × 300 = 1 500 000 vs jet = 2000
- Neutralization = 100% par jet
- Mais ils peuvent pas tous tirer simultanément !
**Contrainte simultanéité** :
```cpp
// Limite engagement simultané
int max_simultaneous = min(applicable_aa, ENGAGEMENT_LIMIT);
// Exemple : Max 100 missiles simultanés par cible
float shield_value = max_simultaneous * aa_combat_value;
```
Avec limite 100 simultanés :
- Shield = 100 × 300 = 30 000 vs jet = 2000
- Neutralization = 100% par jet
- Mais 20 jets → seulement 2000 engagements simultanés total
- Si AA rate coordination : menace reste
**C'est complexe !** Donc en pratique :
```cpp
float calculateAirDefenseEffectiveness(
std::vector<Aircraft> aircraft,
std::vector<AASystem> aa_systems
) {
float total_threat = 0;
float neutralized = 0;
for (auto& ac : aircraft) {
total_threat += ac.combat_value;
// Compter AA applicables
int applicable_count = 0;
for (auto& aa : aa_systems) {
if (canEngageAircraft(aa, ac)) {
applicable_count++;
}
}
// Engagement limité
int engaged = min(applicable_count, MAX_SIMULTANEOUS_PER_TARGET);
// Probabilité kill
float kill_probability = engaged / float(ENGAGEMENTS_NEEDED_FOR_KILL);
kill_probability = min(1.0f, kill_probability);
neutralized += ac.combat_value * kill_probability;
}
return total_threat > 0 ? (neutralized / total_threat) : 0.0f;
}
```
### Évaluation Domaine Naval
**Similaire à terrestre/aérien** mais avec spécificités :
- **Torpilles vs sonars** (submersibles)
- **Anti-ship missiles vs CIWS** (Close-In Weapon Systems)
- **Portée extrême** : Naval combat à 100+ km
Structure identique avec couples spécifiques.
## Évaluation Production
### Principe
La menace ne vient pas seulement des forces actuelles, mais aussi de la **capacité à produire** plus d'équipements.
```cpp
int evaluateProduction(Entity attacker, Entity defender, int projection_months) {
int production_threat = 0;
// Pour chaque type d'équipement
for (auto& equipment_type : all_equipment_types) {
// Taux production attaquant
int attacker_rate = attacker.getProductionRate(equipment_type); // unités/mois
// Taux production défenses défenseur
int defender_rate = defender.getProductionRate(getCounterType(equipment_type));
// Projection future
int attacker_produced = attacker_rate * projection_months;
int defender_produced = defender_rate * projection_months;
// Menace nette production
int net_production = attacker_produced - defender_produced;
if (net_production > 0) {
int equipment_value = getEquipmentValue(equipment_type);
production_threat += net_production * equipment_value;
}
}
return production_threat;
}
```
### Exemple Production
**État A** :
- Production actuelle : 50 tanks/mois
- Projection 12 mois : 600 tanks
- Combat value : 400/tank
- Production threat : 600 × 400 = 240 000
**État B** (défenseur) :
- Production AT : 100 missiles/mois
- Projection 12 mois : 1200 missiles
- Combat value : 300/missile
- Défense production : 1200 × 300 = 360 000
**Net production threat** :
```
Attacker gain : 240 000
Defender gain : 360 000
Net : 240 000 - 360 000 = -120 000 (négatif = défenseur gagne)
production_threat = max(0, net) = 0
```
→ État B a **meilleure production**, donc menace production de A est nulle.
**Si inverse** :
**État A** :
- Production : 200 tanks/mois → 2400 tanks/an
- Threat : 2400 × 400 = 960 000
**État B** :
- Production AT : 50 missiles/mois → 600 missiles/an
- Défense : 600 × 300 = 180 000
**Net** : 960 000 - 180 000 = 780 000
→ État A a **production écrasante**, menace industrielle massive.
## Agrégation Finale
### Formule Complète
```cpp
int calculateThreat(Entity attacker, Entity defender) {
auto params = attacker.getThreatParams();
// Forces actuelles (sword & shield par domaine)
int land_threat = evaluateLandThreat(attacker, defender);
int air_threat = evaluateAirThreat(attacker, defender);
int naval_threat = evaluateNavalThreat(attacker, defender);
int current_military = land_threat + air_threat + naval_threat;
// Production future
int production_threat = evaluateProduction(
attacker,
defender,
params.projection_months
);
// Pondération finale
int total_threat = current_military * params.current_weight +
production_threat * params.production_weight;
return total_threat;
}
```
### Exemples Complets
#### Superpower vs État Moyen
**USA vs France**
**USA forces** :
- Tanks : 8000 (Gen3-4) → 3 200 000
- Aircraft : 2000 (Gen4, furtifs) → 4 000 000
- Naval : 500 ships → 2 500 000
- **Total brut** : 9 700 000
**France défenses** :
- AT systems : 3000 (Gen3) → Shield 50% tanks
- AA systems : 1500 (Gen3) → Shield 30% aircraft
- Naval defenses : 200 → Shield 40% naval
**Après sword & shield** :
- Land : 3 200 000 × 0.5 = 1 600 000
- Air : 4 000 000 × 0.7 = 2 800 000
- Naval : 2 500 000 × 0.6 = 1 500 000
- **Current threat** : 5 900 000
**Production (12 mois)** :
- USA : +600 tanks, +100 aircraft → 360 000
- France : +100 tanks, +50 aircraft → 70 000
- **Net production** : 290 000
**Total (60% current, 40% prod)** :
```
5 900 000 × 0.6 + 290 000 × 0.4 = 3 656 000
```
**Menace colossale**, mais France peut tenir avec alliances
#### PMC vs Company
**PMC_Alpha vs RheinmetallCorp**
**PMC forces** :
- Infantry : 500 (léger armement)
- Vehicles : 20 APCs
- **Total** : 50 000
**Rheinmetall défenses** :
- Security : 100 guards
- Fortifications : minimal
- **Shield** : 5%
**Threat** : 50 000 × 0.95 = 47 500
→ PMC peut menacer installations Company, mais pas capacités production
## Cas Spéciaux
### Menace Économique Pure
Pour Companies sans capacités militaires :
```cpp
int calculateEconomicThreat(Company attacker, Company defender) {
// Part de marché
float market_dominance = attacker.market_share / (defender.market_share + 0.01f);
// Capacité production
float production_ratio = attacker.production_capacity / defender.production_capacity;
// Qualité produits
float quality_advantage = attacker.avg_product_quality / defender.avg_product_quality;
// Menace économique
int economic_threat = (market_dominance * 100000) +
(production_ratio * 80000) +
(quality_advantage * 50000);
return economic_threat;
}
```
### Menace Géographique
Distance géographique module menace :
```cpp
float getGeographicModifier(Entity attacker, Entity defender) {
float distance_km = calculateDistance(attacker.position, defender.position);
// Projection power diminue avec distance
if (distance_km < 500) {
return 1.0f; // Menace pleine
}
else if (distance_km < 2000) {
return 0.7f; // 30% réduction
}
else if (distance_km < 5000) {
return 0.4f; // 60% réduction
}
else {
return 0.1f; // 90% réduction (trop loin)
}
}
```
## Performance et Optimisation
### Cache Threat
```cpp
class ThreatCache {
private:
struct CachedThreat {
int value;
int timestamp;
int ttl = 3600; // 1 heure
};
std::map<std::pair<EntityId, EntityId>, CachedThreat> cache;
public:
int getThreat(EntityId attacker, EntityId defender) {
auto key = std::make_pair(attacker, defender);
if (cache.contains(key)) {
auto& cached = cache[key];
if (getCurrentTime() - cached.timestamp < cached.ttl) {
return cached.value;
}
}
// Recalculer
int threat = calculateThreat(attacker, defender);
cache[key] = {threat, getCurrentTime()};
return threat;
}
void invalidate(EntityId entity) {
// Invalider tous les caches impliquant cette entité
for (auto it = cache.begin(); it != cache.end();) {
if (it->first.first == entity || it->first.second == entity) {
it = cache.erase(it);
} else {
++it;
}
}
}
};
```
### Invalidation Cache
Événements invalidant cache :
- Production nouvelle unité
- Perte unité au combat
- Nouvelle recherche technologique
- Changement doctrine militaire
### Calcul Lazy
```cpp
// Ne calculer que si demandé par IA
int getThreatOnDemand(EntityId attacker, EntityId defender) {
return threatCache.getThreat(attacker, defender);
}
// Pas de recalcul automatique chaque tick
```
## Références Croisées
- `systeme-diplomatique.md` : Utilisation menace dans relations diplomatiques
- `ai-framework.md` : Menace influence décisions IA (achats, alliances)
- `systeme-militaire.md` : Valeurs combat équipements, générations
- `economie-logistique.md` : Production rates, capacités industrielles

1090
docs/systeme-diplomatique.md Normal file

File diff suppressed because it is too large Load Diff

715
docs/systeme-sauvegarde.md Normal file
View File

@ -0,0 +1,715 @@
# Système de Sauvegarde
## Philosophie
Le système de sauvegarde suit la philosophie du projet : **évolution itérative avec versioning des systèmes**. Chaque version améliore progressivement les performances et fonctionnalités tout en maintenant la compatibilité.
Le système est conçu pour être **modulaire et autonome**, chaque module gérant sa propre persistence de manière indépendante.
## Version 1 : Save JSON Local Sans Versioning de Format
### Principes de Base
- **Format** : JSON pur pour lisibilité, debugging et édition manuelle
- **Granularité** : Un fichier par module
- **Distribution** : Chaque serveur sauvegarde localement (multi-serveur ready)
- **Coordination** : Clé de save distribuée par le coordinateur
- **Autonomie** : Chaque module implémente son propre `serialize()` / `deserialize()`
- **Chunks** : Seulement les chunks modifiés (metachunks 512x512)
- **Objectif** : Système fonctionnel et suffisant pour tests et développement
### Architecture de Fichiers
```
saves/
└── abc123_1728226320/ # Clé de save : [id]_[timestamp]
├── save_metadata.json # Métadonnées globales de la save
├── server_1/ # Un dossier par serveur (future-proof)
│ ├── tank.json
│ └── combat.json
├── server_2/
│ ├── economy.json
│ └── factory.json
└── metachunks/
├── 0_0.json # Metachunk coords (x, y) en 512x512
├── 0_1.json
└── ...
```
**Note** : En configuration single-process V1, un seul dossier serveur existe (ex: `server_1/`), mais la structure supporte déjà le multi-serveur.
### Format save_metadata.json
Fichier global décrivant la sauvegarde complète :
```json
{
"save_key": "abc123_1728226320",
"timestamp": "2025-10-06T14:32:00Z",
"game_version": "0.1.0-alpha",
"modules": {
"tank": {
"version": "0.1.15.3847",
"load_status": "ok"
},
"economy": {
"version": "0.2.8.1203",
"load_status": "ok"
},
"factory": {
"version": "0.1.20.4512",
"load_status": "load_pending"
},
"transport": {
"version": "0.1.5.982",
"load_status": "ok"
}
},
"metachunks_count": 42,
"world_seed": 1847293847
}
```
**Champs** :
- `save_key` : Identifiant unique de la save (format : `[id]_[timestamp_unix]`)
- `timestamp` : Date/heure de sauvegarde (ISO 8601)
- `game_version` : Version du jeu ayant créé la save
- `modules` : État de chaque module sauvé
- `version` : Version du module (voir `module-versioning.md`)
- `load_status` : `"ok"`, `"load_pending"`, `"failed"`
- `metachunks_count` : Nombre de metachunks sauvés
- `world_seed` : Seed de génération procédurale
### Format Fichiers Modules
Chaque module implémente `IModuleSave` et définit son propre format JSON.
#### Exemple : tank.json
```json
{
"module_name": "tank",
"module_version": "0.1.15.3847",
"save_timestamp": "2025-10-06T14:32:00Z",
"data": {
"tanks": [
{
"id": "tank_001",
"chassis_type": "m1_abrams",
"position": {"x": 1250.5, "y": 3480.2},
"rotation": 45.0,
"components": [
{
"type": "turret_120mm",
"grid_pos": [2, 3],
"health": 100,
"ammo_loaded": "apfsds"
},
{
"type": "engine_turbine",
"grid_pos": [1, 1],
"health": 85,
"fuel": 0.75
}
],
"inventory": {
"120mm_apfsds": 32,
"120mm_heat": 18
},
"crew_status": "operational"
}
]
}
}
```
#### Exemple : economy.json
```json
{
"module_name": "economy",
"module_version": "0.2.8.1203",
"save_timestamp": "2025-10-06T14:32:00Z",
"data": {
"market_prices": {
"iron_ore": 2.5,
"copper_ore": 3.2,
"steel_plate": 8.0
},
"player_balance": 125000,
"pending_transactions": [
{
"id": "tx_4829",
"type": "buy",
"resource": "steel_plate",
"quantity": 1000,
"price_per_unit": 8.0,
"timestamp": "2025-10-06T14:30:15Z"
}
]
}
}
```
**Convention** : Chaque fichier module contient :
- `module_name` : Nom du module (vérification cohérence)
- `module_version` : Version du module ayant créé la save
- `save_timestamp` : Timestamp de sauvegarde
- `data` : Objet contenant toutes les données spécifiques au module
### Format Metachunks (512x512)
Pour réduire le nombre de fichiers, les chunks 64x64 sont regroupés en **metachunks 512x512** (8x8 chunks par metachunk = 64 chunks).
#### Structure Metachunk
```json
{
"metachunk_coords": {"x": 0, "y": 0},
"metachunk_size": 512,
"chunk_size": 64,
"chunks": {
"0_0": {
"terrain": {
"land_ids_base64": "AQIDBAUGBwg...",
"roof_ids_base64": "CQAKAA..."
},
"buildings": [
{
"id": "building_factory_001",
"type": "assembler_mk1",
"position": {"x": 10, "y": 15},
"recipe": "ammunition_7.62mm",
"progress": 0.45,
"inventory": {
"iron_plate": 50,
"copper_wire": 30
}
}
],
"resources": [
{
"type": "iron_ore",
"patch_id": "iron_patch_042",
"positions": [[5,5], [5,6], [6,5], [6,6]],
"amounts": [1000, 950, 980, 1020]
}
]
},
"0_1": {
"terrain": { "land_ids_base64": "..." },
"buildings": [],
"resources": []
}
}
}
```
**Optimisations** :
- **Base64 compression** : Données terrain denses compressées
- **Sparse storage** : Chunks vides omis du metachunk
- **Dirty tracking** : Seulement metachunks avec chunks modifiés sont sauvés
#### Calcul Coords Metachunk
```cpp
// Chunk 64x64 coords → Metachunk 512x512 coords
MetachunkCoords getMetachunkCoords(int chunkX, int chunkY) {
return {
chunkX / 8, // 512 / 64 = 8 chunks par metachunk
chunkY / 8
};
}
// Coords locales dans le metachunk
ChunkLocalCoords getLocalCoords(int chunkX, int chunkY) {
return {
chunkX % 8,
chunkY % 8
};
}
```
### Interface IModuleSave
Chaque module implémente cette interface pour gérer sa persistence :
```cpp
class IModuleSave {
public:
virtual ~IModuleSave() = default;
// Sérialise l'état complet du module en JSON
virtual nlohmann::json serialize() = 0;
// Restaure l'état du module depuis JSON
// Retourne true si succès, false si erreur (→ load_pending)
virtual bool deserialize(const nlohmann::json& data) = 0;
// Version du module (auto-générée, voir module-versioning.md)
virtual std::string getVersion() const = 0;
};
```
#### Exemple Implémentation
```cpp
class TankModule : public IModule, public IModuleSave {
private:
std::vector<Tank> tanks;
public:
nlohmann::json serialize() override {
json data;
data["module_name"] = "tank";
data["module_version"] = getVersion();
data["save_timestamp"] = getCurrentTimestamp();
json tanksArray = json::array();
for (const auto& tank : tanks) {
tanksArray.push_back(tank.toJson());
}
data["data"]["tanks"] = tanksArray;
return data;
}
bool deserialize(const json& data) override {
try {
// Validation version
if (data["module_name"] != "tank") {
LOG_ERROR("Invalid module name in save file");
return false;
}
// Charge les tanks
tanks.clear();
for (const auto& tankJson : data["data"]["tanks"]) {
tanks.push_back(Tank::fromJson(tankJson));
}
return true;
}
catch (const std::exception& e) {
LOG_ERROR("Deserialization failed: {}", e.what());
return false;
}
}
std::string getVersion() const override {
return MODULE_VERSION; // Défini par CMake
}
};
```
### Workflow de Sauvegarde
#### Déclenchement
**En single-process** :
```cpp
saveSystem->saveGame("abc123");
```
**En multi-serveur** :
Le coordinateur broadcast une clé de save :
```cpp
// Coordinateur
coordinator->broadcastSaveCommand("abc123_1728226320");
// Chaque serveur reçoit et exécute
void Server::onSaveCommand(const std::string& saveKey) {
saveSystem->saveGame(saveKey);
}
```
#### Processus de Sauvegarde
```cpp
void SaveSystem::saveGame(const std::string& saveKey) {
auto startTime = std::chrono::steady_clock::now();
// 1. Créer structure de dossiers
std::string savePath = "saves/" + saveKey + "/";
fs::create_directories(savePath + "server_1/");
fs::create_directories(savePath + "metachunks/");
// 2. Sauvegarder chaque module
json metadata;
metadata["save_key"] = saveKey;
metadata["timestamp"] = getCurrentTimestamp();
metadata["game_version"] = GAME_VERSION;
for (auto& module : moduleSystem->getAllModules()) {
std::string moduleName = module->getName();
try {
// Sérialiser module
json moduleData = module->serialize();
// Écrire fichier
std::string modulePath = savePath + "server_1/" +
moduleName + ".json";
writeJsonFile(modulePath, moduleData);
// Mettre à jour metadata
metadata["modules"][moduleName] = {
{"version", module->getVersion()},
{"load_status", "ok"}
};
LOG_INFO("Module {} saved successfully", moduleName);
}
catch (const std::exception& e) {
LOG_ERROR("Failed to save module {}: {}", moduleName, e.what());
metadata["modules"][moduleName] = {
{"version", module->getVersion()},
{"load_status", "failed"}
};
}
}
// 3. Sauvegarder metachunks modifiés
int metachunkCount = 0;
for (auto& [coords, metachunk] : dirtyMetachunks) {
json metachunkData = metachunk->serialize();
std::string filename = std::to_string(coords.x) + "_" +
std::to_string(coords.y) + ".json";
writeJsonFile(savePath + "metachunks/" + filename, metachunkData);
metachunkCount++;
}
metadata["metachunks_count"] = metachunkCount;
// 4. Écrire metadata
writeJsonFile(savePath + "save_metadata.json", metadata);
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - startTime
);
LOG_INFO("Save completed in {}ms: {} modules, {} metachunks",
duration.count(), metadata["modules"].size(), metachunkCount);
}
```
### Workflow de Chargement
```cpp
void SaveSystem::loadGame(const std::string& saveKey) {
std::string savePath = "saves/" + saveKey + "/";
// 1. Charger metadata
json metadata = readJsonFile(savePath + "save_metadata.json");
// Validation game version (warning seulement en V1)
if (metadata["game_version"] != GAME_VERSION) {
LOG_WARN("Save created with different game version: {} vs {}",
metadata["game_version"], GAME_VERSION);
}
// 2. Charger world data (seed, params)
worldGenerator->setSeed(metadata["world_seed"]);
// 3. Charger chaque module
for (auto& module : moduleSystem->getAllModules()) {
std::string moduleName = module->getName();
std::string modulePath = savePath + "server_1/" +
moduleName + ".json";
if (!fs::exists(modulePath)) {
LOG_WARN("No save file for module {}, using defaults", moduleName);
continue;
}
try {
json moduleData = readJsonFile(modulePath);
// Vérification version module
std::string savedVersion = moduleData["module_version"];
std::string currentVersion = module->getVersion();
if (savedVersion != currentVersion) {
LOG_WARN("Module {} version mismatch: save={}, current={}",
moduleName, savedVersion, currentVersion);
}
// Désérialiser
bool success = module->deserialize(moduleData);
if (!success) {
LOG_ERROR("Module {} deserialization failed, marked as load_pending",
moduleName);
metadata["modules"][moduleName]["load_status"] = "load_pending";
}
else {
LOG_INFO("Module {} loaded successfully", moduleName);
}
}
catch (const std::exception& e) {
LOG_ERROR("Failed to load module {}: {}", moduleName, e.what());
metadata["modules"][moduleName]["load_status"] = "load_pending";
}
}
// 4. Metachunks chargés à la demande (streaming)
// Voir Chunk Streaming ci-dessous
LOG_INFO("Save loaded: {}", saveKey);
}
```
### Chunk Streaming
Les metachunks ne sont **pas tous chargés au démarrage** pour économiser la mémoire. Système de chargement à la demande :
```cpp
Chunk* ChunkManager::getChunk(int x, int y) {
ChunkCoords coords{x, y};
// 1. Vérifier si déjà en RAM
if (loadedChunks.contains(coords)) {
return loadedChunks[coords];
}
// 2. Calculer metachunk parent
MetachunkCoords metaCoords = getMetachunkCoords(x, y);
ChunkLocalCoords localCoords = getLocalCoords(x, y);
// 3. Charger metachunk si nécessaire
if (!loadedMetachunks.contains(metaCoords)) {
loadMetachunk(metaCoords);
}
// 4. Extraire chunk du metachunk
Metachunk* metachunk = loadedMetachunks[metaCoords];
Chunk* chunk = metachunk->getChunk(localCoords);
if (chunk) {
loadedChunks[coords] = chunk;
return chunk;
}
// 5. Si chunk n'existe pas dans save → génération procédurale
return generateNewChunk(coords);
}
void ChunkManager::loadMetachunk(MetachunkCoords coords) {
std::string path = "saves/" + currentSave + "/metachunks/" +
std::to_string(coords.x) + "_" +
std::to_string(coords.y) + ".json";
if (!fs::exists(path)) {
// Metachunk pas encore exploré/modifié
loadedMetachunks[coords] = new Metachunk(coords);
return;
}
// Charger et désérialiser metachunk
json metachunkData = readJsonFile(path);
Metachunk* metachunk = new Metachunk(coords);
metachunk->deserialize(metachunkData);
loadedMetachunks[coords] = metachunk;
LOG_DEBUG("Metachunk loaded: ({}, {})", coords.x, coords.y);
}
```
### Gestion des Erreurs : load_pending
Lorsqu'un module échoue à charger (JSON corrompu, incompatibilité version, etc.), il est marqué `load_pending` dans le metadata.
#### Workflow de Récupération
```cpp
// Au chargement, si erreur détectée
if (!module->deserialize(moduleData)) {
metadata["modules"][moduleName]["load_status"] = "load_pending";
// Sauvegarder metadata mis à jour
writeJsonFile(savePath + "save_metadata.json", metadata);
// Continuer le chargement des autres modules
}
```
#### Interface Utilisateur
```cpp
void UI::displayLoadPendingModules() {
for (const auto& [moduleName, info] : metadata["modules"].items()) {
if (info["load_status"] == "load_pending") {
std::cout << "[WARNING] Module '" << moduleName
<< "' failed to load. Check logs and fix JSON file.\n";
std::cout << " File: saves/" << saveKey << "/server_1/"
<< moduleName << ".json\n";
std::cout << " Version: " << info["version"] << "\n\n";
}
}
}
```
#### Retry Manuel
Après avoir corrigé le fichier JSON manuellement :
```cpp
void SaveSystem::retryLoadModule(const std::string& moduleName) {
auto module = moduleSystem->getModule(moduleName);
std::string modulePath = savePath + "server_1/" + moduleName + ".json";
try {
json moduleData = readJsonFile(modulePath);
bool success = module->deserialize(moduleData);
if (success) {
metadata["modules"][moduleName]["load_status"] = "ok";
writeJsonFile(savePath + "save_metadata.json", metadata);
LOG_INFO("Module {} successfully reloaded", moduleName);
}
}
catch (const std::exception& e) {
LOG_ERROR("Retry failed for module {}: {}", moduleName, e.what());
}
}
```
### Dirty Tracking
Seuls les chunks/metachunks **modifiés** sont sauvegardés pour optimiser la taille des saves.
```cpp
class ChunkManager {
private:
std::unordered_set<MetachunkCoords> dirtyMetachunks;
public:
void markChunkDirty(int chunkX, int chunkY) {
MetachunkCoords metaCoords = getMetachunkCoords(chunkX, chunkY);
dirtyMetachunks.insert(metaCoords);
}
void onBuildingPlaced(int x, int y) {
int chunkX = x / 64;
int chunkY = y / 64;
markChunkDirty(chunkX, chunkY);
}
void onResourceMined(int x, int y) {
int chunkX = x / 64;
int chunkY = y / 64;
markChunkDirty(chunkX, chunkY);
}
};
```
### Performance Targets V1
- **Autosave** : < 100ms pause perceptible (background thread recommandé)
- **Save manuelle** : < 500ms acceptable (avec feedback UI)
- **Load game** : < 3 secondes pour metadata + modules
- **Chunk streaming** : < 16ms par metachunk (1 frame @60fps)
- **Taille save** : ~10-50MB pour partie moyenne (dépend exploration)
### Limitations V1
Ces limitations seront résolues dans les versions futures :
1. **Pas de migration format** : Si le format JSON d'un module change, incompatibilité
2. **JSON verbeux** : Fichiers volumineux comparé à format binaire (acceptable pour debug)
3. **Pas de compression** : Taille disque non optimale
4. **Pas de checksums** : Corruption de données détectée tard (au parsing JSON)
5. **Concurrence limitée** : Save/load synchrones (pas de multi-threading)
## Évolution Future
### Version 2 : Compression et Migration
- **Compression LZ4/Zstd** : Réduction 60-80% taille fichiers
- **Format binaire optionnel** : MessagePack ou Protobuf pour données volumineuses
- **Migration automatique** : Système de conversion entre versions de format
- **Checksums** : Validation intégrité (CRC32, SHA256)
- **Async I/O** : Save/load en background threads
### Version 3 : Incremental Saves
- **Delta encoding** : Sauvegardes différentielles (snapshot + journal de changements)
- **Rollback temporel** : Restaurer état à timestamp spécifique
- **Compression inter-saves** : Déduplication entre sauvegardes
- **Hot-save** : Sauvegarde pendant le jeu sans pause
### Version 4 : Cloud et Multiplayer
- **Synchronisation cloud** : Steam Cloud, Google Drive, etc.
- **Save distribué** : Réplication multi-serveur automatique
- **Conflict resolution** : Merge intelligent pour multiplayer
- **Versioning git-like** : Branches, merge, rollback
## Références Croisées
- `module-versioning.md` : Système de versioning automatique des modules
- `architecture-modulaire.md` : Interface IModule, contraintes autonomie
- `systemes-techniques.md` : Architecture chunks multi-échelle
- `map-system.md` : Génération procédurale, resource patches
- `metriques-joueur.md` : Métriques à sauvegarder (3.1GB analytics)
## Exemples Pratiques
### Créer une Nouvelle Save
```cpp
// Single-process
saveSystem->saveGame("my_first_base");
// Multi-serveur
coordinator->broadcastSaveCommand("my_first_base_1728226320");
```
### Charger une Save Existante
```cpp
saveSystem->loadGame("my_first_base_1728226320");
```
### Éditer une Save Manuellement
```bash
# Ouvrir fichier module
nano saves/abc123_1728226320/server_1/economy.json
# Modifier prix du marché
# "iron_ore": 2.5 → "iron_ore": 5.0
# Recharger le module spécifique
saveSystem->retryLoadModule("economy");
```
### Lister les Saves Disponibles
```cpp
std::vector<SaveInfo> SaveSystem::listSaves() {
std::vector<SaveInfo> saves;
for (const auto& entry : fs::directory_iterator("saves/")) {
if (!entry.is_directory()) continue;
std::string metadataPath = entry.path().string() + "/save_metadata.json";
if (!fs::exists(metadataPath)) continue;
json metadata = readJsonFile(metadataPath);
saves.push_back({
.saveKey = metadata["save_key"],
.timestamp = metadata["timestamp"],
.gameVersion = metadata["game_version"],
.moduleCount = metadata["modules"].size(),
.metachunkCount = metadata["metachunks_count"]
});
}
// Trier par timestamp décroissant (plus récent en premier)
std::sort(saves.begin(), saves.end(), [](const auto& a, const auto& b) {
return a.timestamp > b.timestamp;
});
return saves;
}
```