Files
ProjectFGame/Architecture_Projet.md
T
DodoandClaude Opus 5.5 d19df0a428 Rangement de Content : maps de test dans Maps/DevTest, packs dans Assets/
- Toutes les maps de test regroupées dans Content/Maps/DevTest (+ HLOD/).
- Animations et Kimodo -> Content/Assets/Animations ; Prop, PropsParis,
  RueDeParis et Bridges -> Content/Assets/Environment ; MetaHuman "test"
  -> Characters/MEtahuman/Unpacked/test.
- KitFR et ProjectF restent à la racine (trop lourds pour l'éditeur).
- Suppression des assets AST inutilisés du StateManager, des maps de démo
  tierces, des restes de test et de ~280 redirecteurs.
- CoreRedirect /Game/Bridges/ pour les fichiers de Bridges déplacés sur disque.
- Notifies AST ReloadNotify/SetEquippedWeapon vidés (appelaient l'inventaire AST).
- Architecture_Projet.md à jour (§7, §8, §9, journal).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 20:34:21 +01:00

1054 lines
222 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🏗️ Architecture du Projet — ProjectF
> **Ce document est la référence d'architecture du projet, destinée en priorité aux modèles d'IA (Claude, etc.) qui vont lire, modifier ou créer du contenu dans ce projet Unreal Engine.**
> Il doit permettre de comprendre **où se trouve chaque système, quel fichier est responsable de quoi, et où ajouter du nouveau contenu** — sans avoir à explorer tout le projet à chaque session.
>
> ⚠️ **Ce document doit rester à jour.** Voir la section [10. Maintenir ce document à jour](#10-maintenir-ce-document-à-jour) et le skill `maintain-architecture-doc`.
---
## 1. Vue d'ensemble du projet
### 1.1 Nature du projet
- **Moteur** : Unreal Engine **5.8**.
- **Aucun code C++** : il n'y a pas de dossier `Source/`. Tout le gameplay est écrit en **Blueprint visuel**, dans `Content/`.
- **Genre** : jeu de survie / extraction en vue 3e personne avec un **inventaire en grille façon "Escape from Tarkov"** (armure, sac à dos, armes, munitions, craft sur feu de camp/fonderie, générateurs, lecteurs de carte magnétique, coffres, PNJ pillables, véhicules).
- **Multijoueur** : le système d'inventaire est répliqué (RPC client/serveur visibles partout dans `Inventory_Player`, ex. `Request Switch Weapon SERVER`, `Disarm Request SERVER`). Le projet doit donc être pensé serveur-autoritaire pour tout ce qui touche à l'inventaire, aux stats et au combat.
### 1.2 Les grandes couches du projet
Le projet est construit par empilement de **trois grands blocs** de contenu, plus une couche d'outillage IA :
1. **Framework de personnage / locomotion ("Sandbox Character")** — `Content/Systems/Blueprints/`
Basé sur le sample officiel Epic "Motion Matching" (plugins `Mover`, `MotionWarping`, `MotionTrajectory`, `Chooser`, `AnimationLocomotionLibrary`, `AnimationWarping`). Gère le mouvement, la caméra, le Motion Matching, le traversal (vault/mantle/climb), les modes de mouvement personnalisés. **Ce framework est en grande partie template/tiers** : n'y toucher qu'en connaissance de cause.
2. **Système d'inventaire AGIS** ("Advanced Grid Inventory System", asset marketplace) — `Content/Core/Coreinventory/`, `Content/Systems/Inventory/`, `Content/UI/Inventory_UI/`, `Content/Data/Inventory_Data/`.
C'est le **cœur du gameplay** : inventaire en grille, équipement, craft, loot, sauvegarde. La quasi-totalité des systèmes de jeu ajoutés par l'équipe (stats, mort, sélection rapide d'équipement, interactables) se branchent sur ce système.
3. **Systèmes de jeu spécifiques au projet**, ajoutés par-dessus AGIS — `Content/Systems/Stats/`, `Content/Systems/Scent/`, `Content/Systems/Movable_Object/`, `Content/Systems/Door/`, `Content/PCG/` (dont le système de routes et ponts `PCG/RoadNetwork/`, §7), `Content/Items/`, `Content/UI/Widgets/` (HUD, pause menu). C'est ici que la majorité des évolutions de gameplay futures doivent être ajoutées.
4. **Outillage IA** — plugins `VibeUE`, `EditorToolset`, `ModelContextProtocol`, `Terminal` (déclarés dans `ProjectFv1.uproject`) + `.mcp.json` à la racine. Ils exposent l'éditeur Unreal (Blueprints, Widgets, Input, PCG, Landscape, Niagara, etc.) à un serveur MCP local (`http://127.0.0.1:8000/mcp`) piloté par des agents Claude. **C'est le mécanisme par lequel un modèle d'IA lit/modifie ce projet** — voir la section [9. Notes pour les IA](#9-notes-pour-les-ia-pièges-connus-de-loutillage) pour les pièges connus.
En plus de cela, deux couches d'assets tiers volumineuses sont présentes mais **ne contiennent pas de logique de gameplay propre au projet** : le pack météo **UltraDynamicSky** (`Content/Systems/Weather/UltraDynamicSky/`) et diverses bibliothèques d'assets 3D Fab/Megascans/Quixel (`Content/Fab/`, `Content/Megaplant_Library/`, une bonne partie de `Content/Assets/Animations/` et `Content/Characters/`).
### 1.3 Dépendances principales entre systèmes
```
Character (Pawn, géré par le framework "Sandbox Character")
│
├─ Composant Inventory_Player (Content/Core/Coreinventory/Inventory_Player)
│ └─ hérite de Inventory_Equipper (équipement d'armes/objets)
│ └─ hérite de Inventory__Main (cœur générique AGIS : slots, containers, réseau)
│
│ ⚠️ Pour un item (`_BP_ItemBase` et enfants) qui a besoin de chercher/consommer un item dans
│ l'inventaire de son porteur (ex. l'arc qui cherche des flèches), utiliser sa propriété héritée
│ `Owner Inventory` (fonction `Variables|Runtime|GetOwnerInventory`, self implicite) plutôt que de
│ reconstruire `GetOwnerCharacter → Cast SandboxCharacter_CMC → GetInventoryPlayer → GetListeningInventory` :
│ cette dernière chaîne se résout à `None` tant que le joueur n'a pas ouvert son écran d'inventaire au
│ moins une fois (`Listening Inventory` est un état d'UI, pas l'inventaire persistant). `Owner Inventory`
│ est typée `Inventory_Equipper` mais accepte directement les pins `Inventory__Main` (ex. `BOTH|FindItem`)
│ grâce à cette hiérarchie d'héritage — aucun cast nécessaire. Découvert et corrigé dans
│ `BP_Item_Bow::CountArrows/ConsumeArrow` (voir §7).
│
│ Inventory_Player pilote/possède :
│ ├─ WB_InventoryScreen (UI d'inventaire complète, créée à la demande)
│ │ └─ WB_InventoryPart1 / WB_InventoryPart2
│ │ └─ WB_EquipSlot → WB_Container → WB_InventorySlot → WB_Item
│ ├─ WB_QuickSelect (roue de sélection rapide d'équipement, 4 slots)
│ └─ Les Input Actions IA_*AGIS via l'IMC_AGIS_Character / IMC_AGIS_InventoryTab
│
├─ Composant AC_Stats (Content/Systems/Stats/AC_Stats)
│ └─ Vie, mort (OnDeath), Revive → déclenche WB_YouDied et le respawn du Character.
│ TakeDamage(BodyPart, DMGValue) est le point d'entrée UNIQUE de toute cause de mort :
│ utilisé à la fois par la mort par chute et par la touche de debug K (SandboxCharacter_CMC/EventGraph).
│
├─ Armes (Content/Systems/Weapons/, voir §4.3)
│ └─ AC_Weapon, ActorComponent ajouté à chaque Blueprint d'arme (BP_Item_Bow, BP_Item_AdvancedRifle) :
│ s'auto-abonne (EventBeginPlay, cast Owner → BP_Item_HoldableBase) au dispatcher
│ `BP_Item_HoldableBase::OnHeldStateChanged(bIsHeld)` — PAS à `_BP_ItemBase::OnOwnerChanged`
│ (2026-09 : bug corrigé — OnOwnerChanged se déclenche pour TOUT changement de propriétaire,
│ y compris le simple ramassage/rangement dans un slot d'inventaire non tenu en main, ce qui
│ causait "2 armes équipées en même temps" dès que 2 armes étaient présentes dans l'inventaire.
│ OnHeldStateChanged ne se déclenche que sur le VRAI changement d'état main/pas-en-main, via
│ `BPI_HoldableItem::On Item Attached(ShouldHold)` déjà implémenté par BP_Item_HoldableBase).
│ HandleHeldStateChanged(bIsHeld) → Equip(Owner)/Unequip() selon bIsHeld, qui pilotent `bIsEquipped`.
│ Expose CanUse()/TryUsePrimary()/StopPrimary() (cooldown générique) que chaque item interroge dans ses
│ overrides `Custom|PrimaryUse`/`StopPrimaryUsing` existants (voir §7). BFL_WeaponDamage::ApplyPointDamage
│ route les dégâts d'un projectile vers `AC_Stats::TakeDamage` (mapping nom d'os → E_BodyPart).
│ **Sélection d'animation (2026-09-06)** : `Equip`/`Unequip` pilotent aussi `SandboxCharacter_CMC::Weapon`
│ (enum `Weapon_Equip` : None/Bow/OneArmPistol/TwoArmPistol/Riffle/Bat — lu par la Pose Search Database
│ `CHT_PoseSearchDatabases_Dense` pour sélectionner le pool d'animations armées/désarmées de Motion
│ Matching) et `isArmed` (bool) — ces deux variables existaient déjà sur le personnage mais n'étaient
│ JAMAIS renseignées avant ce fix (dette du template de base), donc la locomotion restait toujours
│ "à mains nues" quelle que soit l'arme tenue. `AC_Weapon` porte une variable `WeaponTypeByte` (byte,
│ PAS l'enum directement — UserDefinedEnum non utilisable comme type de variable membre via les outils,
│ voir §9) configurée par arme sur le composant (1=Bow sur `BP_Item_Bow`, 4=Riffle sur
│ `BP_Item_AdvancedRifle`), convertie via `Utilities|Enum|BytetoEnumWeapon_Equip` avant d'être assignée.
│ **Support du personnage Mover (2026-09-19)** : `Equip`/`Unequip` ne faisaient qu'un `Cast To
│ SandboxCharacter_CMC`. Sur `SandboxCharacter_Mover` (parent `Pawn`, aucune classe commune avec le CMC)
│ le cast échouait silencieusement → `Weapon`/`isArmed` jamais écrits, donc arme en main mais locomotion
│ toujours désarmée. Les deux fonctions ont désormais une branche `Cast Failed` → `Cast To
│ SandboxCharacter_Mover` → `SetEquippedWeapon(WeaponTypeByte)` (nouvelle fonction du pion Mover, voir §4.2.3).
│ **Bras armé qui reste bloqué en pose par défaut à l'équipement (2026-09-06, CORRIGÉ)** : deux causes
│ distinctes cumulées, toutes deux dans `SandboxCharacter_CMC_ABP` :
│ (1) `Get_MMInterruptMode` (utilisée par `Update_MotionMatching` pour choisir le `PoseSearchInterruptMode`
│ passé à `MotionMatchingAnimNodeLibrary.SetDatabasesToSearch`) ne testait QUE MovementMode/MovementState/
│ Gait — jamais `Weapon_Equip` (variable ABP, distincte de `SandboxCharacter_CMC::Weapon`, alimentée depuis
│ celle-ci via PropertyAccess dans `Update_States`, qui a bien SON PROPRE tracker `Weapon_Equip Last Frame`
│ déjà présent mais jamais branché). Résultat : passer armé/désarmé EN RESTANT IMMOBILE ne changeait ni
│ MovementMode/State/Gait, donc `Get_MMInterruptMode` retournait toujours `DoNotInterrupt` et Motion
│ Matching continuait indéfiniment l'ancienne pose (`bIsContinuingPoseSearch=True`) sans jamais essayer
│ la nouvelle base de données pourtant correctement sélectionnée dans `ValidDatabases`/`DatabasesToSearch`.
│ Fix : condition supplémentaire `Weapon_Equip != Weapon_Equip Last Frame` ajoutée au `OR` racine de
│ `Get_MMInterruptMode` (3ᵉ pin ajoutée au nœud `ORBoolean` existant via `add_node_pin`, PAS une réécriture
│ DSL complète — voir §9 pour le piège du wildcard enum). (2) Une fois l'interruption forcée, la recherche
│ échouait quand même : `PSD_Dense_Stand_Bow_Idles`/`_Bow_Aim_Idles`/`PSD_Walk_Bow`/`PSD_Crouch_Bow`
│ (toutes sous `/Game/Assets/Animations/humain/Armed_mov/Bow/`) avaient leur `PoseSearchMode` sur `PCAKD_TREE`
│ et leur build d'index échouait dès le chargement (`LogPoseSearch: Error: ... BuildIndex Failed because
│ of 'No Convergence' while calculating PCA data`) — passées en `BRUTE_FORCE` (pas besoin de PCA, dataset
│ de toute façon minuscule), ce qui a débloqué la recherche. (3) **Lignes armées manquantes dans le
│ chooser (CORRIGÉ 2026-09-06)** : la colonne `Is Armed` de `CHT_PoseSearchDatabases_Dense` (seule des 4
│ tables LOD à en avoir une) n'avait que DEUX lignes `MatchTrue` — "Stand Idles Bow" (ligne 1) et "Stand
│ Walks Armed" (ligne 3). Toutes les autres (courses 4-5, sprint 6, InAir 7, accroupi 8-9, prone 10-11,
│ nage 12-17) exigeaient `MatchFalse`, donc AUCUNE ligne ne correspondait dès qu'on courait/sautait/
│ s'accroupissait armé → chooser vide → Motion Matching gardait la dernière pose (symptôme : "même
│ animation en marchant, courant et accroupi"). Fix : ces lignes passées de `MatchFalse` à `MatchAny`
│ (repli propre sur l'animation non-armée au lieu du gel ; les lignes 0 et 2 DOIVENT rester `MatchFalse`
│ sinon elles masqueraient les lignes arc 1 et 3, l'évaluation étant descendante premier-match-gagne).
│ Aucun sous-chooser concerné n'a de colonne d'arme, le repli est donc sans effet de bord.
│ ⚠️ **RÉGRESSION 2026-09-13 (voir §8.2)** : `CHT_PoseSearchDatabases_Dense` a été ÉCRASÉ par la migration depuis
│ `Project_F` — les lignes armées Bow (`PSD_Dense_Stand_Bow_Idles`, `_Bow_Aim_Idles`, `PSD_Walk_Bow`) et le passage
│ `MatchFalse`→`MatchAny` décrits ci-dessus avaient disparu. **RESTAURÉ le 2026-09-14** : la version du commit `d29748b`
│ a été remise (lignes Bow, Prone, nage, cover accroupi et Push revenues, contexte `Systems/Blueprints/SandboxCharacter_CMC_ABP`)
│ puis le fix MatchAny a été refait par script : colonne `Is Armed` : lignes 0 et 2 en `MatchFalse`, 1 et 3 en `MatchTrue`, lignes 4 à 17 passées de `MatchFalse` à `MatchAny` (la ligne 18 l'était déjà), vérifié après rechargement.
│ **Reste un problème de contenu NON corrigé** (voir §9) : `PSD_Dense_Stand_Bow_Idles` — et ELLE SEULE
│ (`_Bow_Aim_Idles`/`PSD_Walk_Bow`/`PSD_Crouch_Bow` sont sains) — a ses 4 entrées
│ `DatabaseAnimationAssets[i].SamplingRange` réglées sur `(longueur_du_clip, longueur_du_clip)`, une
│ fenêtre de largeur nulle placée à la toute FIN du clip. Or Pose Search ne traite un asset comme
│ bouclable que si sa plage vaut `(0,0)` (= « tout le clip ») : d'où `bLoopAnimation=False` + fenêtre de
│ lecture nulle = une seule frame figée, alors même que les AnimSequences sources sont bien `Loop=True`.
│ Correction = remettre chaque `Sampling Range` à `0 / 0` dans l'éditeur Pose Search Database — à faire à
│ la main : la propriété n'est exposée par AUCUNE API (absente de `ObjectTools.list_properties`, refusée
│ par `get/set_editor_property` ET `ObjectTools`, et le `SET` console ne cible qu'une classe entière donc
│ toutes les databases chargées). C'est aussi ce dataset dégénéré (4 poses uniques) qui faisait échouer
│ le PCA du point (2).
│ `PSD_Crouch_Bow` existe mais n'est référencée par AUCUN chooser (contenu orphelin) : une fois les
│ SamplingRange corrigées, on pourra lui dédier une vraie ligne armée accroupie plutôt que le repli.
│
├─ Composants de mouvement (Content/Systems/Movement/, Systems/Blueprints/MovementModes/)
│ └─ AC_ClimbingComponent, AC_TraversalLogic, AC_PreCMCTick, AC_VisualOverrideManager…
│
├─ Couverture + interactions AST (Content/Systems/Movement/Cover|Interaction|StateManager|ASTShared, §4.2.5)
│ └─ SandboxCharacter_Mover porte BPC_StateManager (hub d'états) + BPC_CoverSystem + BPC_Interaction ;
│ Get_MoveInput / Get_RotationMode / Get_Gait / Get_OrientationIntent / ProduceInput lisent le
│ StateManager ; SandboxCharacter_Mover_ABP lie les calques OverlayGraph (tag Overlay) et InteractionIKGraph.
│
├─ Composant AIPerceptionStimuliSourceComponent (2026-09-05)
│ └─ Ajouté sur SandboxCharacter_CMC pour que le joueur soit détectable par la vue de l'IA
│ (AISense_Sight, auto_register_as_source=true). Sans lui, AUCUN AIPerceptionComponent
│ ne peut jamais détecter le joueur, quels que soient rayon/FOV/LOS configurés côté IA.
│
└─ Autres composants gameplay (BP_ScentPoint, etc. — voir Systems/Scent/ §4.3 et Systems/Wildlife/ §4.4)
Interactables (Content/Systems/Inventory/Interactables/*)
└─ Implémentent BPI_Interactable, exposent un inventaire via un composant
Inventory_Storage / Inventory_Vendor / Inventory_Crafting (mêmes bases AGIS que le joueur)
→ consultés par Inventory_Player lors d'une interaction (touche IA_InteractAGIS)
Données d'items (Content/Data/Inventory_Data/, Content/Items/)
└─ DT_Items (DataTable), ItemDefaults / ItemInfo (structs), DD_Item (DataAsset par item)
alimentent à la fois l'UI d'inventaire ET les Blueprints d'objets sous Content/Items/BP_Item_*
Faune sauvage (Content/Systems/Wildlife/, voir §4.4)
└─ BP_AnimalBase (Character) réutilise TEL QUEL AC_Stats (vie/dégâts/mort, §1.3 ci-dessus) et
BP_ScentPoint (Systems/Scent/, §4.3) — aucun système de vie ni d'odeur parallèle n'a été créé.
BFL_WeaponDamage::ApplyPointDamage fonctionne automatiquement sur un animal (résout AC_Stats
par GetComponentByClass) : aucune adaptation nécessaire côté système d'armes.
```
### 1.4 Conventions de nommage des assets
| Préfixe | Signification | Exemple |
|---|---|---|
| `BP_` | Blueprint d'acteur | `BP_Campfire`, `BP_Item_Helmet` |
| `WB_` / `WBP_` | Widget Blueprint (UMG) | `WB_InventoryScreen`, `WBP_PauseMenu` |
| `AC_` | ActorComponent | `AC_Stats`, `AC_ClimbingComponent` |
| `IA_` | Input Action (Enhanced Input) | `IA_InteractAGIS`, `IA_QuickSelectAGIS` |
| `IMC_` | Input Mapping Context | `IMC_AGIS_Character` |
| `E_` / `EM_` | Enum | `E_MovementMode`, `EM_Road_Type` |
| `S_` / `ST_` | Struct | `S_TraversalCheckInputs`, `ST_Weather_Zone_forecasts` |
| `M_` | Material | `M_Road`, `M_Campfire` |
| `MI_` | Material Instance | `MI_Black`, `MI_Bottle` |
| `SM_` | Static Mesh | `SM_Campfire`, `SM_RifleAdvanced` |
| `SKM_` | Skeletal Mesh | `SKM_BodyArmor` |
| `PA_` | Physics Asset | `PA_BodyArmor` |
| `T_` | Texture | `T_Helmet` |
| `RT_` | Render Target | `RT_InspectRenderer` |
| `DT_` | Data Table | `DT_Items` |
| `DD_` | Data Definition (DataAsset) | `DD_Item` |
| `ML_` | Macro Library (Blueprint) | `ML_InventoryMacros` |
| `SG_` | SaveGame | `SG_AGIS_UID`, `SG_AGIS_World` |
| `GM_` / `PC_` | GameMode / PlayerController | `GM_Sandbox`, `PC_Sandbox` |
| `CR_` | Control Rig | `CR_ClimbingGraphV2` |
| `ABP_` | Animation Blueprint | `ABP_GenericRetarget` |
| `AM_` | Anim Modifier / Anim Montage | `AM_ReloadHip` |
| `NS_` | Niagara System | `NS_Tornado` |
| `BPI_` | Blueprint Interface | `BPI_Interactable` |
Le suffixe `AGIS` sur beaucoup d'assets (`IA_InteractAGIS`, `WB_...AGIS`, fonctions "Target is Inventory Player") signale qu'ils appartiennent au **socle marketplace AGIS**, par opposition aux ajouts spécifiques au projet.
### 1.5 Conventions importantes à respecter
- **Ne jamais renommer/déplacer** les assets du socle AGIS (`Content/Core/Coreinventory/`, une bonne partie de `Content/Systems/Inventory/`, `Content/UI/Inventory_UI/Widgets/WB_Container|WB_EquipSlot|WB_InventorySlot|WB_Item`) sans nécessité — de nombreuses références internes au plugin marketplace en dépendent.
- **Nouveau système de gameplay** → créer un dossier dédié sous `Content/Systems/<NomDuSysteme>/`, à l'image de `Systems/Stats/`, `Systems/Scent/`, `Systems/Movable_Object/`.
- **Nouvel objet d'inventaire (item)** → suivre le patron existant dans `Content/Items/` : un Blueprint `BP_Item_<Nom>` (héritant de `_BP_ItemBase` ou d'une base spécialisée dans `Items/BaseBlueprints/`), ses assets (mesh/matériau/texture) dans un sous-dossier `Content/Items/<Nom>/` ou `Content/Items/Resources/<Nom>/`, et une entrée dans `DT_Items` / les DataAssets sous `Content/Data/Inventory_Data/DataAssets/`.
- **Nouvel Input Action lié à l'inventaire** → `Content/Systems/Inventory/Other/Inputs/Actions/`, mappé dans `IMC_AGIS_Character` ou `IMC_AGIS_InventoryTab`. **Input Action de déplacement/caméra générique** → `Content/Core/PlayerController/Input/`, mappé dans `IMC_Sandbox`.
- **Nouveau widget d'inventaire** → `Content/UI/Inventory_UI/Widgets/`. **Nouveau widget hors inventaire** (HUD, menus) → `Content/UI/Widgets/`.
- **Nouvelle map de test** → `Content/Maps/DevTest/` (jamais dans le dossier du système testé). **Nouveau pack d'art du projet** (import Blender) → `Content/Assets/Environment/<Pack>/` ; **animations** → `Content/Assets/Animations/` (voir §8).
- Le clavier cible est **AZERTY** ; privilégier des touches identiques physiquement sur QWERTY/AZERTY pour les nouveaux raccourcis (ex. `V`, `B`).
- Réplication réseau : toute action qui modifie l'état du jeu côté inventaire/stats doit suivre le patron `Request X SERVER` (Custom Event `...SERVER`, `Replicated From Client, Executes On Server`) déjà présent dans `Inventory_Player`.
---
## 2. Vue d'ensemble de l'arborescence racine
```text
ProjectF/
├── .gitattributes → Règles Git (LFS, line-endings)
├── .gitignore → Exclut Binaries/, Intermediate/, Saved/, DerivedDataCache/, .vscode/…
├── .mcp.json → Déclare le serveur MCP "unreal-mcp" (http://127.0.0.1:8000/mcp) utilisé par les agents IA
├── ProjectFv1.uproject → Fichier projet UE5.8 : liste des plugins activés (voir §1.2/§8)
├── README.md → Description minimale du projet
├── Architecture_Projet.md → CE DOCUMENT
│
├── .claude/ → Skills et configuration Claude Code pour ce projet
│ └── skills/
│ └── maintain-architecture-doc/SKILL.md → Skill de maintenance de ce document (voir §10)
│
├── .codex/, .cursor/, .gemini/, .vscode/ → Configuration d'IDE / d'agents IA tiers (mcp.json, config.toml…)
│
├── Config/ → Configuration moteur & jeu (voir détail §7)
├── Content/ → Tous les assets du jeu (voir détail §3 à §6)
├── Docs/ → Documentation de systèmes : `PCG_RoutesPonts.md` (système PCG routes et ponts, §7), `PCG_Haussmann.md` (système PCG
│ d'immeubles haussmanniens, §7), avec leurs captures de vérification (`*_captures/`)
│
├── Binaries/, Intermediate/, Saved/, DerivedDataCache/ → Générés par l'éditeur, exclus de Git, jamais à éditer/documenter
```
### 2.1 ⚠️ Fichiers centraux autrefois isolés à la racine de `Content/` (rangés le 2026-09-13)
Quelques fichiers importants vivent directement sous `Content/`, hors de toute organisation par dossier — à connaître avant de chercher "où est le personnage" ou "où est le GameInstance" :
```text
Content/
├── Core/Character/SandboxCharacter_CMC.uasset → (déplacé depuis la racine le 2026-09-13) ⭐ Le Blueprint du PERSONNAGE JOUEUR lui-même (Character/Pawn). Référencé par GM_Dev, GM_Sandbox, PC_Sandbox, Gamemode_AGIS, WB_InventoryScreen, WBP_PauseMenu, BP_Moveble_Object, BP_Simply_Door, AC_PreCMCTick, AC_ClimbingComponent et son propre Anim BP (Systems/Blueprints/SandboxCharacter_CMC_ABP). C'est le point d'entrée central de tout le gameplay. Son EventGraph contient aussi : la détection de mort par chute (suivi tick de `IsFalling`/`FallStartZ`, calcul de hauteur à `OnLanded`, dégâts scalés via `AC_Stats.TakeDamage` au-delà de 300 unités, létal au-delà de 800) et une touche de debug K (InputDebugKey) qui force la mort via ce même `TakeDamage` — ajoutés le 2026-09-04.
├── Core/Character/SandboxCharacter_Mover.uasset + _Mover_Ragdoll.uasset → Personnage alternatif basé sur le plugin **Mover** (pion par défaut de `GM_Dev` ; en PIE c'est la variante `_Ragdoll` qui est possédée). Décrits avec le reste du framework Mover en §4.2.1, et leurs systèmes cover/prone/nage en §4.2.2.
├── Core/BP_GameInstance.uasset → (déplacé depuis la racine le 2026-09-13 ; `Config/DefaultEngine.ini` → `GameInstanceClass=/Game/Core/BP_GameInstance.BP_GameInstance_C`) GameInstance réellement utilisé par le projet (à ne pas confondre avec `Core/Coreinventory/GameInstance_AGIS`, la base du plugin — vérifier lequel des deux est configuré comme GameInstance par défaut avant de supposer que c'est l'un ou l'autre).
└── Data/DT_DemoTags.uasset → (déplacé depuis la racine le 2026-09-13 ; l'export texte `DT_DemoTags.T3D` a été supprimé) ⚠️ **À NE PAS SUPPRIMER** : DataTable de gameplay tags du socle AGIS, déclarée dans `Config/DefaultGameplayTags.ini` (`+GameplayTagTableList=/Game/Data/DT_DemoTags`). Elle définit `General Inventory Slot` (indispensable à l'inventaire), `Human.*`, `Attachment.*` (singulier), `Flashlight.Battery`, `Other.*`, `Vehicle.*`. (Corrigé le 2026-09-13 : l'ancienne mention « aucune référence trouvée » était fausse — la référence passe par la config, invisible pour l'Asset Registry.) Dette connue : `DT_Items`, `Inventory_Player` et `AttachmentData_AdvancedRifle` utilisent `Attachments.*` (pluriel, tags inexistants → avertissements « Invalid GameplayTag »), antérieur au commit du 2026-09-04.
```
---
## 3. `Content/Core/` — Socle inventaire (AGIS) + Player Controller + Tick
Le cœur technique de l'inventaire et des entrées génériques du joueur.
```text
Content/Core/
├── Coreinventory/ → Cœur du plugin AGIS (composants, données, sauvegarde)
│ ├── Inventory__Main.uasset → ActorComponent BASE : logique générique de containers/slots, réplication réseau. Racine de la hiérarchie d'inventaire.
│ ├── Inventory_Equipper.uasset → hérite de Inventory__Main : ajoute la logique d'équipement (armes/objets tenus en main)
│ ├── Inventory_Player.uasset → hérite de Inventory_Equipper : composant d'inventaire du JOUEUR. Contient toute la logique spécifique projet ajoutée cette session (SelectQuickSlot, ToggleQuickEquip, GetQuickSlotItemInfo, graphe INPUTS avec tous les bindings Enhanced Input du joueur). (2026-09-05) `ToggleQuickEquip`/`GetQuickSlotItemInfo` (touches V/B) étaient des stubs de prototype (PrintString uniquement, aucun appel d'équipement réel, plus un bool `bQuickSlotArmed` inversé) — corrigés pour appeler réellement `Inventory_Equipper::SwitchWeaponByUID`/`EmptyHands` (hérités). `GetQuickSlotItemInfo` retourne désormais aussi le `ContainerUID` du slot (WEAPON/WEAPON2/Speed1/Speed2) via `WBContainer::GetContainerUID`, nécessaire pour cibler `SwitchWeaponByUID`. B équipe/déséquipe donc réellement l'item du slot présélectionné par V (le slot doit déjà contenir un item via le drag&drop du paperdoll — la roue V/B ne fait que choisir PARMI les 4 emplacements déjà équipés, elle ne pioche pas dans l'inventaire général). Nouvelle fonction `CycleQuickSlot()` (Weapon→Weapon2→Speed1→Speed2→Weapon→…) ajoutée pour fiabiliser la sélection : `WB_QuickSelect::EventConstruct` (Content/UI/Inventory_UI/Widgets/Other/WB_QuickSelect.uasset) l'appelle désormais automatiquement à chaque pression de V (donc à chaque création de la roue), sans dépendre du clic sur un bouton de la roue (`BtnWeapon1` etc. — dont le fonctionnement en jeu n'a pas pu être confirmé). Les boutons de la roue restent câblés à `SelectQuickSlot` en plus, au cas où le clic fonctionne réellement.
│ ├── Inventory_Storage.uasset → Inventaire des containers de stockage (coffres, trunk, etc.)
│ ├── Inventory_Vendor.uasset → Inventaire des PNJ vendeurs
│ ├── Inventory_Crafting.uasset → Inventaire des stations de craft (campfire, smelter, générateur)
│ ├── BP_AttachmentHandler.uasset → ActorComponent gérant les attachements sur les armes (silencieux, lampe, chargeur…)
│ ├── CombatComponent.uasset → ActorComponent : logique de combat (tir, dégâts) liée aux armes AGIS
│ ├── BP_SM_DroppedMesh.uasset → Acteur représentant un objet lâché au sol (mesh statique + interaction de ramassage)
│ ├── ItemMesh_STATIC.uasset / ItemMesh_Skeletal.uasset → Acteurs générant le rendu 3D d'un item tenu/lâché (mesh statique ou squelettal)
│ ├── DD_Item.uasset → DataAsset décrivant un item (référencé par ItemDefaults)
│ ├── FL_AGIS.uasset → Function Library : fonctions statiques utilitaires du plugin AGIS
│ ├── ML_AGISDefaults.uasset → Macro Library : macros par défaut du plugin
│ ├── ML_AGIS_UMG.uasset → Macro Library : macros dédiées à l'UI (UMG)
│ ├── ML_InventoryMacros.uasset → Macro Library : macros génériques d'inventaire
│ ├── GameInstance_AGIS.uasset → GameInstance de base du plugin (état persistant entre les maps)
│ ├── Gamemode_AGIS.uasset → GameMode de base du plugin (indépendant de GM_Sandbox, voir §4)
│ ├── PlayerController_AGIS.uasset → PlayerController de base du plugin
│ └── SG_AGIS_UID.uasset / SG_AGIS_World.uasset → SaveGame : compteur d'UID uniques / état persistant du monde (sauvegarde)
│
├── PlayerController/ → Input générique du joueur (mouvement, caméra), indépendant de l'inventaire
│ ├── IA_InitiateClimb / IA_LeapFromClimb / IA_MovingClimb.uasset → Input Actions liées à l'escalade
│ └── Input/
│ ├── IMC_Sandbox.uasset → Input Mapping Context PRINCIPAL, ajouté dans `SetupInput` par `SandboxCharacter_CMC` ET (depuis le 2026-09-15) par `SandboxCharacter_Mover` (mouvement, caméra, saut, + actions AGIS : `IA_ToggleInventory` Tab, `IA_InteractAGIS`, `IA_QuickEquip/Select/GiveAGIS`, `IA_UseItemPrimary/SecondaryAGIS`). Mappings `IA_TriggerRagdoll` (R, X manette) et `IA_Takedown` (clic gauche, RT) ajoutés le 2026-09-15 pour le Mover ragdoll
│ └── IA_Move, IA_Look, IA_Jump, IA_Sprint, IA_Run, IA_Walk, IA_Crouch, IA_Prone, IA_Strafe,
│ IA_Aim, IA_Interact, IA_ESC, IA_NextPawn, IA_Traverse, IA_TeleportToTarget,
│ IA_SwimmingDeep, IA_TwinStick_AimDirection, IA_Move_WorldSpace, IA_Look_Gamepad,
│ IA_NextVisualOverride, IA_Takedown, IA_TriggerRagdoll, IA_rool.uasset → Input Actions individuelles mappées dans IMC_Sandbox. Aussi écoutées par la chaîne Mover (`SandboxCharacter_Mover`/`_Ragdoll`, `Systems/Mover/PC_Sandbox`, `Systems/Mover/SmartObjects/AC_SmartObjectAnimation`) ; le sprint du Mover écoute `IA_Run` (Started/Completed). `IA_rool` et `IA_Traverse` ne sont écoutés par aucun Blueprint
│
└── Tick/ → Système de gestion centralisée du Tick (perf)
├── BPI_TickManager.uasset → Interface implémentée par les acteurs qui veulent être "tickés" par le manager
├── BP_TickManager.uasset → Acteur central qui distribue les ticks (probablement à fréquence configurable)
└── E_TickFrequency.uasset → Enum des fréquences de tick disponibles
```
---
## 4. `Content/Systems/` — Systèmes de gameplay
### 4.1 `Systems/Inventory/` — Extension du socle AGIS pour ce projet
```text
Content/Systems/Inventory/
├── BP_Spawner.uasset → Acteur de spawn (probablement spawn d'items/loot en début de partie)
├── DT_Items.uasset → DataTable listant tous les items du jeu (clé pour créer/reconnaître un item)
│
├── Interactables/ → Tout ce que le joueur peut "interagir" avec (touche IA_InteractAGIS)
│ ├── BP_EnemyPuppet.uasset → PNJ/mannequin pillable
│ ├── BP_Vendor.uasset → PNJ vendeur (utilise Inventory_Vendor)
│ ├── CraftingInteractables/
│ │ ├── Campfire/BP_Campfire.uasset → Station de cuisson/craft (feu de camp) + ses assets (mesh, matériaux)
│ │ └── Smelter/BP_Smelter.uasset → Station de fonte de minerai + ses assets
│ ├── FuelGenerator/BP_Generator.uasset → Générateur électrique (consomme du carburant)
│ ├── KeycardReader/BP_KeycardReader.uasset → Lecteur de carte magnétique (déverrouillage)
│ ├── Other/BP_SimpleDoor.uasset → Porte interactable simple
│ └── Storages/
│ ├── BP_StorageBox.uasset, BP_Trunk.uasset, BP_SphereStorage.uasset, _BP_StorageBase.uasset → Coffres/contenants de stockage (héritent de _BP_StorageBase)
│ └── Deadbody/BP_Deadman.uasset → Cadavre pillable
│
├── Interfaces/ → Interfaces Blueprint (contrats partagés)
│ ├── BPI_Interactable.uasset → Contrat pour tout objet interactible (implémenté par tous les Interactables ci-dessus)
│ ├── BPI_SlotWidgets.uasset → Contrat pour les widgets de slot d'inventaire
│ ├── BPI_HoldableItem.uasset → Contrat pour un item tenable en main
│ ├── BPI_WeaponAGIS.uasset → Contrat pour les armes
│ ├── BPI_DamageFeedback.uasset → Contrat de retour visuel/sonore lors de dégâts
│ └── BPI_GameInstance_AGIS.uasset → Contrat exposé par le GameInstance côté AGIS
│
├── Other/ → Assets d'appoint de l'inventaire
│ ├── Animations/ → Animations liées aux armes/mains nues (Jog, Rifle, TurnInPlace, WALK) — clips Motion Matching (MM_*) et montages (AM_*)
│ ├── Fonts/ → Polices utilisées par l'UI d'inventaire
│ ├── Inputs/
│ │ ├── Actions/ → **Toutes** les Input Actions liées à l'inventaire (IA_*AGIS), y compris IA_QuickSelectAGIS et IA_QuickEquipAGIS (sélection rapide, ajoutées pour ce projet)
│ │ ├── IMC_AGIS_Character.uasset → Mapping Context actif en jeu (personnage) : touches 1/2/3/4, F, V, B, etc.
│ │ └── IMC_AGIS_InventoryTab.uasset → Mapping Context actif quand l'inventaire plein écran est ouvert
│ ├── InspectCapture/ → Rendu d'inspection d'objet (RT_InspectRenderer + BP_InspectRenderer + M_Inspect)
│ └── Materials/ → Matériaux génériques réutilisés par les items (MI_Black, MI_Red, M_Metal, M_Outline…)
│
└── Utilities/ → Outils d'éditeur (Editor Utility Widgets) pour la création de containers/inventaires
├── EUW_ContainersCreator.uasset, EUW_WidgetToInventoryData.uasset
└── Others/WB_ContainerSpecsUtility.uasset, WB_CreatorContainerUtility.uasset
```
### 4.2 `Systems/Blueprints/` — Framework de personnage / locomotion ("Sandbox Character")
Contenu très largement issu du sample Epic **Motion Matching** (plugins `Mover`, `MotionTrajectory`, `Chooser`, `AnimationLocomotionLibrary`). À traiter comme un framework tiers : préférer l'étendre plutôt que le réécrire.
```text
Content/Systems/Blueprints/
├── BPI_SandboxCharacter_Pawn.uasset → Interface du Pawn joueur (point d'entrée du framework de locomotion). (2026-09-14) + fonctions du sample GASP pour le Mover : `AddTarget`, `RemoveTarget`, `Set_IsPlayingRootMotion`, `Set_PhysicsProfile`, sorties typées de `Get_MMIResult` / `Get_PropertiesForRagdoll`
├── BPI_SandboxCharacter_ABP.uasset → Interface de l'Anim Blueprint du personnage. (2026-09-14) + `Get_Trajectory`, `Update_MMIConstraints`
├── AC_TraversalLogic.uasset → ActorComponent : logique de traversal (vault/mantle/climb contextuel)
├── AC_PreCMCTick.uasset → ActorComponent : logique exécutée avant le tick du mouvement (Mover/CMC)
├── AC_VisualOverrideManager.uasset → ActorComponent : gestion des overrides visuels du personnage
├── GM_Sandbox.uasset / PC_Sandbox.uasset → GameMode / PlayerController du framework de locomotion (indépendants de Gamemode_AGIS/PlayerController_AGIS, voir §3)
│
├── Cameras/ → Système de caméra (Camera Rigs modulaires)
│ ├── CameraAsset_SandboxCharacter, CameraDirector_SandboxCharacter, CHT_CameraRig
│ ├── E_CameraMode.uasset, E_CameraStyle.uasset
│ └── Rigs/CameraRig_* (Close/Medium/Far × Aim/Freecam/Strafe, CrouchOffset, CollisionOffset, TwinStick…)
│
├── MovementModes/ → Modes de mouvement personnalisés (plugin Mover)
│ ├── BP_MovementMode_Walking / _Falling / _Slide
│ ├── BP_MovementTransition_ToSlide / _FromSlide
│ └── BP_ClimbingMode.uasset
│
├── ControlRigs/CR_Biped_FootPlacement.uasset → Control Rig : placement des pieds (foot IK)
├── RetargetedCharacters/ABP_GenericRetarget.uasset → Anim BP générique pour le retargeting
│
├── AnimModifiers/ → Anim Modifiers (post-traitement de clips d'animation à l'import)
├── AnimNotifies/ → Anim Notifies personnalisées (BP_AnimNotify_FoleyEvent*, BP_NotifyState_EarlyTransition/MontageBlendOut) + enums associés
│
├── Data/ → Structs/Enums/Curves partagés par tout le framework de locomotion
│ ├── E_Gait, E_Stance, E_MovementMode, E_MovementState, E_MovementDirection(Bias), E_RotationMode,
│ │ E_AnalogStickBehavior, E_ExperimentalStateMachineState, E_TraversalActionType
│ │ (2026-09-14, convergence §8.2) valeurs du sample ajoutées EN FIN d'enum : E_MovementMode += Ragdoll, Flying ;
│ │ E_ExperimentalStateMachineState += Debug Flying Loop. S_CharacterPropertiesForAnimation += BasedMovementDelta,
│ │ Weapon_Type (`Data/Enums/E_WaponType`) ; S_BlendStackInputs += Blend Curve. Ne jamais réordonner ces enums (valeurs stockées par nom interne).
│ ├── S_CharacterPropertiesForAnimation / _ForCamera / _ForTraversal, S_BlendStackInputs,
│ │ S_MoverCustomInputs, S_PlayerInputState, S_TraversalCheckInputs/Result,
│ │ S_TraversalChooserInputs/Outputs (contexte des choosers `CHT_TraversalMontages_CMC/_Mover`), S_ChooserOutputs
│ │ (contexte de `CHT_CMCCharacterAnimations`), S_MovementDirectionThresholds,
│ │ S_RotationOffsetCurveChooser_Inputs, S_DebugGraphLineProperties
│ ├── Curve_RotationOffset_* , Curve_StrafeSpeedMap, CHT_RotationOffsetCurve → Curve Tables pilotant les offsets d'animation
│ └── BFL_HelpfulFunctions.uasset, BPI_InteractionTransform.uasset
│
├── SmartObjects/ → Intégration du plugin SmartObject (points d'ancrage d'animation contextuelle)
│ ├── CHPA_SmartObject, AC_SmartObjectAnimation
│ └── SmartObjectAnimationPayload, SmartObjectSelectionInputs/Outputs
│
├── SandboxCharacter_CMC_ABP.uasset → Anim Blueprint principal du personnage joueur (piloté par `/Game/Core/Character/SandboxCharacter_CMC`). Anciennement égaré dans un dossier `d/` mal nommé — déplacé ici (nettoyage du 2026-09-04).
│
├── (SUPPRIMÉS de ce dossier) SandboxCharacter_Mover / _ABP / _Ragdoll → **Depuis le 2026-09-13 ces trois assets n'existent plus dans `Systems/Blueprints/`** : les versions actives (personnage Mover conservé à la demande de l'utilisateur) sont dans `Content/Systems/Mover/` (voir §4.2.1). `SandboxCharacter_CMC` (`Content/Core/Character/`) reste le personnage RÉEL du jeu. Vérifié le 2026-09-14 : `GM_Dev` (GameMode de `MapDev1`), `Systems/Blueprints/GM_Sandbox` et `Core/Coreinventory/Gamemode_AGIS` ont `DefaultPawnClass = /Game/Core/Character/SandboxCharacter_CMC` ; `GM_Sandbox_Ragdoll` et `GM_MoverTest` (contenu de test du sample) restent à `None` ; `Systems/Mover/GM_Sandbox` spawne `SandboxCharacter_Mover`.
│
├── GM_Sandbox_Ragdoll.uasset → Variante de `GM_Sandbox` avec `SandboxCharacter_Mover_Ragdoll` comme pawn par défaut (contenu du sample, non utilisée par le jeu réel).
│
├── AI/StateTree/ → **(2026-09-08)** StateTree + tâches pour l'interaction Smart Object du personnage (`ST_Player_SandboxCharacter_SmartObject`, `STT_CharacterIgnoreCollisionsWithOtherActor`, `STT_FindSlotTransforms`, `S_SmartObjectPayload`). Contenu du sample, AI/SmartObject non branché sur le gameplay AGIS.
│
└── (2026-09-08) Contenu supplémentaire fusionné depuis un import du sample officiel Epic **Game Animation Sample** (voir note globale en fin de §8) : `RetargetedCharacters/` (BP_Echo, BP_Manny, BP_Quinn, BP_Twinblast, BP_UE4_Mannequin — **supprimés le 2026-09-13** avec les personnages de démo ; seul `ABP_GenericRetarget` subsiste), des `Cameras/Rigs/` et `MovementModes/` supplémentaires (`CameraRig_Far_Ragdoll`, `CameraRigPrefab_ThirdPerson`, `BP_MovementMode_Ragdoll`, `B_MovementMode_Flying`), des `Data/` supplémentaires (`S_RagdollProperties`, `S_MoverCustomInputs_Ragdoll`, `E_RagdollInjuryState`, `Ragdoll_Fall_to_Ground_BlendCurve`, variantes `S_CHT_*_IN/OUT` pour les choosers de traversal/interaction/get-up), des `AnimNotifies/` supplémentaires (`BP_NotifyState_MMI_IK`, `_MMI_MotionWarping`, `_OverrideCapsuleCollision`, `_OverrideMovementMode`, enums `E_EarlyTransition_*`/`E_FoleyEventSide`/`E_TraversalBlendOutCondition`), et des `SmartObjects/` supplémentaires (`Bench/`, `DistanceToSmartObject`, `PSS_SmartObject`, `TasksAndConditions/STT_ClaimSlot|ClearHandle|PlayAnimFromBestCost|PlayAnimMontage`).
```
### 4.2.1 `Systems/Mover/` et `Systems/Aiming/` — personnage Mover et framework du sample (isolés le 2026-09-14)
Copie du framework "Sandbox Character" du sample Epic **Game Animation Sample**, arrivée avec la migration `Project_F` (§8.2) et conservée pour le personnage alternatif basé sur le plugin **Mover** et pour le système de visée. Déplacée SANS fusion depuis `Content/Blueprints/` et `Content/Input/`. Beaucoup d'assets ont un homonyme dans `Systems/Blueprints/` (`AC_TraversalLogic`, `BPI_*`, `GM_Sandbox`, `PC_Sandbox`, structs `S_*`, caméras…) : ce sont des doublons DIVERGENTS, à ne pas confondre ni fusionner aveuglément (voir §9, piège `consolidate_assets`). Les enums, eux, sont partagés avec `Systems/Blueprints/Data/` (fusionnés le 2026-09-14).
```text
Content/Systems/Mover/
⚠️ (2026-09-18) `SandboxCharacter_Mover`, `_Ragdoll` (et l'ABP du CMC) vivent en réalité dans `Content/Core/Character/` — les deux lignes ci-dessous y correspondent, elles restent décrites ici pour garder le framework Mover d'un seul tenant.
├── SandboxCharacter_Mover.uasset → (dans `Content/Core/Character/`) Personnage basé sur le plugin Mover. (Vérifié le 2026-09-15) `GM_Dev` (GameMode de `MapDev1`) a `DefaultPawnClass = SandboxCharacter_Mover` ; en PIE sur `MapDev1` le pion obtenu est `SandboxCharacter_Mover_Ragdoll`. `SetupInput` ajoute `Core/PlayerController/Input/IMC_Sandbox` (même IMC que le CMC, inputs AGIS/inventaire compris) ; il porte aussi `Inventory_Player`, dont la propriété `Input Mapping Context` DOIT valoir ce même `IMC_Sandbox` (corrigé le 2026-09-15, elle valait `IMC_AGIS_Character`) : à la fermeture de l'inventaire, `WB_InventoryScreen.SwitchInput` fait `ClearAllMappings` puis ne ré-ajoute QUE cette variable — une mauvaise valeur coupe tous les contrôles (ZQSD, caméra) après fermeture. Même piège pour tout futur pion équipé d'`Inventory_Player`. Implémente `BPI_Interactable` depuis le 2026-09-17 (`GetTracePoint` = position/forward du composant `Camera`, comme `SandboxCharacter_CMC`) : sans cette interface, le trace d'interaction d'`Inventory_Player` (`Client Trace`, `Verify Trace`) part de (0,0,0) avec une direction nulle et aucun objet ne peut être ramassé. Tout futur pion équipé d'`Inventory_Player` doit aussi implémenter `BPI_Interactable::GetTracePoint`
├── SandboxCharacter_Mover_Ragdoll.uasset → Variante ragdoll du personnage Mover. ⚠️ C'est LUI le pion réellement possédé en PIE ; il surcharge le composant `CharacterMover` (ses propres `MovementModes`, dont `Ragdoll`) et `ProduceInput`/`On_MovementModeChanged_PostFinalize`. Toute modification du parent qui touche à ces éléments doit être vérifiée ici.
├── SandboxCharacter_Mover_ABP.uasset → Anim Blueprint du Mover (utilise `Systems/Aiming/ABP_AimingMaster`). (2026-09-18) Deux corrections majeures : (1) les nœuds `Evaluate Chooser` de `Update_MotionMatching` n'avaient AUCUN objet de contexte branché (au CMC il est relié à `self`) → le chooser ne pouvait résoudre aucune de ses colonnes, `ValidDatabases` restait vide et la branche Motion Matching de l'`AnimGraph` était morte ; `self` est désormais branché sur les deux nœuds. (2) Nouvelle variable `AnimBranchIndex` (affectée en fin d'`Update_Logic`) branchée sur l'`ActiveChildIndex` du *Blend Poses by int* de l'`AnimGraph`. Elle a d'abord piloté un hybride (Motion Matching en prone/nage, state machine GASP sinon) ; **depuis le 2026-09-18 elle vaut la constante `0`** : l'ABP du Mover est **100 % Motion Matching** (chooser `CHT_PoseSearchDatabases_Mover`), la state machine « State Controller » n'est plus jamais évaluée. Le nœud `Set AnimBranchIndex` et la variable sont conservés pour pouvoir rebasculer sur la voie 1 en changeant une seule valeur. Variables `Is Armed` et `IS Moving Object` ajoutées pour les colonnes du chooser copié.
├── GM_Sandbox.uasset / PC_Sandbox.uasset → GameMode (pawn = SandboxCharacter_Mover) et PlayerController du Mover ; création du widget de démo retirée le 2026-09-14
├── AC_TraversalLogic.uasset → Traversal du personnage Mover (vault/mantle/hurdle/climb). (2026-09-15) La détection de rebords du sample (limitée aux blocs de démo `LevelBlock_Traversable`, retirée le 2026-09-14) est remplacée par celle de `Systems/Blueprints/AC_TraversalLogic` (CMC), portée à l'identique : variables `Min Ledge Width`, `Min Front Ledge Depth`, `Max Ledge Sideways Depth`, `Trace Complex`, `Show Trace`, `Persistent Show Trace`, `RespectPlayersFacingDir`, `Banned Tags` (mêmes valeurs par défaut) et fonctions `ComputeLedgeData`, `TraceAlongHitPlane`, `TraceCorners`, `TraceWidth`, `NudgeTraceTowardsObjectOrigin`, `Set Trace Hit Point`, `Tag Compare`. Dans `TryTraversalAction` : capsule trace sur `TraceTypeQuery1` (Visibility, au lieu du canal `Traversable`), `Hit` → tags acteur+composant → `ComputeLedgeData` → rebords avant/arrière écrits dans `TraversalCheckResult` (équivalent inliné de `TryAndCalculateLedges`, non recopiée car elle prend le struct Systems par référence), puis `Tag Compare` (tags bannis) avant le chooser `CHT_TraversalMontages_Mover`. Le déclenchement (`IA_Jump` → `CanDoTraversal` → `TryTraversalAction`) et l'exécution Mover (`PerformTraversalAction_Mover`, mode `Traversing`) sont inchangés. Les deux composants restent des doublons divergents (structs/BPI `Systems/Mover/` vs `Systems/Blueprints/`).
├── AC_PreCMCTick, AC_PostABPTick, AC_VisualOverrideManager → Composants du sample (versions GASP, divergentes de celles de `Systems/Blueprints/`). (2026-09-27) `AC_VisualOverrideManager` réattache aussi les hitboxes du pion au MetaHuman (`AttachHitboxesTo`, voir §4.2.4)
├── BPI_SandboxCharacter_Pawn / _ABP.uasset → Interfaces GASP implémentées par la chaîne Mover
├── AI/StateTree/ → `ST_Player_SandboxCharacter_SmartObject` + tâches (copie GASP)
├── AnimModifiers/ → `AM_BakePhaseCurveFromFootstepNotifies`, `AM_FootSteps_Run` (référencés par quelques anims `UEFN_Mannequin`)
├── Cameras/ → `CameraAsset_SandboxCharacter`, `CameraDirector_SandboxCharacter`, `CHT_CameraRig`, `CameraShake_Basic`, `Rigs/CameraRig_*` (16)
├── ControlRigs/CR_Biped_FootPlacement.uasset
├── Data/ → Structs GASP (`S_CharacterPropertiesFor*`, `S_Traversal*`, `S_CHT_*` des choosers Mover/GetUp/Interaction, `S_Ragdoll*`, `S_MoverCustomInputs*`…), `Curve_RotationOffset_*`, `CHT_RotationOffsetCurve`, `BFL_HelpfulFunctions`, `BPI_InteractionTransform`
├── MovementModes/ → `BP_MovementMode_Walking/Falling/Slide/Ragdoll`, `B_MovementMode_Flying`, `BP_MovementTransition_To/FromSlide`, **`BP_MovementMode_Swimming`** (2026-09-18). `BP_MovementMode_Walking` : la `Select` d'allure n'alimentait QUE Walk/Run/Sprint — les allures ajoutées (Prone, Swinning, SwinningSprint) donnaient vitesse ET accélération = 0 (personnage figé) ; ajout de la variable `ProneSpeed` (125) et câblage des entrées manquantes (prone → `ProneSpeed`/`WalkAcceleration`, nage → Walk/Run). `BP_MovementMode_Swimming` : enfant de `SimpleFlyingMode` (comme le mode vol), tag `Mover.IsSwimming`, `MaxSpeedOverride = 1` (l'entrée devient un pur vecteur d'intention) ; sa fonction `ComputeSwimTarget` lit le personnage propriétaire (cast `SandboxCharacter_Mover`) et produit la vitesse cible : en surface, Z = flottaison vers `WaterSurfaceZ - SurfaceOffset` (60) bornée par `MaxBobSpeed`, en plongée Z = intention caméra + `SwimVerticalInput` × `VerticalSpeed`, horizontal = intention × `SwimSpeed` (250) ou `SwimSprintSpeed` (450). ⚠️ Le `SwimmingMode` natif du plugin Mover (UE 5.8) n'est PAS utilisable : son `SimulationTick` est vide et rien n'alimente ses données d'eau (`CommonBlackboard::LastWaterResult`).
└── SmartObjects/ → `AC_SmartObjectAnimation` (version GASP, aussi utilisée par des STT de `Systems/Blueprints/`), `CHPA_SmartObject`, `PSS_SmartObject`, `Bench/`, `TasksAndConditions/STT_*`
Content/Systems/Aiming/ → Système de visée développé dans Project_F : `ABP_AimingMaster`, `ABP_AimingMaster_UEFN` (aim offset), choosers `CH_AO`, `CH_Base_AO_Animechooser` (enum `Data/Enums/E_WaponType`)
Content/Blueprints/AnimNotifies/ → SEUL reste de `Content/Blueprints/` : 23 notifies du sample (`BP_AnimNotify_FoleyEvent*`, `BP_NotifyState_EarlyTransition`, `_MontageBlendOut`, `_MMI_*`, `BP_AnimNotify_TriggerRagdoll`, `_OverrideMovementMode`…) liés à 1278 animations de `Characters/UEFN_Mannequin`. Laissés en place volontairement : les déplacer réenregistrerait 1,78 Go d'animations. Pour la plupart doublons de `Systems/Blueprints/AnimNotifies/`.
```
#### 4.2.2 Cover / prone / nage portés du CMC vers le personnage Mover (2026-09-18)
Ces trois fonctionnalités n'existaient que sur `SandboxCharacter_CMC` (via le `CharacterMovementComponent`). Elles ont été reportées sur `Core/Character/SandboxCharacter_Mover`, en passant par les mécanismes du plugin Mover et non par des écritures directes sur la capsule.
- **Composants ajoutés au Mover** : `Systems/Movement/Cover/AC_Cover` *(supprimé depuis, remplacé par la couverture AST — §4.2.5)* (le composant du CMC, générique : il travaille via `GetOwner`) et `Arrow1` (ArrowComponent, `(0,0,90)` / yaw 90). ⚠️ `Arrow1` DOIT être attaché au **SkeletalMesh** (comme au CMC) et non à la capsule : le mesh porte un yaw −90, donc l'arrow attaché au mesh se retrouve aligné sur l'acteur (avant = avant de l'acteur, droite = droite de l'acteur), ce qu'attend le sphere trace de `MoveAlongWall` (il part de 55 uu sur le côté puis trace 60 uu vers l'avant, dans le mur). Attaché à la capsule, ses axes sont tournés de 90° : le trace part dans le vide, `MoveAlongWall` ne produit aucun `AddMovementInput` et on ne peut pas se déplacer en couverture (bug corrigé le 2026-09-18).
- **Variables ajoutées au Mover** — catégorie `Cover` : `In Cover`, `Cover` (`E_CoverD`), `Edge L`, `Edge R` ; catégorie `Prone` : `IsProne`, `ProneHalfHeight` (40), `CrouchHalfHeight` (55) ; catégorie `Swimming` : `CurrentWaterBodyComp`, `IsSwimming`, `IsUnderwater`, `SwimSprint`, `WaterSurfaceZ`, `WaterDepth`, `SwimVerticalInput`, `SwimStartDepth` (95), `SwimStopDepth` (80), `SwimmingModeClass`.
- **Prone** : `IA_Prone` → `ToggleProne` (bascule `IsProne` + `WantsToCrouch`/`WantsToStrafe` dans `PlayerInputState`), refusé en nage ou en cover. La hauteur de capsule N'est PAS écrite directement (contrairement au CMC qui fait `SetCapsuleSize`) : `Apply_CrouchOrProne`, appelée depuis `On_PreSimulateTick`, règle `StanceSettings.CrouchHalfHeight` (40 en prone, 55 accroupi) et force un cycle `UnCrouch`→`Crouch` quand la capsule courante ne correspond pas — c'est le `FStanceModifier` du plugin qui redimensionne la capsule, replace l'acteur et décale le mesh. `IA_Crouch` en prone appelle `ExitProneToCrouch` (retour accroupi).
- **Nage** : les overlaps de la capsule (`OnComponentBegin/EndOverlap` → cast `WaterBody`) renseignent `CurrentWaterBodyComp` ; `Update_Swimming` (appelée depuis `On_PreSimulateTick`, voir l'encadré « tick » plus bas) lit `GetWaterSurfaceInfoAtLocation` (surface + profondeur), met à jour `SwimSprint` (depuis `PlayerInputState.WantsToSprint`) et `SwimVerticalInput` (valeurs brutes de `IA_Jump` − `IA_Crouch`), puis `StartSwimming` (profondeur ≥ `SwimStartDepth` → `QueueNextMovementMode("Swimming")`) / `StopSwimming` (< `SwimStopDepth` → `Falling`). `IA_SwimmingDeep` bascule `IsUnderwater` (plongée). Le mode « Swimming » est enregistré **au BeginPlay** par `AddMovementModeFromClass` (variable `SwimmingModeClass`) et non dans la map `MovementModes` du composant : une instance créée par script dans le template du parent est privée et empêchait la sauvegarde de `SandboxCharacter_Mover_Ragdoll` (« Illegal reference to private object »).
- **Cover** *(⚠️ OBSOLÈTE depuis le 2026-09-27 : `AC_Cover` a été supprimé et remplacé par le système de couverture AST, voir §4.2.5 ; les variables `In Cover`/`Cover`/`Edge L/R`, `ApplyCoverCrouch` et `Update_Cover` restent dans le BP mais ne sont plus alimentées)* : l'événement `OnEntreredCover` d'`AC_Cover` recopie `InCover`/`cover`/`EdgeL`/`EdgeR` sur le personnage puis `ApplyCoverCrouch` (accroupi **et `WantsToStrafe` = en couverture** tant qu'on y est) ; `IA_Move` appelle `AC_Cover.MoveAlongWall` quand `In Cover`. Comme le Mover ignore `AddMovementInput`, `Get_MoveInput` renvoie, en cover, `ConsumeMovementInputVector` (le vecteur accumulé par le composant). `Update_Cover` (`On_PreSimulateTick`) met `CharacterMover.bAcceptExternalMovement` à la valeur d'`AC_Cover.InCover`, sinon le `MoveComponentTo` de collage au mur serait annulé par la simulation Mover. ⚠️ Le mode **strafe est indispensable** en couverture : sans lui le Mover réoriente le personnage dans la direction du glissement dès le premier pas, l'`Arrow1` tourne avec, le trace de `MoveAlongWall` rate le mur et le déplacement se bloque après quelques centimètres (bug corrigé le 2026-09-18). L'entrée en couverture reste sur la touche **P** : depuis le 2026-09-18 le corps de cet `InputKey` est déporté dans l'événement `ToggleCover` d'`AC_Cover` (la touche P l'appelle), ce qui permet de déclencher la couverture par script/test ou de la remapper plus tard sur une Input Action.
- **Alimentation de l'animation** : `Get_PropertiesForAnimation` renvoie désormais `Stance` = Swinning / SwinningDeep / Prone / Crouch / Stand, et `Get_Gait` renvoie Prone en prone, Swinning/SwinningSprint en nage. `Get_MoveInput` utilise la rotation caméra complète (input 3D) en plongée comme en vol. Le chooser `Characters/UEFN_Mannequin/Animations/MotionMatchingData/CHT_PoseSearchDatabases_Mover` est une copie du `_Dense` du CMC dont le `ContextData` pointe sur `SandboxCharacter_Mover_ABP` ; ses deux colonnes booléennes (`Is Armed`, `IS Moving Object`) sont désactivées et la ligne « push » restreinte à `Stance = Stand`.
- **Les deux moteurs d'animation de l'ABP (Motion Matching vs state machine)** : l'`AnimGraph` (CMC comme Mover) contient un `Blend Poses by int` dont l'entrée **0 est le nœud Motion Matching** et l'entrée **1 la state machine « State Controller » + Blend Stack** (la voie « expérimentale » du sample GASP). Voie 0 : `Update_MotionMatching` (fonction de nœud) évalue un chooser qui renvoie une LISTE de `PoseSearchDatabase`, `SetDatabasesToSearch` les donne au nœud, qui recherche chaque frame la meilleure pose d'après la trajectoire (`Update_Trajectory`) — variables `ValidDatabases`, `CurrentSelectedDatabase/Anim`, `MM Search Cost`, réglages `Get_MMBlendTime`/`Get_MMInterruptMode`. Voie 1 : à chaque entrée d'état, `SetBlendStackAnimFromChooser` évalue `CHT_MoverCharacterAnimations_PoseMatch` (chooser **par clip**, une ligne = une animation) qui renvoie UNE anim + ses réglages de blend, poussée dans le `Blend Stack` — variables `StateName`, `BlendStackInputs`, `Search Cost`, `NoValidAnim`. Dans les deux ABP l'index valait la constante `0` (Motion Matching en permanence) ; sur le Mover il passe par la variable `AnimBranchIndex`, qui vaut elle aussi `0` en permanence depuis le 2026-09-18 (l'hybride state machine / Motion Matching a été abandonné à la demande de l'utilisateur, voir §4.2.1). Les CVars GASP `DDCvar.UseExperimentalStateMachine` / `DDCvar.ExperimentalStateMachine` n'existent pas dans ce projet (avertissement « Failed to find console variable » en PIE) : elles ne commandent rien.
- **Correction de `MovementModeMap`** : « Ragdoll » pointait sur *Climbing* et « Flying » sur *SwimmingOnSurface* (décalage laissé par la fusion des enums GASP → projet, §8.2). Remis sur `Ragdoll`/`Flying` + ajout de `Swimming` → `SwimmingOnSurface`. Effet de bord bénéfique : le ragdoll du pion refonctionne (son Tick et `EnteredRagdollSuccessfullyCheck` comparent le mode courant à `Ragdoll`).
- **⚠️ Tick du personnage Mover (corrigé le 2026-09-18)** : `SandboxCharacter_Mover` et `_Ragdoll` avaient `PrimaryActorTick.TickInterval = 30 s` (le CMC est à 0). Tout leur `Event Tick` — `CacheInputsFromMover`, `Update_FloorValues`, `Update_ControlRotationRate`, `Update_SlidingAudio`, `Update_TargetedActor`, `Update_TwinStickMode`, `Update_SmoothedAnalogInputAmount`, `DebugDraws` — ne tournait donc qu'une fois toutes les 30 secondes (c'est ce qui empêchait la nage de se déclencher : `Update_Swimming` y était branchée). Intervalle remis à **0** sur les deux pions, et par sécurité `Update_Swimming` + `Update_Cover` ont été déplacées du `Event Tick` vers `On_PreSimulateTick` (délégué du `CharacterMover`, exécuté à chaque pas de simulation). Si un jour le tick de ces pions est de nouveau throttlé (politique `BP_TickManager`), la nage et la couverture continueront de fonctionner, mais les `Update_*` du framework GASP, eux, en souffriront.
- **⚠️ Bases Pose Search de nage vides → T-pose (contourné le 2026-09-18)** : sur les 6 bases de nage, **une seule est réellement remplie**, `Animations/humain/SwimmingAnimation/Swimming_animation/Breastok/surface/PSD_SwimmingMoving` (5 clips de brasse en surface). `PSD_SwimmingIdle`, `PSD_SwimmingMovingSprint`, `PSD_SwimmingDeepIdle`, `PSD_SwimmingDeepMoving`, `PSD_SwimmingDeepMovingSprint` ont des lignes dont l'`AnimAsset` est **None** (log : `OnGetComplete - Database None entry N has no associated animation asset`), et la table imbriquée `SwimmingInDeepIdle` du chooser n'a **aucun résultat**. L'ABP étant désormais 100 % Motion Matching, une base vide = aucune pose = **T-pose**, exactement le bug remonté. Contournement appliqué dans `CHT_PoseSearchDatabases_Mover` : les lignes qui pointent sur une base vide (12, 14, 15, 17) sont rendues inatteignables (`Stance = Prone` alors que `Movement Mode = Swimming`, combinaison impossible par construction) et les deux lignes qui pointent sur `PSD_SwimmingMoving` (13 pour Swinning, 16 pour SwinningDeep) passent `Movement State` et `Gait` en **MatchAny** pour couvrir tous les cas de nage. Vérifié en PIE : nage de surface immobile, nage de surface en mouvement et nage profonde sélectionnent bien `RM_Breastroke_overwater_01_*`.
- **Vraie correction à faire à la main** (impossible par script : la propriété `DatabaseAnimationAssets` d'un `PoseSearchDatabase` et le `ResultsStructs` d'un `ChooserTable` ne sont exposés ni au Python d'UE ni à `ObjectTools.set_properties` — seules `columnsStructs`/`contextData` le sont) : ouvrir chaque base vide et y glisser les clips qui existent déjà sous `Animations/humain/SwimmingAnimation/` — `IDLE/IP_idle_underwater_01_Retargeted` + `IDLE/RM_Idle_turn_*` pour `PSD_SwimmingIdle`, `Swimming_animation/Crawl/RM_Crawl_*` pour `PSD_SwimmingMovingSprint`, `Swimming_animation/Breastok/RM_Breastroke_underwater_*` pour `PSD_SwimmingDeepIdle/Moving/MovingSprint` — puis remettre les lignes 12/14/15/17 du chooser sur leur `Stance` d'origine (3, 3, 4, 4) et donner un résultat à la table imbriquée `SwimmingInDeepIdle`.
- **Restes connus** : le cover du Mover joue les anims accroupies GASP, pas les bases de couverture du CMC (`PSD_Dense_Crouch_Idles_CoverLeft/Right`) ni le nœud `Mirror` de l'AnimGraph du CMC ; en couverture le personnage conserve un écart d'orientation d'une dizaine de degrés par rapport au mur (orientation strafe qui converge lentement).
#### 4.2.3 Système d'armes porté sur le personnage Mover (2026-09-19)
Ce qui marchait déjà sur le Mover avant cette session (vérifié en PIE) : le ramassage (`IA_InteractAGIS`), le rangement au dos (socket `SpineRifle`), le passage en main par la roue V/B (`IA_QuickSelectAGIS` / `IA_QuickEquipAGIS` → socket `Rifle`), l'appel de `AC_Weapon::Equip`/`Unequip` via `HandleHeldStateChanged`, et le tir (`IA_UseItemPrimaryAGIS` → `Inventory_Player::UseHeldPrimarySERVER` → `BP_Item_AdvancedRifle::EventPrimaryUse`). `Inventory_Equipper` est générique : son `OwnerMesh` se résout bien sur le composant `SkeletalMesh` du Mover.
Ce qui ne marchait PAS et a été corrigé :
- **`AC_Weapon` ne parlait qu'au CMC** : `Equip`/`Unequip` faisaient un `Cast To SandboxCharacter_CMC`, qui échoue sur le Mover (`Pawn` vs `Character`, aucune classe commune). Résultat : arme bien en main, mais `Weapon` et `isArmed` jamais écrits → locomotion désarmée en permanence. Une branche `Cast Failed` → `Cast To SandboxCharacter_Mover` a été ajoutée dans les deux fonctions ; le CMC est inchangé.
- **Nouvelle fonction `SandboxCharacter_Mover::SetEquippedWeapon(WeaponTypeByte)`** (appelée par `AC_Weapon`, `0` = déséquipement) : écrit `Weapon_Equip` (nouvelle variable, enum `Weapon_Equip`), `isArmed` (= byte ≠ 0), `WantToshot` (remis à false au déséquipement) et `Weapon`. ⚠️ **Trois enums d'arme coexistent dans le projet** : `Weapon_Equip` (None/Bow/OneArmPistol/TwoArmPistol/Riffle/Bat — c'est l'espace de `AC_Weapon::WeaponTypeByte` et des colonnes du chooser de locomotion), `E_WaponType` (None/Riffle/Pistoll/Bow — c'est le type de `SandboxCharacter_Mover::Weapon` et du champ `S_CharacterPropertiesForAnimation.Weapon_Type`, lu par les choosers d'aim offset de `Systems/Aiming/`) et `E_WeaponType` (inutilisé ici). `SetEquippedWeapon` fait donc la conversion 1(Bow)→3, 2/3(Pistol)→2, 4(Riffle)→1, reste→0.
- **IA_Aim renseigne `WantToshot`** (nouvelle variable du pion) : `WantToshot = ActionValue AND isArmed` sur `Triggered`, remis à `false` sur `Completed`. C'est ce bool que lit la colonne « Want Toshot » du sous-chooser `Stand Idles Bow` pour passer de l'idle arc à l'idle arc en visée.
- **`SandboxCharacter_Mover_ABP` reçoit l'état d'arme** : nouvelles variables `Weapon_Equip`, `Weapon_Equip Last Frame`, `Want Toshot` (+ `Is Armed` et `IS Moving Object` déjà présentes), toutes alimentées en fin de `Update_PropertiesFromCharacter` (**thread principal**, comme le CMC le fait dans son `EventBlueprintUpdateAnimation` — surtout pas dans `Update_Logic`, qui peut tourner en thread-safe update).
- **`Is Armed` de l'ABP = `isArmed` ET `Weapon_Equip == Bow`** — *stopgap assumé* : l'arc est la SEULE arme qui possède des bases Motion Matching armées (`PSD_Dense_Stand_Bow_Idles`, `_Bow_Aim_Idles`, `PSD_Walk_Bow`). Sans cette condition, un fusil mettait `Is Armed = true`, les lignes 0/2 du chooser (`MatchFalse`) étaient rejetées, les lignes arc 1/3 échouaient sur leur colonne `Weapon_Equip`, et on retombait sur la ligne 18 « Push » (ou sur rien du tout). Avec la condition, fusil/pistolet jouent proprement la locomotion désarmée, arme en main. À retirer le jour où des bases fusil existeront.
- **`Update_MotionMatching`** : le chooser `CHT_PoseSearchDatabases_Mover` n'était consulté que si `Stance` ∉ {Stand, Crouch} (prone/nage). La condition devient `(Stance ≠ Stand ET ≠ Crouch) OU Is Armed`, sinon les lignes arc — qui sont des lignes *debout* — n'étaient jamais atteintes. La sortie `False` du Branch est en plus reliée à `SetDatabasesToSearch` pour que les deux chemins poussent bien leur liste dans le nœud MM.
- **`Get_MMInterruptMode`** : ajout du terme `Weapon_Equip ≠ Weapon_Equip Last Frame` sur le `OR` racine. Sans lui, changer d'arme ne forcçait aucune interruption : Motion Matching gardait la pose continue (anim désarmée) et ne basculait jamais sur la base arc, même une fois celle-ci dans la liste (coût de continuité toujours plus bas). C'est le même piège que celui décrit en §1.3 pour le CMC.
- **`CHT_PoseSearchDatabases_Mover`** : colonne `Is Armed` **réactivée** (elle avait été désactivée le 2026-09-18 faute de propriété correspondante sur l'ABP Mover), colonne `IS Moving Object` réactivée aussi (sinon la ligne 18 « Push » devient un attrape-tout qui remporte tous les cas debout non couverts), et **ligne 1 (« Stand Idles Bow ») : colonne `Gait` passée de `MatchEqual Walk` à `MatchAny`**. C'était le dernier blocage : l'allure par défaut du personnage est `Run`, donc la ligne arc ne pouvait jamais matcher à l'arrêt. (Le même défaut existe dans `CHT_PoseSearchDatabases_Dense` côté CMC, non corrigé ici.)
Vérifié en PIE, dans cet ordre : ramassage du fusil → dos ; V+B → main + `Weapon = Riffle`, `Weapon_Equip = Riffle`, `isArmed = true` côté pion ET côté ABP ; locomotion désarmée propre avec le fusil en main (pas de T-pose) ; arc → `PSD_Dense_Stand_Bow_Idles` → `StandingIdle01_UE_Anim3` ; arc + clic droit → `Want Toshot = true` → `PSD_Dense_Stand_Bow_Aim_Idles` ; arc en course → repli propre sur `PSD_Dense_Stand_Run_Loops` ; clic gauche → `EventPrimaryUse` du fusil (qui refuse de tirer faute de chargeur équipé — comportement normal, non lié au Mover) ; non-régression prone (`PSD_Dense_Prone_Idles`) et nage (`PSD_SwimmingMoving`).
Restant à faire :
- Aucune base Motion Matching armée pour le fusil / le pistolet (seul l'arc en a) ; `PSD_Crouch_Bow` existe mais n'est référencée par aucune ligne de chooser.
- `SandboxCharacter_Mover` a désormais un `AC_Stats` et ses capsules de hitbox (voir « Hitboxes calées sur le MetaHuman » en §4.2.4) ; il lui manque encore le `PerceptionStimuliSource` du CMC.
- La visée n'a pas d'équivalent de `SandboxCharacter_CMC::ISaiming` sur le Mover (seuls `PlayerInputState.WantsToAim` et `WantToshot` existent).
#### 4.2.4 MetaHuman en « visual override » du personnage Mover (2026-09-19)
Approche retenue (celle du sample GASP, cf. la vidéo de référence) : **le mannequin UEFN reste le mesh du pion et porte toute l'animation ; le MetaHuman est un acteur enfant qui le recopie par retarget IK.** Le mesh du pion est simplement rendu invisible. Avantage : rien ne change côté Motion Matching, sockets, prone/nage, ragdoll — et on peut changer de « peau » à chaud.
Le pipeline existait déjà dans le projet mais était **incomplet** : `SandboxCharacter_Mover` a son `ChildActorComponent` `VisualOverride` (tagué `VisualOverride`) et son `AC_VisualOverrideManager`, mais tous les acteurs de skin du sample (`BP_Manny`, `BP_Quinn`, `BP_Echo`, `BP_Twinblast`, `BP_UE4_Mannequin`) **et l'AnimBP qui les pilote** avaient été supprimés au nettoyage de la démo GASP.
Assets créés / restaurés, tous dans `Content/Systems/Mover/VisualOverrides/` :
- **`ABP_GenericRetarget`** — récupéré du commit initial (`538ebd2a`). C'est un **Animation Blueprint Template** (`bIsTemplate`, donc sans squelette cible : utilisable sur n'importe quel mesh). Son AnimGraph tient un seul nœud `Retarget Pose From Mesh` ; son `EventBlueprintUpdateAnimation` choisit l'`IKRetargeter` dans la map `IKRetargeter_Map` **en utilisant le premier Component Tag du composant qui l'héberge** comme clé. La map garde ses entrées d'origine (toutes à `None`, les retargeters du sample ayant été supprimés) plus la nôtre.
- **`IK_MetaHuman_test`** — IK Rig sur `/Game/Characters/MEtahuman/Unpacked/test/Body/SKM_test_BodyMesh`, généré par `IKRigController.apply_auto_generated_retarget_definition()` (l'auto-caractérisation reconnaît le squelette MetaHuman : bassin = `pelvis`, 29 chaînes standard).
- **`RTG_UEFN_to_MetaHuman_test`** — source `IK_UEFN_Mannequin`, cible `IK_MetaHuman_test`. `auto_map_chains(EXACT)` mappe tout sauf deux chaînes, l'auto-caractérisation MetaHuman nommant `LeftFoot`/`RightFoot` ce que le mannequin appelle `LeftToe`/`RightToe` — mappées à la main.
⚠️ **`add_default_ops()` ne crée PAS l'op des chaînes IK.** La pile par défaut est Pelvis Motion / FK Chains / Run IK Rig / Root Motion / Remap Curves ; or `ABP_GenericRetarget::UpdateRetargetProfile` va chercher l'op **nommée « Retarget IK Goals »** (`GetOpControllerFromRetargetProfile` → cast vers `IKRetarget_IKChains_Controller`) pour moduler l'alpha d'IK par chaîne. Sans elle, le cast renvoie None et `SetSettings` spamme un *Blueprint Runtime Error* à chaque frame. Correctif : `add_retarget_op("/Script/IKRig.IKRetargetIKChainsOp")` — le **chemin de classe complet est obligatoire**, le nom court est refusé (retourne -1). L'op arrive déjà nommée « Retarget IK Goals » et se place juste avant « Run IK Rig » ; il faut ensuite refaire `assign_ik_rig_to_all_ops` + `auto_map_chains` (les mappings de chaînes sont **par op**). Résultat : 4 chaînes IK (LeftLeg, LeftArm, RightLeg, RightArm), plus aucune erreur en PIE.
- **`BP_VisualOverride_MetaHuman`** — Actor : `Body` (racine, `SKM_test_BodyMesh`, `AnimClass = ABP_GenericRetarget_C`, **Component Tag `RTG_UEFN_to_MetaHuman_test`**) → `Face` (`SKM_test_FaceMesh`, `ABP_Face_C`) ; plus un `MetaHumanComponentUE` (`BodyComponentName = "Body"`, `FaceComponentName = "Face"`, post-process `ABP_Clothing_PostProcess`). Pas de grooms : ce MetaHuman n'en a aucun d'assigné.
Câblage de la sélection :
- `GM_Sandbox` (`Systems/Mover/`) reçoit `BP_VisualOverride_MetaHuman` dans `VisualOverrides_Soft`.
- **`GM_Dev` est reparenté sur `GM_Sandbox`.** C'était un `GameModeBase` totalement vide (aucune variable, aucun nœud) qui ne servait qu'à fixer `DefaultPawnClass` ; or `AC_VisualOverrideManager::FindAndApplyVisualOverride` fait un `Cast To GM_Sandbox` — sans ce reparentage, la DevMap n'appliquait **jamais** d'override (pire : le cast raté le remettait activement à zéro). `DefaultPawnClass`, `PawnClasses_Soft` et `VisualOverrides_Soft` sont renseignés sur `GM_Dev`.
- La sélection se fait par le CVar **`DDCvar.VisualOverride`** (index dans la liste, `-1` = pas d'override). Son défaut est passé de `-1` à **`0`** dans `Config/DefaultEngine.ini` pour que le MetaHuman soit affiché d'entrée. Repasser à `-1` (ou taper `DDCvar.VisualOverride -1` en console) rend le mannequin.
Vérifié en PIE : le mannequin passe invisible, `BP_VisualOverride_MetaHuman` est spawné dans le `ChildActorComponent`, le MetaHuman (corps + tête + matériaux) suit l'animation à l'arrêt comme en course, le Motion Matching continue de tourner normalement sur le mannequin caché (`PSD_Relaxed_Stand_Idles` → `PSD_Relaxed_Stand_Run_F_Loops`).
⚠️ **Limite structurelle de cette approche** : les objets équipés s'accrochent aux sockets du **mesh pilote caché**, pas du MetaHuman (`Inventory_Equipper` attache sur `OwnerMesh`, qui est le composant `SkeletalMesh` du pion). Mesuré en PIE : ~**12 cm** d'écart entre `hand_r` du mannequin et `hand_r` du MetaHuman — une arme en main flottera d'autant. Correctif à prévoir : pointer `OwnerMesh` sur le composant `Body` de l'override quand un override est actif, ou n'utiliser l'override que pour des skins aux proportions du mannequin.
**Hitboxes calées sur le MetaHuman (2026-09-27)** : les 10 `CapsuleComponent` de hitbox du pion (`Head_Capsule`, `Torso_Capsule`, `harm_Up/Down_L/R_Capsule`, `Leg_Up/Down_L/R_Capsule`) portent le Component Tag **`Hitbox`** et sont dimensionnées d'après le Physics Asset du MetaHuman (`/Game/test/Body/PHYS_test`, rayon +1 cm pour les vêtements, avant-bras prolongé jusqu'au milieu de la main, torse = une capsule de `pelvis` à `spine_04` exprimée dans `spine_01`) ; échelle remise à 1 (l'ancienne échelle non uniforme compensait les os mis à l'échelle du mannequin UEFN : tête ×1,15, jambes ×1,12). `AC_VisualOverrideManager` (`Systems/Mover/`) a une fonction **`AttachHitboxesTo(Target)`** : elle réattache tout composant tagué `Hitbox` du propriétaire sur `Target`, même socket, `KeepRelative`. `ApplyVisualOverride` l'appelle deux fois : d'abord vers le mesh pilote **avant** `SetChildActorClass` (détruire l'override détache les composants étrangers qui y sont attachés, qui perdent alors leur socket), puis vers `GetChildComponent(VisualOverride, 0)` (= `Body`) si un override est actif. Les capsules restent des composants du pion, donc les hits résolvent toujours le pion et son `AC_Stats`. Avec `DDCvar.VisualOverride -1`, elles retombent sur le mannequin, avec des dimensions de MetaHuman (légèrement trop grandes). `Torso_Capsule` et les jambes sont en `NoCollision` (réglage antérieur, inchangé).
Autres points :
- **Portrait de l'inventaire (2026-09-21)** : `RefreshInventoryCapture` (Mover) appelle en fin de chaîne le nouveau custom event `AddAttachedActorsToInventoryCapture`, qui ajoute à la show-only list de `SceneCaptureComponent2D` les composants de chaque acteur attaché au pion (récursif). Sans ça, les objets équipés (vêtements `BP_Item_SurvivorOutfit_*`, armes) n'apparaissaient pas dans le portrait : ils appartiennent à l'acteur item, pas au pion ni à ses child actors.
- Le ragdoll n'a pas été testé avec l'override actif (il simule sur le mesh caché ; le MetaHuman devrait suivre par retarget).
- Les 13 sockets recopiées sur `SKM_test_BodyMesh` lors de la tentative précédente ont été retirées : elles ne servent plus, les attaches restant sur le mannequin.
- Pour ajouter une autre peau : créer `IK_X` + `RTG_UEFN_to_X`, ajouter l'entrée dans `ABP_GenericRetarget.IKRetargeter_Map`, dupliquer `BP_VisualOverride_MetaHuman` en changeant meshes et Component Tag, et l'ajouter à `VisualOverrides_Soft`.
#### 4.2.5 Couverture + interactions portées depuis le projet AST (2026-09-27)
Source : projet frère `E:\AST_V1_17\AST_V1_17` (framework GASP + plugins `AST_*`). Le contenu des plugins Cover / Interaction / StateManager a été copié à la main dans `Systems/Movement/` ; ses références internes (`/AST_CoverSystem/…`, `/AST_StateManager/…`, `/Game/Blueprints/…` du GASP d'AST) sont résolues par les **`[CoreRedirects]` de `Config/DefaultEngine.ini`** (bloc « Port AST ») puis les assets ont été resauvegardés. Ne pas retirer ces redirections tant que tout le contenu AST n'a pas été resauvegardé (ceux hors du périmètre porté, ex. `StateManager/Maps`, gardent les anciens chemins).
- **Personnage `Core/Character/SandboxCharacter_Mover`** : composants `BPC_StateManager` (tag `StateManager`), `BPC_CoverSystem` (tag `CoverSystem`), `BPC_Interaction` (tag `InteractionSystem`) — les tags sont obligatoires, `StateActorComponent::SetComponents` retrouve les composants par tag.
- `IA_TakeCover` (**touche A**, `IMC_Sandbox` — AZERTY, Q = déplacement) `Started` → `BPC_CoverSystem.FindCoverWall`.
- `IA_Interact` (**touche E**, partagée avec les SmartObjects GASP) `Started` → `BPC_Interaction.CheckInteraction(StateManager.GetClosestActor)`.
- `Get_MoveInput` : si `CoverState ≠ None` → pousse l'input brut dans `StateManager.SetMovementInputAsVector` puis renvoie `BPC_CoverSystem.GetCoverMovement` (0 pendant les transitions, `CanCoverMove` faux).
- `Get_RotationMode` (→ Strafe), `Get_Gait` (→ `StateManager.Get_Gait`, qui met aussi à jour la collision de l'objet poussé) et `Get_OrientationIntent` (→ avant de l'acteur) sont court-circuités quand `CoverState ≠ None` **ou** `OverlayState = Pullpush`.
- `ProduceInput` : synchronise `StateManager.SetInputCrouch(PlayerInputState.WantsToCrouch)` et `WantsToCrouch = joueur OR StateManager.Get_Crouch` (couverture basse ⇒ accroupi).
- **ABP `Systems/Mover/SandboxCharacter_Mover_ABP`** : `Slot DefaultSlot → OverlayGraph (Linked Anim Graph, tag Overlay) → Dead Blending` et `Procedural_FullBody → InteractionIKGraph → Pose History`. `Get/Set_InteractionTransform` existaient déjà (GASP 5.8).
- **`OverlayGraph::CanDynamicTransition` renvoie toujours false** : les transitions ALS (petits pas « Grounded Slot ») étaient jouées en montage dynamique et, le slot `Grounded Slot` étant dans `DefaultGroup` du squelette `SK_UEFN_Mannequin` chez nous (groupe à part `GroundedGroup` chez AST), elles interrompaient au bout de 4 frames les montages de couverture/interaction/traversée. Pour les réactiver proprement : dans le squelette → Anim Slot Manager, créer `GroundedGroup` et y déplacer `Grounded Slot` (AST range aussi `HitReactionSlot` dans `HitGroup` et Fire/Legs/Arm R/Curves/Spine/Head/Arm L/Pelvis dans `Layer`) — non scriptable (SlotGroups non exposé à Python).
- `BPC_Interaction::Release PullPush Object` remet l'overlay sur **None** (AST : Default) pour retrouver l'état de départ du perso.
- Vérifié en PIE (`Maps/DevTest/L_ASTCoverTest`) : couverture basse et haute (entrée par montage motion-matché, glissement, demi-tour, sortie en reculant), porte, porte électronique, placard, levier, pousser/tirer. `MapDev1` : aucune erreur runtime liée à AST.
- **Restes connus** : l'objet poussé passe 0,2 s en physique au relâchement et peut glisser ; `BP_MovementMode_WalkingAST` (décélération instantanée en couverture) est porté mais non utilisé ; les os virtuels d'arme AST (`VB RHS_WeaponTrace`…) manquent au squelette (avertissements de compilation d'`OverlayGraph`, sans effet hors armes AST) ; la visée depuis la couverture (`UpdateCoverAim`) n'a pas été testée.
### 4.3 Autres systèmes de gameplay (ajouts spécifiques au projet)
```text
Content/Systems/
├── Stats/
│ └── AC_Stats.uasset → ActorComponent ACTIF : gère la vie du personnage (Health), la mort (bIsDead, dispatcher OnDeath), Heal, Revive (voir §1.3). Ajouté cette session pour le système de mort/respawn. (2026-09-05) Extension additive pour la faune : nouveau dispatcher `OnDamaged(BodyPart, DMGValue)`, diffusé au point de convergence partagé de `TakeDamage` (après le switch par bodypart, avant le calcul de mort) — permet à un consommateur (ex. BP_AnimalBase) de réagir à CHAQUE coup reçu, pas seulement à la mort, sans dupliquer la logique de dégâts.
│
├── Weapons/ (2026-09-05) → Architecture d'armes générique, réutilisable par toute arme (arc, arme à feu, futur mêlée). Voir §1.3 pour le mécanisme d'équipement.
│ ├── AC_Weapon.uasset → ActorComponent commun à TOUTE arme (ajouté à BP_Item_Bow et BP_Item_AdvancedRifle). Ne contient AUCUNE logique spécifique à un type d'arme. Variables : bIsEquipped, bIsBusy, CooldownDuration, LastUseTime, OwnerCharacter. Dispatchers : OnEquipped/OnUnequipped/OnPrimaryUse(Stopped)/OnSecondaryUse(Stopped). Fonctions : CanUse(), Equip/Unequip, TryUsePrimary/StopPrimary, TryUseSecondary/StopSecondary. Son EventGraph s'auto-abonne à `OnOwnerChanged` de son propre acteur (pattern `Default|AssignOnOwnerChanged`) pour appeler Equip/Unequip automatiquement — zéro couplage avec Inventory_Player/Equipper.
│ ├── BFL_WeaponDamage.uasset → Blueprint Function Library. `ApplyPointDamage(Target, Instigator, HitBoneName, Damage)` : résout le composant `AC_Stats` de la cible (`GetComponentByClass` + cast, no-op silencieux si absent) et route vers `AC_Stats::TakeDamage`, en mappant le nom d'os touché (bones du squelette UEFN_Mannequin : head/neck_*→Head, thigh|calf|foot|ball_L/R→LeftLeg/RightLeg, clavicle|upperarm|lowerarm|hand_L/R→LeftArm/RightArm, tout le reste dont spine_*/pelvis/root→Torso par défaut). Réutilisable par toute arme/projectile/piège futur.
│ ├── BP_Projectile_Base.uasset → Actor projectile générique : StaticMeshComponent racine `ArrowMesh` (mesh + collision, PAS de mesh par défaut — à fournir par les enfants) + `ProjectileMovement`. `OnComponentHit` : ignore les hits sur son propre `Owner` (évite l'auto-hit au tir), appelle `BFL_WeaponDamage::ApplyPointDamage` puis `HandleImpact(Hit)` — fonction overridable (défaut : `DestroyActor`). Variable `BaseDamage` (instance-editable).
│ └── BP_Projectile_Arrow.uasset → Hérite de BP_Projectile_Base. Override du mesh `ArrowMesh` (mesh Fab `cc0_wooden_arrow`, collision `BlockAllDynamic`) via override de composant hérité (PAS le template du parent — voir piège §9). Override `HandleImpact` : au lieu de se détruire, se fige (désactive la simulation + la collision) et s'attache (KeepWorld) au composant touché pour rester planté visuellement dans le décor/la cible.
│
├── Wildlife/ (2026-09-05) → Premiers animaux sauvages (cerf mâle + biche). Architecture générique : toute la logique commune vit dans `BP_AnimalBase` + un unique StateTree partagé ; une espèce (Content/Systems/Wildlife/BP_Deer_Stag, BP_Deer_Doe) ne fait qu'assigner mesh/anims/tunables — ajouter un sanglier ou un loup plus tard consiste à créer UN NOUVEAU Character enfant de `BP_AnimalBase`, sans toucher au StateTree ni aux tâches. Réutilise tel quel `AC_Stats` (vie/dégâts/mort) et `BP_ScentPoint` (odeurs) — voir §1.3. Nécessite le plugin **GameplayStateTree** (ajouté à `ProjectFv1.uproject` cette session, `StateTreeAIComponentSchema`/`StateTreeComponentSchema` indisponibles sans lui — seul le plugin `StateTree` de base était actif auparavant) ; premier usage de StateTree dans ce projet (aucune Behavior Tree/Blackboard/AIController n'existait avant).
│ ├── E_AnimalState.uasset → Enum (UserDefinedEnum, créé via `unreal.EnumStructService`) : Idle/Wander/Graze/Rest/Alert/Flee/Dead. Sert d'étiquette de debug + de sélecteur d'anim d'arrêt (voir `BP_AnimalBase::ApplyIdleForState`) ; le StateTree stocke en interne l'état courant sous forme de `byte` (les outils Blueprint disponibles ne permettent pas d'ajouter une variable membre ou un paramètre de fonction/dispatcher directement typé sur un enum utilisateur — limitation déjà documentée §9 pour AC_Stats, confirmée à nouveau ici).
│ ├── AC_AnimalSenses.uasset → ActorComponent générique (vue + odorat), ajouté à `BP_AnimalBase`. Vue : ne fait AUCUNE détection elle-même — se contente de variables (`bCanSeePlayer`, `DistanceToPlayer`, `LastKnownPlayerLocation`) mises à jour par `BP_AnimalAIController` via `ReportPlayerSighted`/`ReportPlayerLostSight`. Odorat : self-contained, `CheckScent` tourne sur un timer (`ScentCheckInterval`, 0.5s par défaut) démarré au `BeginPlay`, interroge `GetAllActorsOfClass(BP_ScentPoint)` et retient le plus proche (`bScentDetected`/`ScentSourceLocation` si distance ≤ `ScentDetectionRadius`, 800 par défaut). `DistanceToPlayer` est recalculée à CHAQUE tick de `CheckScent` (pas seulement quand le joueur est vu) — nécessaire pour que le StateTree puisse détecter le retour au calme (`DistanceToPlayer >= SafeDistance`) même après que l'animal ait fui hors de vue (bug rencontré et corrigé en test PIE, voir §9). (2026-09-14) `CheckScent` réécrit : distance calculée par point d'odeur dans la boucle (l'ancienne version comparait une valeur hors boucle), pawn joueur relu à chaque passe via `GetPlayerPawn(0)` (le cache du `BeginPlay` devenait invalide après mort/respawn → fuite infinie), `LastKnownPlayerLocation` rafraîchie tant que le joueur est vu (la perception ne notifie qu'au gain/perte de vue), et `bCanSeePlayer=false` si aucun pawn.
│ ├── BP_AnimalAIController.uasset → AIController, `AIPerceptionComponent` natif configuré en Python (`AISenseConfig_Sight`, rayon 2500/3000, FOV 90°). `OnTargetPerceptionUpdated` relaie vers `AC_AnimalSenses` du pion possédé (`ReportPlayerSighted`/`ReportPlayerLostSight` selon `Stimulus.WasSuccessfullySensed()`) — ne fonctionne QUE parce que le joueur porte un `AIPerceptionStimuliSourceComponent` (voir §1.3). (2026-09-14) Filtre ajouté : l'événement ne relaie QUE si `Actor == GetPlayerPawn(0)` — sans lui, les autres animaux (pawns) perçus faisaient passer `bCanSeePlayer` à vrai/faux et déclenchaient alertes/fuites entre cerfs. (2026-09-14) Le template `AIPerception` avait perdu son `AISenseConfig_Sight` (`SensesConfig = [None]` → aucune détection) : recréé par Python (`unreal.new_object(AISenseConfig_Sight, outer=<template AIPerception_GEN_VARIABLE>)` via `SubobjectDataSubsystem`), rayon 2500/3000, angle périphérique 90°, MaxAge 5, toutes affiliations. Le pawn joueur actuel (`SandboxCharacter_Mover`, pawn par défaut de `GM_Dev`) n'a pas de `AIPerceptionStimuliSourceComponent` mais est détecté via `AISense_Sight.bAutoRegisterAllPawnsAsSources` (vrai par défaut) ; en revanche il n'émet PAS d'odeurs (`ScentFunct` n'existe que sur `SandboxCharacter_CMC`).
│ ├── BP_AnimalBase.uasset → Character générique, base de TOUTE la faune. Composants : `Stats` (AC_Stats), `AnimalSenses` (AC_AnimalSenses), `StateTree` (StateTreeComponent, schema `StateTreeComponentSchema`, contexte = l'acteur lui-même — PAS `StateTreeAIComponentSchema`, pour rester détaché de l'AIController et garder le StateTree simple). `AIControllerClass` = `BP_AnimalAIController`, `AutoPossessAI` = Placed in World or Spawned. Tunables instance-editable : WalkSpeed/RunSpeed, WanderRadius, IdleTime/GrazeTime/RestTime(Min/Max), AlertTime, FleeDistance (400-600 : distance de panique immédiate si vu de près), SafeDistance (doit être largement > FleeDistance : distance à atteindre pour se recalmer), HerdID (Name — animaux partageant le même HerdID se relaient une alerte), DebugEnabled, Anim_Idle/Wander/Graze/Rest/Alert/Flee/Dead/Hit + Anim_RestEnter/Anim_RestExit (AnimSequence, un jeu à fournir par espèce ; RestEnter/Exit optionnelles). Mouvement (2026-09-14) : `bOrientRotationToMovement=true`, `bUseControllerRotationYaw=false`, RotationRate 220, accélération sur les chemins (`NavMovementProperties.bUseAccelerationForPaths`) ; mesh tourné de Yaw −90 (les squelettes DeerStag/DeerDoe regardent +Y). Fonctions clés : `MoveTo`/`StopMovementAnimal` (wrappers autour de l'AIController caché, utilisés par les tâches StateTree), `SetCurrentState` (met à jour l'état, `bIsFleeing` (=Flee), `bInDeadState` (=Dead) et consomme `bHerdAlertReceived` à l'entrée d'Alert/Flee — ne joue plus d'anim), animation (2026-09-14) : toujours **Single Node** (`SkeletalMeshComponent::PlayAnimation`, pas d'AnimBlueprint) mais pilotée par la VITESSE à chaque tick via `DebugTick→UpdateAnimation` : course (`Anim_Flee`, play rate = vitesse/RunSpeed) au-delà de (Walk+Run)/2, marche (`Anim_Wander`, play rate = vitesse/WalkSpeed) au-delà de ~30, sinon `UpdateIdleAnimation` (Rest avec transitions RestEnter/RestExit verrouillées par `AnimLockUntil`, sinon `ApplyIdleForState` : Graze/Alert/Idle). `ApplyAnim(Anim, Loop, Rate)` ne relance l'anim QUE si elle change (`CurrentAnim`) — l'ancienne version relançait l'anim à chaque changement d'état. `PlayStateAnimation` n'est plus appelée (conservée, inutilisée), `GetThreatLocation` (position du joueur si vu, sinon source d'odeur, sinon position du pawn joueur, sinon position de l'animal), `BroadcastAlertToHerd`/`ReceiveHerdAlert` (alerte les autres membres du même HerdID sans jamais lire une variable d'une AUTRE instance directement — le HerdID appelant est passé en paramètre de fonction, voir piège §9), `HandleDeath` (2026-09-14 : plus de ragdoll — `DisableMovement`, arrêt du polling de vie, capsule en `NoCollision`, `Anim_Dead` joué une fois et figé sur sa dernière frame ; lié à `AC_Stats::OnDeath` via un Custom Event `OnAnimalDeathEvt` — PAS un binding direct sur une fonction classique, voir piège §9), `CheckHealthChanged` (polling toutes les 0.2s de `AC_Stats::GetHealth` : une baisse de vie hors mort → `HandleDamaged` : `bWasHitRecently=true`, remis à false par le timer `ClearHitFlag` 6s après le dernier coup (auparavant JAMAIS remis à false → fuite relancée à chaque tick), anim `Anim_Hit` verrouillée 0.4s ; préféré à un binding direct sur le dispatcher `AC_Stats::OnDamaged`, qui provoque une erreur interne du compilateur Blueprint dans ce projet, voir piège §9).
│ ├── ST_AnimalBase.uasset → StateTree partagé par toute la faune (schema `StateTreeComponentSchema`, contexte = `BP_AnimalBase`). États : `Normal` (groupe) {Idle→Wander→Graze→Rest→Idle, cycle via transitions `OnStateCompleted`/`NextState`}, `Alert` (hésite `AlertTime` secondes puis retourne à Idle si la menace n'est pas confirmée — deny-timeout), `Flee` (fuite), `Dead` (terminal). Transitions clés, TOUTES en conditions liées par binding direct au contexte (`bind_transition_condition_property_to_context`, propriétés imbriquées type `AnimalSenses.bCanSeePlayer` — inclure le préfixe `b` des booléens, voir piège §9), aucune n'exige de Blueprint condition custom : `Normal→Alert` (vue OU odorat OU alerte de horde, combinées en OR via `set_transition_condition_operand`), `Normal→Flee` et `Alert→Flee` (priorité High : vu ET distance ≤ FleeDistance — "trop près, panique immédiate"), `Flee→Normal/Idle` (distance ≥ SafeDistance ET plus vu ET `bWasHitRecently` faux — fuite minimale de 6s après un coup, même tiré de loin), transitions globales posées sur `Root` (s'appliquent depuis N'IMPORTE quel état actif) : `→Flee` (priorité High, `bWasHitRecently` ET pas mort ET `bIsFleeing` faux — sans cette dernière condition, la transition ré-entrait dans Flee à chaque tick) et `→Dead` (priorité Critical, `bIsDead` ET `bInDeadState` faux — même raison que `bIsFleeing` : sans elle, Dead était ré-entré à chaque tick).
│ └── Tasks/ → StateTree Tasks en Blueprint (parent natif `StateTreeTaskBlueprintBase`), chacune réutilisée par PLUSIEURS états via ses propriétés instance-editable liées au contexte (`bind_task_property_to_context`) :
│ ├── STT_SetAnimalState.uasset → Appelle `BP_AnimalBase::SetCurrentState(NewState)` et ne se termine JAMAIS (2026-09-14 : l'ancien `FinishTask` immédiat, avec `TasksCompletion=Any`, terminait chaque état dans la frame même de son entrée → Idle→Wander→Graze→Rest bouclaient à chaque frame, animaux immobiles et anims relancées en permanence ; `set_task_considered_for_completion(false)` n'a PAS suffi, voir §9). C'est la tâche Wait/Move de l'état qui le termine. Un exemplaire par état-feuille, seul `NewState` diffère.
│ ├── STT_Wait.uasset → Attend une durée aléatoire entre `MinTime`/`MaxTime` (liés au contexte : IdleTimeMin/Max, GrazeTimeMin/Max, RestTimeMin/Max, ou AlertTime/AlertTime pour une durée fixe). Nécessite `should_call_tick_only_on_events = true` sur la classe (sinon `ReceiveLatentTick` n'est jamais appelé — propriété peu intuitive, voir piège §9).
│ ├── STT_MoveToRandomPoint.uasset → Utilisé par `Wander`. Tire un point via `NavigationSystem::GetRandomReachablePointInRadius` autour de `HomeLocation` (capturée une fois au spawn), appelle `BP_AnimalBase::MoveTo`, termine en arrivant (≤120 en XY), si l'animal est bloqué (vitesse < 5 après 1.5s) ou après 25s ; `ExitState` arrête le mouvement (une alerte interrompt proprement la marche).
│ └── STT_MoveAwayFromThreat.uasset → Utilisé par `Flee`. À l'entrée : diffuse l'alerte à la horde puis `IssueFleeMove` : point à 1500 dans la direction opposée à `GetThreatLocation` (XY), projeté sur le NavMesh (extent 300/300/1000), sinon point atteignable aléatoire dans 1500 autour de l'animal. Ré-émis tant que l'état Flee est actif dès que l'animal arrive (≤200) ou est bloqué (vitesse < 20 après 1s) — c'est la transition du StateTree qui termine la fuite. `ExitState` arrête le mouvement.
│
│ Espèces (enfants de BP_AnimalBase, aucun nouvel asset graphique créé — réutilisent les meshes/anims
│ déjà présents sous Content/Characters/Animal/DeerStagAndDoe/ et Content/Assets/Animations/DeerAndDoe/) :
│ ├── BP_Deer_Stag.uasset → SK_DeerStag + jeu d'anims ANIM_DeerStag_* (RestEnter/Exit = GoToRest/RestToGoBackUp), HerdID "DeerHerd_A".
│ └── BP_Deer_Doe.uasset → SK_DeerDoe + jeu d'anims ANIM_DeerDoe_* (`Anim_Rest`=IdleRest, manquante avant le 2026-09-14), HerdID "DeerHerd_A" (même horde que le cerf → réactivité croisée via BroadcastAlertToHerd/ReceiveHerdAlert).
│
│ Une instance de chaque a été placée dans `Content/Maps/DevMap/MapDev1` avec un `NavMeshBoundsVolume`
│ (`NavMeshBounds_Wildlife`, RuntimeGeneration = Dynamic — le mode par défaut `Static` ne (re)génère
│ JAMAIS le NavMesh automatiquement, voir piège §9). Étend le NavMesh existant du niveau (aucun n'existait
│ avant cette session).
│
├── Door/
│ ├── Garage_Door/BP_Garage_door.uasset → Porte de garage interactable
│ └── Simply_Door/BP_Simply_Door.uasset → Porte simple interactable
│
├── Movement/
│ ├── Climbing/AC_ClimbingComponent.uasset, CR_ClimbingGraphV2.uasset (Control Rig), CR_Mannequin_BasicFootIK.uasset, ClimbingMaterial.uasset
│ ├── Cover/ → Système de couverture AST (copie du plugin AST_CoverSystem, §4.2.5). L'ancien `AC_Cover` a été supprimé par l'utilisateur.
│ │ ├── Blueprints/Components/BPC_CoverSystem.uasset → Composant cover : `FindCoverWall` (capsule trace devant le perso vers un `CoverWallActor`), montage d'entrée choisi par Motion Matching (`CHT_IdleToCover`), `CoverMovement` (déplacement le long de la spline), demi-tours (`CHT_TurnSide`), visée (`CHT_CoverAim`).
│ │ ├── Blueprints/Actors/CoverWallActor.uasset → Repère de couverture INVISIBLE sans collision bloquante (spline + SplineMesh QueryOnly), à poser ~10 cm devant un vrai mur ; `HighCover` = mur haut (debout) sinon bas (accroupi).
│ │ ├── Blueprints/Interfaces/BPI_CoverSystem, BPI_CoverWall ; Structs/S_CoverMontage, S_CoverAimChooserInput, S_IdleToCoverChooserInput
│ │ └── Character/Animation/ (HighCover/, LowCover/, Montages/, PSD_Cover, MotionMatchingData/CHT_*), Meshes/, Textures/
│ ├── Interaction/ → Système d'interaction AST (copie d'AST_InteractionSystem, §4.2.5).
│ │ ├── Blueprints/ActorComponent/BPC_Interaction.uasset → Composant : sphère de détection (90 uu) → objet le plus proche (`StateManager.ClosestActor`), `CheckInteraction` → MoveTo par montage (`CHT_MovetoInteraction`) puis montage d'action ; pousser/tirer (`Grab/Release PullPush Object`, overlay PULLPUSH).
│ │ ├── Actors/DoorActor, ElectronicDoorActor, CupboardActor, LeverActor, PullPushObject → Interactifs à poser dans les maps (leurs points d'interaction sont des ChildActor `BP_Door`/`BP_Cupboard`… de ASTShared/Inventory).
│ │ ├── Blueprints/Enums, Interfaces, Structs, Data/CHT_MovetoInteraction ; Character/AnimationNotify (InteractionEvent, ANS_TrackInteractionIKLocation, AN_GrabPullPushObje)
│ │ └── Character/Animations/ (Door, Cupboard, Lever, ElectronicDoor, MoveTo), Props/ (maillages porte/placard/levier)
│ ├── StateManager/ → Copie du plugin AST_StateManager, **réduite le 2026-10-30 aux ~220 assets réellement utilisés** (249 assets non référencés supprimés : caméras AST, poses/anims d'armes et de mêlée, loot, `AST_GameInstance`, maps de démo `ShowcaseMap`/`DefaultLevel1`). Gardés malgré l'absence de référence : les 2 tables de tags (déclarées en config) et `Maps/LevelPrototyping/Meshes/SM_Cube` (cible d'un `[CoreRedirects]`). Les notifies `ReloadNotify` et `SetEquippedWeapon` (utilisés par les montages d'armes AST) appelaient l'inventaire AST non porté : leur graphe est vidé (ne font rien), sinon ils ne compilaient pas.
│ │ ├── Blueprints/ActorComponent/StateActorComponent + BPC_StateManager → Hub d'états AST (CoverState, InteractionState, OverlayState, entrées, ClosestActor…), cherché par les autres composants via GetComponentByClass/tags.
│ │ ├── Character/Animations/AnimationLayers/OverlayGraph (+ OverrideOverlayLayer) → calque lié (tag `Overlay`) : poses de couverture/pousser-tirer. `CanDynamicTransition` forcé à false (transitions ALS désactivées, §4.2.5).
│ │ ├── Character/Animations/AnimGraphs/InteractionIKGraph → calque lié : IK des mains pendant les interactions.
│ │ ├── Blueprints/Data/AST_GameplayTagDT → table de tags `AST.*` (déclarée dans DefaultGameplayTags.ini ; `AST_StateTreeGameplayTagDT` n'était qu'un redirecteur vers elle, supprimé avec sa ligne de config le 2026-10-30).
│ │ └── Inputs/IA_TakeCover (+ IMC_AST non utilisé)
│ ├── ASTShared/ → Sous-ensemble minimal (~44 assets) des plugins AST non portés, pour que StateManager/Interaction se chargent sans références cassées : Inventory/ (E_ItemType, BPI_ItemBase, BPI_InventorySystem, BP_InteractionActorBase + BP_Door/BP_ElectronicDoor/BP_Cupboard/BP_Lever/BP_PullpushObject, InteractionWidget), Melee/, NPC/ (BPI_AIController, EnemyDetectBS…), Parkour/ (interfaces, ParkourActor). Aucune logique de ces systèmes n'est active.
│ └── (Test/L_ASTCoverTest.umap déplacée dans `Content/Maps/DevTest/` le 2026-10-30, voir §7)
│
├── Scent/BP_ScentPoint.uasset → Point émetteur d'odeur, spawné par `SandboxCharacter_CMC::ScentFunct` (toutes les 2s pendant que le joueur se déplace) et consommé par `AC_AnimalSenses::CheckScent` (Systems/Wildlife/, §4.4). (2026-09-05) Deux bugs corrigés à la réutilisation par la faune : (a) `ScentFunct` ne créait le point QUE via `SpawnActorFromClass`, sans jamais appeler son événement `Custom|Create(life)` — la durée de vie (`TimeToLife`) restait donc à 0 ; ajout d'un appel explicite `Create(life=20.0)` juste après le spawn. (b) Le décompte de durée de vie (`Custom|CustomEvent(DeltaTime)`) existait déjà mais n'était appelé par PERSONNE (`BeginPlay` enregistrait l'acteur auprès de `BP_TickManager` mais rien n'invoquait réellement l'event) — le point ne s'auto-détruisait donc jamais ; ajout d'un `EventTick` natif qui appelle directement ce Custom Event (évite de toucher au brancher `BP_TickManager` partagé, hors-scope). Comparaison de fin de vie durcie de `== 0` à `<= 0` (float exact-equality fragile). Aucune nouvelle logique d'odeur créée — uniquement réparation du mécanisme existant.
├── Movable_Object/BP_Moveble_Object.uasset → Objet physique déplaçable par le joueur
├── Interact_Interface.uasset → Interface d'interaction générique (racine de Systems/, à distinguer de BPI_Interactable dans Inventory/Interfaces/)
│
└── Weather/
├── Tornado/BP_Tornado.uasset, NS_Tornado.uasset → Tornade dynamique (acteur + système Niagara), spécifique au projet (`NS_Tornado.uasset`, supprimé du disque avant le 2026-09-13 sans commit, restauré depuis git le 2026-09-14 à la demande de l'utilisateur)
└── UltraDynamicSky/ → 🧩 **Asset marketplace tiers complet** (~789 fichiers : Blueprints, matériaux, sons, textures de ciel/météo dynamique). Ne pas modifier directement ; utiliser ses points d'extension documentés par le plugin (Weather_Presets, Climate_Presets, Sky_Modifiers). Structure interne : Blueprints/ (Enum, Functions, Occlusion, Sky_Modifiers, Sound, System, Tools, Utilities, Weather_Effects, Widgets), Materials/, Meshes/, Particles/, Sound/, Textures/, Maps/ (map de démo du plugin). Les systèmes Niagara `Particles/` (Snow, Dust, Wind_Debris, Radial_Storm, Puddle_FluidGrid, DLWE_Interaction_System, VolumetricCloud_LightRays, Actor_Exposure_Test, Standalone/Dripping_*) référençaient encore l'ancien chemin `/Game/UltraDynamicSky/Particles/UDW_Niagara_Parameters` / `UDS_VolumetricClouds_Niagara_Properties` : `[CoreRedirects]` ajouté dans `Config/DefaultEngine.ini` + re-sauvegarde des 10 systèmes (2026-09-14).
⚠️ **Exception — code météo PROPRE AU PROJET rangé dans ce dossier tiers** (à ne pas confondre avec le plugin) :
├── Blueprints/WB_WeatherControl.uasset → Acteur contrôleur météo (1 par map, porte UDS/UDW en ChildActor). Tick via `BP_TickManager` : fait avancer la saison (`DT_Season`, durée en minutes via `SeasonDurationScale`=60), puis si le joueur est dans une zone (`ActiveZone`) pousse la météo de cette zone vers UDW, sinon `GetNearZone` choisit les 2 zones les plus proches (poids inversement proportionnels à la distance) et `setWeatherbetweenZone` mélange leurs états dans UDW.
└── Blueprints/WB_WeatherZone.uasset → Zone météo (Box). `WeatherData` (ST_Weather_ZoneData : temp/humidité/vent/sable) = climat de base. `MakeForcast` génère 16 prévisions (`ST_Weather_Zone_forecasts`, pas de 60–240 s via `ForecastStepMin/Max`) avec `makestate` (nuages `SetNuage`, température `MakeTemp` tirée vers la moyenne de saison, neige/pluie/orage/vent/brouillard/poussière) ; `StepForecast(Delta)` interpole l'état courant et régénère en fin de séquence ; `ApplyToUDW` pousse l'état dans UDW ; `GetInflueZone` liste les zones voisines dans `InfluenceRadius` (15000 uu) non masquées (lissage entre zones via `GetZoneState`). Overlap filtré sur le pawn contrôlé par un joueur. Fonction `Trie zone` restée dans le BP mais plus appelée (2026-09-14).
```
---
## 5. `Content/Data/` — Structures de données globales
```text
Content/Data/
├── DataTable/DT_Season.uasset → DataTable des saisons (météo)
│
├── Enums/
│ ├── E_CoverD.uasset, E_WeaponType.uasset
│ ├── E_WaponType.uasset → (2026-09-14, venu de `Characters/UEFN_Mannequin/Animations/Props/`) type d'arme lu par les choosers d'aim offset de `Systems/Aiming/` et par `S_CharacterPropertiesForAnimation.Weapon_Type` ; volontairement NON fusionné avec `E_WeaponType`
│ ├── Enums/E_BodyPart.uasset
│ └── Movement_enum/Weapon_Equip.uasset
│ (⚠️ à ne pas confondre avec Data/Inventory_Data/Enumerations/ ci-dessous — deux emplacements d'enums coexistent)
│
├── Structure/Weather/ → Structs météo (ST_Season, ST_WeatherZoneState, ST_Weather_Zone_CurentWeather/forecasts/_Data, ST_Zone_Influance)
│
└── Inventory_Data/ → Données du système d'inventaire AGIS
├── DataAssets/
│ ├── AttachmentData/ → AttachmentData (base) + AttachmentData_AdvancedRifle, AttachmentData_Flashlight : configuration des attachements d'armes
│ ├── ContainerGroupData/ → Un DataAsset par "groupe de containers" (Backpack BIG/SMALL, Box/Chest Storage, Vest_1/2, ExampleStorage…) : définit la mise en page des slots d'un contenant
│ └── InventoryData/ → Un DataAsset par inventaire prédéfini (PlayerInventory, PlayerInventoryPart1/2, CardReader_Inventory, Generator_Inventory, Vehicle_Inventory). `PlayerInventory` (2026-09-16) = inventaire du JOUEUR : widget `WB_InventoryPlayer`, containers = les 12 de PlayerInventoryPart1 puis les 3 de PlayerInventoryPart2 (même ordre que `WB_InventoryPlayer::GetAllContainerWidgets`) ; le container 12 (poches) est en 4×3 / 12 slots depuis le 2026-09-17 pour correspondre à `PocketContainer` (il était en 2×2 → seule la 1re ligne était utilisable ; `PlayerInventoryPart2`, utilisé par le pillage, a toujours ce défaut). `PlayerInventoryPart1/2` restent utilisés par les PNJ/cadavres fouillables (`BP_Deadman`, `BP_EnemyPuppet` → `WB_LootingHuman`)
├── Enumerations/ → Enums spécifiques AGIS : EAnimOverlayState, EBallDirections, EFourDirections/ETwoDirections, EInteractionType, EItemActions, ENestingRules, EPrintType, EReloadType, EServerVerificationType, ESlotAppearance, EVendorBuyOption
└── Structures/ → Structs spécifiques AGIS
├── ItemDefaults.uasset → Struct CENTRALE décrivant un item (Name, etc.) — lue par WB_Item et par la logique de sélection rapide (Inventory_Player)
├── ItemInfo, SlotInfo, SlotAddress, SlotAppearance, SubItemInfo, RandomSubItem
├── ContainerInfo, ContainerAppearance
├── AttachmentInfo, EquipSlot_KeySettings/_OwnContainersSettings/_TextureSettings/_TitleSettings
├── ClientWidgetData, CombinedTexturesInfo, TextureInfo
├── CraftingSlotRowInfo, DragSounds, DebugPrint, FindResult, InspectCameraInfo, InteractCosmetics, ItemActorInfo, MenuActions
└── SaveStructures/SaveData_Player, SaveData_Storages, Savedata_ItemActor → Structures de sauvegarde (non encore branchées à un vrai système de save, voir §1.3 du système de mort)
```
---
## 6. `Content/UI/` — Interfaces utilisateur
```text
Content/UI/
├── Inventory_UI/ → TOUTE l'UI de l'inventaire (socle AGIS + ajouts projet)
│ ├── MaterialResources/ → Matériaux/Material Instances de fond des slots (MI_Background_*, M_RuntimeItemMaterial…)
│ └── Widgets/
│ ├── WB_Container.uasset → Widget représentant un container (grille de slots) — contient SlotWidgets[]. `CreateSlots` construit la grille d'après la `Container Size` DU WIDGET (les SlotInfo du DataAsset doivent avoir la même taille, sinon seules les premières cases sont utilisables). `GetParentEquipSlot` (corrigé le 2026-09-17) ne renvoie l'EquipSlot englobant (Outer.Outer) que si ce container EST son `Container` principal : les sous-containers d'un sac/gilet affichés ailleurs (sous les poches) gardent leur taille et leurs cases de 75 px au lieu d'être réduits à 1×1 en taille d'EquipSlot
│ ├── WB_EquipSlot.uasset → Widget d'un slot d'équipement (arme/speed) — contient Container. Var `OwnContainersHost` (2026-09-04, typée `WB_InventoryPlayer` depuis 2026-09-16) : si définie, `OnContainerUpdate` ajoute les sous-containers de l'item équipé (ex. grille du sac à dos) dans ce widget via `AddContainerBelowPockets` au lieu de son propre `ContainersCanvas` interne. Câblée uniquement pour `EquipSlotBACKPACK` par `WB_InventoryScreen::InitializeWidgets`.
│ ├── WB_InventorySlot.uasset → Widget d'un slot individuel dans une grille — expose "ItemWidget On Slot"
│ ├── WB_Item.uasset → Widget représentant un item posé dans un slot — expose "Item Defaults" (struct ItemDefaults)
│ │
│ ├── InventoryMenu/
│ │ ├── WB_InventoryScreen.uasset → Widget racine de l'inventaire plein écran. Depuis 2026-09-16 contient un seul panneau joueur `Part2Box → OutlinePart2 → BackgroundPart2 → InventoryPlayer` (WB_InventoryPlayer : équipements à gauche, poches + sac à dos défilants à droite ; plus de ScrollBox extérieur depuis 2026-09-17, sinon la colonne de droite n'a pas de hauteur bornée) ; `InitializeWidgets` initialise `InventoryPlayer` et câble `EquipSlotBACKPACK.OwnContainersHost = InventoryPlayer`. `Inventory_Player::GetQuickSlotItemInfo` lit les slots WEAPON/WEAPON2/Speed1/Speed2 via `InventoryScreen.InventoryPlayer`. **Overlay personnage (2026-09-21)** : `Border_184` (→ `Overlay_261` : portrait `Image_163`, textes h/hunger, `WB_stat_player_Body`) est replié (`Collapsed`) dès qu'un inventaire tiers est ouvert (corps, coffre, feu de camp…) : `CreateLootingWidget` appelle `HideCharacterOverlay` après avoir affiché `LootWindow` ; `ShowCharacterOverlay` (appelé après `AddToViewport` dans `ToggleScreen` et dans `Clear Looting Widget Panel`) le remet `Visible` seulement si `LootWindow` n'est pas visible (une fermeture sans fondu laisse le panneau de loot en place — comportement d'origine)
│ │ ├── WB_InventoryPlayer.uasset → (2026-09-16) Fusion de WB_InventoryPart1 + WB_InventoryPart2 pour l'inventaire du joueur, dans un seul cadre en deux colonnes (2026-09-17) : `EquipsVertical` → `ColumnsBox` (HorizontalBox, Fill) → [`EquipSlotsBox` (SizeBox 760×866) → `SlotsCanvas` avec les 12 EquipSlots] + [`PocketsScroll` (ScrollBox, barre visible) → `VerticalBox_0` (titre Pockets + `PocketContainer`, puis les containers du sac à dos ajoutés dessous par `Add Container Below Pockets`)]. `EquipSlotBACKPACK` et `EquipSlotVEST` ont `Item Containers Settings.ShowInventory = True` et `OwnContainersHost = InventoryPlayer` (câblé dans `WB_InventoryScreen::InitializeWidgets`) (défaut de WB_EquipSlot = False : sans ça, `OnContainerUpdate` ne crée jamais les grilles du sac). `GetAllEquipSlots` = même ordre que Part1 ; `GetAllContainerWidgets` = containers des équipements puis PocketContainer. Données : DataAsset `PlayerInventory`
│ │ ├── WB_InventoryPart1.uasset → (utilisé seulement par WB_LootingHuman depuis 2026-09-16) Contient les 4 slots d'équipement (EquipSlotWEAPON, EquipSlotWEAPON2, EquipSlotSpeed1, EquipSlotSpeed2) + autres slots d'équipement (casque, gilet, gants, sac à dos…)
│ │ └── WB_InventoryPart2.uasset → (utilisé seulement par WB_LootingHuman depuis 2026-09-16 ; sa fonction `Add Container Below Pockets` n'est plus appelée, WB_EquipSlot pointe désormais WB_InventoryPlayer) Grille(s) d'inventaire principale(s) (PocketContainer + titre, dans `VerticalBox_0`). Fonction `Add Container Below Pockets(Container)` (2026-09-04) : insère un WB_Container juste après le bloc Pockets (`PocketContainer.GetParent().GetParent()`) — utilisée par `WB_EquipSlot` (sac à dos) pour afficher ses slots supplémentaires sous les slots Pocket plutôt que dans son propre canvas.
│ │
│ ├── ItemActionsMenu/WB_ItemMenu.uasset, WB_GeneralButton.uasset → Menu contextuel d'actions sur un item
│ │
│ ├── LootWidgets/ → Fenêtres de pillage, une par type de source (Human, Storage, Vendor, Vehicle, Generator, KeycardReader) héritant de _WB_LootWidgetBASE ; LootingCrafting/ pour les stations de craft
│ │
│ ├── MovableWindows/ → Système de fenêtres déplaçables (_WB_Window_Base) : ItemInspect/ (inspection 3D d'un item), ItemInventory/ (fenêtre d'inventaire flottante), WB_TitleBar_Window (barre de titre commune)
│ │
│ └── Other/
│ ├── WB_QuickSelect.uasset → 🆕 Roue de sélection rapide (4 slots : Arme1/Arme2/Speed1/Speed2 + "Aucun"). Résout seule son Inventory_Player propriétaire au Construct (GetOwningPlayerPawn → GetComponentByClass). Ouverte/fermée depuis Inventory_Player (touche V maintenue). Le graphe `INPUTS` d'Inventory_Player bascule aussi Input Mode/curseur/Ignore Look Input à l'ouverture/fermeture (même pattern que `ToggleInventory`) — fix du 2026-09-04. **Cause réelle du bug (2026-09-04, deuxième fix)** : IA_QuickSelectAGIS/IA_QuickEquipAGIS (touches V/B) n'étaient mappées QUE dans `IMC_AGIS_Character` (Systems/Inventory/Other/Inputs/), un Input Mapping Context jamais ajouté au joueur nulle part dans le projet (seule une variable `Input Mapping Context` orpheline sur Inventory_Player y faisait référence, sans AddMappingContext associé) — donc l'input V n'atteignait même pas l'Enhanced Input, avant tout problème de widget. Fix : V et B ajoutés directement dans `IMC_Sandbox` (Core/PlayerController/Input/), l'unique IMC réellement ajouté au joueur (via SandboxCharacter_CMC/SetupInput). `IMC_AGIS_Character` reste inutilisée — à nettoyer/fusionner un jour si confirmé obsolète.
│ ├── WB_QuickGive.uasset → 🆕 (2026-09-05) Menu debug "Quick Give" (touche F9, mappée dans `IMC_Sandbox` via `IA_QuickGiveAGIS`). Champ Item ID + Amount + bouton "Give" générique, plus des boutons raccourcis "Give Bow"/"Give 20 Arrows". Fonction `GiveItem(ItemID, Amount)` : résout l'`Inventory_Player` du joueur possédant (`GetOwningPlayerPawn`+`GetComponentByClass`), construit un item via `FL_AGIS::Create Item Structure`, trouve un slot libre via `Inventory__Main::Find Space For Item`, puis le pose via `Inventory__Main::Set Item On Container` — même chaîne AGIS bas niveau que celle utilisée en interne par `Reload Graph` de `BP_Item_AdvancedRifle`. Toggle (créer/`AddToViewport`+InputMode GameAndUI vs `RemoveFromParent`+InputMode GameOnly) câblé dans `SandboxCharacter_CMC::EventGraph` sur `IA_QuickGiveAGIS` (Started), variable `QuickGiveWidget` sur le Character. Outil de test, pas une fonctionnalité shippable.
│ ├── WB_YouDied.uasset → Écran "Vous êtes mort" (système de mort/respawn)
│ ├── WB_ActionScreen, WB_InteractScreen, WB_InteractBall, WB_PickupDisplay → Feedback d'interaction/pickup au sol
│ ├── WB_CombatHUD, WB_DisplayHUD, WB_WeaponAmmoInfo → HUD de combat/munitions
│ ├── WB_InventoryBackground, WB_ItemInfo, WB_SlotTitle → Éléments visuels d'appoint
│ └── WidgetTextures/ → Icônes des slots d'équipement (T_EquipSlot_*) et textures d'UI diverses
│
├── TextureUI/M_CharacterUI.uasset, RT_CharacterView.uasset → Rendu de la vue personnage dans l'UI (ex. écran d'inspection du personnage)
│
└── Widgets/ → UI HORS inventaire
├── HUD/WB_HUD.uasset, WB_New_HUD.uasset, WB_New_InterfacePlayer.uasset
├── PauseMenu/WBP_PauseMenu.uasset, WBP_Options.uasset
└── interaction/WB_Interact.uasset
```
---
## 7. `Content/Items/`, `Content/PCG/`, `Content/Maps/`, `Content/VFX/`
```text
Content/Items/ → Objets ramassables/équipables du jeu
├── BaseBlueprints/
│ ├── _BP_ItemBase.uasset → Blueprint BASE de tout item (racine de hiérarchie). Event `Custom|UpdateCollisionandPhysics` (appelé à l'attache/au drop) bascule aussi la réponse au canal `Visibility` (Ignore quand équipé / Block quand lâché) du mesh principal ET de tous les `WidgetComponent` de l'item (ex. `AmmoUI`) — fix du 2026-09-04 : un item équipé bloquait les BoxTrace (canal Visibility) du système de grimpe/parkour (`AC_ClimbingComponent`), empêchant de grimper une fois une arme en main.
│ ├── BP_Item_HoldableBase.uasset → Base des items tenables en main. Implémente `BPI_HoldableItem::On Item Attached(ShouldHold)` (seul signal fiable du VRAI état main/pas-en-main) et diffuse ce booléen via son propre dispatcher `OnHeldStateChanged(bIsHeld)` (2026-09) — c'est CE dispatcher (et non `_BP_ItemBase::OnOwnerChanged`, qui se déclenche aussi pour un simple rangement en inventaire) qu'`AC_Weapon` écoute pour piloter Equip/Unequip, voir §4.3.
│ ├── BP_Item_Armor_Base.uasset → Base des items d'armure/équipement porté
│ ├── BP_Item_Skeletal_Base.uasset → Base des items avec mesh squelettal
│ └── BP_Item_OutfitPiece_Base.uasset → (2026-09-21) Base des vêtements MetaHuman équipables (hérite `BP_Item_Armor_Base`). `ItemMesh_STATIC` (tag ItemMeshAGIS = mesh au sol) + `OutfitCloth` (ChaosClothComponent, asset `OA_SurvivorOutfit`, caché par défaut). BeginPlay → `SetupOutfitBinding` (ignoré pour les acteurs de prévisualisation d'icône) : s'abonne à `OnOwnerChanged` → `RefreshOutfit`, puis `ApplyVisibleSlots`. `RefreshOutfit` : avec propriétaire, attache `OutfitCloth` au mesh enfant du ChildActorComponent tagué `VisualOverride` (le `Body` du MetaHuman ; à défaut le 1er SkeletalMeshComponent du pion) + `SetLeaderPoseComponent`, montre le cloth, cache le mesh statique ; sans propriétaire, l'inverse. `ApplyVisibleSlots` : si `VisibleMaterialSlots` (Name[]) n'est pas vide, remplace le matériau de chaque autre slot par `M_OutfitPieceHidden` → une pièce = même tenue complète dont on n'affiche que ses matériaux. Coût : chaque pièce équipée simule la tenue entière (3 simulations cloth si les 3 sont portées). ⚠️ Tenue taillée pour le corps `Bof2` : la peau du MetaHuman `test` traverse le pantalon aux cuisses (correctif propre = garde-robe du MetaHuman Creator).
│
├── BP_Item_AdvancedRifle / _Backpack_Camper / _Backpack_Sport / _BodyArmor / _Flashlight /
│ _Gloves / _Helmet / _KevlarVest / _LightVest / _Siren / _Bow.uasset → Blueprints d'items "portés" (racine d'Items/). `BP_Item_Bow` (hérite `BP_Item_HoldableBase`) porte un composant `ItemMesh_STATIC` (mesh Fab `survival_kit_bow_fixed` — voir §8 pour l'incident de corruption/ré-import du 2026-09-05 —, socket `ArrowSocket` ajouté pour l'encoche — position approximative, à ajuster visuellement), équipé sur le socket `weapon_l` (Spine `SpineRifle`, réutilisé faute de socket dédié). `BP_Item_AdvancedRifle` a reçu le même composant `AC_Weapon` (2026-09-05, retrofit de validation) pour l'équipement/déséquipement générique ; sa cadence de tir reste pilotée par sa propre macro interne `Weapon|FiringGate` (non branchée sur `AC_Weapon.CanUse()` — dette technique documentée, voir §9).
│
│ **Gameplay de l'arc** (`BP_Item_Bow`, 2026-09-05/06) — override des hooks existants `Custom|PrimaryUse`/`StopPrimaryUsing` de `_BP_ItemBase`, sans nouveau système d'input : `PrimaryUse` vérifie `AC_Weapon->CanUse()` ET `CountArrows() >= 1` (fonction propre, voir §1.3 pour `Owner Inventory`) avant de démarrer la tension (horodatage `DrawStartTime`, pas de Timeline), puis joue `Draw Montage` (`AM_Bow_Draw`, voir ci-dessous) sur le mesh du personnage. `StopPrimaryUsing` calcule la puissance (`Clamp((Now-DrawStartTime)/MaxDrawTime, MinDrawPower, 1.0)`), joue `Fire Montage` (`AM_Bow_Fire`), appelle `ConsumeArrow()` et, si réussi, spawn `BP_Projectile_Arrow` au socket `ArrowSocket` avec vitesse/dégâts scalés par la puissance, puis appelle `AC_Weapon->TryUsePrimary()`.
│ **`ConsumeArrow()` — fix du 2026-09-06** : retrouve le stack "Arrow" via `BOTH|FindItem`, puis réduit son montant via `Inventory__Main::AddItemAmount(ItemAddress, -1)` — une opération purement sur les DONNÉES de l'inventaire, par adresse, ne nécessitant aucun acteur monde. L'implémentation d'origine appelait `Utilities|GetItemActor` (macro `ML_InventoryMacros::Get Item Actor`) puis `SERVER|ReduceAmount` sur cet acteur — ce qui échoue TOUJOURS silencieusement pour un item empilé passif comme "Arrow" qui, tant qu'il n'est pas équipé/lâché dans le monde, n'a jamais d'acteur spawné (`GetItemActor` retourne systématiquement invalide). Symptôme observé : le tir semblait démarrer (logs "TIR EN COURS") mais aucune flèche ne partait et aucune munition n'était consommée. Voir aussi §9 pour la même leçon appliquée à toute consommation de stock passif (ne jamais utiliser `GetItemActor`+fonction-sur-acteur pour un item qui n'est pas garanti d'être spawné dans le monde — utiliser `Inventory__Main::AddItemAmount` par adresse à la place).
│ **Animations de l'arc** (2026-09-06) — `AM_Bow_Draw`/`AM_Bow_Fire.uasset` (`Content/Items/`) sont des `AnimMontage` à piste unique générés depuis `standingdrawarrow_UE`/`standingaimrecoil_UE` (`Content/Assets/Animations/humain/Armed_mov/Bow/`), assignés au **slot `UpperBody`** (voir `SandboxCharacter_CMC_ABP` ci-dessous — PAS `DefaultSlot`). Variables `Draw Montage`/`Fire Montage` sur `BP_Item_Bow`, jouées via un nœud natif `Animation|Montage|PlayMontage` sur le mesh du personnage (`Cast Owner Character → Character → GetMesh`). Appel direct (pas de variante Multicast `...CLIENTS` comme pour `PlayReloadMontageCLIENTS` du fusil) — suffisant en PIE solo ; à revoir si une réplication multijoueur de l'anim est nécessaire plus tard.
│ **Intégration à la locomotion (2026-09-06)** — un montage plein-corps sur `DefaultSlot` remplaçait TOUT le squelette (jambes comprises) par la pose figée de l'animation source, ce qui semblait correct à l'arrêt (la pose "debout" du montage coïncide avec Motion Matching à l'arrêt) mais cassait la marche/course/crouch (jambes figées dans la pose d'arc "debout" enregistrée). Fix : ajout d'un **`Slot 'UpperBody'`** + nœud **`Layered Blend Per Bone`** dans l'`AnimGraph` de `SandboxCharacter_CMC_ABP`, avec un filtre de branche sur l'os `spine_01` (BlendDepth=-1 → toute la branche colonne+bras+tête, jambes/bassin exclus) — voir §9 pour le détail technique (le skeleton `SK_UEFN_Mannequin` avait déjà un Blend Profile "UpperBody" masquant exactement les mêmes os, confirmant que `spine_01` est le bon point de coupure, mais ce Blend Profile n'est pas directement utilisable par ce nœud en 5.8 — filtre de branche manuel utilisé à la place). Le `DefaultSlot` existant (plein corps) n'est pas touché, réutilisable tel quel pour de futures actions plein-corps. Motion Matching, Pose Search, Aim Offset (`Enable_AO`/`Get_AOValue`), Foot/Leg IK et Orientation Warping — tous déjà présents dans cet AnimBP — continuent de piloter le bas du corps et la visée sans changement.
├── BP_Item_SurvivorOutfit_Top / _Pants / _Boots.uasset → (2026-09-21) Les 3 pièces de la tenue MetaHuman « SurvivorOutfit », enfants de `BP_Item_OutfitPiece_Base`. Lignes DT_Items `SurvivorOutfit_Top` (`Human.Body` → EquipSlotBODY, partagé avec BodyArmor), `SurvivorOutfit_Pants` (`Human.Pant` → EquipSlotPANTS), `SurvivorOutfit_Boots` (`Human.Shoes` → EquipSlotSHOES) ; icônes générées au runtime. Chaque enfant ne fixe que `VisibleMaterialSlots` (Top = MI_Survivor_Coat + MI_Survivor_Tshirt, Pants = MI_Survivor_Pant, Boots = MI_Survivor_Boot) et l'override du mesh au sol `ItemMesh_STATIC` (SM_SurvivorOutfit_Coat/Pant/Boot). `BP_Spawner5` de MapDev1 pointe sur `SurvivorOutfit_Top`.
├── Attachments/BP_Item_MagazineBase, BP_Item_RifleFlashlight, BP_Item_Suppressor.uasset → Attachements d'armes
├── ItemObjects/BPI_MergeableItemObject, ItemObject__BASE, ItemObject_ConsumableBASE, ItemObject_Magazine.uasset → Objets "monde" associés aux items (empilables, consommables, munitions)
│
├── Ammo/ (Arrow), Weapon/ (Base_Bow, Base_knife), Tool/ (saucepan), Water/ (water_bottle),
│ Food/ (Red_Apple, meat, cooked_Meat, Burnt_Meat), Resource/ (Stick, Stone)
│ → Chaque sous-dossier contient les assets (mesh/matériau/texture/Thumbnail) d'un item ; les Blueprints correspondants sont soit à la racine d'Items/, soit dans Resources/. `Weapon/Base_Bow/Thumbnail` et `Ammo/Arrow/Thumbnail` ne contenaient que des icônes préparées à l'avance (2026-09-04) — item "Bow" et "Arrow" ajoutés dans DT_Items pour les utiliser (voir §5).
│
├── Resources/ → Assets 3D (mesh/matériau/texture) groupés par équipement porté, + cas particuliers
│ ├── Backpack_Camper/, Backpack_Sport/, BodyArmor/, Gloves/, Helmet/, KevlarVest/, LightVest/, Rifle/
│ ├── Outfits/M_OutfitPieceHidden.uasset → (2026-09-21) Matériau masqué à opacité 0, utilisé par `BP_Item_OutfitPiece_Base` pour cacher les pièces d'une tenue qui n'appartiennent pas à l'item
│ ├── _Others/ → Assets des petits objets (Keycard, Log, Meat, AmmoBox, Battery, Flashlight, FuelCanister, GoldIngot/GoldOre, Headphone, MoneyBag, WaterBottle)
│ └── ABP_SkeletalEquippables.uasset → Anim Blueprint partagé par les items squelettaux équipés
│
└── ItemTextures/ → Textures d'icônes/inventaire communes à tous les items (T_*)
Content/PCG/ → Génération procédurale de contenu (plugin PCG)
├── Haussmann/ → (2026-10-30) Système PCG d'immeubles haussmanniens (Paris 2127, abandonnés depuis 60 ans). Doc complète :
│ │ `Docs/PCG_Haussmann.md`. Assemble les modules existants `/Game/ProjectF/Meshes/Facades|Facades_Variantes|Interieurs`
│ │ (SM_PF_HM_*, teintes MI_PF_HM_V_*_Gris/_Ocre), décor RueDeParis/PropsParis/Megascans ; aucun mesh nouveau
│ ├── Blueprints/BP_PF_Haussmann.uasset → Acteur : spline (façade ouverte, bâti à gauche, ou îlot fermé) + `PCG_Structure` (tag `HM_Structure`,
│ │ graphe PCG_PF_HM_Structure) + `PCG_Decoration`. Variables « PF » : `FacadeLength` (façade droite sans spline),
│ │ `Floors` (R+n), `BayWidth`, `BuildingBays`, `Depth`, `Degradation` (0..4), `Tint` (-1 = par immeuble), `bShopFront`.
│ │ Graine = Seed des composants (résultat reproductible, vérifié par empreinte)
│ ├── Graphs/PCG_PF_HM_Structure.uasset → Segments → pourtour (rue, cour, mitoyens) → travées/immeubles → étages → type par immeuble×étage →
│ │ ruine (colonnes effondrées, toitures perdues) → état par travée ; mansardes d'angle, chaînes d'angle,
│ │ planchers, terrasson zinc, souches, battants, gravats ; intérieurs : cage d'escalier (travée de porte cochère),
│ │ murs entre immeubles, appartements (refend rue/cour, cloisons, portes). ISM avec collision. Sa sortie alimente la décoration
│ ├── Graphs/PCG_PF_HM_Decoration.uasset → Lit la sortie de la structure (Get Actor Data, tag HM_Structure) : lierre, herbes, détritus, gravats
│ │ d'étage, enseignes ; sans collision. Générer la structure AVANT la décoration
│ │ Les deux graphes sont générés par script (`E:/projet/PF_Haussmann_Work/scripts/ue_hm_graph_*.py`) : les modifier là
│ ├── Data/ST_PF_HM_* + DT_PF_HM_*.uasset → Paramètres centralisés : DT_PF_HM_Levels (étages par Floors), DT_PF_HM_Degradation (H0-H4),
│ │ DT_PF_HM_Tints, DT_PF_HM_Types (type de module par Kind×Role), DT_PF_HM_Variants (mesh/matériau pondérés
│ │ par TypeId×teinte×dégradation), DT_PF_HM_LeafSlots (charnières des battants), DT_PF_HM_Tiles.
│ │ Source CSV unique : `E:/projet/PF_Haussmann_Work/scripts/hm_data_csv.py` (+ `ue_hm_data.py` pour réimporter)
│ └── (niveau de test : `Maps/DevTest/L_PF_HaussmannTest`)
├── RoadNetwork/ → (2026-10-28/29) Système PCG de routes et ponts post-apo (France 2127). Doc complète : `Docs/PCG_RoutesPonts.md`
│ ├── Blueprints/BP_PF_Road.uasset → Route : spline + `PCG_Structure` (PCG_PF_Road_Structure) + `PCG_Decoration` (PCG_PF_Road_Decoration). Variables
│ │ `Profile` (Name, ligne de DT_PF_RoadProfiles), `Degradation` (0..3), `bSnapToLandscape`. Graine = Seed du composant PCG
│ ├── Blueprints/BP_PF_Bridge.uasset → Pont : spline (2 points) + PCG_PF_Bridge_Structure / _Decoration. `BridgeType`, `Degradation` (0..4),
│ │ `CollapsedSpan` (-1 = tirée par la graine), `bWalkable` (passerelle de fortune), `bVehicleTraversable` (barrage)
│ ├── Blueprints/BP_PF_RoadNetwork.uasset → Un par niveau : dalles de carrefour (PCG_PF_Network). Désactivé (couche JunctionPad vide) : les croisements sont faits par la variante J des routes
│ ├── Graphs/PCG_PF_*.uasset → Les 5 graphes (structure route, décoration route, structure pont, décoration pont, réseau). Générés par
│ │ script (`ProjetF blender/RoadNetwork/scripts/ue_rn_graph_*.py`) : les reconstruire par script plutôt qu'à la main
│ ├── Data/ST_PF_* + DT_PF_*.uasset → Paramètres centralisés : DT_PF_Variants (toutes les variantes pondérées par couche/famille/niveau),
│ │ DT_PF_RoadProfiles, DT_PF_Degradation (R0-R3, B0-B4), DT_PF_Bands (bandes latérales), DT_PF_BridgeTypes,
│ │ DT_PF_BridgeModules. Source CSV : `rn_data_csv.py` (+ `ue_rn_data.py` pour réimporter)
│ ├── Meshes/Road|Equipment|Bridge/ → Segments de route (7 profils × 8 variantes), carrefours, panneaux FR, bornes, J6, glissières (spline meshes de 4 m), blocs,
│ │ débris, modules de ponts (travées D0/D1/D2/effondrée, piles, culées, passerelle, barrage) — générés dans Blender
│ ├── Materials/ → M_PF_Road (couches par couleurs de sommets + marquages), M_PF_Structure (triplanaire), M_PF_Sign,
│ │ M_PF_DecalMesh (inutilisé : couche Decal désactivée) + Instances/MI_*
│ ├── Textures/ → T_PF_* (surfaces Fab/Megascans 2K, atlas de panneaux, masques de decals)
│ └── (niveau de test : `Maps/DevTest/L_PF_RoadBridgeTest`)
├── Road/ → Prototype antérieur (une route, conservé tel quel, instance dans MapDev1). Remplacé par RoadNetwork/ pour les nouvelles routes
│ ├── PCG_Road.uasset → Graphe PCG générant la route procédurale (matériaux vieillis/mousse/humidité, potholes via GeometryScript)
│ ├── BP_AbandonedRoad.uasset → Acteur portant le graphe PCG
│ ├── M_Road.uasset, ST_Road_data.uasset, EM_Road_Type.uasset → Matériau, struct de données et enum de types de route
└── Vegetation/TreeTest.uasset → Graphe PCG de placement de végétation (arbres, générés via PVE pour la route)
Content/Maps/
├── DevMap/ → Map de développement principale et rien d'autre
│ ├── MapDev1.umap → 🗺️ Map de développement PRINCIPALE (GameDefaultMap / EditorStartupMap, voir Config/DefaultEngine.ini)
│ ├── GM_Dev.uasset → GameMode utilisé sur MapDev1 (enfant de GM_Sandbox, §4.2.4)
│ └── _GENERATED/ → Meshes fusionnés générés par l'éditeur, placés dans MapDev1
└── DevTest/ → (2026-10-30) TOUTES les maps de test, rangées à plat (règle : une nouvelle map de test va ici,
│ jamais dans le dossier du système testé)
├── L_ASTCoverTest.umap → Couverture + interactions AST (murs bas/haut + CoverWallActor, portes, placard, levier, caisse ; GM_Dev)
├── L_MoverTest.umap + GM_MoverTest → Ancien banc d'essai du personnage Mover (GameMode propre à cette map)
├── L_BridgeTest.umap, L_BridgeTest_Geo.umap → Kits de ponts (Assets/Environment/Bridges) ; _Geo charge les ponts entiers (très lourds en mémoire)
├── L_KitFR_Demo.umap → Présentation du kit KitFR
├── L_PF_Demo.umap → Présentation des assets ProjectF + variantes (acteurs tagués PF_VARIANTES)
├── L_RueDeParis.umap → Rue de test (RueDeParis + PropsParis + KitFR)
├── L_PF_HaussmannTest.umap → PCG Haussmann : 8 variantes (H0 à H4, R+4 à R+7, FacadeLength, îlot fermé, angle), non partitionnée
├── L_PF_RoadBridgeTest.umap → PCG routes et ponts : 9 scénarios, World Partition (terrain 1 km², rivière, ruisseau, vallée)
├── pcg_Test_Dev.umap → Ancienne map de test PCG (World Partition)
└── HLOD/ → Couches HLOD générées des deux maps World Partition
Content/VFX/
├── Firetrail/BP_FireTrail.uasset, Effects/P_BulletTrail.uasset, Materials/M_BulletTrail.uasset → Traînée de balle
└── MI_MuzzleFlash, M_MuzzleFlash, MuzzleFlash, P_MuzzleFlash → Effet de tir (bouche de canon)
├── M_Blood.uasset, T_Blood.uasset → Effet de sang
└── crosshair.uasset, hitmarker.uasset → Éléments de HUD de tir
```
---
## 8. Assets tiers / bibliothèques (peu ou pas de logique de gameplay)
Ces dossiers contiennent des assets achetés/importés (Marketplace, Fab/Quixel Megascans, Megaplant, packs d'animation mocap). Ils sont volumineux (des centaines de fichiers) et **ne sont volontairement pas détaillés fichier par fichier** ici — voir §10 pour la règle sur quand les documenter.
**Règle de rangement (2026-10-30)** : les packs d'art faits pour le projet (générés dans Blender, importés par les scripts de `ProjetF blender/`) vont sous `Content/Assets/Environment/<Pack>/`, les bibliothèques d'animation sous `Content/Assets/Animations/`. Les imports dont le chemin est imposé par l'outil (Fab, Megaplant_Library, MetaHumans) restent où l'outil les met. **Exception assumée** : `KitFR/` et `ProjectF/` sont restés à la racine de `Content/` (2 000 et 1 600 assets ; les déplacer dans l'éditeur le fait planter par manque de mémoire, décision utilisateur du 2026-10-30).
```text
Content/Assets/Animations/ → Clips de mocap tiers : DeerAndDoe/, Fox/, Wolf/, humain/ (Armed_mov, Avoid, Carrying, Cover,
Effect_Animation, INteraction, Prone, Push, SwimmingAnimation…). Ex-`Content/Animations/` (déplacé le 2026-10-30).
Kimodo/ : animations générées par le skill kimodo-motion (Source/ = import du GLB skinné + IK_Kimodo_SOMA30
+ RTG_Kimodo_to_UEFN, Retargeted/ = anims retargetées sur l'UEFN Mannequin). Ex-`Content/Kimodo/`.
Content/Assets/Environment/ → Packs d'environnement faits pour le projet (déplacés ici le 2026-10-30) :
PropsParis/ (props urbains parisiens : mobilier, commerces, enseignes, débris ; meshes SM_PR_*),
RueDeParis/ (rue de test : sol, façades et décor RDP_*, script `kit_fr_scripts/ue_rdp.py`),
Bridges/ (voir ligne dédiée plus bas), Prop/Wooden_Door/ (porte en bois des BP de porte interactable).
Content/Characters/ → Meshes/matériaux/textures des personnages et animaux (Animal/DeerStagAndDoe|Fox|Wolf,
UEFN_Mannequin/ avec ses données de Motion Matching : AimOffset, Crouch, Idle, Jump,
MotionMatchingData/ [Channels, Databases, Normalization_Sets, Schemas], Poses, Run, Slide,
Sprint, Traversal, Walk, Rigs, Materials, Meshes, Textures)
MEtahuman/ : le MetaHuman `test` (asset MetaHuman Character `test`) et son assemblage
`Unpacked/test/` (BP_test, SKM_test_BodyMesh/FaceMesh, PHYS_test, matériaux baked), utilisé
par le visual override (§4.2.4). Ex-`Content/test/` (déplacé le 2026-10-30).
Content/Fab/ → Assets Fab/Quixel Megascans : MaterialFunctions/, MaterialParameterCollection/, Materials/
(Standard/VT), Megascans/Plants/ (Common_Hemp_Nettle, Wild_Grass ×2), Megascans/Surfaces/
(Damaged_Asphalt, Dirt_Ground, Forest_Floor), Textures/ (Standard/VT)
MetaHuman/ (2026-09-21) : tenue MetaHuman « SurvivorOutfit » (manteau+t-shirt+pantalon+bottes)
— `OA_SurvivorOutfit` (ChaosOutfitAsset, 4 slots de matériaux = 4 pièces, porté par `BP_Item_OutfitPiece_Base` et ses 3 enfants, §7),
`WI_OA_SurvivorOutfit` (Wardrobe Item du MetaHuman Creator), cloth assets/Dataflow,
SM_SurvivorOutfit_Coat/Pant/Boot (meshes statiques, servent d'objet au sol), textures.
Le SkeletalMesh `bodyShapeBof2_CombinedSkelMesh` est le CORPS de référence du fitting, pas la tenue.
Survival_Kit_-_Bow/ et CC0_-_Wooden_Arrow/ (StaticMesh + Textures + MI_SurvivalBow/MI_WoodenArrow,
utilisés par les items Bow/Arrow, voir §4.4) — ⚠️ ces deux imports glTF avaient leurs slots de
matériau pointant vers un `MaterialInstanceDynamic` transitoire (jamais sauvegardable, cause
d'échecs d'auto-save « Illegal reference to private object »). Corrigé le 2026-09-04 en créant de
vraies `MaterialInstanceConstant` (parent `/InterchangeAssets/gltf/Substrate/M_GLTF`) avec les
mêmes textures. Si un futur import Fab/glTF échoue à l'auto-save de la même façon, vérifier le(s)
slot(s) de matériau du StaticMesh importé pour ce même symptôme.
⚠️ **2026-09-05** : après une coupure de courant, `survival_kit_bow.uasset` (StaticMesh) s'est retrouvé
avec 0 vertex/0 triangle/bounds nuls (asset vidé, probablement une sauvegarde tronquée en cours
d'écriture) — l'arc n'avait plus de mesh visible ni de collision en jeu. Diagnostic : toujours
vérifier `mesh.get_num_vertices(0)` / `get_bounds()` après une fermeture anormale de l'éditeur sur
un asset suspect avant de chercher ailleurs. Fix : ré-import depuis la source Fab d'origine
(chemin retrouvé via `mesh.get_editor_property("asset_import_data").extract_filenames()`, cache
`%LOCALAPPDATA%/Temp/FabLibrary/<guid>/`) vers un dossier scratch, puis renommage vers
`.../StaticMeshes/survival_kit_bow_fixed` (le nom d'origine `survival_kit_bow` n'a pas pu être
réutilisé : `delete_asset`/`consolidate_assets` échouaient silencieusement sur l'asset corrompu
malgré 0 référenceur restant — laissé orphelin en l'état, sans danger). Toutes les références
(`BP_Item_Bow::ItemMesh_STATIC`, `DT_Items` ligne "Bow" → `Spawner Preview Mesh`) repointées vers
`survival_kit_bow_fixed`. Le socket `ArrowSocket` a dû être recréé (perdu avec l'ancien asset).
Content/KitFR/ → Kit modulaire de bâtiments français post-apo (généré dans Blender : `ProjetF blender/Kit_Habitat_FR.blend`,
export `UE_Export/Kit_FR`, import `kit_fr_scripts/ue_import_kit.py`). Meshes/<Style>/SM_FR_*_S0..S4,
3 maîtres M_KitFR_Opaque/Masked/Glass + Materials/Instances/MI_* , Textures/ (Megascans 2K + CC0).
**2026-10-28** : réimporté aux MÊMES chemins (niveaux non cassés) avec textures Megascans,
normales OpenGL → `flip_green_channel`, tuiles `TileSizeU/V`, AO branchée ; 88 anciennes textures
CC0 et 8 doublons `SM_FR_*_Floor_S0_001` supprimés. Dossier NON versionné dans git à cette date.
Content/ProjectF/ → **(2026-10-28)** Assets « Project F » (`ProjetF blender/ProjectF_Assets`, lots 1–5 + prototype, SANS les
véhicules) : Meshes/<Lot>/SM_PF_* (224, Nanite sauf meshes avec verre, UCX sinon collision complexe),
Maps/L_PF_Demo (présentation). Matériaux PRÉCALCULÉS dans Blender (`Scripts/pf_bake.py`) sur l'UV1 :
1 MI par asset de `M_PF_Baked` (BC + RMA = rugosité/métal/opacité + NO = normale espace OBJET,
Transform Local→World, donc indépendante des tangentes de l'UV0) ; verre `M_PF_BakedGlass`
(translucide) ; decal `M_PF_DecalCoulure` (masqué, UV0). Les slots papier peint/gravats/sombre
utilisent les MI de /Game/KitFR. Import : `ProjectF_Assets/Scripts/ue_import_pf.py`
(étapes textures → masters → mis → meshes). **Variantes** (précalculées aussi) : `MI_PF_<asset>_<Gris|Ocre>`
(teintes de pierre, 42 modules lot 2b) et `MI_PF_<asset>_<Sec|Humide>` (pièce sèche/infiltrée,
26 assets lot 4) — mêmes slots que la version par défaut : les appliquer en surcharge de matériau
sur l'instance placée (exemples dans L_PF_Demo, acteurs tagués `PF_VARIANTES`, script
`Scripts/ue_pf_demo_variants.py`).
Content/Assets/Environment/Bridges/ → Kits de ponts générés dans Blender (Truss, Bailey, Geo ; `ProjetF blender/UE_Export/README.md`) : travées D0-D3,
piles, culées, ponts entiers, un graphe PCG par kit. Le système PCG/RoadNetwork réutilise les travées Truss
(type `MetalAncien`). Dossier NON versionné dans git (2026-10-29). ⚠️ Déplacé le 2026-10-30 depuis
`Content/Bridges/` : en partie dans l'éditeur, et pour 81 fichiers de `Geo/` (matériaux + PCG), sur disque
éditeur fermé + `[CoreRedirects]` `/Game/Bridges/` → `/Game/Assets/Environment/Bridges/` (DefaultEngine.ini).
**Ne pas retirer cette redirection** : les ponts entiers `Geo/Full/SM_Geo_*` (90-180 Mo chacun) référencent
encore leurs matériaux par l'ancien chemin. Charger ces ponts demande ~10 Go de RAM chacun.
Content/Megaplant_Library/ → Végétation Megaplant : Plant_Bilberry/, Tree_European_Aspen/, Tree_Hornbeam/
(chacun avec Instances/, Materials/, Textures/)
Content/Audio/Foley/ → **(2026-09-08)** Système de sons de pas/foley du sample Game Animation Sample :
`AC_FoleyEvents` (ActorComponent, ajouté à `SandboxCharacter_Mover`, voir §8.2),
`S_FoleyEventParams`, `DefaultFoleyEventAudioBank`. Reste du dossier `Content/Audio/` :
sons/bancs audio divers du sample, non branchés sur le gameplay AGIS.
Content/MetaHumans/ et Content/Characters/Echo|Paragon|UE4_Mannequin|UE5_Mannequins/ → **SUPPRIMÉS le 2026-09-13**
(nettoyage) : assets MetaHuman et personnages de démonstration du sample Game Animation
Sample (~2,6 Go), utilisés uniquement par les BP de démo `RetargetedCharacters/BP_*`
(supprimés aussi). Copie de sauvegarde : `E:\projet\ProjectFv1_backup_migration_2026-09-13\nettoyage_etape1_demo\`.
`UEFN_Mannequin` reste le seul squelette de personnage humanoïde du projet.
Content/Levels/, Content/Widgets/, Content/dev_map → **SUPPRIMÉS le 2026-09-14** : démo du sample Game Animation Sample (kit de blockout
LevelPrototyping, widgets et map de démo). Seul lien avec le projet : le cast vers
`LevelBlock_Traversable` dans `Systems/Mover/AC_TraversalLogic`, retiré (voir §4.2.1).
Sauvegarde : commit git `6eb7516`.
Content/Misc/ → SandboxAnimCurveCompressionSettings.uasset (réglages de compression d'anim, référencés par ~1 100
animations : laissé en place, le déplacer resauvegarderait toutes ces anims).
Content/Thumbnails/ → **SUPPRIMÉ le 2026-10-30** : T_aaa/T_bbbb (inutilisées) supprimées, T_SM_WaterBottle_Thumbnail
rangée dans `Items/ItemTextures/`.
Content/__ExternalActors__/ → ⚙️ Généré automatiquement par World Partition (données par-acteur des maps, ex. LocomotionAnimPack).
Content/__ExternalObjects__/ → ⚙️ Généré automatiquement par World Partition (objets externes liés aux maps).
→ Ces deux derniers dossiers ne doivent JAMAIS être édités ou documentés à la main :
leur contenu suit automatiquement les maps et les acteurs qu'elles contiennent.
```
### 8.1 Import du sample Epic "Game Animation Sample" (2026-09-07/08)
L'utilisateur a importé le contenu brut du sample officiel Epic **Game Animation Sample** directement dans `Content/Blueprints/` (structure de dossier d'origine du sample), dans le but de disposer d'une référence complète/fonctionnelle du framework "Sandbox Character" (voir §4.2) pendant que sa migration vers le plugin `Mover` était en cours. Ce sample est la source originelle du framework `Systems/Blueprints/` déjà présent dans ce projet : l'import a donc créé **95 collisions de nom exactes** (mêmes noms : `SandboxCharacter_CMC`, `AC_TraversalLogic`, `BP_MovementMode_Walking`, `E_Gait`, `IMC_Sandbox`, tous les `IA_*` génériques, etc.) entre le contenu fraîchement importé et les versions déjà personnalisées de ce projet.
**Politique appliquée (décision utilisateur explicite)** : garder la version du projet partout, ne conserver du nouvel import que le contenu réellement nouveau (RetargetedCharacters, SmartObjects/AI supplémentaires, Ragdoll, Audio/Foley, MetaHumans, Characters de démo — voir §4.2/§8). Les 95+17 doublons (17 supplémentaires découverts après coup sous `Content/Input/` et `Content/Data/`, dont `IMC_Sandbox` lui-même) ont été fusionnés via `EditorAssetLibrary.consolidate_assets()` (redirecteur automatique + suppression du doublon) quand c'était possible, ou supprimés directement (`delete_asset`) quand rien ne référençait plus la copie importée. Le contenu unique restant a ensuite été déplacé vers son emplacement définitif (`Content/Systems/Blueprints/`, `Content/Core/PlayerController/Input/`) via `AssetToolsHelpers.get_asset_tools().rename_assets()` (déplacement avec réécriture de toutes les références, pas un déplacement de fichier brut).
**Reste à faire / connu (2026-09-08)** :
- `Content/Systems/Mover/SmartObjects/AC_SmartObjectAnimation` (anciennement `Content/Blueprints/SmartObjects/`, déplacé le 2026-09-14) : un doublon n'a **pas** pu être fusionné/supprimé (l'outil `consolidate_assets` fait planter l'éditeur sur cet ActorComponent précis, voir §9) — laissé en l'état intentionnellement, sans impact gameplay (fonctionnalité SmartObject annexe).
- ⚠️ *Obsolète depuis le 2026-09-13 (voir §8.2)* — `Content/Blueprints/` contenait alors de nombreux fichiers `ObjectRedirector` (résidus de `consolidate_assets`) — passer un **"Fixup Redirectors" manuel** (clic droit sur le dossier dans le Content Browser) quand pratique ; aucune API Python trouvée pour cette opération, et forcer une suppression brute des redirecteurs risquerait de casser des références logicielles (soft references) non encore réécrites.
### 8.2 Migration `Project_F` → `ProjectFv1` (2026-09-13) — incident et état après réparation
`E:\projet\Project_F` est un **autre dépôt git** (projet Game Animation Sample nettoyé, où l'utilisateur a développé un système de visée). Le 2026-09-13 (~22:00–22:12), l'éditeur de `Project_F` a migré **3073 packages** directement dans `ProjectFv1/Content` **pendant que l'éditeur de ProjectFv1 était ouvert** (preuve : `Project_F/Saved/Logs/Project_F.log`, lignes `LogSavePackage: Moving ... to E:/projet/ProjectFv1/Content/...`). ⚠️ Ne jamais migrer vers ce projet avec son éditeur ouvert.
Dégâts constatés : 1449 assets suivis par git de `Characters/UEFN_Mannequin/` écrasés (animations + chooser tables, liées désormais aux notifies/enums de `Content/Blueprints/`), 782 fichiers `Systems/Weather/UltraDynamicSky/` écrasés (widgets météo sans leurs structs `ST_Weather*` → 149 erreurs), `Content/Blueprints/` recréé comme copie complète (137 vrais assets) du framework de `Systems/Blueprints/`. Les enums/structs des deux copies DIVERGENT (ex. `E_MovementDirection` : `F/B/LL/LR/RL/RR` vs `Forward/Back/left/…`, `E_Gait` sans Prone/Swimming) : ne JAMAIS les fusionner aveuglément.
Réparation (décisions utilisateur) :
- **Météo** : les 782 fichiers UltraDynamicSky restaurés depuis le commit `d29748b` (LFS). Versions migrées sauvegardées dans `E:\projet\ProjectFv1_backup_migration_2026-09-13\`.
- **Anims/choosers migrés conservés**. Contexte (`ContextData`) des 7 chooser tables CMC/traversal remis aux valeurs du commit : `CHT_PoseSearchDatabases(+_Dense/_Sparse/_ExtremeSparse)` et `CHT_CMCCharacterAnimations` → `Systems/Blueprints/SandboxCharacter_CMC_ABP` (+ `S_ChooserOutputs` Systems) ; `CHT_TraversalMontages_CMC/_Mover` → `Systems/Blueprints/Data/S_TraversalChooserInputs/Outputs`. Lignes Bow du chooser Dense perdues (voir §1.3).
- **Structs strictement identiques fusionnées** (`consolidate_assets`, redirecteurs laissés) : `Content/Blueprints/Data/S_CHT_TraversalMontages_IN/OUT` → `Systems/.../S_TraversalChooserInputs/Outputs`, `Content/Blueprints/Data/S_ChooserOutputs` → `Systems/.../S_ChooserOutputs`. Pins de `Content/Blueprints/AC_TraversalLogic` (traversal du Mover) rebranchées.
- **Doublon GASP du personnage CMC supprimé** : `Content/Blueprints/SandboxCharacter_CMC` + `_ABP`. Références de démo redirigées vers `/Game/SandboxCharacter_CMC` (depuis déplacé dans `/Game/Core/Character/`) / l'ABP Systems (`PC_Sandbox.Characters`, `AC_PreCMCTick`, `GM_Sandbox.PawnClasses_Soft`, `EUW_PawnOverrideButton.Object`, variable locale de `BP_NotifyState_EarlyTransition`). (`Content/Widgets/` a été supprimé le 2026-09-14.)
- `STT_PlayAnimFromBestCost` (Systems) pointe désormais vers `Systems/Blueprints/SmartObjects/AC_SmartObjectAnimation`.
- `Config/DefaultGameplayTags.ini` : ajout des tags du sample `Foley.Event.*` et `SmartObject.ObjectType.Player/Bench` (manquants → avertissements massifs + StateTree SmartObject non liable).
Contenu du framework du sample conservé volontairement (déplacé le 2026-09-14, voir §4.2.1 et « Convergence partielle » ci-dessous) :
- `Systems/Aiming/` (`ABP_AimingMaster`, `ABP_AimingMaster_UEFN`, `CH_AO`, `CH_Base_AO_Animechooser`) → système de visée/aim offset développé dans `Project_F`, utilisé par `SandboxCharacter_Mover_ABP`.
- `Systems/Mover/SandboxCharacter_Mover` / `_ABP` / `_Ragdoll` → personnage alternatif plugin Mover (+ `Systems/Mover/GM_Sandbox`/`PC_Sandbox` ; `dev_map` de démo supprimé le 2026-09-14).
- le reste du framework dupliqué, dans `Systems/Mover/` (AC_TraversalLogic, BPI_*, Data/, Cameras/…) ; ses notifies restent dans `Content/Blueprints/AnimNotifies/` (liés à 1278 animations). Chooser tables toujours liées à ce framework : `CHT_PoseSearchDatabases_Relaxed`, `CHT_MoverCharacterAnimations_PoseMatch`, `CHT_GetUpMontages`, `CHT_CharacterInteractionPSDs`.
**Convergence partielle (2026-09-14)** :
- **Enums fusionnés** : les 17 enums du sample de `Content/Blueprints/Data|Cameras|AnimNotifies/` (`E_Gait`, `E_Stance`, `E_MovementMode`, `E_MovementState`, `E_MovementDirection`, `E_MovementDirectionBias`, `E_RotationMode`, `E_AnalogStickBehavior`, `E_ExperimentalStateMachineState`, `E_RagdollInjuryState`, `E_TraversalActionType`, `E_CameraMode`, `E_CameraStyle`, `E_EarlyTransition_Condition`, `E_EarlyTransition_Destination`, `E_FoleyEventSide`, `E_TraversalBlendOutCondition`) ont été fusionnés dans leurs jumeaux de `Systems/Blueprints/` (17 redirecteurs laissés à l'ancien emplacement). Valeurs propres au sample ajoutées en fin d'enum côté projet (voir §4.2) et pins/switchs de la chaîne Mover remappés dessus. Interfaces et structs Systems enrichies en conséquence (§4.2).
- **Réparations consécutives** (piège décrit en §9) : variables d'enum vidées retypées et liens rebranchés dans `SandboxCharacter_Mover`, `SandboxCharacter_Mover_Ragdoll`, `SandboxCharacter_Mover_ABP` (restauré depuis sauvegarde puis remappé), `BP_AnimNotify_TriggerRagdoll`, `AnimNotifies/BP_NotifyState_EarlyTransition` et `BP_NotifyState_MontageBlendOut` (liens comparés un à un à leurs jumeaux Systems). Chaque asset validé par rechargement de son package.
- **Arrêt de la convergence (décision utilisateur)** : à cause de ce piège, le reste de `Content/Blueprints/` (Mover, Aiming, AC/BPI/structs/notifies/caméras du sample) n'est PAS fusionné. Il a été seulement déplacé, sans fusion, le 2026-09-14 (voir §4.2.1) ; les doublons restants sont une dette connue.
**Restauration Motion Matching (2026-09-14, décision utilisateur)** : un audit des 189 données Motion Matching modifiées depuis `d29748b` (comparaison des références `/Game/...` des blobs git avant/après) a trouvé 7 assets dégradés par la migration, tous restaurés depuis `d29748b` (toutes leurs références existent encore) : `CHT_PoseSearchDatabases_Dense` (19 bases perdues : Bow, Prone, nage, cover accroupi, Push ; colonnes `E_CoverD`/`Weapon_Equip`), `Channels/PSC_Traversal_Head` et `_Pos` (pointaient sur la BPI du sample au lieu de `Systems/Blueprints/BPI_SandboxCharacter_ABP`), `Schemas/PSS_Default` et `PSS_Idle` (table miroir `Rigs/MDT_UEFN_Mannequin` perdue), `Normalization_Sets/PSN_Relaxed_All` (le Mover y perd `PSD_SM_Mover_TraversalTransitions`), `Databases/Dense/PSD_Dense_Stand_Idles` (mesh d'aperçu). Seul autre écart trouvé, conservé : `Traversal/CHT_TraversalMontages_Mover` (montages Climb Relaxed → Neutral, chooser du Mover).
État vérifié en fin de réparation : passe de compilation complète, **435 Blueprints, 0 erreur** (non encore testé en PIE). Dettes connues non liées à la migration : tags `Attachments.*` (§2.1), animations AGIS sans squelette (voir §9 : `Systems/Inventory/Other/Animations/DeathAnim_AGIS` et `Rifle/*` — compatibles à 100 % avec `SK_UEFN_Mannequin`, à assigner à la main dans l'éditeur ; `Jog/`, `WALK/`, `TurnInPlace/`, `AIM_Rifle_Idle_ADS` inutilisées et incompatibles), « Unknown structure » au chargement de `Systems/Blueprints/BPI_SandboxCharacter_Pawn` (antérieur au 2026-09-07).
---
## 9. Notes pour les IA : pièges connus de l'outillage
Ce projet est modifié via le plugin **VibeUE** (service MCP `unreal-mcp`, voir `.mcp.json`), qui expose des services Python (`unreal.BlueprintService`, `unreal.WidgetService`, `unreal.InputService`, `unreal.EnumStructService`, etc.) pour piloter l'éditeur Unreal sans l'ouvrir manuellement. Quelques pièges rencontrés et leurs solutions :
- **Contenu copié à la main depuis un autre projet/plugin (2026-09-27, port AST)** : les `.uasset` gardent leurs chemins d'origine (`/AST_…/`). Tant que ces chemins ne sont pas redirigés (`[CoreRedirects]` + redémarrage éditeur), NE PAS les charger : l'auto-save du MCP resauvegarde le Blueprint avec ses types perdus (c'est arrivé le 2026-09-21 à `BPC_StateManager`/`StateActorComponent`, restaurés depuis AST). Pour une spline d'acteur modifiée par script, mettre `spline_has_been_edited = True` sinon le script de construction repart des points du template. En Python, `unreal.Rotator(a, b, c)` = **(roll, pitch, yaw)** : toujours nommer les arguments.
- **PIÈGE — un nœud `Evaluate Chooser` sans objet de contexte branché renvoie TOUJOURS une liste vide (2026-09-18)** : constaté sur `SandboxCharacter_Mover_ABP::Update_MotionMatching`, dont les deux nœuds avaient leur pin de contexte (`SandboxCharacter_Mover_ABP_C`) laissé vide — aucune colonne du chooser ne pouvait être résolue, `ValidDatabases` restait vide en permanence et toute la branche Motion Matching de l'`AnimGraph` était inerte (`MM Search Cost` = FLT_MAX, `CurrentSelectedAnim` = None) sans qu'aucune erreur ne soit loguée. Toujours brancher `self` (type id créable : `Variables|Getareferencetoself`). Symptôme jumeau à surveiller : un chooser dupliqué dont on a changé le `ContextData` fonctionne, les liaisons se résolvant par NOM de variable sur la nouvelle classe de contexte.
- **PIÈGE — créer par script un sous-objet instancié dans un template de composant casse la sauvegarde des Blueprints ENFANTS (2026-09-17)** : `unreal.new_object(cls, outer=<template du composant>)` (ici un mode de mouvement ajouté à `CharacterMover.MovementModes`) produit un objet **privé** ; l'enfant (`SandboxCharacter_Mover_Ragdoll`, qui surcharge le composant via son `InheritableComponentHandler`) le prend comme archétype et sa sauvegarde échoue avec `Illegal reference to private object`. Préférer un enregistrement à l'exécution (`MoverComponent::AddMovementModeFromClass` au BeginPlay) ou l'édition manuelle dans l'éditeur.
- **PIÈGE — `SubobjectDataSubsystem.rename_subobject` juste après `BlueprintService.remove_component` provoque une assertion fatale (2026-09-17)** : `Renaming an object (…Arrow_GEN_VARIABLE) on top of an existing object (…Arrow1_GEN_VARIABLE) is not allowed` — l'ancien template n'est pas encore collecté. L'interpréteur Python est ensuite corrompu (`0xC0000005` au premier accès au Blueprint) et **le MCP sauvegarde les packages modifiés à chaque appel**, donc l'état intermédiaire peut se retrouver sur disque : redémarrer l'éditeur et vérifier le Blueprint (composants, liaisons) après ce type d'erreur. Corollaire : un composant ajouté par `BlueprintService.add_component(..., parent_name="Capsule")` reçoit un `ParentComponentOrVariableName` explicite qui déclenche l'avertissement `Reparenting the 'X' component … possible cyclic linkage` à chaque compilation ; l'ajouter sans parent (comme les autres composants racine du SCS) évite l'avertissement.
- **Lire l'état de jeu en PIE depuis Python est décalé d'une frame ou deux** (surtout pour les variables d'Anim Blueprint écrites dans l'update thread-safe) : une même valeur peut sembler « fausse » (allure restée à Run, `ValidDatabases` vide) puis correcte à l'appel suivant. Toujours relire dans un second appel avant de conclure à un bug. De plus, `BlueprintService.list_graphs`/`list_variables` renvoient **0 élément pendant une session PIE** : arrêter le PIE avant d'inspecter un graphe.
- **`build_graph`** attend un champ **`"ref"`** pour identifier chaque nœud dans la liste `nodes` (pas `"id"`). Un mauvais nom de champ échoue silencieusement (retourne `None` sans erreur explicite).
- Les **variables locales à une fonction** (paramètres, locals créés via `add_object_variable` avec `graph=...`) ne sont pas fiables avec `build_graph`'s `variable_get`/`variable_set` : préférer des **variables membres** du Blueprint.
- Un `K2Node_VariableSet`/`Get` créé via un spawner_key générique (`discover_nodes` + `create_node_by_key`) peut se résoudre sur la **mauvaise classe** si plusieurs Blueprints chargés ont une variable de même nom affiché (ex. "Owner Inventory" existe à la fois sur `WB_Container` et sur un widget créé cette session) — toujours vérifier les pins du nœud créé (`get_node_pins`) avant de câbler, ou utiliser `build_graph` avec `type: "variable_set"` + `params.variable` en contexte "self" qui est fiable.
- Appeler une fonction Blueprint **préexistante** (créée dans une session antérieure/par le plugin marketplace) sur un **autre** Blueprint peut échouer silencieusement selon la méthode utilisée. Privilégier l'accès direct aux propriétés (`member_get`, fiable) plutôt que l'appel de fonction cross-Blueprint quand c'est possible ; sinon tester `add_function_call_on_variable` et vérifier le résultat via `get_node_pins`/`get_nodes_in_graph`.
- Écrire une valeur littérale sur un pin de type `Text`, `Object` ou `Class` via `set_node_pin_value` échoue souvent **silencieusement à l'affichage** (`get_node_pins` renvoie toujours `default_value: ""`) même quand l'assignation a réellement fonctionné — vérifier via une **compilation** (erreurs de compilation manquantes = probablement OK) plutôt que via une relecture du pin.
- **Toujours compiler ET sauvegarder** un Blueprint après modification (`BlueprintEditorLibrary.compile_blueprint` + `EditorAssetLibrary.save_loaded_asset`), puis **tester en PIE** et lire les logs (`EditorToolset.LogsToolset.GetLogEntries`, filtrer sur `"Blueprint Runtime Error"`) pour détecter les erreurs à l'exécution qu'une compilation réussie ne révèle pas (ex. accès à un élément de tableau hors limites).
- Le clavier cible du jeu est **AZERTY** : vérifier les mappings de touches existants (`IMC_Sandbox`, `IMC_AGIS_Character`, `IMC_AGIS_InventoryTab`) avant d'assigner une nouvelle touche, et choisir des touches physiquement identiques entre QWERTY/AZERTY.
**Pièges du toolset `editor_toolset.toolsets.blueprint.BlueprintTools`** (create_node/connect_pins/write_graph_dsl/get_node_infos, utilisé depuis le système d'armes du 2026-09-05) :
- La **DSL `write_graph_dsl`** casse sur un `type_id` contenant une parenthèse fermante suivie d'autres caractères (ex. `BOTH|GetItem(copy)ByUID` échoue avec "does not exist" car le tokenizer coupe au premier `)`) ; un `type_id` se terminant PAR une parenthèse fonctionne (`Utilities|Array|Get(acopy)`). Dans ce cas, construire le nœud à la main (`create_node`/`connect_pins`) plutôt que via la DSL.
- Un `type_id` retourné par `find_node_types` (ex. `Variables|Default|GetACWeapon`) peut différer du `type_id` affiché ensuite par `get_node_infos` pour CE nœud (ex. `|GetAC_Weapon`, avec underscore) : toujours utiliser la chaîne de `find_node_types` pour **créer**/référencer un nœud dans la DSL, jamais celle affichée après coup par `get_node_infos`.
- Éditer une propriété d'un **composant SCS hérité** (ex. changer le mesh d'un `StaticMeshComponent` défini dans une classe parente, depuis le Blueprint enfant) : `SubobjectDataBlueprintFunctionLibrary.get_object`/`get_associated_object` renvoient le **template du PARENT** (le modifier casse le parent pour toutes ses classes filles !). Utiliser `get_object_for_blueprint(data, child_blueprint)` qui crée correctement l'override propre à l'enfant.
- Itérer un `Array[SubobjectDataHandle]` avec `for h in handles:` casse `get_data`/`get_variable_name` (erreur de nativisation) ; indexer explicitement (`for i in range(len(handles)): h = handles[i]`) fonctionne.
- Un Blueprint Function Library (BFL) se crée avec `unreal.BlueprintFunctionLibraryFactory()` (pas le `BlueprintFactory` générique + `parent_class`, qui échoue avec "Cannot create a blueprint based on the class").
- Depuis `execute_python_code`, appeler une fonction Blueprint sur une instance PIE via `actor.call_method(...)` nécessite le nom **d'affichage exact avec ses espaces** (ex. `"Set Item Actor Owner"`, `"Primary Use"`), pas une version condensée/snake_case — sauf pour certains getters de variable auto-générés dont le nom interne réel ne correspond ni à l'un ni à l'autre (à vérifier au cas par cas).
- Créer un `InputAction` en Python : `unreal.InputAction_Factory()` (PAS `unreal.InputActionFactory`, qui n'existe pas) + `AssetToolsHelpers.get_asset_tools().create_asset(...)`. Pour mapper une touche dans un `InputMappingContext` existant, préférer `unreal.InputService.add_key_mapping(imc_path, ia_path, "F9")` (service VibeUE dédié) plutôt que d'éditer la propriété dépréciée `mappings` de l'IMC directement en Python.
- Construire un widget UMG en Python : `unreal.WidgetService.add_component`/`set_property`/`bind_event` fonctionnent bien pour la hiérarchie et les propriétés, mais il n'existe **aucun** `unreal.WidgetBlueprintLibrary.create_widget` exposé pour instancier un widget à la volée. Pour un test rapide en PIE, utiliser `unreal.WidgetService.spawn_widget_in_pie(chemin)` (retourne un handle qu'on repasse à `remove_widget_from_pie`) — mais ce handle n'expose pas d'appel de fonction arbitraire sur l'instance ; il ne sert qu'à l'afficher/la retirer et lire ses propriétés (`get_live_property`).
- `EditorToolset.EditorAppToolset.CaptureAssetImage`/`CaptureViewport` sont le seul moyen fiable de **voir** un résultat (mesh, matériau, scène) : le retour JSON est trop volumineux pour être lu tel quel (limite de tokens) — toujours passer par un script Python (`base64.b64decode` du champ `returnValue.data`, écrit dans le scratchpad) puis `Read` sur le PNG obtenu.
- Un asset `.uasset` peut se retrouver **corrompu à vide** (0 vertex/triangle, bounds nuls) après une fermeture anormale de l'éditeur (coupure de courant, crash) sans que sa taille de fichier ni son chargement ne signalent d'erreur — toujours vérifier `get_num_vertices(0)`/`get_bounds()` (StaticMesh) après un redémarrage suspect avant de chercher un autre bug. `delete_asset`/`consolidate_assets` peuvent échouer silencieusement (retour `False`, sans exception) sur un tel asset corrompu même sans référenceur restant — dans ce cas, renommer le nouvel asset importé sous un autre nom plutôt que de s'acharner à réutiliser le chemin d'origine.
- Toute variable membre **booléenne** (`bXxx`) affiche son nom SANS le préfixe `b` dans les nœuds Get/Set générés par la DSL (`GetCanSeePlayer`, pas `GetbCanSeePlayer`) — mais son **nom de propriété brut**, tel qu'attendu par `get_editor_property`/`set_editor_property` en Python ET par `StateTreeService.bind_*_property_to_context` (chemins de contexte), **conserve le préfixe `b`** (`"bCanSeePlayer"`, `"AnimalSenses.bScentDetected"`). Utiliser le nom sans `b` dans l'un de ces deux contextes échoue silencieusement en DSL (nœud introuvable) ou avec "Malformed target property path" côté StateTree.
- `delete_node` sur un nœud dont une pin est encore référencée par un AUTRE nœud (ex. un nœud `Self`/`GetXxx` pur, réutilisé par plusieurs consommateurs) peut laisser une **connexion fantôme** côté survivant : `get_node_infos` continue de lister une pin connectée vers le node supprimé (qui n'apparaît plus dans `find_nodes`), et la compilation échoue avec l'erreur générique `Internal compiler error inside CreateExecutionSchedule` sans désigner le nœud fautif. Diagnostic : dumper `get_node_infos` de TOUS les nœuds du graphe et chercher une pin connectée vers un node absent de `find_nodes` ; le node fantôme existe encore assez longtemps en mémoire (non garbage-collecté) pour que `break_pins` fonctionne dessus malgré tout — l'appeler explicitement sur cette connexion règle l'erreur.
- Une **UserDefinedEnum** (créée via `unreal.EnumStructService.create_enum`) ne peut PAS servir de type à une variable membre, un paramètre de fonction ou un paramètre de dispatcher via les outils disponibles (`add_variable`/`add_function_param` n'acceptent que bool/int/float/byte/name/string/text/Vector/Rotator/Transform/Vector2D/LinearColor) — limitation déjà notée pour `AC_Stats::OnDamaged`, reconfirmée pour `E_AnimalState`. Contournement : stocker en `byte` et convertir à l'affichage seulement si besoin (`Utilities|String|ToString(Byte)`).
- Une **StateTree Task Blueprint** (`StateTreeTaskBlueprintBase`) ne tique QUE si sa CDO a `should_call_tick_only_on_events = true` — malgré son nom qui suggère l'inverse ("seulement sur événements"), c'est en réalité l'interrupteur général qui active `ReceiveLatentTick`. Aucune property `bShouldCallTick` distincte n'est exposée.
- Chaîner plus de deux concaténations `(+ a b)` de **String** dans une même expression DSL imbriquée casse avec `Could not connect pin ReturnValue to A` (constaté sur `BP_AnimalBase::DebugTick`) — même en séparant chaque étape par un `bind` intermédiaire. Contournement : limiter chaque nœud `+` à DEUX opérandes littéraux/variables simples, et multiplier les appels `DrawDebugString`/lignes plutôt que de construire une seule chaîne longue.
- Une donnée cross-instance (lire la variable d'un AUTRE acteur de même classe, ex. le `HerdID` d'un autre `BP_AnimalBase` pendant `BroadcastAlertToHerd`) n'est PAS accessible via le getter `Variables|Default|GetXxx` généré par la DSL (celui-ci n'a pas de pin `self`/cible, contrairement aux nœuds `Class|MaClasse|MaFonction` qui, eux, exposent bien un `self` substituable). Solution : passer la donnée nécessaire en PARAMÈTRE de la fonction appelée sur l'autre instance plutôt que de tenter de la lire directement (voir `ReceiveHerdAlert(ThreatLocation, CallerHerdID)`).
- Une nouvelle **StateTree** (`unreal.StateTreeService.create_state_tree`) démarre SANS aucun état racine : `add_state(path, "Root", ...)` échoue silencieusement (retourne `False`) tant qu'un premier `add_state(path, "", "Root", "State")` (parent_path **vide**) n'a pas créé l'état racine lui-même.
- Lier une `AC_Stats::OnDamaged` (dispatcher avec paramètres) directement à une fonction Blueprint CLASSIQUE (pas un Custom Event) via `K2Node_CreateDelegate.set_create_delegate_function` **compile sans erreur immédiate** mais casse la compilation à la volée suivante avec `Internal compiler error inside CreateExecutionSchedule` — et peut même faire apparaître des nœuds `AddEvent` parasites (EventTick, EventActorBeginOverlap…) ailleurs dans le graphe. Un dispatcher avec paramètres doit être lié à un **Custom Event** (`add_event` avec un nom arbitraire crée un vrai `K2Node_CustomEvent`, contrairement à `write_graph_dsl`'s `(event Nom ...)` qui n'accepte que les noms d'événements natifs "AddEvent|Nom" connus). Un dispatcher SANS paramètre (ex. `AC_Stats::OnDeath`) ne pose aucun problème lié à une fonction classique.
- (2026-09-14) `BlueprintTools.write_graph_dsl` **échoue en silence** : en cas d'erreur (nœud inexistant, pin incompatible) il ne lève rien, retourne `None` et laisse le graphe inchangé ou VIDE ; le vrai message n'apparaît que dans `Saved/Logs/ProjectFv1.log` (`LogScript: Warning: AssertionError: The node could not be created / … does not exist` ou `RuntimeError: Could not connect pin …`). Toujours vérifier chaque écriture par `read_graph_dsl` (chercher un nœud attendu) avant de sauvegarder. Pièges constatés : (a) `Math|Byte|Equal(Byte)` et `Math|Vector|vector+vector` sont RELUS par `read_graph_dsl` mais REFUSÉS à l'écriture — utiliser les opérateurs `==`/`+` ; (b) les appels de fonction du Blueprint lui-même (`CallFunction|MaFonction`) ont une pin `self` en première position : passer les arguments par mot-clé (`:Anim x :Loop true`), sinon le premier argument est branché sur `self` ; (c) les getters de variables à underscore perdent l'underscore dans le type_id (`Anim_Idle` → `Variables|Default|GetAnimIdle`), et `find_node_types` renvoie aussi `CallFunction|ApplyIdleforState` avec la casse de l'affichage ; (d) `read_graph_dsl` affiche parfois un nœud sous une catégorie trompeuse (`CustomizableObjectInstance|SetCurrentState` pour un appel à `BP_AnimalBase::SetCurrentState`) — vérifier avec `get_node_infos` avant de conclure à un mauvais binding ; (e) `BlueprintTools.create_node` attend un `unreal.IntPoint` pour `pos`, pas un `Vector2D`.
- (2026-09-14) StateTree : une tâche Blueprint qui appelle `FinishTask` dans `EnterState` termine l'état dans la même frame (`TasksCompletion=Any`), et `StateTreeService.set_task_considered_for_completion(..., False)` renvoie `True` mais n'a PAS empêché ce comportement en PIE (UE 5.8). Pour une tâche « fire and forget » (ex. `STT_SetAnimalState`), ne PAS appeler `FinishTask`. Diagnostic : `log LogStateTree Verbose` en PIE puis compter les `Enter state` dans le log.
- (2026-09-14) StateTree : une transition posée sur `Root` (trigger `OnTick`) reste évaluée quand sa CIBLE est déjà active et la ré-entre à chaque tick (Exit/Enter de toutes ses tâches). Toute transition globale doit donc porter une condition « pas déjà dans cet état » (ex. `bIsFleeing == false` pour `Root→Flee`).
- Un **NavMeshBoundsVolume** ajouté par script (`SceneTools.add_to_scene_from_class`) ne génère PAS automatiquement de NavMesh : le `RecastNavMesh-Default` correspondant naît avec `RuntimeGeneration = Static` (aucune régénération, ni en éditeur ni au runtime, sans un "Build Paths" manuel — introuvable dans l'API Python exposée). Fix : `recastNavMesh.set_editor_property("runtime_generation", unreal.RuntimeGenerationType.DYNAMIC)` PUIS `unreal.NavigationSystem.on_navigation_bounds_updated(nav_sys, volume)` pour forcer une première génération immédiate (sans ce second appel, le volume reste sans tuiles jusqu'au premier changement de géométrie pertinent).
- **FAUX AMI corrigé (2026-09-05)** : `read_graph_dsl` insère parfois un `_` de lisibilité dans le nom affiché d'une variable membre commençant par un préfixe court en majuscule (ex. la variable réelle `LContainer` s'affiche `(|GetL_Container)`, PAS `(|GetLContainer)` — idem `LItemName`→`L_ItemName`). Utiliser cette forme AVEC underscore pour créer/écrire un nœud (`create_node`, `write_graph_dsl`) échoue avec "... does not exist", ce qui peut faire croire à tort à une variable locale non-créable (variables locales et membres se comportent en réalité de façon identique côté création). Avant toute chirurgie sur un Get/Set qui échoue ainsi, vérifier le nom SANS underscore via `find_node_types` (ex. filtrer `"LContainer"`) plutôt que de supposer une limitation de l'outil — le nom réel de la propriété (`get_editor_property` en Python inclus) ne contient jamais cet underscore ajouté par l'affichage.
- Un Blueprint qui est la classe d'un **composant actif dans une session PIE en cours** peut compiler sans erreur mais **échouer silencieusement à la sauvegarde** (`save_loaded_asset` renvoie `False` sans exception) tant que le PIE n'est pas arrêté — toujours `StopPIE` avant de sauvegarder un asset qu'une session PIE utilise, et re-tenter la sauvegarde après l'arrêt.
- **CORROLAIRE DANGEREUX** : après tout échec de `save_loaded_asset` dû au PIE, **StopPIE PUIS ressauvegarder IMMÉDIATEMENT**, et **toujours revérifier via `read_graph_dsl`/`find_nodes` après un redémarrage/arrêt du PIE** qu'une édition récente est bien encore présente avant de continuer à construire dessus — une édition compilée avec succès pendant que le PIE tournait peut sembler perdue à la prochaine lecture si la sauvegarde qui a suivi a échoué silencieusement.
- **PIÈGE MAJEUR — graphe d'implémentation d'interface Blueprint fragile et auto-régénéré (2026-09-06)** : le graphe qui implémente une fonction de Blueprint Interface (ex. `BP_Item_HoldableBase:On Item Attached`, implémentant `BPI_HoldableItem::On Item Attached`) peut **revenir tout seul à son état d'origine** après une compilation ultérieure — de N'IMPORTE QUEL Blueprint de la hiérarchie, pas seulement celui qu'on édite — même quand `read_graph_dsl` avait confirmé l'édition juste après une sauvegarde réussie (`save_loaded_asset` → `True`, mtime du `.uasset` à jour). Un `reload_packages` forcé depuis Python confirme que c'est bien le contenu SUR DISQUE qui a régressé, pas juste l'état en mémoire — la persistance de ce type de graphe est donc bien plus fragile qu'une fonction normale. Reproduit 3 fois de suite sur le même graphe dans cette session malgré des re-sauvegardes systématiques. **Ne JAMAIS héberger de logique métier propre (dispatcher broadcast, etc.) directement dans un graphe d'implémentation d'interface** — s'en servir seulement comme passe-plat trivial si besoin, ou mieux : contourner entièrement l'interface et appeler la fonction/le dispatcher cible directement depuis l'appelant via un `Cast` explicite vers la classe concrète (ex. `Inventory_Equipper::AttachItemToOwner` caste `Item` en `BP_Item_HoldableBase` et appelle `CallOnHeldStateChanged` directement, sans jamais passer par l'interface) — cette approche s'est révélée stable après le contournement.
- **Référence d'interface `K2Node_Message`/`K2Node_CallFunction` corrompue après un renommage d'interface (2026-09-06)** : un appel d'interface existant peut afficher un `type_id`/catégorie qui ne correspond à AUCUN asset actuel du projet (ex. `BPIEquippableItem|OnItemAttached` alors que seule `BPI_HoldableItem` existe — probablement un renommage `BPI_EquippableItem`→`BPI_HoldableItem` fait avant cette session) tout en compilant sans erreur et en affichant des types de pins statiquement cohérents (`get_node_type_pins` montrait bien `self: BPI Holdable Item Interface`) — la compilation réussit car un système de redirection (CoreRedirects ou équivalent) fait correspondre le nom au niveau des TYPES, mais l'appel ne déclenche RIEN à l'exécution (confirmé par prints de debug ne s'affichant jamais côté récepteur, malgré la classe cible confirmée implémenter l'interface via `unreal.SystemLibrary.does_class_implement_interface`). Un nœud fraîchement créé avec le MÊME `type_id` peut même se résoudre en un type de nœud DIFFÉRENT (`K2Node_CallFunction` au lieu du `K2Node_Message` d'origine) — signe supplémentaire d'une référence historique corrompue plutôt que d'un vrai type_id valide. Diagnostic : ajouter un print côté récepteur et vérifier empiriquement qu'il se déclenche ; si l'appelant confirme passer par le nœud (print juste avant) mais que le récepteur reste silencieux, suspecter ce piège. Fix : ne pas réutiliser l'ancien nœud ni recréer un nœud du même `type_id` — contourner l'interface entièrement (voir point ci-dessus).
- **Consommer un stock d'inventaire PASSIF (jamais équipé/lâché) ne doit jamais passer par un acteur monde (2026-09-06)** : la macro `ML_InventoryMacros::Get Item Actor` (et donc tout appel `Utilities|GetItemActor`) retourne TOUJOURS invalide pour un item empilé qui n'a jamais été spawné comme acteur (ex. un stack de flèches "Arrow" qui reste en inventaire, jamais équipé) — ce n'est pas un bug ponctuel, c'est le comportement normal : ces items n'ont simplement pas d'acteur tant qu'ils ne sont pas équipés/lâchés dans le monde. Une fonction du type `ConsumeArrow`/`ReduceAmmo` qui suit le patron `FindItem → GetItemActor → SERVER|ReduceAmount(item_actor, ...)` échoue donc silencieusement (retourne false) pour ce genre d'item, même quand `FindItem` a bien trouvé le stack. Fix correct : opérer directement sur les DONNÉES via l'adresse de l'item, avec `Inventory__Main::AddItemAmount(ItemAddress, DeltaAmount)` (négatif pour consommer) — une fonction RPC serveur qui gère nativement le retrait de l'array si le montant tombe à 0, sans jamais nécessiter d'acteur. Le mécanisme `GetItemActor`+fonction-sur-acteur reste correct UNIQUEMENT pour des items dont l'acteur est garanti d'exister (ex. un chargeur d'arme équipé, actif comme `Magazine Reference`).
- **PIÈGE CRITIQUE — brancher UNE sortie de pose sur PLUSIEURS entrées dans un `AnimGraph` casse toute la locomotion (2026-09-06)** : contrairement à un graphe Blueprint normal, une sortie `Pose Link` (`AnimGraphNode_*`) qui alimente directement 2+ entrées fait ÉVALUER EN DOUBLE, à chaque frame, TOUT le sous-graphe en amont — pas seulement recalculer une valeur, mais réellement re-tick les nœuds AVEC ÉTAT (State Machine, Motion Matching, Blend Space avec accumulation de temps, Inertialization) une deuxième fois. Symptôme observé : le personnage "bouge de façon bizarre" (mouvement erratique/saccadé) dès qu'un nœud en amont d'un fan-out a un état interne dépendant du temps — la Motion Matching et les State Machines de ce projet en amont de `Slot 'DefaultSlot'` étaient tickées deux fois par frame après l'ajout (sans précaution) d'un second consommateur sur la sortie de ce Slot. Fix obligatoire : **ne jamais connecter directement une sortie de pose partagée à plusieurs entrées** — insérer un `Save Cached Pose 'NomUnique'` juste après la source, puis un `Use Cached Pose 'NomUnique'` par consommateur (le projet utilise déjà ce patron ailleurs dans `SandboxCharacter_CMC_ABP`, ex. cache `MotionMachin` consommé par `Mirror_0` ET `BlendListByBool_3` — repérable via `find_nodes`/`get_node_infos` avant de dupliquer une sortie, pour voir si le patron existe déjà). Une régression de ce type NE PROVOQUE AUCUNE ERREUR DE COMPILATION — le graphe compile et sauvegarde sans avertissement, le bug n'est visible qu'en jouant.
- **Créer un nœud de Get/Set VARIABLE d'une AUTRE classe (pas une fonction) via `create_node` (2026-09-06)** : `find_node_types` avec `context_pins` pointant vers une pin déjà castée à la classe cible retourne bien le type_id correct (ex. `Variables|Default|SetWeapon`), mais ce type_id échoue à la création via `create_node` (`does not exist`) MÊME avec `declaring_class` renseigné — `declaring_class` ne sert qu'à désambiguïser un type_id déjà trouvé, pas à élargir la recherche. Fix : rappeler `find_node_types` avec `context_pins: []` (SANS contexte) sur le même filtre — la forme SANS contexte révèle le type_id RÉELLEMENT créable, qui suit la convention `Class|<NomClasseSansUnderscore>|<NomPropriété>` (ex. `Class|SandboxCharacterCMC|SetWeapon`), différent de la forme `Variables|Default|...` que le contexte affichait. Une fois créé, le nœud peut ensuite s'afficher avec un LABEL DSL trompeur (catégorie `SandboxCharacterCMCABP` au lieu de `SandboxCharacterCMC` par ex.) — vérifier le type réel de la pin `self` via `get_node_infos` (pas la catégorie DSL) pour confirmer la bonne classe cible.
- **Configurer un nœud `AnimGraphNode_*` (Slot, LayeredBoneBlend, etc.) au-delà de ses pins (2026-09-06)** : le nom d'un `Slot` (`SlotName`) et les filtres de branche d'un `Layered Blend Per Bone` (`LayerSetup[i].BranchFilters`) ne sont PAS des pins — ce sont des propriétés du sous-struct `Node` (le `FAnimNode_*` runtime) porté par le `AnimGraphNode_*`. Invisibles via `get_node_infos`/`create_node`/`set_pin_value`. Accès uniquement via Python : `node.get_editor_property("Node")` renvoie le struct runtime, dont les champs se lisent/écrivent avec `get_editor_property`/`set_editor_property` en snake_case (`slot_name`, `layer_setup`, `blend_weights`...) — mais pour un ARRAY de structs imbriqués (ex. `layer_setup: [InputBlendPose]`, chaque `InputBlendPose.branch_filters: [BranchFilter]`), modifier un élément en place ne suffit pas : construire les structs indépendamment (`unreal.BranchFilter()`, `unreal.InputBlendPose()`), les remplir via `set_editor_property`, PUIS réassigner le tableau complet sur le struct parent (`ibp.set_editor_property("branch_filters", [bf])`, puis `node_struct.set_editor_property("layer_setup", [ibp])`, puis `node.set_editor_property("Node", node_struct)`) — une mutation in-place sans cette réassignation finale ne persiste pas. `to_dict()` sur ces structs renvoie souvent `{}` de façon trompeuse même quand les propriétés sont bien accessibles individuellement via `get_editor_property` — ne pas se fier à `to_dict()` pour vérifier un struct AnimGraph, toujours relire chaque propriété nommément.
- Le cache de découverte de `find_node_types` pour un AUTRE Blueprint référençant fraîchement une fonction ajoutée ailleurs (ex. `Class|InventoryPlayer|MaNouvelleFonction` cherché depuis un Widget qui possède une variable de type `Inventory_Player`) peut rester périmé et ne rien retourner même après compilation+sauvegarde de la classe modifiée — `create_node` avec le `type_id` déduit par convention (`Class|<NomClasseSansUnderscore>|<NomFonction>`) fonctionne quand même ; ne pas conclure à une impossibilité sur la seule base d'un `find_node_types` vide.
- `unreal.get_editor_subsystem(unreal.EditorActorSubsystem).get_all_level_actors()` (et plus généralement les APIs `EditorActorSubsystem`) redevient **silencieusement vide** pendant qu'une session PIE est active — ces APIs ciblent le monde éditeur, pas le monde PIE. Pour inspecter/piloter des acteurs PENDANT une session PIE (vérifier qu'une IA se déplace, lire une variable en temps réel), utiliser `editor_toolset.toolsets.scene.SceneTools.find_actors` (qui résout correctement vers le monde `UEDPIE_0_<NomNiveau>`) puis `unreal.load_object(None, refPath)` sur le chemin PIE retourné.
- **`write_graph_dsl` (réécriture complète d'un graphe) casse sur 2 instances DIFFÉREMMENT TYPÉES d'un même nœud wildcard enum (2026-09-06)** : `Utilities|Enum|NotEqual(Enum)`/`Equal(Enum)` sont des nœuds génériques dont le type se fixe au premier pin connecté — un graphe EXISTANT peut très bien contenir plusieurs instances typées différemment (constaté sur `Get_MMInterruptMode` : 3× `NotEqual(Enum)` + 2× `Equal(Enum)`, types `E_MovementMode`/`E_MovementState`/`E_Gait` mélangés, qui coexistent sans problème). Mais **reconstruire ce même graphe en un seul appel `write_graph_dsl`** en y ajoutant une NOUVELLE instance sur un type enum différent (ex. `Weapon_Equip`) échoue systématiquement avec `Could not connect pin ReturnValue to B`, quel que soit l'ordre des clauses — le compilateur DSL semble partager le typage wildcard entre toutes les instances créées dans une même passe, contrairement au graphe déjà compilé où chaque instance est indépendante. Heureusement l'échec est bien transactionnel (`read_graph_dsl` confirme le graphe intact après l'erreur, aucun état partiel). Fix : ne PAS passer par une réécriture DSL complète pour ce genre d'ajout — construire les nœuds un par un (`create_node` pour les 2 `Variables|Default|GetXxx` + le nouveau `NotEqual(Enum)`, `connect_pins` pour les typer), puis brancher le résultat sur le nœud logique existant. Un nœud `Math|Boolean|ORBoolean`/`ANDBoolean` (`K2Node_CommutativeAssociativeBinaryOperator`) accepte nativement un pin de PLUS via `add_node_pin` (retourne le nouveau `PinID`, ex. `C` après `A`/`B`) — pas besoin de recréer tout l'arbre logique, juste ajouter le pin et y connecter la nouvelle condition. `read_graph_dsl` n'affiche ensuite QUE les 2 premiers pins d'un `or`/`and` à 3+ entrées (limite d'affichage du lecteur DSL) — vérifier un ajout de ce type via `get_node_infos` sur le nœud lui-même, pas via une relecture DSL qui semblerait ignorer l'édition.
- **Architecture Motion Matching — pourquoi une nouvelle base de données "valide" peut être ignorée indéfiniment (2026-09-06)** : `Update_MotionMatching` (fonction ABP thread-safe, tourne chaque frame) fait `EvaluateChooser → SetValidDatabases → MotionMatchingAnimNodeLibrary.SetDatabasesToSearch(node, databases, InterruptMode)` — le 3ᵉ paramètre (`Get_MMInterruptMode`, une fonction séparée) contrôle si le nœud `AnimGraphNode_MotionMatching` a le DROIT de remettre en cause la pose en cours (`DoNotInterrupt` = non, il continue de jouer/boucler l'ancienne sélection MÊME SI elle n'appartient plus à `DatabasesToSearch` ; `InterruptOnDatabaseChange(AndInvalidateContinuingPose)` = oui, force une recherche complète). **Changer `ValidDatabases` ne suffit donc JAMAIS à lui seul à faire réagir Motion Matching** si `Get_MMInterruptMode` ne détecte pas la transition responsable — vérifier scrupuleusement, pour TOUT nouveau contexte de sélection (nouvelle variable de state driving un chooser), qu'il existe bien une condition correspondante dans cette fonction (schéma déjà en place : une paire `GetXxx`/`GetXxxLastFrame`, mise à jour chaque frame dans `Update_States`, comparée avec `Utilities|Enum|NotEqual(Enum)` puis OR-ée dans la condition racine). Diagnostic fiable : `obj dump` sur l'instance `AnimInstance` vivante en PIE (`unreal.SystemLibrary.execute_console_command(actor, 'obj dump "<chemin_instance>"')`, résultat lu via `EditorToolset.LogsToolset.GetLogEntries`) expose le struct COMPLET `AnimGraphNode_MotionMatching=(...)`, notamment `MotionMatchingState.SearchResult.(SelectedAnim|SelectedDatabase|bIsContinuingPoseSearch|SearchCost)` et `AnimPlayers[].InternalTimeAccumulator` (qui doit AVANCER entre deux dumps espacés dans le temps — sinon la pose est réellement figée, pas juste "en cours de transition").
- **`PoseSearchDatabase` — échec silencieux de build d'index et non-avancement figé sur une frame (2026-09-06)** : un `PoseSearchDatabase` en mode `PCAKD_TREE` (valeur par défaut) peut échouer à construire son index dès le chargement du projet si son jeu d'animations sources est trop petit/peu varié pour que le calcul PCA converge — log `LogPoseSearch: Error: <hash> - <NomDatabase> BuildIndex Failed because of 'No Convergence' while calculating PCA data` (visible uniquement via `GetLogEntries` sur `LogPoseSearch`, jamais remonté côté Blueprint/PIE). Tant que l'index reste dans cet état, TOUTE recherche dessus échoue en silence (`SearchCost=FLT_MAX`, `SelectedAnim=None`) et spam `LogPoseSearch: Warning: UPoseSearchLibrary::MotionMatch - some searches have been skipped, since databases AsyncBuildIndex are in still in progress or failed` à chaque tick. Fix testé et qui fonctionne : `db.set_editor_property("PoseSearchMode", unreal.PoseSearchMode.BRUTE_FORCE)` (au lieu de `PCAKD_TREE`) + sauvegarde (`EditorAssetLibrary.save_asset(path, only_if_is_dirty=False)` — nécessite PIE ARRÊTÉ, sinon retourne `False` sans erreur) — `BRUTE_FORCE` n'a pas besoin de PCA et convient très bien à une petite database (coût de recherche linéaire négligeable à cette échelle). **Second piège indépendant, NON contournable trouvé à ce jour** : le tableau `DatabaseAnimationAssets` (alias historique `AnimationAssets`) d'un `PoseSearchDatabase` est protégé en lecture ET écriture pour TOUTE méthode testée (`get_editor_property`/`set_editor_property` Python, ET `ObjectTools.get_properties`/`set_properties` en C++ natif — qui pourtant contournent d'autres restrictions Python) ; seule la lecture BRUTE via `obj dump "<chemin>.<NomAsset>"` (PAS juste `<chemin>` seul, qui ne dump que le Package) expose son contenu texte (`DatabaseAnimationAssets(i)=(AnimAsset=...,SamplingRange=(Min=...,Max=...),...)`), sans équivalent en écriture connu. **ATTENTION à la sémantique de `SamplingRange`** (erreur de diagnostic commise puis corrigée le 2026-09-06) : `(Min=0, Max=0)` ne signifie PAS « plage vide » mais « ÉCHANTILLONNER TOUT LE CLIP » — c'est la valeur NORMALE, utilisée par toutes les databases saines du projet (vérifié sur `PSD_Dense_Stand_Walk_Loops`, `PSD_Dense_Stand_Idles`). Le cas réellement pathologique est `Min == Max != 0`, en particulier `(longueur_du_clip, longueur_du_clip)` : fenêtre de largeur nulle collée à la FIN du clip. Double conséquence : (a) plus rien à lire, et (b) `FPoseSearchDatabaseSequence::IsLooping()` exige `SamplingRange == (0,0)` EN PLUS de `AnimSequence->bLoop`, donc l'asset devient non-bouclable (`bLoopAnimation=False` dans le dump du nœud) même si l'AnimSequence source est bien `Loop=True` — d'où un gel sur une frame unique. Symptôme observable : `AnimPlayers[].InternalTimeAccumulator` identique entre deux `obj dump` espacés de plusieurs secondes réelles en PIE. Correction = remettre la plage à `0 / 0` à la main dans l'éditeur Pose Search Database.
- **PIÈGE CRITIQUE — `EditorAssetLibrary.consolidate_assets()` fait planter l'éditeur sur certains types de classes (2026-09-08, dédoublonnage de l'import Game Animation Sample)** : cette fonction (fusionne toutes les références d'un doublon vers l'asset gardé, supprime le doublon, laisse un `ObjectRedirector`) est **fiable à 100% sur les assets de données pures** (UserDefinedEnum, UserDefinedStruct, CurveFloat, ChooserTable, CameraRigAsset, CameraAsset, ControlRigBlueprint, ProxyAsset — testé sur ~85 assets sans un seul échec) mais **crashe le processus éditeur de façon reproductible** (`Assertion failed: NewEditorElement.Private_GetInternalData()->GetRefCount() <= 1 ... The old and new element both have external references!`, `LogPython: Error: Python execution caused a crash (exception code: 0xC0000005)` — l'éditeur entier finit par `Engine exit requested`, pas juste l'interpréteur Python) sur les **ActorComponent Blueprints activement utilisés comme composant** (constaté sur `AC_PreCMCTick`, `AC_SmartObjectAnimation`) et sur les **Blueprint Interfaces largement implémentées** (`BPI_SandboxCharacter_Pawn`). Les Blueprints "de classe" simples (GameMode, modes de mouvement `Mover`, transitions) consolident sans problème. **Ne jamais retenter `consolidate_assets` une seconde fois sur le même asset après un premier crash** — utiliser à la place, dans l'ordre de préférence : (1) `EditorAssetLibrary.delete_asset()` en brut si `AssetRegistry.get_referencers()` confirme que plus rien ne référence le doublon (le plus sûr, fonctionne même sur les types à risque — mais peut prendre 1 à 4 minutes sur un projet avec des dizaines de milliers d'assets, l'appel `execute_python_code` timeout côté client mais l'opération continue et réussit côté serveur : revérifier via `does_asset_exist` plutôt que de conclure à un échec sur un timeout) ; (2) si des références externes subsistent, les repointer manuellement d'abord — `BlueprintTools.set_parent()` pour un reparentage, `BlueprintTools.retarget_node_class(node, old_class, new_class)` pour un nœud de cast/appel de fonction/événement avec une classe figée à l'ancien doublon (fonctionne bien, contrairement à `consolidate_assets`) — puis `delete_asset` une fois les référenceurs à zéro. **Effet de bord du crash** : le processus éditeur entier redémarre, ce qui **remet l'interpréteur Python à zéro mais PAS l'état sauvegardé sur disque** — toute opération déjà sauvegardée (compile+save) avant le crash est conservée ; seule la commande en cours au moment du crash est perdue. Toujours vérifier `is_in_pie`/un `print("ping")` trivial après CE type d'erreur avant de continuer : contrairement à une simple corruption d'interpréteur (`SystemError: unknown opcode`, récupérable seulement par redémarrage éditeur elle aussi), celle-ci relance le processus tout seul en quelques dizaines de secondes.
- **Corollaire — reparenter un Blueprint dont les enfants référencent des COMPOSANTS PAR NOM (2026-09-08)** : si un Blueprint enfant (ex. `SandboxCharacter_Mover_Ragdoll`) a des nœuds `Variables|Default|GetXxx`/`SetXxx` qui lisent des variables membres du PARENT (ex. `SkeletalMesh`, `Capsule`, `CharacterMover` — des variables de CACHE exposant les composants SCS réels, PAS les composants eux-mêmes) et qu'on reparente vers un parent qui n'a pas ces variables sous ce nom exact, la compilation échoue avec `The property associated with <Nom> could not be found`. **Fix simple et à fort effet de levier** : ajouter au NOUVEAU parent des variables membres du même nom/type exacts (`BlueprintTools.add_object_variable`/`add_struct_variable`/`add_variable`) — TOUS les nœuds orphelins portant ce nom, dans TOUTES les fonctions de TOUS les enfants, se re-résolvent automatiquement à la compilation suivante, sans toucher un seul nœud individuellement (testé sur ~90 références réparties dans 28 fonctions, résolu par l'ajout de 8 variables). Il n'est PAS nécessaire que ces variables soient effectivement peuplées/câblées pour que la compilation réussisse — seule leur EXISTENCE avec le bon nom/type compte pour le compilateur ; les peupler (ex. dans `EventBeginPlay`) reste nécessaire pour un comportement correct à l'exécution.
- **`connect_pins`/`write_graph_dsl` peuvent devenir silencieusement des no-op après une session très longue avec plusieurs crashs éditeur (2026-09-08)** : en fin de session, après plusieurs cycles crash/redémarrage, `connect_pins(PinID_output, PinID_input)` a cessé de produire une erreur mais aussi de produire un effet (`get_node_infos` confirme après coup qu'aucune des deux pins n'est connectée) — reproduit même sur une paire de nœuds fraîchement créés dans le MÊME appel Python, et même sur un Blueprint différent qui avait fonctionné plus tôt dans la session. Aucune cause identifiée avec certitude (suspecté : état de l'éditeur dégradé par l'accumulation de crashs, pas un bug systématique de l'outil — `connect_pins` a fonctionné de façon fiable pendant le reste de la session). Si `connect_pins` semble réussir sans erreur mais que `get_node_infos` ne montre ensuite AUCUNE connexion sur les pins concernées, ne pas insister : passer à une méthode alternative (repenser le fix pour n'avoir besoin que d'ajouts de variables/fonctions, voir point ci-dessus) plutôt que de perdre du temps à re-essayer la même primitive.
- **Écrire dans une `ChooserTable` (colonnes) depuis un script (2026-09-06)** : `ColumnsStructs` se LIT en Python (`get_editor_property` + `.export_text()` sur chaque `InstancedStruct`) mais ne s'ÉCRIT PAS par cette voie — `InstancedStruct.import_text(nouveau_texte)` renvoie `True` en mentant (le contenu de l'élément reste inchangé), et le `set_editor_property("ColumnsStructs", ...)` qui suit ne remonte donc rien, sans lever d'exception. **Voie qui fonctionne** : le toolset Python `editor_toolset.toolsets.object.ObjectTools` (importable directement : `from editor_toolset.toolsets.object import ObjectTools`), qui expose la table en JSON — `json.loads(ObjectTools.get_properties(chooser, ["ColumnsStructs"]))`, modification du dict (ex. `col["rowValuesWithAny"][i] = "MatchAny"`), puis `ObjectTools.set_properties(chooser, json.dumps(data))` qui renvoie `True` et applique réellement. `ResultsStructs` (résultats des lignes) reste inaccessible par toutes les voies testées : on peut donc MODIFIER une ligne existante mais pas en AJOUTER une nouvelle par script.
- **`EditorAssetLibrary.save_asset`/`save_loaded_asset` peuvent renvoyer `False` alors que l'asset est parfaitement sauvegardable, PIE arrêté et fichier non-verrouillé** (constaté sur `CHT_PoseSearchDatabases_Dense` après une écriture via `ObjectTools.set_properties`, probablement parce que le package n'est pas marqué dirty par cette voie). Ne pas en conclure à un échec : passer par `unreal.EditorLoadingAndSavingUtils.save_packages([obj.get_outermost()], False)` qui renvoie `True` et met bien à jour le `mtime` du `.uasset` (à vérifier via `os.path.getmtime`, seule preuve fiable d'écriture disque).
- **Outillage découvert le 2026-09-13 (réparation post-migration)** : (a) `BlueprintEditorLibrary.compile_blueprint` / `BlueprintTools.compile_blueprint` ne lèvent AUCUNE exception sur erreur de compilation — encadrer les compilations de marqueurs `unreal.log_warning("MARQUEUR")` puis lire `Saved/Logs/ProjectFv1.log` entre les marqueurs (`LogBlueprint: Error`). (b) `BlueprintTools.set_pin_value` (et d'autres outils du toolset) peut SAUVEGARDER l'asset sur disque sans le dire — sauvegarder le fichier avant d'expérimenter. (c) `retarget_node_class` / `change_member_variable_type` ne rafraîchissent pas les types de pins en cache (erreurs « X is not compatible with X ») : sauvegarder, `EditorLoadingAndSavingUtils.reload_packages`, recompiler — le rechargement reconstruit les nœuds. (d) Le struct `PinID` n'est pas exposé en Python : pour `connect_pins`/`break_pins`, passer par `ProgrammaticToolset.execute_tool_script` (appel `get_execution_environment` requis avant) avec des PinID JSON `{direction, index_id, node:{refPath}}`. (e) Les pins d'un nœud `Evaluate Chooser` portent le NOM de l'asset struct de contexte : changer/renommer/fusionner la struct laisse des pins orphelines liées (« In use pin ... no longer exists ») à rebrancher. (f) `EditorAssetLibrary.consolidate_assets` sur des `UserDefinedStruct` sauvegarde automatiquement les référenceurs et crée les redirecteurs. (g) `ChooserTable.ContextData` s'écrit via `ObjectTools.set_properties` (JSON `_structType`/`class`/`struct` → `refPath`) + `EditorLoadingAndSavingUtils.save_packages`. (h) Une référence passant par un `.ini` (ex. `GameplayTagTableList`) est invisible pour l'Asset Registry : vérifier `Config/` avant de déclarer un asset « sans référence ».
- **PIÈGE CRITIQUE — `consolidate_assets` sur une UserDefinedEnum (probablement aussi une UserDefinedStruct) vide silencieusement les types de variables des référenceurs (2026-09-14)** : les références dures sont réécrites, mais le `PinSubCategoryObject` des variables membres (et types de valeur de map) typées par l'enum fusionné tombe à `None` : la variable devient un simple `byte`. La classe compilée garde le bon type, donc tout compile. Au rechargement suivant du package (les référenceurs sont auto-sauvegardés par la fusion), les liens devenus incompatibles sont supprimés, des nœuds `Equal/NotEqual (Enum)` redeviennent Wildcard et l'éditeur insère des `CastByteToEnum`. Une entrée qui garde une valeur littérale compile toujours : la logique est fausse SANS erreur (ex. `SandboxCharacter_Mover_ABP` : 19 variables vidées → 2259 erreurs au rechargement ; `BP_NotifyState_EarlyTransition` : 5 liens perdus dont 1 sans erreur). Les `K2Node_SwitchEnum` ne reconstruisent leurs sorties qu'au rechargement du package. Procédure : sauvegarder tous les référenceurs avant la fusion ; juste après et AVANT tout rechargement, scanner `BlueprintEditorLibrary.get_member_variable_type(bp, v).export_text()` (`PinSubCategoryObject=None`) et retyper via `change_member_variable_type` ; si un référenceur a déjà été rechargé cassé, le restaurer depuis la sauvegarde (éditeur fermé) ou comparer ses liens à un jumeau intact (`BlueprintTools.get_node_infos`), rebrancher et supprimer les casts (`delete_node`) ; valider par `reload_packages` d'UN seul package à la fois (jamais un Blueprint en même temps qu'un Blueprint dont il dépend). Pour des Blueprints entiers, préférer un déplacement à une fusion.
- **CRASH ÉDITEUR — handles Python sur des nœuds supprimés (2026-09-14)** : `BlueprintTools.delete_node` puis `compile_blueprint` dans le MÊME `exec`, alors que des variables Python gardent les handles de nœuds / structs de pins (`get_node_infos`) des nœuds supprimés → `EXCEPTION_ACCESS_VIOLATION` dans python311.dll et fermeture de l'éditeur. Fix qui a fonctionné : exécuter l'édition, la compilation/sauvegarde et tout ce qui déclenche un GC (suppression d'assets, `reload_packages`) dans des callbacks `register_slate_post_tick_callback` SÉPARÉS, chacun dans un dictionnaire de globals neuf, avec `scope.clear()` + `gc.collect()` entre les étapes et `unreal.SystemLibrary.collect_garbage()` au début de l'étape suivante. Ne sauvegarder qu'après une vérification : un crash ne laisse alors rien de corrompu sur disque. Relance : `E:\UE_5.8\Engine\Binaries\Win64\UnrealEditor.exe "E:\projet\ProjectFv1\ProjectFv1.uproject"` (MCP disponible ~20 s après).
- **Asset Registry périmé après sauvegarde + rechargement (2026-09-14)** : `get_dependencies`/`get_referencers` peuvent encore lister une référence qu'un asset n'a plus (ex. `PC_Sandbox` → widget de démo retiré, alors que le `.uasset` ne contient plus ce nom). Forcer `AssetRegistry.scan_paths_synchronous([dossier], True)` avant de vérifier des référenceurs.
- **`EditorAssetLibrary.delete_asset` sur une map non chargée** (`/Game/dev_map`) peut renvoyer `True` en laissant le `.umap` sur disque et une dépendance fantôme dans l'Asset Registry (qui ferait recharger/réenregistrer la map lors d'un renommage de ses dépendances). Vérifier le fichier sur disque ; s'il reste, le supprimer puis `scan_files_synchronous([fichier], True)`.
- **Assigner un squelette à une animation qui n'en a pas (2026-09-14)** : `AnimSequence.Skeleton` est en lecture seule pour `set_editor_property` ET refusé par `ObjectTools.set_properties` ; aucun service VibeUE (`SkeletonService`, `AnimSequenceService`, `AnimMontageService`) ne l'expose (lecture/compatibilité seulement). Seule voie : ouvrir l'asset dans l'éditeur, qui propose de choisir un squelette, puis sauvegarder. Avant, vérifier la compatibilité : `AnimationLibrary.get_animation_track_names(anim)` comparé aux os du mesh cible (`SkinnedMeshComponent.get_num_bones`/`get_bone_name` sur le composant mesh du CDO du personnage).
- **Détecter des régressions d'assets binaires via git** : `git show <commit>:<fichier> | git lfs smudge` restitue l'ancien `.uasset` ; comparer l'ensemble des chaînes `/Game/...` (table des noms) entre ancienne et nouvelle version révèle les références perdues (lignes de chooser, bases d'un schéma, BPI d'un canal) sans ouvrir l'éditeur.
- **Déplacer un dossier via `AssetTools.rename_assets`** (liste d'`AssetRenameData`, un seul appel pour tout le lot) réécrit et sauvegarde lui-même les référenceurs, sans laisser de redirecteur quand tous sont chargeables (112 assets en ~32 s le 2026-09-14). Le coût dépend du nombre de référenceurs : un notify utilisé par des milliers d'animations oblige à toutes les réenregistrer.
**Pièges PCG et éditeur rencontrés sur `PCG/RoadNetwork` (2026-10-28/29, détail dans `Docs/PCG_RoutesPonts.md` §5)** :
- `EditorAssetLibrary.delete_asset` sur un asset encore tenu par Python (ex. un `PCGGraph` juste créé) échoue avec un message « in use » et un `ensure`, puis un `create_asset` du même nom ouvre une boîte modale « Overwrite Existing Object » qui bloque le MCP. Ne jamais supprimer puis recréer : recharger l'asset et le vider (`PCGGraph.remove_node` sur chaque nœud).
- `PCGGraph.add_edge` échoue **en silence** sur un nom de pin inexistant : vérifier les libellés (`node.input_pins[i].properties.label`). Pins utiles : `ModulesInfo` (sans espace), `Match Data`, `Projection Target`, `Input A`/`Input B`, `InsideFilter`/`OutsideFilter`, `Chosen`/`Discarded` ; les opérations unaires d'*Attribute Maths* ont un pin `In`, les binaires `InA`/`InB` (les deux doivent être branchés, même sur la même donnée).
- Opération unaire `SET` d'*Attribute Maths* : ne produit aucune sortie (aucune erreur) → utiliser *Copy Attributes*. *Match And Set Attributes* ne recopie pas la colonne de poids.
- *Get Spline Data* sur d'autres acteurs : `components_must_overlap_self` vaut vrai par défaut (on ne voit que ce qui chevauche l'acteur) et `bIgnoreSelfAndChildren` n'exclut pas la spline de l'acteur. *Distance* : utiliser `SourceShape`/`TargetShape = CENTER`, la distance par sphères englobantes est fausse sur des points de spline. Si la cible peut être vide, ajouter un point sentinelle très lointain.
- Le *Spline Sampler* en mode distance ne place pas de point à l'extrémité de la spline ; il ne lit pas la sortie d'*Offset Spline*. *Subdivide Spline* centre les points sur les modules et son attribut de taille est illisible par les opérations binaires.
- Les points de spline posés par Python sont réinitialisés quand le script de construction se relance : poser `bSplineHasBeenEdited = True` via `ObjectTools.set_properties`.
- Les collisions des ISM générés par PCG dans l'éditeur ne répondent aux rayons et au NavMesh qu'après rechargement du niveau.
- L'éditeur en arrière-plan tourne à environ 3 images/s : la génération PCG et `take_high_res_screenshot` attendent le premier plan (fenêtre amenée devant par script).
- `LandscapeService.get_height_at_location().height` ne renvoie pas une hauteur monde en cm : prendre la hauteur par `line_trace_single`.
- Monde partitionné ouvert par script : seuls quelques acteurs sont chargés ; `WorldPartitionBlueprintLibrary.load_actors(guids de get_actor_descs())` les charge tous.
**Pièges PCG supplémentaires rencontrés sur `PCG/Haussmann` (2026-10-30, détail dans `Docs/PCG_Haussmann.md` §5)** :
- *Copy Points* : `match_based_on_attribute` ne filtre pas (chaque source copiée sur chaque cible) → filtrer puis copier par valeur. En `TARGET_FIRST`, un attribut homonyme de la cible (même à sa valeur par défaut, après une fusion de données) écrase celui de la source → `SOURCE_FIRST` ou `seed_inheritance = RELATIVE`.
- *Merge* refuse les ensembles d'attributs → *Merge Attributes*. *Attribute Noise* (mode `SET`) exige que l'attribut existe déjà.
- *Spline To Segment* : tangentes seulement avec `extract_tangents` ; extrémité ouverte = angle 0 et tangente nulle ; dernier segment d'une spline ouverte : `SegmentNextIndex` = 0. *Subdivide Segment* : ⌊L / taille⌋ modules étirés, attributs transmis, mais pas de nouvelle graine (hacher une graine par module).
- *Match And Set* avec `match_attributes` : tirage pondéré parmi les lignes de même clé, piloté par la graine du point (même graine = même tirage, utile pour un tirage par groupe).
- Lire la sortie d'un autre composant PCG du même acteur : *Get Actor Data*, mode `GET_DATA_FROM_PCG_COMPONENT`, sélecteur de composant par tag (le graphe source doit brancher ses points sur sa sortie).
- `EnumStructService.add_struct_property` : type `FSoftObjectPath` (`SoftObjectPath` échoue sans erreur). Un import CSV de DataTable avec des « problems » ouvre une boîte modale « Message » qui bloque le MCP.
- Débogage : *Export Selected Attributes* (JSON) écrit les points d'un nœud dans un fichier ; reconstruire un graphe régénère tous les composants qui l'utilisent (le fichier est celui du dernier acteur).
**Déplacer des assets en masse (rangement du 2026-10-30) :**
- **Map World Partition** : `AssetTools.rename_assets` ne déplace QUE les acteurs externes chargés. Les autres restent dans l'ancien dossier `__ExternalActors__`, rattachés à l'ancienne map, et disparaissent de la nouvelle. Toujours faire d'abord `WorldPartitionBlueprintLibrary.load_actors([d.guid for d in get_actor_descs()])` sur la map ouverte, puis le renommage, puis compter les fichiers `__ExternalActors__` avant/après.
- **Mémoire** : renommer un asset recharge et resauvegarde tous ses référenceurs. Déplacer un matériau des ponts entiers `Bridges/Geo/Full` (ou tout asset utilisé par de gros meshes Nanite) les recharge et les recompile (~10 Go chacun) → « Ran out of memory / fichier de pagination insuffisant ». La mémoire n'est pas rendue après un lot, même avec `collect_garbage()` : redémarrer l'éditeur tous les 2-3 gros meshes. Pour les dossiers trop lourds, déplacer sur disque éditeur fermé + `[CoreRedirects]` `MatchSubstring` (méthode utilisée pour Bridges).
- **Copies fantômes** : `rename_assets` renvoie True mais laisse parfois l'ancien fichier sur disque comme un VRAI asset (pas un redirecteur) ; c'est arrivé pour des SkeletalMesh, PhysicsAsset, DNA, textures de MetaHuman et Kimodo. Après un déplacement, chercher dans l'ancien dossier les fichiers sans `ObjectRedirector`, vérifier qu'ils ne sont référencés par rien, puis les supprimer.
- Ne jamais garder une référence Python à un monde (`w = get_editor_world()`) au moment de charger un autre niveau : assertion « World Memory Leaks » et plantage. Faire le chargement dans une fonction, sans variable globale.
- Il n'existe pas d'API Python pour « Fix Up Redirectors » : resauvegarder les référenceurs (`EditorLoadingAndSavingUtils.save_packages`), vérifier qu'il n'y en a plus, puis effacer les redirecteurs.
- `EditorAssetLibrary.delete_asset` sur une map ou un asset très référencé peut prendre plusieurs minutes (l'éditeur vérifie les références de tout le projet) ; `delete_loaded_assets` sur une liste est beaucoup plus rapide.
---
## 10. Maintenir ce document à jour
Ce document **doit toujours refléter l'état réel du projet**. Un skill dédié — **`maintain-architecture-doc`** (`.claude/skills/maintain-architecture-doc/SKILL.md`) — rappelle systématiquement aux modèles d'IA de :
1. Consulter ce document avant de modifier la structure du projet.
2. Le mettre à jour après toute création/suppression/déplacement/renommage de fichier ou dossier important.
3. Ajouter une description pour tout nouveau fichier important.
4. Retirer les entrées obsolètes (fichiers supprimés).
5. Corriger les chemins après un déplacement.
6. Mettre à jour les descriptions quand un système évolue significativement.
7. Vérifier périodiquement la cohérence entre ce document et l'arborescence réelle.
8. Ne jamais laisser ce document dans un état sciemment obsolète après une modification de structure.
**Règle de detail** : un fichier "important" à documenter est un Blueprint/Widget/Data Asset/Struct/Enum/Input Action qui porte de la **logique ou des données de gameplay**. Les assets purement artistiques individuels (textures, meshes, clips d'animation, miniatures) n'ont pas besoin d'entrée dédiée — il suffit que leur **dossier parent** soit listé et décrit (cf. §8).
*Historique de nettoyage :*
- *2026-09-04 : `Content/Systems/Blueprints/d/SandboxCharacter_CMC_ABP` renommé/déplacé vers `Content/Systems/Blueprints/SandboxCharacter_CMC_ABP` via l'outil de renommage Unreal (les 7 références ont été recâblées automatiquement, compilation vérifiée OK).*
- *2026-09-04 : suppression de 8 redirecteurs Unreal morts (résidus de renommages passés, 0 référence chacun, confirmés sans danger) : `Content/Systems/Stats/ACStats.uasset`, `Content/Systems/Movement/BPC_ClimbingComponent.uasset`, `Content/Systems/Movement/ClimbingMaterial.uasset`, `Content/Systems/Movement/CR_ClimbingGraphV2.uasset`, `Content/Systems/Movement/CR_Mannequin_BasicFootIK.uasset`, `Content/BP_TickManager.uasset`, `Content/dqs.uasset`, `Content/E_WeaponType.uasset`. Les vraies versions de ces assets restent à `Content/Systems/Movement/Climbing/`, `Content/Core/Tick/BP_TickManager` et `Content/Data/Enums/E_WeaponType`.*
- *2026-09-13 : réparation après la migration `Project_F` → `ProjectFv1` (voir §8.2) — 782 fichiers UltraDynamicSky restaurés depuis git, contexte de 7 chooser tables remis sur `Systems/Blueprints`, 3 structs identiques de `Content/Blueprints/Data/` fusionnées (redirecteurs), suppression de `Content/Blueprints/SandboxCharacter_CMC` + `_ABP` (doublon GASP), tags `Foley.Event.*`/`SmartObject.ObjectType.*` ajoutés. Compilation complète : 435 Blueprints, 0 erreur.*
- *2026-09-13 (nettoyage, étapes 1-2) : suppression des personnages de démo Echo/Paragon/UE4_Mannequin/UE5_Mannequins, de `MetaHumans/` et des BP `RetargetedCharacters/BP_*` ; rangement de `SandboxCharacter_CMC` → `Core/Character/`, `BP_GameInstance` → `Core/`, `DT_DemoTags` → `Data/` (config mise à jour) ; suppression de `DT_DemoTags.T3D` et des dossiers vides (dont `Content/Collections/`, `Content/Developers/`). Compilation : 405 Blueprints, 0 erreur.*
- *2026-09-13/14 (nettoyage, étape 3) : suppression des redirecteurs morts après re-sauvegarde de leurs référenceurs (dont `Animations/anim_Swim*`, `Core/PlayerController/IA_InitiateClimb|IA_LeapFromClimb|IA_MovingClimb`, `PSD_Relaxed_Stand_Sprint_Pivots`, les redirecteurs du 2026-09-04 encore suivis par git, et les redirecteurs de structs de `Content/Blueprints/Data/`).*
- *2026-09-14 : convergence des enums du framework dupliqué et réparation des Blueprints touchés (voir §8.2) ; `GM_Dev` → `SandboxCharacter_CMC`. Passe complète : 405 Blueprints ; seule erreur restante `BP_Item_AdvancedRifle` (`Reload_Rifle_Hip` sans squelette, antérieure) + avertissement `Ultra_Dynamic_Weather`. Commit intermédiaire (point de restauration) avant le déplacement du reste de `Content/Blueprints/` vers `Content/Systems/Mover/`.*
- *2026-09-14 (isolation du framework du sample) : démo GASP supprimée (`dev_map`, `Content/Levels/LevelPrototyping/`, `Content/Widgets/`) après avoir détaché `AC_TraversalLogic` (branche `LevelBlock_Traversable`) et `PC_Sandbox` (widget de démo) ; 17 redirecteurs d'enum supprimés ; 112 assets déplacés sans fusion (`Content/Blueprints/` → `Systems/Mover/` sauf `AnimNotifies/`, `Aiming/` → `Systems/Aiming/`, `Content/Input/` → `Systems/Mover/Input/`, `E_WaponType` → `Data/Enums/`), références réécrites, aucun redirecteur restant. Contrôle après redémarrage complet de l'éditeur : liens de graphe, valeurs de pins et types de variables des 66 Blueprints concernés encore existants identiques au relevé pris avant le déplacement (seuls écarts : chemins réécrits et artefacts de l'outil de comparaison). Compilation complète : 379 Blueprints, seule erreur `BP_Item_AdvancedRifle` (`Reload_Rifle_Hip` sans squelette, antérieure).*
- *2026-09-14 (reprise) : test PIE sur `MapDev1` — `GM_Dev` fait apparaître `SandboxCharacter_CMC`, aucune erreur de Blueprint à l'exécution. Seuls messages, antérieurs : `Systems/Weather/UltraDynamicSky/Particles/Snow` référence encore l'ancien chemin `/Game/UltraDynamicSky/Particles/UDW_Niagara_Parameters`, et 4 avertissements Pose Search d'entrées vides dans `PSD_SwimmingIdle`/`PSD_SwimmingMovingSprint` (contenu inchangé depuis `d29748b`, de nouveau chargé grâce au chooser restauré). Restauration depuis `d29748b` des 7 données Motion Matching dégradées par la migration (voir §8.2) avec fix MatchAny refait, et de `NS_Tornado`. Compilation complète : 379 Blueprints, seule erreur `BP_Item_AdvancedRifle` (`Reload_Rifle_Hip` sans squelette, antérieure).*
- *2026-09-14 (météo) : corrections du système météo projet (`WB_WeatherControl`/`WB_WeatherZone`, voir §4 Weather). Bugs corrigés : `setWeatherbetweenZone` lisait deux fois Zone2 ; `GetNearZone` ne trouvait jamais de zone (distances locales à 0, poids 0/0 et inversés) ; `SetNuage`/`MakeTemp` retournaient 0 sans zone voisine (return dans la boucle) et clampaient la température à 0–10 ; `makestate` renvoyait un état vide (branche froide nuages ≥ 8) et ne calculait ni vent/brouillard/poussière ni humidité ; `MakeForcast` avait des pas de 0–1 s ; `UpdateWeatherstate` n'avançait jamais le temps (+0) ; rayon d'influence ≈ 900 uu et tri `Trie zone` pouvant boucler à l'infini ; overlaps déclenchés par n'importe quel acteur ; saison en secondes. Nouveaux : fonctions `StepForecast`, `ApplyToUDW`, `GetZoneState` ; variables `ForecastStepMin/Max`, `InfluenceRadius`, `LastUpdateTime` (zone), `SeasonDurationScale` (contrôleur). `[CoreRedirects]` UDS + 10 systèmes Niagara re-sauvegardés. PIE OK (zones prêtes, 16 prévisions, poids 0.52/0.48, UDW alimenté, aucune erreur météo). Restant : `WeatherData` des 2 zones de MapDev1 vide (0 °C / 0 %), `BP_TickManager` transmet un `DeltaTime` ~10× trop petit au contrôleur (saison lente), avertissement d'attache des ChildActor UDS/UDW (mobilité). Sauvegarde des assets avant modification : `E:\projet\ProjectFv1_backup_migration_2026-09-13\weather_before_fixes_2026-09-14\`.*
- *2026-09-18 (migration cover / prone / nage du CMC vers le Mover, voir §4.2.2) : `SandboxCharacter_Mover` reçoit `AC_Cover` + `Arrow1`, les variables et fonctions de cover/prone/nage, l'enregistrement du mode « Swimming » au BeginPlay, et ses fonctions `Get_Gait`/`Get_MoveInput`/`Get_PropertiesForAnimation`/`On_PreSimulateTick` étendues. Nouveaux assets : `Systems/Mover/MovementModes/BP_MovementMode_Swimming`, `Characters/UEFN_Mannequin/Animations/MotionMatchingData/CHT_PoseSearchDatabases_Mover`. Corrections annexes : `BP_MovementMode_Walking` (vitesse/accélération nulles pour les allures prone et nage), `MovementModeMap` (Ragdoll→Climbing et Flying→SwimmingOnSurface, séquelles de la fusion des enums — le ragdoll du pion refonctionne), `SandboxCharacter_Mover_ABP` (contexte `self` absent des `Evaluate Chooser` → branche Motion Matching inerte ; nouvelle bascule `AnimBranchIndex`). Testé en PIE : prone (capsule 40 ↔ 86 via le `StanceModifier`, base `PSD_Dense_Prone_Idles` sélectionnée), nage dans `WaterBodyLake_0` (entrée/flottaison à la profondeur cible/plongée/sortie, `PSD_SwimmingIdle` sélectionnée), accroupi de couverture. NON testé : entrée en couverture par la touche P et déplacement le long d'un vrai mur. Sauvegarde des assets avant modification : `E:\projet\ProjectFv1_backup_mover_migration_2026-09-17\`.*
- *2026-09-18 (suite) : à la demande de l'utilisateur, `SandboxCharacter_Mover_ABP` passe **entièrement en Motion Matching** — `Update_Logic` ne calcule plus la bascule, `AnimBranchIndex` est fixé à `0` (nœuds `Select` / `Not Equal (Enum)` / `AND` / `Get Stance` supprimés, valeur par défaut de la variable mise à 0). Diagnostic de la **T-pose en nage** : ce n'était pas l'ABP mais le contenu — 5 des 6 bases Pose Search de nage n'ont que des lignes à `AnimAsset = None` et la table `SwimmingInDeepIdle` du chooser est vide ; seul `PSD_SwimmingMoving` est rempli. Contournement dans `CHT_PoseSearchDatabases_Mover` (lignes vers bases vides neutralisées, lignes 13/16 élargies en MatchAny), détail et procédure de correction définitive en §4.2.2. Vérifié en PIE avec MM permanent : idle debout (`PSD_Relaxed_Stand_Idles`), course avant (`PSD_Relaxed_Stand_Run_F_Starts/Loops`), prone (`PSD_Dense_Prone_Idles` → `anim_Prone_Idle`), nage surface immobile / en mouvement et nage profonde (`PSD_SwimmingMoving` → `RM_Breastroke_overwater_01_right_turn_Retargeted`).*
- *2026-09-19 (système d'armes sur le Mover, voir §4.2.3) : `AC_Weapon::Equip/Unequip` reçoivent une branche `Cast Failed` → `SandboxCharacter_Mover` (leur `Cast To SandboxCharacter_CMC` échouait en silence sur le Mover, d'où `Weapon`/`isArmed` jamais renseignés). Nouvelle fonction `SandboxCharacter_Mover::SetEquippedWeapon` + variables `Weapon_Equip`/`WantToshot` sur le pion, `Weapon_Equip`/`Weapon_Equip Last Frame`/`Want Toshot` sur `SandboxCharacter_Mover_ABP` (alimentées dans `Update_PropertiesFromCharacter`, thread principal). `IA_Aim` renseigne `WantToshot`. `Update_MotionMatching` consulte aussi `CHT_PoseSearchDatabases_Mover` quand `Is Armed`, et `Get_MMInterruptMode` force l'interruption au changement d'arme. Dans `CHT_PoseSearchDatabases_Mover` : colonnes `Is Armed` et `IS Moving Object` réactivées, ligne 1 (« Stand Idles Bow ») passée en `Gait = MatchAny`. Testé en PIE de bout en bout (ramassage → main → anims arc → visée → tir) avec non-régression prone/nage. `Inventory_Equipper.uasset` apparaît modifié sans changement de contenu (resauvegarde automatique du MCP, mêmes références `/Game/`).*
- *2026-09-19 (MetaHuman en visual override, voir §4.2.4) : une première tentative mettait le corps MetaHuman directement comme mesh du pion (squelettes déclarés compatibles + sockets recopiées) ; elle a été **annulée** au profit de l'approche du sample GASP. Restauration de `ABP_GenericRetarget` depuis le commit initial (supprimé au nettoyage de la démo GASP), création de `IK_MetaHuman_test`, `RTG_UEFN_to_MetaHuman_test` et `BP_VisualOverride_MetaHuman` dans `Systems/Mover/VisualOverrides/`, `GM_Dev` reparenté sur `GM_Sandbox` (il était vide) et `VisualOverrides_Soft` renseigné, défaut du CVar `DDCvar.VisualOverride` passé de -1 à 0. Limite connue : les objets équipés restent accrochés au mannequin caché, ~12 cm d'écart avec la main du MetaHuman.*
- *2026-09-27 (port des systèmes AST Cover + Interaction, voir §4.2.5) : contenu AST copié sous `Systems/Movement/{Cover,Interaction,StateManager}` et réparé par `[CoreRedirects]` ; nouveau `Systems/Movement/ASTShared/` (dépendances minimales des autres plugins AST) et map de test `Systems/Movement/Test/L_ASTCoverTest` ; `SandboxCharacter_Mover` reçoit `BPC_StateManager`/`BPC_CoverSystem`/`BPC_Interaction` et les overrides de déplacement ; `SandboxCharacter_Mover_ABP` lie `OverlayGraph` et `InteractionIKGraph` ; `IMC_Sandbox` : A → `IA_TakeCover` ; tables de tags AST ajoutées à `DefaultGameplayTags.ini`. `AC_Cover` supprimé (par l'utilisateur).*
- *2026-10-28/29 : nouveau système `Content/PCG/RoadNetwork/` (routes et ponts post-apo, voir §7 et `Docs/PCG_RoutesPonts.md`), dossier racine `Docs/`, ligne `Content/Bridges/` ajoutée en §8, pièges PCG en §9. Le prototype `Content/PCG/Road/` est conservé tel quel. Aucun asset existant modifié ni supprimé (seul le dossier d'essai `RoadNetwork/_Spike`, créé et supprimé pendant le travail).*
- *2026-10-30 : nouveau système `Content/PCG/Haussmann/` (immeubles haussmanniens, voir §7 et `Docs/PCG_Haussmann.md` + `Docs/PCG_Haussmann_captures/`), pièges PCG ajoutés en §9 ; scripts hors projet dans `E:/projet/PF_Haussmann_Work/scripts`.*
- *2026-10-30 (rangement de `Content/`) : toutes les maps de test regroupées dans `Content/Maps/DevTest/` (+ `HLOD/`) ; packs `PropsParis`, `RueDeParis`, `Bridges`, `Prop` → `Content/Assets/Environment/`, `Animations` + `Kimodo` → `Content/Assets/Animations/`, `Content/test` (MetaHuman) → `Characters/MEtahuman/Unpacked/test`. `KitFR/` et `ProjectF/` laissés à la racine (trop lourds à déplacer). Supprimés : 249 assets AST inutilisés du StateManager, maps de démo (`ShowcaseMap`, 2× `DefaultLevel1`, UltraDynamicSky `DemoMap`), `Untitled_HLOD0_Instancing`, `PCG/test/NewPCGGraph1`, `Thumbnails/T_aaa|T_bbbb`, ~280 redirecteurs, dossiers vides. `[CoreRedirects]` ajouté pour `/Game/Bridges/`. Notifies AST `ReloadNotify`/`SetEquippedWeapon` vidés. Chemins mis à jour dans les scripts `ProjetF blender/` et le skill kimodo-motion. Vérifié : 476 Blueprints compilent sans erreur, PIE MapDev1 sans erreur. Sauvegarde : `E:\projet\ProjectFv1_backup_reorg_2026-10-30\` + commit `e829ff2`. Pièges en §9.*