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:
parent
076acd4812
commit
5e4235889a
677
docs/03-implementation/configuration/module-versioning.md
Normal file
677
docs/03-implementation/configuration/module-versioning.md
Normal 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
1414
docs/ai-framework.md
Normal file
File diff suppressed because it is too large
Load Diff
752
docs/calcul-menace.md
Normal file
752
docs/calcul-menace.md
Normal 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
1090
docs/systeme-diplomatique.md
Normal file
File diff suppressed because it is too large
Load Diff
715
docs/systeme-sauvegarde.md
Normal file
715
docs/systeme-sauvegarde.md
Normal 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;
|
||||||
|
}
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user