PROP / ERR-043 : props d'animation retires avec le corps du PNJ (LPPersonPropGuardSubsystem) (CHG-244)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ebae9242a7
commit
7a0b3f3671
6 changed files with 540 additions and 0 deletions
|
|
@ -1131,6 +1131,7 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol
|
||||||
| CHG-155 | 2026-10-05 | ShowCouple | Un lit est reconnu par son nom. Il ne bloque un déplacement que vers le bas, pour se poser ; le retour horizontal n'est plus arrêté et le contact ne renvoie plus la boîte | après avoir quitté le lit, la boîte ne pouvait plus y revenir | revenir au test `DoesPackageExist` et au filtre du flanc seul |
|
| CHG-155 | 2026-10-05 | ShowCouple | Un lit est reconnu par son nom. Il ne bloque un déplacement que vers le bas, pour se poser ; le retour horizontal n'est plus arrêté et le contact ne renvoie plus la boîte | après avoir quitté le lit, la boîte ne pouvait plus y revenir | revenir au test `DoesPackageExist` et au filtre du flanc seul |
|
||||||
| CHG-156 | 2026-10-05 | Clavier | `LPKeyboardSubsystem` : bouton « Clavier » ajouté à l'exécution dans `MainMenu.SettingsScrollBox` ; panneau pour changer, ajouter ou retirer une touche (actions et axes clavier). Écrit dans `Saved/Config/Windows`, pas dans `GameSettings.sav` | pouvoir réaffecter les touches, dont Q, sans modifier les Blueprints | retirer `LPKeyboardSubsystem.h/.cpp` et la dépendance `UMG` |
|
| CHG-156 | 2026-10-05 | Clavier | `LPKeyboardSubsystem` : bouton « Clavier » ajouté à l'exécution dans `MainMenu.SettingsScrollBox` ; panneau pour changer, ajouter ou retirer une touche (actions et axes clavier). Écrit dans `Saved/Config/Windows`, pas dans `GameSettings.sav` | pouvoir réaffecter les touches, dont Q, sans modifier les Blueprints | retirer `LPKeyboardSubsystem.h/.cpp` et la dépendance `UMG` |
|
||||||
| CHG-158 | 2026-10-05 | Réglages | `LPSettingsSubsystem` : onglets Audio, Video, Lewd, General, Keyboard, Misc au-dessus de `SettingsScrollBox`. Les lignes du Blueprint restent en place et sont montrées selon leur texte ; le reste va dans Misc. Keyboard affiche la liste des touches | la liste unique ne montrait pas le clavier | retirer `LPSettingsSubsystem.h/.cpp` |
|
| CHG-158 | 2026-10-05 | Réglages | `LPSettingsSubsystem` : onglets Audio, Video, Lewd, General, Keyboard, Misc au-dessus de `SettingsScrollBox`. Les lignes du Blueprint restent en place et sont montrées selon leur texte ; le reste va dans Misc. Keyboard affiche la liste des touches | la liste unique ne montrait pas le clavier | retirer `LPSettingsSubsystem.h/.cpp` |
|
||||||
|
| CHG-244 | 2026-10-08 | PROP / ERR-043 | `Docs/Migration/etat/PROP/PROP-BUG.md`, `PROP-CART.md` ; dumps `etat/PROP/PROP_LPDumpFunction_20261008-*.txt` ; `Source/LifePlay/LPPersonPropGuardSubsystem.h/.cpp` (nouveaux) | « seul le verre reste apparent » : `destroyDummy` sans `clearProps` + mesh vide. Garde runtime : si Person caché ou `Mesh` sans SkeletalMesh → `clearProps` + purge tableau `Props` + enfants SMC `NODE_AddStaticMeshComponent*` seulement (pas les vetements `*SM`) ; si visible → retire props orphelines (parent ≠ Mesh ou socket None/invalide). Fiche bug complète (symptôme, repro, cause, non-causes, acceptation). Compile `LifePlayEditor` PASS ; **PIE à valider** | — → créés | supprimer `etat/PROP/` et les deux sources |
|
||||||
| CHG-243 | 2026-10-08 | ERR-008 | `Source/LifePlay/LPMapMarkersGuardSubsystem.h/.cpp` (nouveaux) | garde runtime : si `MapMarkers.WidgetComponents` est vide, clear `Vector2Ds`, reconstitue depuis les `UWidgetComponent` existants ou ajoute un slot de focus invisible — évite le spam `Array_Get` / `Set Input Mode Game And UI` (FocusOnMapMarkers sans Length). Corrélé à l'approche PNJ (`updateAllPersonButtons` → `SpawnMapMarkers`). Live Coding PASS (nouvelle classe) | — → créés | supprimer ces deux fichiers |
|
| CHG-243 | 2026-10-08 | ERR-008 | `Source/LifePlay/LPMapMarkersGuardSubsystem.h/.cpp` (nouveaux) | garde runtime : si `MapMarkers.WidgetComponents` est vide, clear `Vector2Ds`, reconstitue depuis les `UWidgetComponent` existants ou ajoute un slot de focus invisible — évite le spam `Array_Get` / `Set Input Mode Game And UI` (FocusOnMapMarkers sans Length). Corrélé à l'approche PNJ (`updateAllPersonButtons` → `SpawnMapMarkers`). Live Coding PASS (nouvelle classe) | — → créés | supprimer ces deux fichiers |
|
||||||
| CHG-242 | 2026-10-08 | VQ-fix | `Source/LifePlay/LPSettingsSubsystem.cpp`, `LPSettingsSubsystem.h` | contraste UI Settings : `HardenSettingsControls` force un style Slate sombre/lisible sur toutes les cases et SpinBox (valeurs pâles sur fond flou) ; ticker Settings à 0 pour que `ApplyRows` gagne face au BP. Compile `LifePlayEditor` PASS | — → modifiés | retirer `HardenSettingsControls` / remettre ticker 0.5 |
|
| CHG-242 | 2026-10-08 | VQ-fix | `Source/LifePlay/LPSettingsSubsystem.cpp`, `LPSettingsSubsystem.h` | contraste UI Settings : `HardenSettingsControls` force un style Slate sombre/lisible sur toutes les cases et SpinBox (valeurs pâles sur fond flou) ; ticker Settings à 0 pour que `ApplyRows` gagne face au BP. Compile `LifePlayEditor` PASS | — → modifiés | retirer `HardenSettingsControls` / remettre ticker 0.5 |
|
||||||
| CHG-241 | 2026-10-08 | VQ-fix | `Source/LifePlay/LPSettingsSubsystem.cpp`, `Source/LifePlay/LPVideoQualitySubsystem.cpp` | correctifs post-VQ : (1) `CategoryOf` ignore les infobulles (faux positifs `screen`/`quality` → Sex Description, Map Marker, NewAnimationsOnly hors Video) + classements par nom UMG (`ShowFPS`, etc.) ; (2) `MainMenu.GI` rattaché sur l'écran titre (spinbox DoF / cases) ; (3) `RepairShowFPS` (style + enabled) ; (4) `ApplyRows` à chaque tick ouvert ; (5) `ApplyToScalability` n'écrit plus que les 4 `sg.*` en `SetByConsole` (fin du spam `SetByScalability ignored` sur ScreenPercentage/PoolSize/SSS). Live Coding PASS | — → modifiés | revenir sur les deux `.cpp` |
|
| CHG-241 | 2026-10-08 | VQ-fix | `Source/LifePlay/LPSettingsSubsystem.cpp`, `Source/LifePlay/LPVideoQualitySubsystem.cpp` | correctifs post-VQ : (1) `CategoryOf` ignore les infobulles (faux positifs `screen`/`quality` → Sex Description, Map Marker, NewAnimationsOnly hors Video) + classements par nom UMG (`ShowFPS`, etc.) ; (2) `MainMenu.GI` rattaché sur l'écran titre (spinbox DoF / cases) ; (3) `RepairShowFPS` (style + enabled) ; (4) `ApplyRows` à chaque tick ouvert ; (5) `ApplyToScalability` n'écrit plus que les 4 `sg.*` en `SetByConsole` (fin du spam `SetByScalability ignored` sur ScreenPercentage/PoolSize/SSS). Live Coding PASS | — → modifiés | revenir sur les deux `.cpp` |
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,4 @@
|
||||||
| ERR-040 | À la création du personnage (nouvelle partie), la femme proposée par défaut est chauve | retour utilisateur 2026-10-03 (test UI15) | à chaque nouvelle partie | faible (visuel) | — | **préexistant en V1.4** (confirmé par l'utilisateur) ; 4.27 et V1.3 non vérifiés. Piste non vérifiée : première entrée de `GI.HairstyleTable` = « NONE » pour une femme. **Ouvert** |
|
| ERR-040 | À la création du personnage (nouvelle partie), la femme proposée par défaut est chauve | retour utilisateur 2026-10-03 (test UI15) | à chaque nouvelle partie | faible (visuel) | — | **préexistant en V1.4** (confirmé par l'utilisateur) ; 4.27 et V1.3 non vérifiés. Piste non vérifiée : première entrée de `GI.HairstyleTable` = « NONE » pour une femme. **Ouvert** |
|
||||||
| ERR-041 | Module absent de `GI.ModEnableds` tenu pour actif par `LPHairstyleSubsystem` (coiffures d'un mod décoché présentes) | retour utilisateur 2026-10-03 (test H3a) | toujours | moyenne | H3a | défaut introduit en H1, présent en V1.4 ; **corrigé (CHG-102)**, validé en jeu le 2026-10-03 |
|
| ERR-041 | Module absent de `GI.ModEnableds` tenu pour actif par `LPHairstyleSubsystem` (coiffures d'un mod décoché présentes) | retour utilisateur 2026-10-03 (test H3a) | toujours | moyenne | H3a | défaut introduit en H1, présent en V1.4 ; **corrigé (CHG-102)**, validé en jeu le 2026-10-03 |
|
||||||
| ERR-042 | LifePlay Revival et le LifePlay d'origine partagent le dossier de sauvegardes `%LOCALAPPDATA%\LifePlay\Saved\SaveGames` (emplacements de partie et `GameSettings.sav` communs) | question d'un joueur sur Discord, 2026-10-03 (V1.5) | permanent | moyenne (risque d'écraser une partie du jeu d'origine ; choix des mods réécrit) | SAV1 | **ouvert, à faire** (lot SAV1, `04_Cahier_Des_Charges.md` §6) ; consigne aux joueurs d'ici là : copier le dossier `SaveGames` avant le premier lancement |
|
| ERR-042 | LifePlay Revival et le LifePlay d'origine partagent le dossier de sauvegardes `%LOCALAPPDATA%\LifePlay\Saved\SaveGames` (emplacements de partie et `GameSettings.sav` communs) | question d'un joueur sur Discord, 2026-10-03 (V1.5) | permanent | moyenne (risque d'écraser une partie du jeu d'origine ; choix des mods réécrit) | SAV1 | **ouvert, à faire** (lot SAV1, `04_Cahier_Des_Charges.md` §6) ; consigne aux joueurs d'ici là : copier le dossier `SaveGames` avant le premier lancement |
|
||||||
|
| ERR-043 | Prop d'animation (verre `drinkglass_prop`, etc.) reste visible alors que le corps du PNJ a disparu (« seul le verre reste apparent ») ; parfois verre flottant devant un PNJ encore visible | retour utilisateur 2026-10-08 (captures pub) ; PIE `LifePlay.log` ; fiche `etat/PROP/PROP-BUG.md` | 21 083 `No SkeletalMesh` CharacterMesh0 au `DestroyDummies` / session | moyenne (visuel) | PROP | **corrigé code (CHG-244)** : `destroyDummy` sans `clearProps` → `LPPersonPropGuardSubsystem` ; compile PASS ; **à valider en PIE** — détail cause / non-causes / acceptation : `Docs/Migration/etat/PROP/PROP-BUG.md` |
|
||||||
|
|
|
||||||
119
Docs/Migration/etat/PROP/PROP-BUG.md
Normal file
119
Docs/Migration/etat/PROP/PROP-BUG.md
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# ERR-043 — Seul le verre reste apparent
|
||||||
|
|
||||||
|
**ID registre :** ERR-043
|
||||||
|
**Lot :** PROP
|
||||||
|
**Date signalement :** 2026-10-08
|
||||||
|
**Gravité :** moyenne (visuel)
|
||||||
|
**Statut :** corrigé en code (CHG-244) — **à valider en PIE**
|
||||||
|
**Fiches liées :** [PROP-CART](PROP-CART.md) (dumps / graphes), CHG-243 (ERR-008 MapMarkers, corrélé), ERR-011 (bruit `No SkeletalMesh`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Symptôme
|
||||||
|
|
||||||
|
Dans un lieu intérieur (ex. pub `pub___vin01`), un PNJ en animation quotidienne avec accessoire (`drinkglass`, parfois suivi d’une autre pose) laisse **le verre (prop StaticMesh) visible** alors que :
|
||||||
|
|
||||||
|
1. le **corps** du personnage a disparu (mesh skeletal vide / dummy recyclé), ou
|
||||||
|
2. le personnage est encore là mais un **verre flotte** à hauteur de torse / tête, détaché de la main (souvent pendant qu’il tient déjà un autre prop, ex. carnet `write`).
|
||||||
|
|
||||||
|
Phrase utilisateur : *« seul le verre reste apparent »*.
|
||||||
|
|
||||||
|
| Capture | Contenu |
|
||||||
|
|---|---|
|
||||||
|
| Verre orphelin + barmaid | Verre à pied opaque blanc flottant devant le buste ; PNJ visible avec bloc-notes |
|
||||||
|
| Cliente qui boit | État nominal : corps + verre à la bouche (référence du comportement attendu) |
|
||||||
|
|
||||||
|
Chemins Cursor (session) :
|
||||||
|
`…/assets/…image-d4b12d9d-651a-4448-b327-8c6923b9a1e0.png`,
|
||||||
|
`…/assets/…image-c2441b41-47f4-4c3a-9cad-0120376c2833.png`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Reproduction
|
||||||
|
|
||||||
|
1. Lancer PIE (carte `CentralLondon`), entrer dans un **pub**.
|
||||||
|
2. Laisser les PNJ play daily (`drinkglass` / occupations meubles).
|
||||||
|
3. S’approcher d’un PNJ **ou** changer de lieu (déclenche `DestroyDummies`).
|
||||||
|
4. Observer : spam log `GetSocketInfoByName(…): No SkeletalMesh for Component(CharacterMesh0)` sur `Person_C_*`, messages `test0204 DestroyDummies` / `test0304 DESTROY…`, et éventuellement verre flottant.
|
||||||
|
|
||||||
|
**Preuve session :** `Saved/Logs/LifePlay.log` (PIE 2026-10-07→08) — premier warning mesh vide ~l.5331 (`22.39.21`), **~21 083** occurrences dans la session, acteurs `Person_C_0`…`Person_C_5`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Cause racine
|
||||||
|
|
||||||
|
### 3.1 Cycle de vie des props
|
||||||
|
|
||||||
|
| Étape | Blueprint | Comportement |
|
||||||
|
|---|---|---|
|
||||||
|
| Apparition | `Person::spawnProp` | `AddComponent` (`NODE_AddStaticMeshComponent-0`) → `AttachToComponent(Mesh, Socket)` → `SetStaticMesh` → ajout au tableau **`Props`** |
|
||||||
|
| Nettoyage prévu | `Person::clearProps` | détruit les SMC de `Props` + enfants SMC de `Mesh`, vide le tableau |
|
||||||
|
| Recycle dummy | `GI::destroyDummy` | `SetActorHiddenInGame(true)` → `ResetVars` → transform origine → `unOccupyFnt` — **n’appelle pas `clearProps`** |
|
||||||
|
|
||||||
|
Le verre n’est **pas** un acteur séparé : c’est un composant du `Person`. Quand `CharacterMesh0` n’a plus de `SkeletalMesh`, le corps disparaît ; les SMC de props peuvent rester le seul rendu.
|
||||||
|
|
||||||
|
### 3.2 Chaîne observée
|
||||||
|
|
||||||
|
```
|
||||||
|
DestroyDummies
|
||||||
|
→ destroyDummy(Person) // sans clearProps
|
||||||
|
→ CharacterMesh0.SkeletalMesh = None
|
||||||
|
→ warnings GetSocketInfoByName(head|pelvis|…)
|
||||||
|
→ verre (Props / SMC attaché) encore visible
|
||||||
|
```
|
||||||
|
|
||||||
|
Dumps : `etat/PROP/PROP_LPDumpFunction_20261008-010449` (`spawnProp`), `-010554` (`clearProps`), `-010647` (`destroyDummy`). Détail dans PROP-CART §3.
|
||||||
|
|
||||||
|
### 3.3 Variante « PNJ encore visible + verre flottant »
|
||||||
|
|
||||||
|
Après un glitch mesh / changement d’anim (`drinkglass` → `write`) sans clear : le prop peut rester parenté au `Mesh` avec **socket None ou invalide** → position relative à l’origine du mesh (torse), d’où l’aspect « flottant ».
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Non-causes (à ne pas re-investiguer pour ce symptôme)
|
||||||
|
|
||||||
|
| Piste | Verdict |
|
||||||
|
|---|---|
|
||||||
|
| ERR-008 `MapMarkers.WidgetComponents[0]` | **Corrélée** à l’approche (`updateAllPersonButtons` → `SpawnMapMarkers`), **pas** la cause du verre. Atténuée CHG-243. |
|
||||||
|
| `Person::updateButtonSize` | Masque seulement **`ButtonSM`** (bouton de nom), pas le corps ni les props. |
|
||||||
|
| Réglages VQ / `LPSettingsSubsystem` | Aucune lecture de `Props` / `spawnProp` / mesh Person. |
|
||||||
|
| ERR-011 `No SkeletalMesh` « normal » | Bruit accepté en 4.27/5.5 ; ici le **pic** au DESTROY + props non clear = bug distinct. |
|
||||||
|
| Verres blancs opaques sur les étagères | Matériau / translucency — hors ERR-043. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Correctif
|
||||||
|
|
||||||
|
| Élément | Détail |
|
||||||
|
|---|---|
|
||||||
|
| CHG | **CHG-244** |
|
||||||
|
| Code | `Source/LifePlay/LPPersonPropGuardSubsystem.h/.cpp` |
|
||||||
|
| Mécanisme | Sous-système d’instance de jeu, tick ~0,2 s |
|
||||||
|
| Si Person caché **ou** Mesh sans asset skeletal | `ProcessEvent(clearProps)` + purge C++ du tableau `Props` + destruction des enfants SMC nommés `NODE_AddStaticMeshComponent*` (pas les vêtements `*SM`) |
|
||||||
|
| Si Person visible | Retrait des entrées `Props` orphelines (mauvais parent, socket None / inexistant) |
|
||||||
|
| BP modifiés | Aucun |
|
||||||
|
| Compile | `LifePlayEditor` Win64 Development **PASS** (2026-10-08) |
|
||||||
|
|
||||||
|
Alternative BP (non retenue pour ce lot) : appeler `clearProps` au début de `GI::destroyDummy` (et idéalement avant chaque nouveau `spawnProp` d’anim).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Critères d’acceptation
|
||||||
|
|
||||||
|
- [ ] Relancer l’éditeur avec la DLL CHG-244.
|
||||||
|
- [ ] Pub : PNJ qui boivent / changent d’occupation ; changer de lieu (DestroyDummies).
|
||||||
|
- [ ] Aucun verre flottant orphelin après DESTROY / disparition temporaire du corps.
|
||||||
|
- [ ] Log : au moins une ligne `PersonPropGuard : clearProps` ou `props orphelines` lors d’un recycle.
|
||||||
|
- [ ] Pas de régression vêtements (les SMC `*SM` ne doivent pas être détruits à tort).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Assets / références
|
||||||
|
|
||||||
|
| Objet | Chemin |
|
||||||
|
|---|---|
|
||||||
|
| Prop verre | `Content/Heavy/New3/Anims/Daily/Props/drinkglass_prop.uasset` |
|
||||||
|
| Anims | `…/Anims/Daily/drinkglass_F.uasset`, `drinkglass_M.uasset` |
|
||||||
|
| BP | `Content/BPs/Person.uasset` (`spawnProp`, `clearProps`), `Content/BPs/GI.uasset` (`destroyDummy`) |
|
||||||
|
| Registre | `Docs/Migration/Registre_Erreurs.md` → ERR-043 |
|
||||||
|
| Journal | `Docs/Migration/Journal_Modifications.md` → CHG-244 |
|
||||||
158
Docs/Migration/etat/PROP/PROP-CART.md
Normal file
158
Docs/Migration/etat/PROP/PROP-CART.md
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# PROP-CART — Diagnostic technique (dumps / graphes)
|
||||||
|
|
||||||
|
**Fiche :** PROP-T1
|
||||||
|
**Date :** 2026-10-08
|
||||||
|
**Workspace :** `d:\Game\LifePlay-master 5.5`
|
||||||
|
**Statut :** Correctif C++ compilé (`LPPersonPropGuardSubsystem`) — **PIE à valider**
|
||||||
|
**Fiche bug (symptôme, repro, cause, acceptation) :** [PROP-BUG.md](PROP-BUG.md) — **ERR-043**
|
||||||
|
|
||||||
|
Complément dumps pour le bug « seul le verre reste apparent ».
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Contexte PIE (preuves journal)
|
||||||
|
|
||||||
|
Source : `Saved/Logs/LifePlay.log` (session PIE 2026-10-07 → 08).
|
||||||
|
|
||||||
|
| Observation | Détail |
|
||||||
|
|---|---|
|
||||||
|
| Premier `No SkeletalMesh` CharacterMesh0 | Ligne ~5331, `22.39.21` — **21 083** warnings dans la session |
|
||||||
|
| Déclencheur immédiat | `GI` : `test0204 DestroyDummies` puis `test0304 DESTROY…` (Jared / Jack / Stephanie / Isaac) |
|
||||||
|
| Acteurs concernés | `Person_C_0` … `Person_C_5` (pool dummies) |
|
||||||
|
| Corrélation approche | Juste avant : `customer_approaches.lpscene`, `pub___vin01`, spam ERR-008 `WidgetComponents` length 0 (atténué CHG-243) |
|
||||||
|
| ERR-011 | `GetSocketInfoByName … No SkeletalMesh` déjà **accepté** comme bruit (existe en 4.27) — ici le volume explose au `DestroyDummies` |
|
||||||
|
|
||||||
|
Chaîne typique :
|
||||||
|
|
||||||
|
```
|
||||||
|
DestroyDummies → destroyDummy(Person)
|
||||||
|
→ SetActorHiddenInGame(true)
|
||||||
|
→ ResetVars() ← appelé ; graphe Person introuvable au dump (voir §3)
|
||||||
|
→ transform origine
|
||||||
|
→ unOccupyFnt
|
||||||
|
→ (PAS de clearProps)
|
||||||
|
→ CharacterMesh0 sans SkeletalMesh → sockets head/pelvis/… en échec
|
||||||
|
→ StaticMesh props (verre) encore présents / visibles
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Inventaire des dumps `LPDumpFunction` (lot PROP)
|
||||||
|
|
||||||
|
Commande type :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\RunCommandlet.ps1 -Lot PROP -Commandlet LPDumpFunction `
|
||||||
|
-Args '-BP=Person -Fn=spawnProp -Pins' -ProjectRoot 'd:\Game\LifePlay-master 5.5'
|
||||||
|
```
|
||||||
|
|
||||||
|
Moteur : `D:\Game\Launcher\Engine\UE_5.5\Engine`.
|
||||||
|
Journaux bruts : `Saved/Logs/portage/PROP_LPDumpFunction_<stamp>.log`.
|
||||||
|
Résumés : `Docs/Migration/etat/PROP/PROP_LPDumpFunction_<stamp>.txt`.
|
||||||
|
|
||||||
|
| Horodatage | Cible | Exit | Résultat |
|
||||||
|
|---|---|---:|---|
|
||||||
|
| `20261008-010449` | `Person` / `spawnProp` | 0 | 9 nœuds — voir §3.1 |
|
||||||
|
| `20261008-010554` | `Person` / `clearProps` | 0 | 17 nœuds — voir §3.2 |
|
||||||
|
| `20261008-010647` | `GI` / `destroyDummy` | 0 | 39 nœuds — voir §3.3 |
|
||||||
|
| `20261008-010803` | `Person` / `ResetVars` | **1** | Fonction **introuvable** dans `Person` (appelée pourtant par `destroyDummy`) |
|
||||||
|
| `20261008-010919` | `Person` (liste) | 0 | **134** graphes — `spawnProp`, `clearProps`, `ClearSMCs` présents ; **aucun** `ResetVars` / `PlayDaily` |
|
||||||
|
| `20261008-011027` | `Person` / `ClearSMCs` | 0 | 9 nœuds — voir §3.4 |
|
||||||
|
| `20261008-011115` | `Person` / `PlayDaily` | **1** | Fonction introuvable (appel GI EventGraph vers `Person_C::PlayDaily` ailleurs) |
|
||||||
|
|
||||||
|
Dump antérieur utile (hors lot PROP) :
|
||||||
|
|
||||||
|
| Fichier | Usage |
|
||||||
|
|---|---|
|
||||||
|
| `Docs/Migration/etat/DOC-MOD_LPDumpFunction_20261007-083554.txt` | `GI` EventGraph : appels `destroyDummy`, un appel `Person_C::clearProps` (ligne ~26340, autre chemin — pas dans `destroyDummy`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Synthèse des graphes
|
||||||
|
|
||||||
|
### 3.1 `Person::spawnProp` (9 nœuds)
|
||||||
|
|
||||||
|
1. `AddComponent` template `NODE_AddStaticMeshComponent-0`
|
||||||
|
2. `K2_AttachToComponent(Parent = Person.Mesh, SocketName)` — KeepRelative
|
||||||
|
3. `SetStaticMesh(SM)`
|
||||||
|
4. `Array_Add` → variable **`Props`**
|
||||||
|
5. `SetCollisionEnabled(NoCollision)`
|
||||||
|
|
||||||
|
Le verre (`drinkglass_prop`, etc.) est donc un **composant** du Person, attaché au skeletal mesh, référencé dans `Props` — pas un acteur séparé.
|
||||||
|
|
||||||
|
### 3.2 `Person::clearProps` (17 nœuds)
|
||||||
|
|
||||||
|
- Boucle sur `Props` : `SetVisibility(false)` + `K2_DestroyComponent` + `SetStaticMesh(None)`
|
||||||
|
- `Array_Clear(Props)`
|
||||||
|
- Puis `Mesh->GetChildrenComponents` → cast `StaticMeshComponent` → même nettoyage
|
||||||
|
|
||||||
|
**Cette fonction nettoie correctement les verres.** Elle n’est **pas** appelée par `destroyDummy`.
|
||||||
|
|
||||||
|
### 3.3 `GI::destroyDummy` (39 nœuds) — trou critique
|
||||||
|
|
||||||
|
Ordre utile :
|
||||||
|
|
||||||
|
| Étape | Appel | Effet |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `Array_RemoveItem(DummyPersonsInUse)` | retire du pool « en service » |
|
||||||
|
| 2 | garde `isPlayer` | — |
|
||||||
|
| 3 | **`SetActorHiddenInGame(true)`** | cache l’acteur |
|
||||||
|
| 4 | **`Person::ResetVars()`** | dump Person : **fonction absente de la liste** |
|
||||||
|
| 5 | `SetActorTransform(0,0,0)` | téléporte à l’origine |
|
||||||
|
| 6 | `removeAlreadyGot` / `PersonsUsedThisScene` | cleanup mappings |
|
||||||
|
| 7 | `unOccupyFnt` | libère le meuble |
|
||||||
|
| 8 | `PrintString("test0304 DESTROY…")` | log observé en PIE |
|
||||||
|
| — | **`clearProps`** | **ABSENT** |
|
||||||
|
|
||||||
|
Conséquence : quand le skeletal mesh est vidé (ResetVars / recyclage) ou l’acteur caché de façon incomplète côté composants dynamiques, les SMC de `Props` (verre) restent le seul rendu apparent.
|
||||||
|
|
||||||
|
### 3.4 `Person::ClearSMCs` (9 nœuds)
|
||||||
|
|
||||||
|
`GetChildrenComponents(Mesh, bIncludeAllDescendants=true)` → détruit les enfants dont le **nom se termine par `SM`**.
|
||||||
|
Complément éventuel à `clearProps`, **pas** branché dans `destroyDummy` non plus.
|
||||||
|
|
||||||
|
### 3.5 Liste `Person` (134 graphes) — extraits props / pose
|
||||||
|
|
||||||
|
Présents : `spawnProp`, `clearProps`, `ClearSMCs`, `UpdateDailyPose`, `PlayIdle`, `HideSlot`, `unOccupyFnt`, …
|
||||||
|
Absents au dump : `ResetVars`, `PlayDaily` (alors que GI les référence) — piste dette de nom / parent / graphe non listé ; à ne pas confondre avec un échec de compilation (le jeu tourne et imprime `DESTROY`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Hypothèses retenues
|
||||||
|
|
||||||
|
| # | Hypothèse | Statut |
|
||||||
|
|---|---|---|
|
||||||
|
| H1 | `destroyDummy` sans `clearProps` → verre orphelin au recycle dummy | **retenue** (dump + log) |
|
||||||
|
| H2 | Mesh `CharacterMesh0` nul → corps invisible, prop SMC encore là | **retenue** (21k warnings au DESTROY) |
|
||||||
|
| H3 | ERR-008 MapMarkers cause la disparition du corps | **écartée** comme cause directe (corrélée à l’approche ; garde CHG-243). Confirmation [Investigate MapMarkers approach](8d7f20d3-0709-4655-bea7-b82196a94500) : `FocusOnMapMarkers` lit `WidgetComponents[0]` sans Length ; `HideAllMarkers` ne clear pas `Vector2Ds` (déjà compensé par la garde). Même chemin approche → `updateAllPersonButtons` → `SpawnMapMarkers`, mais `Person.updateButtonSize` ne masque que **`ButtonSM`** (bouton nom), pas le skeletal mesh ni les props `spawnProp`. |
|
||||||
|
| H4 | Verre flottant PNJ **encore visible** (barmaid + carnet) = prop d’anim précédente (`drinkglass`) non clear avant `write` / socket perdu après glitch mesh | **plausible** — garde orphelines (socket invalide / parent ≠ Mesh) |
|
||||||
|
| H5 | Matériaux verre opaques blancs (étagères) | **hors périmètre** PROP (qualité / translucency) |
|
||||||
|
| H6 | « Disparition » perçue à l’approche = seul `ButtonSM` qui se cache | **partielle** — explique un clignotement UI, pas le verre orphelin ni `No SkeletalMesh` au `DestroyDummies` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Correctif (CHG-244)
|
||||||
|
|
||||||
|
`Source/LifePlay/LPPersonPropGuardSubsystem.h/.cpp` — compile `LifePlayEditor` Win64 Development **PASS** (2026-10-08) :
|
||||||
|
|
||||||
|
- Tick ~0,2 s, acteurs `Person_C` du GI courant
|
||||||
|
- Si `IsHidden()` **ou** `Mesh` sans `SkeletalMeshAsset` → `ProcessEvent(clearProps)` + purge C++ du tableau `Props` + enfants SMC dont le nom contient `NODE_AddStaticMeshComponent` uniquement (évite de casser les SMC vêtements `*SM`)
|
||||||
|
- Sinon : retire les props orphelines (parent ≠ Mesh, ou socket None / inexistant — cas verre collé à l’origine du mesh)
|
||||||
|
|
||||||
|
Aucun Blueprint modifié.
|
||||||
|
|
||||||
|
**Validation PIE :** relancer l’éditeur (nouvelle DLL), entrer dans un pub, forcer un recycle dummies / changer de lieu ; chercher `PersonPropGuard :` dans le log ; plus de verre flottant après DESTROY.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Index des artefacts
|
||||||
|
|
||||||
|
| Artefact | Chemin |
|
||||||
|
|---|---|
|
||||||
|
| Fiche bug ERR-043 | `Docs/Migration/etat/PROP/PROP-BUG.md` |
|
||||||
|
| Résumés dumps | `Docs/Migration/etat/PROP/PROP_LPDumpFunction_*.txt` |
|
||||||
|
| Logs commandlets | `Saved/Logs/portage/PROP_LPDumpFunction_*.log` |
|
||||||
|
| PIE session | `Saved/Logs/LifePlay.log` |
|
||||||
|
| Captures utilisateur | workspace Cursor `assets/…image-d4b12d9d…png`, `…image-c2441b41…png` |
|
||||||
|
| Garde MapMarkers | CHG-243 / `LPMapMarkersGuardSubsystem` |
|
||||||
|
| Assets props | `Content/Heavy/New3/Anims/Daily/Props/drinkglass_prop.uasset`, anims `drinkglass_F/M` |
|
||||||
223
Source/LifePlay/LPPersonPropGuardSubsystem.cpp
Normal file
223
Source/LifePlay/LPPersonPropGuardSubsystem.cpp
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
// Voir LPPersonPropGuardSubsystem.h.
|
||||||
|
|
||||||
|
#include "LPPersonPropGuardSubsystem.h"
|
||||||
|
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "EngineUtils.h"
|
||||||
|
#include "LifePlay.h"
|
||||||
|
#include "UObject/UnrealType.h"
|
||||||
|
|
||||||
|
namespace LPPersonPropGuard
|
||||||
|
{
|
||||||
|
/** Props d'anim (spawnProp) : template NODE_AddStaticMeshComponent-0 — pas les SMC vetements *SM. */
|
||||||
|
bool IsAnimPropComponent(const UActorComponent* Comp)
|
||||||
|
{
|
||||||
|
if (!Comp)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const FString Name = Comp->GetName();
|
||||||
|
return Name.Contains(TEXT("NODE_AddStaticMeshComponent"), ESearchCase::CaseSensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyProp(UStaticMeshComponent* SMC)
|
||||||
|
{
|
||||||
|
if (!IsValid(SMC))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SMC->SetVisibility(false, false);
|
||||||
|
SMC->SetHiddenInGame(true, false);
|
||||||
|
SMC->SetStaticMesh(nullptr);
|
||||||
|
SMC->DestroyComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
FArrayProperty* PropsArray(AActor* Person)
|
||||||
|
{
|
||||||
|
return Person ? CastField<FArrayProperty>(Person->GetClass()->FindPropertyByName(TEXT("Props"))) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearPropsArrayEntries(AActor* Person)
|
||||||
|
{
|
||||||
|
FArrayProperty* Arr = PropsArray(Person);
|
||||||
|
if (!Arr || !CastField<FObjectPropertyBase>(Arr->Inner))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FScriptArrayHelper Helper(Arr, Arr->ContainerPtrToValuePtr<void>(Person));
|
||||||
|
for (int32 Index = Helper.Num() - 1; Index >= 0; --Index)
|
||||||
|
{
|
||||||
|
UObject* Entry = *reinterpret_cast<UObject**>(Helper.GetRawPtr(Index));
|
||||||
|
if (UStaticMeshComponent* SMC = Cast<UStaticMeshComponent>(Entry))
|
||||||
|
{
|
||||||
|
DestroyProp(SMC);
|
||||||
|
}
|
||||||
|
Helper.RemoveValues(Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULPPersonPropGuardSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
TickHandle = FTSTicker::GetCoreTicker().AddTicker(
|
||||||
|
FTickerDelegate::CreateUObject(this, &ULPPersonPropGuardSubsystem::Tick), 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULPPersonPropGuardSubsystem::Deinitialize()
|
||||||
|
{
|
||||||
|
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
||||||
|
Super::Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ULPPersonPropGuardSubsystem::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
(void)DeltaTime;
|
||||||
|
UGameInstance* GI = GetGameInstance();
|
||||||
|
UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||||||
|
if (!World || !World->IsGameWorld())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TActorIterator<AActor> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
AActor* Candidate = *It;
|
||||||
|
if (!IsValid(Candidate) || Candidate->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Candidate->GetClass()->GetName() != TEXT("Person_C"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Candidate->GetGameInstance() != GI)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GuardPerson(Candidate);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
USkeletalMeshComponent* ULPPersonPropGuardSubsystem::GetPersonMesh(AActor* Person)
|
||||||
|
{
|
||||||
|
if (!Person)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (const FObjectPropertyBase* Prop = CastField<FObjectPropertyBase>(Person->GetClass()->FindPropertyByName(TEXT("Mesh"))))
|
||||||
|
{
|
||||||
|
if (USkeletalMeshComponent* FromProp = Cast<USkeletalMeshComponent>(Prop->GetObjectPropertyValue_InContainer(Person)))
|
||||||
|
{
|
||||||
|
return FromProp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TInlineComponentArray<USkeletalMeshComponent*> Meshes(Person);
|
||||||
|
return Meshes.Num() > 0 ? Meshes[0] : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULPPersonPropGuardSubsystem::CallClearProps(AActor* Person)
|
||||||
|
{
|
||||||
|
if (!Person)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (UFunction* Fn = Person->FindFunction(TEXT("clearProps")))
|
||||||
|
{
|
||||||
|
Person->ProcessEvent(Fn, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULPPersonPropGuardSubsystem::ForceHidePropComponents(USkeletalMeshComponent* Mesh)
|
||||||
|
{
|
||||||
|
if (!IsValid(Mesh))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TArray<USceneComponent*> Children;
|
||||||
|
Mesh->GetChildrenComponents(true, Children);
|
||||||
|
for (USceneComponent* Child : Children)
|
||||||
|
{
|
||||||
|
UStaticMeshComponent* SMC = Cast<UStaticMeshComponent>(Child);
|
||||||
|
if (!SMC || !LPPersonPropGuard::IsAnimPropComponent(SMC))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LPPersonPropGuard::DestroyProp(SMC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULPPersonPropGuardSubsystem::ClearOrphanProps(AActor* Person, USkeletalMeshComponent* Mesh) // instance : log bLoggedOnce
|
||||||
|
{
|
||||||
|
FArrayProperty* Arr = LPPersonPropGuard::PropsArray(Person);
|
||||||
|
if (!Arr || !CastField<FObjectPropertyBase>(Arr->Inner))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FScriptArrayHelper Helper(Arr, Arr->ContainerPtrToValuePtr<void>(Person));
|
||||||
|
bool bRemoved = false;
|
||||||
|
for (int32 Index = Helper.Num() - 1; Index >= 0; --Index)
|
||||||
|
{
|
||||||
|
UObject* Entry = *reinterpret_cast<UObject**>(Helper.GetRawPtr(Index));
|
||||||
|
UStaticMeshComponent* SMC = Cast<UStaticMeshComponent>(Entry);
|
||||||
|
if (!IsValid(SMC))
|
||||||
|
{
|
||||||
|
Helper.RemoveValues(Index);
|
||||||
|
bRemoved = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawnProp attache toujours avec un SocketName ; socket None ou invalide = verre colle a l'origine du mesh.
|
||||||
|
const FName Socket = SMC->GetAttachSocketName();
|
||||||
|
const bool bBadParent = SMC->GetAttachParent() != Mesh;
|
||||||
|
const bool bBadSocket = Socket.IsNone() || (Mesh && !Mesh->DoesSocketExist(Socket));
|
||||||
|
if (!bBadParent && !bBadSocket)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPPersonPropGuard::DestroyProp(SMC);
|
||||||
|
Helper.RemoveValues(Index);
|
||||||
|
bRemoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bRemoved && !bLoggedOnce)
|
||||||
|
{
|
||||||
|
bLoggedOnce = true;
|
||||||
|
UE_LOG(LogLifePlay, Display, TEXT("PersonPropGuard : props orphelines retirees (verre/animation sans socket)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ULPPersonPropGuardSubsystem::GuardPerson(AActor* Person)
|
||||||
|
{
|
||||||
|
USkeletalMeshComponent* Mesh = GetPersonMesh(Person);
|
||||||
|
const bool bBodyGone = !IsValid(Mesh) || Mesh->GetSkeletalMeshAsset() == nullptr;
|
||||||
|
const bool bHidden = Person->IsHidden();
|
||||||
|
|
||||||
|
if (bHidden || bBodyGone)
|
||||||
|
{
|
||||||
|
if (!ClearedWhileInvalid.Contains(Person))
|
||||||
|
{
|
||||||
|
CallClearProps(Person);
|
||||||
|
// Ceinture : clearProps BP peut etre absent / Props desync ; ne touche pas aux SMC vetements.
|
||||||
|
LPPersonPropGuard::ClearPropsArrayEntries(Person);
|
||||||
|
ForceHidePropComponents(Mesh);
|
||||||
|
ClearedWhileInvalid.Add(Person);
|
||||||
|
if (!bLoggedOnce)
|
||||||
|
{
|
||||||
|
bLoggedOnce = true;
|
||||||
|
UE_LOG(LogLifePlay, Display,
|
||||||
|
TEXT("PersonPropGuard : clearProps (Person cache=%d meshVide=%d) — corrige verre orphelin"),
|
||||||
|
bHidden ? 1 : 0, bBodyGone ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearedWhileInvalid.Remove(Person);
|
||||||
|
ClearOrphanProps(Person, Mesh);
|
||||||
|
}
|
||||||
38
Source/LifePlay/LPPersonPropGuardSubsystem.h
Normal file
38
Source/LifePlay/LPPersonPropGuardSubsystem.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Garde props d'animation (verre, carnet, etc.) : GI::destroyDummy masque le Person et
|
||||||
|
// ResetVars / mesh vide sans appeler Person::clearProps. Les StaticMeshComponent ajoutes
|
||||||
|
// par spawnProp restent alors visibles ("seul le verre reste apparent").
|
||||||
|
// Ce sous-systeme appelle clearProps (et force le masquage des enfants SMC) quand le corps
|
||||||
|
// est cache ou sans SkeletalMesh, et detruit les props orphelines (socket invalide).
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Containers/Ticker.h"
|
||||||
|
#include "Subsystems/GameInstanceSubsystem.h"
|
||||||
|
#include "LPPersonPropGuardSubsystem.generated.h"
|
||||||
|
|
||||||
|
class AActor;
|
||||||
|
class USkeletalMeshComponent;
|
||||||
|
class UStaticMeshComponent;
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class LIFEPLAY_API ULPPersonPropGuardSubsystem : public UGameInstanceSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
virtual void Deinitialize() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool Tick(float DeltaTime);
|
||||||
|
void GuardPerson(AActor* Person);
|
||||||
|
void ClearOrphanProps(AActor* Person, USkeletalMeshComponent* Mesh);
|
||||||
|
static USkeletalMeshComponent* GetPersonMesh(AActor* Person);
|
||||||
|
static void CallClearProps(AActor* Person);
|
||||||
|
static void ForceHidePropComponents(USkeletalMeshComponent* Mesh);
|
||||||
|
|
||||||
|
FTSTicker::FDelegateHandle TickHandle;
|
||||||
|
TSet<TWeakObjectPtr<AActor>> ClearedWhileInvalid;
|
||||||
|
bool bLoggedOnce = false;
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue