- 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>
1415 lines
38 KiB
Markdown
1415 lines
38 KiB
Markdown
# AI Framework - Unified Decision System
|
|
|
|
## Philosophie
|
|
|
|
Le système d'IA de Warfactory repose sur un **framework unifié de prise de décision par scoring**. Tous les types d'IA (tactique, opérationnelle, économique, diplomatique) utilisent le même pattern fondamental :
|
|
|
|
1. **Générer options** disponibles dans le contexte actuel
|
|
2. **Scorer chaque option** selon poids configurables et situation
|
|
3. **Choisir la meilleure option** (highest score)
|
|
4. **Exécuter l'action** correspondante
|
|
|
|
Ce pattern unifié permet :
|
|
- **Cohérence** : Même logique à travers tous les systèmes IA
|
|
- **Configurabilité** : Comportements ajustables via JSON
|
|
- **Apprentissage** : Reinforcement Learning ajuste les poids
|
|
- **Modularité** : Nouveaux types de décisions ajoutables facilement
|
|
- **Testabilité** : Chaque décision isolée et vérifiable
|
|
|
|
## Architecture Globale
|
|
|
|
### Hiérarchie des Modules IA
|
|
|
|
```
|
|
AI Framework
|
|
├── Core Decision System
|
|
│ ├── IDecision (interface de base)
|
|
│ ├── Decision Factory (création objets)
|
|
│ └── Scoring Engine (évaluation uniforme)
|
|
│
|
|
├── Helper Modules (services, pas de décisions)
|
|
│ ├── PathfinderModule (calcul chemins)
|
|
│ ├── AcquisitionModule (sélection cibles)
|
|
│ └── MovementModule (exécution mouvement)
|
|
│
|
|
├── Decision Modules (utilisent helpers)
|
|
│ ├── TacticalModule (combat temps-réel)
|
|
│ ├── OperationalModule (stratégie bataille)
|
|
│ ├── CompanyAIModule (business)
|
|
│ └── StateAIModule (politique)
|
|
│
|
|
└── Data Modules (consultés par tous)
|
|
├── DiplomacyModule (relations, traités)
|
|
└── EconomyModule (marché, prix)
|
|
```
|
|
|
|
### Flux de Décision Type
|
|
|
|
```
|
|
OperationalModule
|
|
↓ (donne posture: "rush blindé")
|
|
TacticalModule
|
|
↓ (demande chemins pour flanking)
|
|
PathfinderModule → retourne 3 chemins possibles
|
|
↓ (demande cibles prioritaires)
|
|
AcquisitionModule → retourne 5 cibles scorées
|
|
↓ (choisit meilleure tactique)
|
|
TacticalModule → décision: flanking par chemin 2, cible 1
|
|
↓ (exécute mouvement)
|
|
MovementModule → unités se déplacent
|
|
```
|
|
|
|
## Interface IDecision - Core Pattern
|
|
|
|
### Définition
|
|
|
|
Toutes les décisions IA implémentent cette interface :
|
|
|
|
```cpp
|
|
class IDecision {
|
|
public:
|
|
virtual ~IDecision() = default;
|
|
|
|
// 1. Générer toutes les options disponibles
|
|
virtual std::vector<Option> generateOptions() = 0;
|
|
|
|
// 2. Évaluer score d'une option dans le contexte
|
|
virtual float scoreOption(const Option& opt, const Context& ctx) = 0;
|
|
|
|
// 3. Choisir la meilleure option
|
|
virtual Option chooseBest() = 0;
|
|
|
|
// 4. Exécuter l'option choisie
|
|
virtual Action execute(const Option& opt) = 0;
|
|
|
|
// 5. Obtenir les poids configurés
|
|
virtual Weights getWeights() const = 0;
|
|
|
|
// 6. Définir nouveaux poids (pour RL)
|
|
virtual void setWeights(const Weights& weights) = 0;
|
|
};
|
|
```
|
|
|
|
### Pattern d'Usage : Decision Objects Éphémères
|
|
|
|
**Principe** : New → Use → Delete
|
|
|
|
```cpp
|
|
// Création décision éphémère
|
|
IDecision* decision = DecisionFactory::create(
|
|
"flanking_maneuver",
|
|
context,
|
|
weights
|
|
);
|
|
|
|
// Évaluation et choix
|
|
Option best = decision->chooseBest();
|
|
|
|
// Exécution
|
|
Action action = decision->execute(best);
|
|
|
|
// Destruction immédiate
|
|
delete decision;
|
|
```
|
|
|
|
**Avantages** :
|
|
- **Pas d'état persistant** : Chaque décision isolée
|
|
- **Parallélisation** : Évaluer N décisions simultanément sans conflits
|
|
- **Hot-reload** : Charger nouveaux types depuis modules .so
|
|
- **Memory safety** : Objets détruits après usage
|
|
|
|
**Alternative avec smart pointers** :
|
|
```cpp
|
|
auto decision = DecisionFactory::createUnique("flanking_maneuver", ctx, weights);
|
|
Option best = decision->chooseBest();
|
|
Action action = decision->execute(best);
|
|
// Auto-delete à la sortie de scope
|
|
```
|
|
|
|
### Structures de Données
|
|
|
|
#### Option
|
|
|
|
Représente une action possible :
|
|
|
|
```cpp
|
|
struct Option {
|
|
std::string type; // "flanking", "frontal_assault", etc.
|
|
json parameters; // Paramètres spécifiques
|
|
float base_score; // Score de base (avant modifiers)
|
|
std::vector<Modifier> modifiers; // Bonus/malus contextuels
|
|
};
|
|
```
|
|
|
|
#### Context
|
|
|
|
État du monde pertinent pour la décision :
|
|
|
|
```cpp
|
|
struct Context {
|
|
json world_state; // État général (terrain, météo, etc.)
|
|
json entities; // Entités pertinentes (unités, ennemis)
|
|
json objectives; // Objectifs actuels
|
|
json constraints; // Contraintes (budget, temps, etc.)
|
|
Doctrine* doctrine; // Doctrine appliquée (USSR, OTAN, etc.)
|
|
};
|
|
```
|
|
|
|
#### Weights
|
|
|
|
Poids configurables pour le scoring :
|
|
|
|
```cpp
|
|
struct Weights {
|
|
std::map<std::string, float> values; // "aggression": 0.8, "caution": 0.3
|
|
|
|
float get(const std::string& key, float default_val = 0.5f) const {
|
|
auto it = values.find(key);
|
|
return (it != values.end()) ? it->second : default_val;
|
|
}
|
|
};
|
|
|
|
// Chargement depuis JSON
|
|
Weights Weights::fromJson(const json& data) {
|
|
Weights w;
|
|
for (auto& [key, val] : data.items()) {
|
|
w.values[key] = val.get<float>();
|
|
}
|
|
return w;
|
|
}
|
|
```
|
|
|
|
## Système de Scoring Unifié
|
|
|
|
### Formule Générale
|
|
|
|
```cpp
|
|
float IDecision::scoreOption(const Option& opt, const Context& ctx) {
|
|
float score = opt.base_score;
|
|
|
|
// Application des poids
|
|
for (auto& [factor, importance] : weights.values) {
|
|
float factor_value = evaluateFactor(factor, opt, ctx);
|
|
score += factor_value * importance;
|
|
}
|
|
|
|
// Application des modifiers contextuels
|
|
for (auto& modifier : opt.modifiers) {
|
|
score *= modifier.multiplier;
|
|
}
|
|
|
|
// Normalisation (optionnelle)
|
|
return std::clamp(score, 0.0f, 1.0f);
|
|
}
|
|
```
|
|
|
|
### Exemple Concret : PathfinderModule
|
|
|
|
**Options** : 5 chemins possibles vers objectif
|
|
|
|
```cpp
|
|
class PathDecision : public IDecision {
|
|
std::vector<Option> generateOptions() override {
|
|
std::vector<Option> paths;
|
|
|
|
// Générer chemins alternatifs
|
|
for (auto& path : pathfinder->findAllPaths(start, end)) {
|
|
Option opt;
|
|
opt.type = "path";
|
|
opt.parameters["route"] = path.waypoints;
|
|
opt.parameters["distance"] = path.length;
|
|
opt.parameters["safety"] = path.threat_level;
|
|
opt.parameters["cover"] = path.cover_percentage;
|
|
opt.base_score = 0.5f;
|
|
|
|
// Modifiers contextuels
|
|
if (path.crosses_open_terrain) {
|
|
opt.modifiers.push_back({"open_terrain", 0.7f});
|
|
}
|
|
if (path.has_chokepoints) {
|
|
opt.modifiers.push_back({"chokepoint", 0.85f});
|
|
}
|
|
|
|
paths.push_back(opt);
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
float scoreOption(const Option& opt, const Context& ctx) override {
|
|
float score = opt.base_score;
|
|
|
|
// Facteurs de scoring
|
|
float distance = opt.parameters["distance"];
|
|
float safety = opt.parameters["safety"];
|
|
float cover = opt.parameters["cover"];
|
|
|
|
// Application poids configurables
|
|
score += (1.0f - distance / max_distance) * weights.get("speed");
|
|
score += safety * weights.get("safety");
|
|
score += cover * weights.get("stealth");
|
|
|
|
// Modifiers
|
|
for (auto& mod : opt.modifiers) {
|
|
score *= mod.multiplier;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Configuration Poids (JSON)** :
|
|
```json
|
|
{
|
|
"pathfinder_weights": {
|
|
"speed": 0.6,
|
|
"safety": 0.3,
|
|
"stealth": 0.1
|
|
}
|
|
}
|
|
```
|
|
|
|
**Usage** :
|
|
```cpp
|
|
auto decision = new PathDecision(start, end, context, weights);
|
|
Option bestPath = decision->chooseBest();
|
|
Action moveAction = decision->execute(bestPath);
|
|
delete decision;
|
|
```
|
|
|
|
### Exemple : AcquisitionModule
|
|
|
|
**Options** : Cibles ennemies visibles
|
|
|
|
```cpp
|
|
class AcquisitionDecision : public IDecision {
|
|
std::vector<Option> generateOptions() override {
|
|
std::vector<Option> targets;
|
|
|
|
for (auto& enemy : visibleEnemies) {
|
|
Option opt;
|
|
opt.type = "target";
|
|
opt.parameters["entity_id"] = enemy.id;
|
|
opt.parameters["threat"] = enemy.calculateThreat();
|
|
opt.parameters["vulnerability"] = enemy.calculateVulnerability();
|
|
opt.parameters["priority"] = enemy.strategic_value;
|
|
opt.base_score = 0.5f;
|
|
|
|
// Modifiers
|
|
if (enemy.is_wounded) {
|
|
opt.modifiers.push_back({"wounded", 1.2f});
|
|
}
|
|
if (enemy.has_cover) {
|
|
opt.modifiers.push_back({"covered", 0.8f});
|
|
}
|
|
|
|
targets.push_back(opt);
|
|
}
|
|
|
|
return targets;
|
|
}
|
|
|
|
float scoreOption(const Option& opt, const Context& ctx) override {
|
|
float score = opt.base_score;
|
|
|
|
float threat = opt.parameters["threat"];
|
|
float vulnerability = opt.parameters["vulnerability"];
|
|
float priority = opt.parameters["priority"];
|
|
|
|
// Poids doctrine
|
|
score += threat * weights.get("threat_priority");
|
|
score += vulnerability * weights.get("opportunism");
|
|
score += priority * weights.get("strategic_focus");
|
|
|
|
// Modifiers
|
|
for (auto& mod : opt.modifiers) {
|
|
score *= mod.multiplier;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Configuration** :
|
|
```json
|
|
{
|
|
"acquisition_weights": {
|
|
"threat_priority": 0.7,
|
|
"opportunism": 0.2,
|
|
"strategic_focus": 0.1
|
|
}
|
|
}
|
|
```
|
|
|
|
## Modules Spécialisés
|
|
|
|
### Helper Modules (Services)
|
|
|
|
Ces modules **ne prennent pas de décisions**, ils fournissent des services aux decision modules.
|
|
|
|
#### PathfinderModule
|
|
|
|
**Responsabilité** : Calculer chemins navigables entre deux points
|
|
|
|
**Interface** :
|
|
```cpp
|
|
class PathfinderModule : public IModule {
|
|
public:
|
|
// Trouver meilleur chemin unique
|
|
Path findPath(Position start, Position end, PathConstraints constraints);
|
|
|
|
// Trouver tous les chemins alternatifs
|
|
std::vector<Path> findAllPaths(Position start, Position end, int max_paths = 5);
|
|
|
|
// Vérifier si chemin encore valide
|
|
bool validatePath(const Path& path);
|
|
};
|
|
|
|
struct Path {
|
|
std::vector<Position> waypoints;
|
|
float length;
|
|
float threat_level; // Exposition ennemie
|
|
float cover_percentage; // % du chemin avec couvert
|
|
bool crosses_open_terrain;
|
|
bool has_chokepoints;
|
|
};
|
|
```
|
|
|
|
**Utilisé par** : TacticalModule, OperationalModule
|
|
|
|
#### AcquisitionModule
|
|
|
|
**Responsabilité** : Identifier et scorer cibles potentielles
|
|
|
|
**Interface** :
|
|
```cpp
|
|
class AcquisitionModule : public IModule {
|
|
public:
|
|
// Obtenir toutes les cibles visibles
|
|
std::vector<Target> getVisibleTargets(Unit observer);
|
|
|
|
// Scorer une cible selon contexte
|
|
float scoreTarget(Target target, Context ctx, Weights weights);
|
|
|
|
// Filtrer cibles selon critères
|
|
std::vector<Target> filterTargets(std::vector<Target> targets, FilterCriteria criteria);
|
|
};
|
|
|
|
struct Target {
|
|
EntityId id;
|
|
Position position;
|
|
float threat; // Dangerosité
|
|
float vulnerability; // Facilité élimination
|
|
float strategic_value; // Importance stratégique
|
|
bool is_wounded;
|
|
bool has_cover;
|
|
};
|
|
```
|
|
|
|
**Utilisé par** : TacticalModule
|
|
|
|
#### MovementModule
|
|
|
|
**Responsabilité** : Exécuter déplacements d'unités
|
|
|
|
**Interface** :
|
|
```cpp
|
|
class MovementModule : public IModule {
|
|
public:
|
|
// Déplacer unité selon chemin
|
|
void moveUnit(Unit unit, Path path);
|
|
|
|
// Déplacer formation
|
|
void moveFormation(std::vector<Unit> units, Path path, FormationType formation);
|
|
|
|
// Vérifier si mouvement possible
|
|
bool canMove(Unit unit, Position target);
|
|
};
|
|
```
|
|
|
|
**Utilisé par** : TacticalModule
|
|
|
|
### Decision Modules
|
|
|
|
Ces modules **prennent des décisions** en utilisant les helpers.
|
|
|
|
#### TacticalModule
|
|
|
|
**Responsabilité** : Décisions combat temps-réel (secondes/minutes)
|
|
|
|
**Décisions prises** :
|
|
- Choix tactique (flanking, frontal assault, suppression, retreat)
|
|
- Sélection cibles
|
|
- Positionnement unités
|
|
- Utilisation couvert
|
|
- Timing engagements
|
|
|
|
**Dépendances** :
|
|
- PathfinderModule (chemins)
|
|
- AcquisitionModule (cibles)
|
|
- MovementModule (exécution)
|
|
|
|
**Exemple Décision** :
|
|
```cpp
|
|
class FlankingDecision : public IDecision {
|
|
std::vector<Option> generateOptions() override {
|
|
std::vector<Option> options;
|
|
|
|
// Pour chaque flanc possible
|
|
for (auto& flankPosition : identifyFlankPositions(enemy)) {
|
|
// Demander chemins au PathfinderModule
|
|
auto paths = pathfinder->findAllPaths(myPosition, flankPosition);
|
|
|
|
for (auto& path : paths) {
|
|
// Demander cibles à AcquisitionModule
|
|
auto targets = acquisition->getVisibleTargets(myUnit);
|
|
|
|
Option opt;
|
|
opt.type = "flanking";
|
|
opt.parameters["path"] = path;
|
|
opt.parameters["flank_side"] = flankPosition.side; // "left"/"right"
|
|
opt.parameters["targets"] = targets;
|
|
opt.base_score = 0.6f; // Flanking généralement bon
|
|
|
|
// Modifiers
|
|
if (path.crosses_open_terrain) {
|
|
opt.modifiers.push_back({"exposed_approach", 0.7f});
|
|
}
|
|
if (targets.size() > 3) {
|
|
opt.modifiers.push_back({"many_targets", 1.2f});
|
|
}
|
|
|
|
options.push_back(opt);
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
float scoreOption(const Option& opt, const Context& ctx) override {
|
|
// Récupérer poids doctrine
|
|
Doctrine* doctrine = ctx.doctrine;
|
|
float flanking_preference = doctrine->tactical_weights["flanking"];
|
|
|
|
float score = opt.base_score * flanking_preference;
|
|
|
|
// Facteurs tactiques
|
|
Path path = opt.parameters["path"];
|
|
score += path.cover_percentage * weights.get("safety");
|
|
score -= path.threat_level * weights.get("caution");
|
|
|
|
// Modifiers
|
|
for (auto& mod : opt.modifiers) {
|
|
score *= mod.multiplier;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Configuration Poids** :
|
|
```json
|
|
{
|
|
"tactical_weights": {
|
|
"safety": 0.4,
|
|
"caution": 0.3,
|
|
"aggression": 0.7
|
|
}
|
|
}
|
|
```
|
|
|
|
#### OperationalModule
|
|
|
|
**Responsabilité** : Choix stratégie/posture bataille (heures/jours)
|
|
|
|
**Décisions prises** :
|
|
- Posture opérationnelle (rush blindé, raid cavalerie, défense en profondeur)
|
|
- Allocation forces entre secteurs
|
|
- Timing offensives
|
|
- Objectifs prioritaires
|
|
|
|
**Dépendances** :
|
|
- TacticalModule (exécution tactique)
|
|
- DiplomacyModule (contexte stratégique)
|
|
|
|
**Exemple Décision** :
|
|
```cpp
|
|
class OperationalPostureDecision : public IDecision {
|
|
std::vector<Option> generateOptions() override {
|
|
std::vector<Option> postures;
|
|
|
|
// Postures disponibles selon doctrine
|
|
for (auto& [posture_name, posture_weight] : doctrine->operational_weights) {
|
|
Option opt;
|
|
opt.type = "posture";
|
|
opt.parameters["name"] = posture_name;
|
|
opt.base_score = posture_weight;
|
|
|
|
// Évaluer faisabilité
|
|
if (posture_name == "rush_blinde" && terrain.is_open) {
|
|
opt.modifiers.push_back({"favorable_terrain", 1.3f});
|
|
}
|
|
if (posture_name == "infiltration" && units.has_infantry) {
|
|
opt.modifiers.push_back({"suitable_units", 1.2f});
|
|
}
|
|
|
|
postures.push_back(opt);
|
|
}
|
|
|
|
return postures;
|
|
}
|
|
|
|
Action execute(const Option& opt) override {
|
|
std::string posture = opt.parameters["name"];
|
|
|
|
// Donner directive au TacticalModule
|
|
tacticalModule->setPosture(posture);
|
|
|
|
// Ajuster poids tactiques selon posture
|
|
if (posture == "rush_blinde") {
|
|
tacticalModule->adjustWeights({
|
|
{"flanking", 1.5f}, // Boost flanking
|
|
{"caution", 0.5f} // Réduire caution
|
|
});
|
|
}
|
|
|
|
return Action{posture, opt.parameters};
|
|
}
|
|
};
|
|
```
|
|
|
|
**Doctrines Opérationnelles** :
|
|
|
|
```json
|
|
// doctrines/ussr.json
|
|
{
|
|
"name": "USSR Deep Battle",
|
|
"operational_weights": {
|
|
"deep_battle": 0.8,
|
|
"overwhelming_assault": 0.9,
|
|
"mobile_defense": 0.4,
|
|
"raid_cavalry": 0.3,
|
|
"infiltration": 0.2
|
|
},
|
|
"tactical_weights": {
|
|
"frontal_assault": 0.8,
|
|
"flanking": 0.5,
|
|
"combined_arms": 0.7,
|
|
"retreat_threshold": 0.2
|
|
}
|
|
}
|
|
|
|
// doctrines/nato.json
|
|
{
|
|
"name": "NATO AirLand Battle",
|
|
"operational_weights": {
|
|
"airland_battle": 0.9,
|
|
"maneuver_warfare": 0.8,
|
|
"deep_strike": 0.7,
|
|
"overwhelming_assault": 0.4,
|
|
"mobile_defense": 0.6
|
|
},
|
|
"tactical_weights": {
|
|
"frontal_assault": 0.3,
|
|
"flanking": 0.9,
|
|
"combined_arms": 0.9,
|
|
"retreat_threshold": 0.6
|
|
}
|
|
}
|
|
```
|
|
|
|
#### CompanyAIModule
|
|
|
|
**Responsabilité** : Décisions business (jours/semaines)
|
|
|
|
**Décisions prises** :
|
|
- Investissements R&D
|
|
- Production (quoi produire, combien)
|
|
- Prix et stratégie commerciale
|
|
- Lobbying états
|
|
- Acquisitions/expansions
|
|
|
|
**Dépendances** :
|
|
- EconomyModule (prix marché, demande)
|
|
- DiplomacyModule (relations, influence)
|
|
|
|
**Exemple Décision** :
|
|
```cpp
|
|
class CompanyInvestmentDecision : public IDecision {
|
|
std::vector<Option> generateOptions() override {
|
|
std::vector<Option> investments;
|
|
|
|
// R&D options
|
|
for (auto& tech : availableTechnologies) {
|
|
Option opt;
|
|
opt.type = "research";
|
|
opt.parameters["tech_id"] = tech.id;
|
|
opt.parameters["cost"] = tech.research_cost;
|
|
opt.parameters["expected_profit"] = estimateProfit(tech);
|
|
opt.base_score = 0.5f;
|
|
|
|
if (tech.is_breakthrough) {
|
|
opt.modifiers.push_back({"breakthrough", 1.5f});
|
|
}
|
|
|
|
investments.push_back(opt);
|
|
}
|
|
|
|
// Production expansion
|
|
Option expand;
|
|
expand.type = "expand_production";
|
|
expand.parameters["cost"] = calculate_expansion_cost();
|
|
expand.parameters["capacity_increase"] = 0.3f;
|
|
expand.base_score = 0.4f;
|
|
investments.push_back(expand);
|
|
|
|
// Lobbying
|
|
for (auto& state : states) {
|
|
float influence = diplomacy->getInfluence(company_id, state.id);
|
|
if (influence < 0.8f) {
|
|
Option lobby;
|
|
lobby.type = "lobbying";
|
|
lobby.parameters["state_id"] = state.id;
|
|
lobby.parameters["cost"] = calculateLobbyCost(state);
|
|
lobby.parameters["influence_gain"] = 0.1f;
|
|
lobby.base_score = 0.3f;
|
|
investments.push_back(lobby);
|
|
}
|
|
}
|
|
|
|
return investments;
|
|
}
|
|
|
|
float scoreOption(const Option& opt, const Context& ctx) override {
|
|
float score = opt.base_score;
|
|
|
|
if (opt.type == "research") {
|
|
float expected_profit = opt.parameters["expected_profit"];
|
|
float cost = opt.parameters["cost"];
|
|
float roi = expected_profit / cost;
|
|
|
|
score += roi * weights.get("innovation");
|
|
score -= (cost / company_budget) * weights.get("risk_aversion");
|
|
}
|
|
else if (opt.type == "lobbying") {
|
|
float influence_gain = opt.parameters["influence_gain"];
|
|
score += influence_gain * weights.get("political_focus");
|
|
}
|
|
|
|
for (auto& mod : opt.modifiers) {
|
|
score *= mod.multiplier;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Configuration Company** :
|
|
```json
|
|
{
|
|
"company_ai_weights": {
|
|
"innovation": 0.7,
|
|
"risk_aversion": 0.4,
|
|
"political_focus": 0.6,
|
|
"profit_maximization": 0.8,
|
|
"quality_focus": 0.6
|
|
}
|
|
}
|
|
```
|
|
|
|
#### StateAIModule
|
|
|
|
**Responsabilité** : Décisions étatiques (semaines/mois)
|
|
|
|
**Décisions prises** :
|
|
- Achats militaires
|
|
- Alliances diplomatiques
|
|
- Budgets sectoriels
|
|
- Objectifs stratégiques
|
|
|
|
**Dépendances** :
|
|
- DiplomacyModule (relations)
|
|
- EconomyModule (budget disponible)
|
|
- OperationalModule (besoins militaires)
|
|
|
|
**Exemple Décision** :
|
|
```cpp
|
|
class StateMilitaryPurchaseDecision : public IDecision {
|
|
std::vector<Option> generateOptions() override {
|
|
std::vector<Option> purchases;
|
|
|
|
// Pour chaque Company
|
|
for (auto& company : companies) {
|
|
// Vérifier relation diplomatique
|
|
float relation = diplomacy->getRelation(state_id, company.id);
|
|
if (relation < 0.3f) continue; // Trop mauvaise relation
|
|
|
|
// Pour chaque véhicule disponible
|
|
for (auto& vehicle : company.catalog) {
|
|
Option opt;
|
|
opt.type = "purchase";
|
|
opt.parameters["company_id"] = company.id;
|
|
opt.parameters["vehicle_id"] = vehicle.id;
|
|
opt.parameters["price"] = vehicle.price;
|
|
opt.parameters["performance"] = vehicle.combat_rating;
|
|
opt.base_score = 0.5f;
|
|
|
|
// Modifiers
|
|
if (relation > 0.8f) {
|
|
opt.modifiers.push_back({"good_relations", 1.2f});
|
|
}
|
|
if (company.reputation > 0.9f) {
|
|
opt.modifiers.push_back({"trusted_brand", 1.1f});
|
|
}
|
|
|
|
purchases.push_back(opt);
|
|
}
|
|
}
|
|
|
|
return purchases;
|
|
}
|
|
|
|
float scoreOption(const Option& opt, const Context& ctx) override {
|
|
float score = opt.base_score;
|
|
|
|
float price = opt.parameters["price"];
|
|
float performance = opt.parameters["performance"];
|
|
float value = performance / price;
|
|
|
|
score += value * weights.get("cost_effectiveness");
|
|
score += performance * weights.get("quality_priority");
|
|
|
|
// Influence politique
|
|
CompanyId company = opt.parameters["company_id"];
|
|
float influence = diplomacy->getInfluence(company, state_id);
|
|
score += influence * weights.get("lobbying_susceptibility");
|
|
|
|
for (auto& mod : opt.modifiers) {
|
|
score *= mod.multiplier;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
};
|
|
```
|
|
|
|
**Configuration State** :
|
|
```json
|
|
{
|
|
"state_ai_weights": {
|
|
"cost_effectiveness": 0.6,
|
|
"quality_priority": 0.7,
|
|
"lobbying_susceptibility": 0.4,
|
|
"strategic_autonomy": 0.5
|
|
}
|
|
}
|
|
```
|
|
|
|
## Decision Factory
|
|
|
|
### Création Dynamique
|
|
|
|
```cpp
|
|
class DecisionFactory {
|
|
public:
|
|
// Enregistrer types de décisions
|
|
static void registerDecision(const std::string& type, DecisionCreator creator);
|
|
|
|
// Créer décision par nom
|
|
static IDecision* create(
|
|
const std::string& type,
|
|
const Context& ctx,
|
|
const Weights& weights
|
|
);
|
|
|
|
// Créer avec smart pointer
|
|
static std::unique_ptr<IDecision> createUnique(
|
|
const std::string& type,
|
|
const Context& ctx,
|
|
const Weights& weights
|
|
);
|
|
|
|
private:
|
|
static std::map<std::string, DecisionCreator> registry;
|
|
};
|
|
|
|
// Type pour fonction de création
|
|
using DecisionCreator = std::function<IDecision*(const Context&, const Weights&)>;
|
|
```
|
|
|
|
### Enregistrement Décisions
|
|
|
|
```cpp
|
|
// Dans l'initialisation du module
|
|
void TacticalModule::initialize() {
|
|
DecisionFactory::registerDecision("flanking", [](auto ctx, auto weights) {
|
|
return new FlankingDecision(ctx, weights);
|
|
});
|
|
|
|
DecisionFactory::registerDecision("frontal_assault", [](auto ctx, auto weights) {
|
|
return new FrontalAssaultDecision(ctx, weights);
|
|
});
|
|
|
|
DecisionFactory::registerDecision("retreat", [](auto ctx, auto weights) {
|
|
return new RetreatDecision(ctx, weights);
|
|
});
|
|
}
|
|
```
|
|
|
|
### Chargement depuis Modules .so
|
|
|
|
```cpp
|
|
// Hot-reload de nouveaux types de décisions
|
|
void AIFramework::loadDecisionModule(const std::string& module_path) {
|
|
void* handle = dlopen(module_path.c_str(), RTLD_LAZY);
|
|
|
|
// Récupérer fonction d'enregistrement
|
|
auto registerFunc = (RegisterDecisionsFunc)dlsym(handle, "registerDecisions");
|
|
|
|
// Enregistrer les décisions du module
|
|
registerFunc(DecisionFactory::getInstance());
|
|
}
|
|
```
|
|
|
|
## Reinforcement Learning Integration
|
|
|
|
### Ajustement des Poids
|
|
|
|
Le système RL ajuste les poids selon résultats des décisions :
|
|
|
|
```cpp
|
|
class RLEngine {
|
|
public:
|
|
// Enregistrer une décision et son outcome
|
|
void recordDecision(
|
|
const std::string& decision_type,
|
|
const Weights& weights_used,
|
|
const Option& chosen_option,
|
|
float outcome_reward // -1.0 à +1.0
|
|
);
|
|
|
|
// Ajuster poids selon historical performance
|
|
Weights adjustWeights(
|
|
const std::string& decision_type,
|
|
const Weights& current_weights,
|
|
float learning_rate = 0.1f
|
|
);
|
|
|
|
// Obtenir poids optimisés pour contexte
|
|
Weights getOptimizedWeights(
|
|
const std::string& decision_type,
|
|
const Context& ctx
|
|
);
|
|
|
|
private:
|
|
struct DecisionRecord {
|
|
Weights weights;
|
|
Option option;
|
|
float reward;
|
|
Context context;
|
|
};
|
|
|
|
std::map<std::string, std::vector<DecisionRecord>> history;
|
|
};
|
|
```
|
|
|
|
### Exemple RL Tactique
|
|
|
|
```cpp
|
|
// Enregistrer décision
|
|
auto decision = new FlankingDecision(ctx, current_weights);
|
|
Option best = decision->chooseBest();
|
|
Action action = decision->execute(best);
|
|
delete decision;
|
|
|
|
// Observer résultat combat
|
|
float outcome = evaluateCombatOutcome(); // -1.0 (défaite) à +1.0 (victoire)
|
|
|
|
// Enregistrer pour RL
|
|
rlEngine->recordDecision("flanking", current_weights, best, outcome);
|
|
|
|
// Ajuster poids pour prochaine fois
|
|
Weights new_weights = rlEngine->adjustWeights("flanking", current_weights);
|
|
saveWeights("tactical_flanking_weights.json", new_weights);
|
|
```
|
|
|
|
### Poids Initiaux (Manuels)
|
|
|
|
**Important** : En V1, pas de training automatique. Poids configurés manuellement dans JSON.
|
|
|
|
```json
|
|
// config/ai/tactical_initial_weights.json
|
|
{
|
|
"flanking": {
|
|
"safety": 0.4,
|
|
"caution": 0.3,
|
|
"aggression": 0.7,
|
|
"speed": 0.6
|
|
},
|
|
"frontal_assault": {
|
|
"safety": 0.2,
|
|
"caution": 0.1,
|
|
"aggression": 0.9,
|
|
"overwhelming_force": 0.8
|
|
},
|
|
"retreat": {
|
|
"safety": 0.9,
|
|
"caution": 0.8,
|
|
"preservation": 0.9
|
|
}
|
|
}
|
|
```
|
|
|
|
Le RL ajuste ces valeurs au fil du temps selon expérience.
|
|
|
|
## Performance et Optimisation
|
|
|
|
### Object Pooling
|
|
|
|
Pour éviter new/delete fréquents :
|
|
|
|
```cpp
|
|
class DecisionPool {
|
|
public:
|
|
template<typename T>
|
|
T* acquire(const Context& ctx, const Weights& weights) {
|
|
if (pool.empty()) {
|
|
return new T(ctx, weights);
|
|
}
|
|
T* obj = pool.back();
|
|
pool.pop_back();
|
|
obj->reset(ctx, weights);
|
|
return obj;
|
|
}
|
|
|
|
template<typename T>
|
|
void release(T* obj) {
|
|
pool.push_back(obj);
|
|
}
|
|
|
|
private:
|
|
std::vector<IDecision*> pool;
|
|
};
|
|
```
|
|
|
|
**Usage** :
|
|
```cpp
|
|
auto decision = decisionPool->acquire<FlankingDecision>(ctx, weights);
|
|
Option best = decision->chooseBest();
|
|
decisionPool->release(decision);
|
|
```
|
|
|
|
### Parallélisation
|
|
|
|
Évaluer plusieurs décisions simultanément :
|
|
|
|
```cpp
|
|
std::vector<Option> evaluateDecisionsParallel(
|
|
const std::vector<std::string>& decision_types,
|
|
const Context& ctx,
|
|
const Weights& weights
|
|
) {
|
|
std::vector<std::future<Option>> futures;
|
|
|
|
for (auto& type : decision_types) {
|
|
futures.push_back(std::async(std::launch::async, [&]() {
|
|
auto decision = DecisionFactory::createUnique(type, ctx, weights);
|
|
return decision->chooseBest();
|
|
}));
|
|
}
|
|
|
|
std::vector<Option> results;
|
|
for (auto& future : futures) {
|
|
results.push_back(future.get());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
```
|
|
|
|
### Performance Targets
|
|
|
|
- **TacticalModule** : 60 décisions/seconde (temps-réel combat)
|
|
- **OperationalModule** : 10 décisions/seconde (moins fréquent)
|
|
- **CompanyAI** : 1 décision/seconde (batch processing)
|
|
- **StateAI** : 0.1 décision/seconde (décisions rares)
|
|
|
|
## Intégration Architecture Modulaire
|
|
|
|
### AI Framework comme Module
|
|
|
|
```cpp
|
|
class AIFrameworkModule : public IModule {
|
|
public:
|
|
void initialize() override {
|
|
// Charger configurations
|
|
loadDoctrines("config/doctrines/");
|
|
loadWeights("config/ai/");
|
|
|
|
// Enregistrer décisions
|
|
registerAllDecisions();
|
|
|
|
// Initialiser RL engine
|
|
rlEngine = new RLEngine();
|
|
}
|
|
|
|
json process(const json& input) override {
|
|
// Traiter requêtes de décision
|
|
std::string decision_type = input["decision_type"];
|
|
Context ctx = Context::fromJson(input["context"]);
|
|
Weights weights = getWeights(decision_type);
|
|
|
|
auto decision = DecisionFactory::createUnique(decision_type, ctx, weights);
|
|
Option best = decision->chooseBest();
|
|
|
|
json output;
|
|
output["chosen_option"] = best.toJson();
|
|
return output;
|
|
}
|
|
|
|
void shutdown() override {
|
|
delete rlEngine;
|
|
}
|
|
};
|
|
```
|
|
|
|
### Communication avec Autres Modules
|
|
|
|
**TacticalModule demande décision** :
|
|
|
|
```cpp
|
|
// TacticalModule process()
|
|
json request;
|
|
request["decision_type"] = "flanking";
|
|
request["context"] = currentContext.toJson();
|
|
|
|
json response = sendToModule("ai_framework", request);
|
|
Option chosen = Option::fromJson(response["chosen_option"]);
|
|
|
|
// Exécuter
|
|
executeOption(chosen);
|
|
```
|
|
|
|
**OperationalModule configure TacticalModule** :
|
|
|
|
```cpp
|
|
// OperationalModule choisit posture
|
|
auto decision = new OperationalPostureDecision(ctx, weights);
|
|
Option posture = decision->chooseBest();
|
|
|
|
// Envoyer directive à TacticalModule
|
|
json directive;
|
|
directive["posture"] = posture.parameters["name"];
|
|
directive["weight_adjustments"] = calculateAdjustments(posture);
|
|
|
|
sendToModule("tactical", directive);
|
|
```
|
|
|
|
## Configuration et Données
|
|
|
|
### Structure Fichiers Config
|
|
|
|
```
|
|
config/
|
|
├── ai/
|
|
│ ├── tactical_weights.json
|
|
│ ├── operational_weights.json
|
|
│ ├── company_weights.json
|
|
│ └── state_weights.json
|
|
├── doctrines/
|
|
│ ├── ussr.json
|
|
│ ├── nato.json
|
|
│ ├── china.json
|
|
│ └── guerrilla.json
|
|
└── rl/
|
|
├── learning_rates.json
|
|
└── reward_functions.json
|
|
```
|
|
|
|
### Exemple Configuration Complète
|
|
|
|
**config/ai/tactical_weights.json** :
|
|
```json
|
|
{
|
|
"flanking": {
|
|
"safety": 0.4,
|
|
"caution": 0.3,
|
|
"aggression": 0.7,
|
|
"speed": 0.6,
|
|
"stealth": 0.5
|
|
},
|
|
"frontal_assault": {
|
|
"safety": 0.2,
|
|
"caution": 0.1,
|
|
"aggression": 0.9,
|
|
"overwhelming_force": 0.8,
|
|
"speed": 0.4
|
|
},
|
|
"suppression": {
|
|
"safety": 0.6,
|
|
"rate_of_fire": 0.9,
|
|
"ammunition_conservation": 0.3,
|
|
"area_denial": 0.8
|
|
},
|
|
"retreat": {
|
|
"safety": 0.9,
|
|
"caution": 0.8,
|
|
"preservation": 0.9,
|
|
"speed": 0.7,
|
|
"covering_fire": 0.6
|
|
}
|
|
}
|
|
```
|
|
|
|
**config/doctrines/ussr.json** (complet) :
|
|
```json
|
|
{
|
|
"name": "USSR Deep Battle Doctrine",
|
|
"description": "Emphasis on overwhelming force, combined arms, and continuous offensive operations",
|
|
|
|
"operational_weights": {
|
|
"deep_battle": 0.8,
|
|
"overwhelming_assault": 0.9,
|
|
"echeloned_attack": 0.7,
|
|
"mobile_defense": 0.4,
|
|
"raid_cavalry": 0.3,
|
|
"infiltration": 0.2,
|
|
"attritional_warfare": 0.6
|
|
},
|
|
|
|
"tactical_weights": {
|
|
"frontal_assault": 0.8,
|
|
"flanking": 0.5,
|
|
"combined_arms": 0.7,
|
|
"artillery_preparation": 0.9,
|
|
"retreat_threshold": 0.2,
|
|
"counterattack": 0.7,
|
|
"urban_combat": 0.6
|
|
},
|
|
|
|
"resource_priorities": {
|
|
"armor_quantity": 0.9,
|
|
"artillery": 0.8,
|
|
"infantry_mass": 0.7,
|
|
"air_support": 0.5,
|
|
"logistics": 0.6
|
|
},
|
|
|
|
"special_characteristics": {
|
|
"accepts_high_casualties": true,
|
|
"prefers_night_operations": true,
|
|
"emphasizes_political_officers": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**config/doctrines/nato.json** (complet) :
|
|
```json
|
|
{
|
|
"name": "NATO AirLand Battle Doctrine",
|
|
"description": "Deep strike, maneuver warfare, and combined arms with heavy air integration",
|
|
|
|
"operational_weights": {
|
|
"airland_battle": 0.9,
|
|
"maneuver_warfare": 0.8,
|
|
"deep_strike": 0.7,
|
|
"defensive_depth": 0.6,
|
|
"overwhelming_assault": 0.4,
|
|
"mobile_defense": 0.6,
|
|
"decapitation_strike": 0.7
|
|
},
|
|
|
|
"tactical_weights": {
|
|
"frontal_assault": 0.3,
|
|
"flanking": 0.9,
|
|
"combined_arms": 0.9,
|
|
"artillery_preparation": 0.6,
|
|
"retreat_threshold": 0.6,
|
|
"air_strikes": 0.9,
|
|
"precision_engagement": 0.8
|
|
},
|
|
|
|
"resource_priorities": {
|
|
"armor_quality": 0.9,
|
|
"air_superiority": 0.9,
|
|
"precision_munitions": 0.8,
|
|
"C4ISR": 0.9,
|
|
"logistics": 0.8
|
|
},
|
|
|
|
"special_characteristics": {
|
|
"accepts_high_casualties": false,
|
|
"prefers_technology_advantage": true,
|
|
"emphasizes_initiative": true
|
|
}
|
|
}
|
|
```
|
|
|
|
## Évolution Future
|
|
|
|
### Version 2 : Apprentissage Actif
|
|
|
|
- **Auto-training** : RL s'entraîne via simulations automatiques
|
|
- **Meta-learning** : Adaptation rapide à nouvelles situations
|
|
- **Transfer learning** : Connaissances transférées entre doctrines
|
|
|
|
### Version 3 : Comportements Émergents
|
|
|
|
- **Genetic algorithms** : Companies/States évoluent génération par génération
|
|
- **Multi-agent RL** : Plusieurs IA apprennent en compétition/coopération
|
|
- **Adaptive doctrines** : Doctrines mutent selon expérience
|
|
|
|
### Version 4 : Neural Networks
|
|
|
|
- **Deep RL** : Réseaux neurones pour décisions complexes
|
|
- **Pattern recognition** : Reconnaître situations similaires historiques
|
|
- **Predictive modeling** : Anticiper actions adversaires
|
|
|
|
## Références Croisées
|
|
|
|
- `systeme-militaire.md` : Combat, véhicules, doctrines militaires
|
|
- `economie-logistique.md` : Marché, pricing, supply chains
|
|
- `architecture-modulaire.md` : Intégration modules, hot-reload
|
|
- `systeme-sauvegarde.md` : Persistence états IA, poids RL
|
|
|
|
## Exemples Pratiques
|
|
|
|
### Exemple Complet : Décision Tactique
|
|
|
|
```cpp
|
|
// Contexte bataille
|
|
Context ctx;
|
|
ctx.world_state = {
|
|
{"terrain", "open_plains"},
|
|
{"weather", "clear"},
|
|
{"time_of_day", "dawn"}
|
|
};
|
|
ctx.entities = {
|
|
{"my_units", {{"tanks", 10}, {"ifv", 5}}},
|
|
{"enemy_units", {{"tanks", 8}, {"infantry", 20}}}
|
|
};
|
|
ctx.objectives = {
|
|
{"primary", "secure_hill"},
|
|
{"secondary", "minimize_casualties"}
|
|
};
|
|
ctx.doctrine = doctrineManager->getDoctrine("nato");
|
|
|
|
// Charger poids
|
|
Weights weights = loadWeights("config/ai/tactical_weights.json", "flanking");
|
|
|
|
// Créer décision
|
|
auto decision = DecisionFactory::createUnique("flanking", ctx, weights);
|
|
|
|
// Générer et évaluer options
|
|
std::vector<Option> options = decision->generateOptions();
|
|
for (auto& opt : options) {
|
|
float score = decision->scoreOption(opt, ctx);
|
|
LOG_DEBUG("Option: {} - Score: {}", opt.type, score);
|
|
}
|
|
|
|
// Choisir meilleure
|
|
Option best = decision->chooseBest();
|
|
LOG_INFO("Chosen: {} (score: {})", best.type, best.base_score);
|
|
|
|
// Exécuter
|
|
Action action = decision->execute(best);
|
|
|
|
// Observer résultat et apprendre
|
|
float outcome = waitForBattleResult();
|
|
rlEngine->recordDecision("flanking", weights, best, outcome);
|
|
```
|
|
|
|
### Exemple : Company AI Investment
|
|
|
|
```cpp
|
|
// Contexte company
|
|
Context ctx;
|
|
ctx.world_state = {
|
|
{"market_demand", "high"},
|
|
{"tech_level", "gen3"},
|
|
{"competition", "moderate"}
|
|
};
|
|
ctx.entities = {
|
|
{"budget", 1000000},
|
|
{"reputation", 0.75},
|
|
{"research_capacity", 5}
|
|
};
|
|
|
|
Weights weights = companyProfile.weights; // Profil company unique
|
|
|
|
auto decision = new CompanyInvestmentDecision(ctx, weights);
|
|
Option investment = decision->chooseBest();
|
|
|
|
if (investment.type == "research") {
|
|
TechId tech = investment.parameters["tech_id"];
|
|
startResearch(tech);
|
|
}
|
|
else if (investment.type == "lobbying") {
|
|
StateId state = investment.parameters["state_id"];
|
|
float cost = investment.parameters["cost"];
|
|
lobbyState(state, cost);
|
|
}
|
|
|
|
delete decision;
|
|
```
|
|
|
|
## Testing et Validation
|
|
|
|
### Test Unitaire Decision
|
|
|
|
```cpp
|
|
#ifdef TESTING
|
|
void testFlankingDecision() {
|
|
// Setup contexte test
|
|
Context ctx = createTestContext();
|
|
Weights weights = {{"safety", 0.5}, {"aggression", 0.8}};
|
|
|
|
// Créer décision
|
|
auto decision = new FlankingDecision(ctx, weights);
|
|
|
|
// Vérifier génération options
|
|
auto options = decision->generateOptions();
|
|
assert(options.size() > 0);
|
|
|
|
// Vérifier scoring
|
|
for (auto& opt : options) {
|
|
float score = decision->scoreOption(opt, ctx);
|
|
assert(score >= 0.0f && score <= 1.0f);
|
|
}
|
|
|
|
// Vérifier choix cohérent
|
|
Option best = decision->chooseBest();
|
|
assert(best.type == "flanking");
|
|
|
|
delete decision;
|
|
}
|
|
#endif
|
|
```
|
|
|
|
### Benchmark Performance
|
|
|
|
```cpp
|
|
void benchmarkDecisionSpeed() {
|
|
Context ctx = createBenchmarkContext();
|
|
Weights weights = loadWeights("tactical_weights.json", "flanking");
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
auto decision = new FlankingDecision(ctx, weights);
|
|
Option best = decision->chooseBest();
|
|
delete decision;
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
|
|
|
std::cout << "1000 decisions in " << duration.count() << "μs\n";
|
|
std::cout << "Average: " << (duration.count() / 1000.0f) << "μs per decision\n";
|
|
}
|
|
```
|