83 lines
13 KiB
Markdown
83 lines
13 KiB
Markdown
Fiche STG-SF4v — Niveau N1 — Modèle prévu lp-validator | utilisé Sonnet 5.5 (claude-sonnet-5-5) — Agent lp-validator — Skills lp-validate, sk-protocol, sk-recount, ue-cpp-build
|
||
# STG-SF4v-VAL : validation statique du correctif S-F4 (plancher Room.Box en extérieur) (2026-10-13)
|
||
|
||
Verdict global : **PASS-STATIC** (lecture seule, aucune compilation, aucun lancement ; le build reste à faire dans COMMUN-2).
|
||
|
||
## 1. Fiches amont relues
|
||
- `Docs/Migration/etat/ST/STG/STG-M1-ANALYSE.md` § 2 et § 4 (lignes 13-14, 27-34, 53-54) : motif et attendu.
|
||
- `Docs/Migration/etat/post-v1/PawnDyn/PawnDyn_LPDumpComponents_20261004-223842.txt` l. 116-130 : `Box` (BoxComponent) `parent=StaticMeshComponent0`, `BoxExtent = (400, 400, 64)`, `AreaClass = NavArea_Obstacle`, `ObjectType=ECC_Vehicle` ; racine `StaticMeshComponent0` `RelativeScale3D = (8, 8, 0.2)`, `Mobility = Static`. La `Box` n'imprime ni `RelativeLocation` ni `RelativeScale3D` ni `Mobility` (valeurs par défaut du dump : estimé 0 / 1 / non Static ; la mobilité réelle n'est pas établie, d'où le traitement du cas Static par le code).
|
||
- `Docs/Migration/etat/ST/STG/STG-TACHES.md` l. 25 (ligne STG-M1a, correctif S-F4 proposé).
|
||
- Aucune fiche IMPL, ni fichier `STG-SF4-IMPL.md`, ni fiche `STG-SF4*.md` dans le dossier STG (Glob `STG-*`) : seul le patch `STG-SF4.patch` tient lieu de livraison amont. Aucun « point à confirmer » d'IMPL à reprendre (voir § 2).
|
||
- Instantané d'origine `D:\Game\LifePlay_Backup_5.5\STG\manifest.csv` : .cpp MD5 `1340EE4F64FC10E84F829AEBCEE44161`, .h `66F21A879542DA505C744AE7C424A762`.
|
||
|
||
## 2. Points à confirmer des IMPL
|
||
sans objet : aucune IMPL amont pour S-F4 (Glob `STG-*` du dossier STG : pas de livrable IMPL pour SF4). Les points propres à cette VAL sont aux § 3 et § 6.
|
||
|
||
## 3. Critères rejoués (commande → résultat)
|
||
|
||
### a. Diff, périmètre
|
||
- `git status --short -- Source` → 4 fichiers modifiés : `LPDebugSubsystem.cpp`, `LPDebugSubsystem.h`, `LPOutdoorRoomSubsystem.cpp`, `LPOutdoorRoomSubsystem.h`.
|
||
- `LPDebugSubsystem.*` ne relève pas de S-F4 : ils sont déjà dans le manifeste d'instantané STG (date 13:41:41, avant S-F4 à 17:11) ; leur diff est hors périmètre de cette fiche et non relu ligne à ligne (`git diff --stat` : cpp +249, h 2+/1-). **Verdict a : CONFORME pour S-F4** (les deux fichiers attendus portent le correctif ; deux fichiers voisins modifiés relèvent d'un autre travail du lot STG, à rattacher à son ticket par l'orchestrateur).
|
||
- `diff <(git diff -- LPOutdoorRoomSubsystem.cpp LPOutdoorRoomSubsystem.h) STG-SF4.patch` → `PATCH_IDENTIQUE` : le patch livré est exactement le diff de l'arbre de travail.
|
||
- `git diff --numstat` → `.cpp 77 ajouts / 2 suppressions`, `.h 15 / 0`.
|
||
- MD5 (`md5sum`) : .cpp `dd8646f808aaab150e5ea1a330f870ea`, .h `ca18ac44c1e682490b689a81dfec0bd2` = valeurs de la fiche.
|
||
|
||
### b. API UE 5.8 (racine `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime`)
|
||
| API | Source | Verdict |
|
||
|---|---|---|
|
||
| `UBoxComponent::SetBoxExtent(FVector, bool=true)` | `Engine/Classes/Components/BoxComponent.h:39` ; corps `Engine/Private/Components/BoxComponent.cpp:28-40` (met à jour bornes, rendu, corps, forme physique) | OK |
|
||
| `GetUnscaledBoxExtent() const` → `FVector` | `BoxComponent.h:47`, inline l. 80-83 (renvoie `BoxExtent`) | OK |
|
||
| `GetComponentScale()` (monde) | `Engine/Classes/Components/SceneComponent.h:1084-1087` | OK |
|
||
| `GetRelativeScale3D()`, `GetRelativeLocation()` | `SceneComponent.h:1521`, `:1435` (const, par valeur) | OK |
|
||
| `SetRelativeLocation(FVector, bool bSweep=false, …)` | `SceneComponent.h:434` | OK |
|
||
| `SetMobility(EComponentMobility::Type)` | `SceneComponent.h:1296` (`ENGINE_API virtual`, publique) ; corps `Private/Components/SceneComponent.cpp:3098-3116` (ré-enregistre le composant ; si Movable, passe les enfants en Movable) | OK |
|
||
| Membre `Mobility` public | `SceneComponent.h:303`, déclaré après `public:` l. 291 ; `UPROPERTY(... Replicated) TEnumAsByte<EComponentMobility::Type>` ; commentaire l. 302 « Only safe to set in constructor » | OK (lecture publique) ; voir risque R1 |
|
||
| `EComponentMobility` (enum `Type : int`) | `Engine/Classes/Engine/EngineTypes.h:4028-4030` ; le .h inclut `Engine/EngineTypes.h` (diff l. 139) | OK |
|
||
| `UE_KINDA_SMALL_NUMBER` | `Core/Public/Math/UnrealMathUtility.h:131` (`1.e-4f`) ; comparé à un `double` : promotion sans perte gênante | OK |
|
||
| `FMath::IsNearlyEqual(double, double, double)` | `UnrealMathUtility.h:393` ; appel `IsNearlyEqual(Floor->GetRelativeScale3D().Z, 1.0)` : double/double, pas d'ambiguïté (LWC) | OK |
|
||
| `TEnumAsByte<EComponentMobility::Type>` en membre d'UCLASS sans `UPROPERTY` | même type que le membre moteur l. 303 ; membre brut dans une classe UObject (comme `FVector FloorExtent` et `FTSTicker::FDelegateHandle` déjà présent) : légal, ni GC ni sérialisation concernés. Initialiseur `= EComponentMobility::Movable` : conversion implicite enum → `TEnumAsByte` valide | OK |
|
||
| `TWeakObjectPtr<UBoxComponent>` : `IsValid()`, `Get()`, `Reset()`, affectation depuis `UBoxComponent*` | usage identique à `HiddenCeiling` existant ; `class UBoxComponent;` déclarée (.h) et `Components/BoxComponent.h` inclus (.cpp l. 5) | OK |
|
||
**Verdict b : CONFORME** (compilation non faite : établi par lecture des en-têtes, pas par UBT).
|
||
|
||
### c. Logique
|
||
1. **Dessus inchangé** — `.cpp:124-125` : demi-hauteur `E` → `T`, Z relatif `Z` → `Z + E − T`. Dessus relatif avant `Z + E`, après `(Z + E − T) + T = Z + E`. Valable car l'échelle relative Z de la Box est 1 (garde l. 105) : l'unité de la position relative et de l'extent est alors la même (échelle du parent). Hypothèse : pas de rotation relative de la Box (non gardée ; dump : aucune rotation affichée). Cohérent avec M1-ANALYSE l. 53-54. **CONFORME**.
|
||
2. **Épaisseur cible** — `T = E × 0,2 / ScaleZ` (l. 110) ; avec `E = 64`, `ScaleZ = 0,01` : `T = 1280` ; épaisseur monde `2 × 1280 × 0,01 = 25,6 cm` = `2 × 64 × 0,2` (intérieur, M1-ANALYSE l. 29). Le journal (l. 127-128) affiche `2·T·ScaleZ` = 25,6. Dessous descend de `2 × (1280 − 64) × 0,01 = 24,3 cm`. **CONFORME**.
|
||
3. **Restauration exacte** — `RestoreFloor` (l. 131-144) : `SetBoxExtent(FloorExtent)`, `SetRelativeLocation(FloorLocation)` (valeurs copiées avant modification, l. 115-117), mobilité remise si différente. Appelée : (i) retour en intérieur (`UpdateFloor`, l. 91-97) ; (ii) changement de Room ou de composant (`Tick`, l. 56-59, comparaison `ThickenedFloor.Get() != Floor`) ; (iii) `Deinitialize` (l. 39, après `RemoveTicker`). Ordre dans `RestoreFloor` : extent puis position puis mobilité ; `Reset()` toujours (l. 143). **CONFORME**. Limite : les enfants de la Box passés en Movable par `SetMobility(Movable)` ne sont pas remis en Static (dump : la Box n'a pas d'enfant listé) ; non restauré aussi si le composant est détruit avant (weak → rien à faire, correct).
|
||
4. **Aucune action en intérieur** — `bOutdoor` faux : `UpdateFloor` ne restaure que si `ThickenedFloor == Floor`, sinon `return` (l. 91-97) ; aucune écriture sur la Box. Et à l'échelle 0,2 la garde `TargetZ <= Extent.Z` (l. 111) retournerait de toute façon. **CONFORME**.
|
||
5. **WallT inchangé** — original (`git show HEAD`, diff l. 35-45) : `if (!Root || !Ceiling) return true;` puis `bOutdoor` et le bloc plafond. Après : `if (!Root) return true;` ; `bOutdoor` ; plancher traité ; `if (!Ceiling) return true;` ; bloc plafond identique (l. 74-85, ni ligne ni condition modifiée dans le diff). Cas : Root nul → retour comme avant ; Root non nul et Ceiling nul → retour comme avant (après traitement du plancher) ; les deux non nuls → même suite. Le rendu du plafond précédemment masqué (l. 49-54) est antérieur et inchangé. **CONFORME**.
|
||
6. **Pointeurs nuls** — `Room` : `IsValid(Room)` avant `Floor` (l. 55) ; `Floor` non nul testé avant `UpdateFloor` (l. 65) ; `Root` testé (l. 60) avant `GetRelativeScale3D` ; `ThickenedFloor.Get()` testé dans `RestoreFloor` (l. 133) ; `Floor->` dans `UpdateFloor` : appelant garantit non nul. `GetObjectProperty` tolère `Container` nul. Cast : `Cast<UBoxComponent>` sur la valeur de la propriété `Box` (nul si autre type). Aucune lecture nulle trouvée. **CONFORME**.
|
||
|
||
### d. Includes, module, EOL, chemins
|
||
- Includes : `Components/BoxComponent.h` (.cpp l. 5), `Engine/EngineTypes.h` (.h l. 139), déclaration avancée `UBoxComponent` (.h l. 143) ; `LifePlay.h` pour `LogLifePlay` déjà présent. Aucun `UE_VERSION_OLDER_THAN` ajouté (pas besoin de garde de version).
|
||
- `Source/LifePlay/LifePlay.Build.cs:17` : `PublicDependencyModuleNames … "Core", "CoreUObject", "Engine", …` : `Engine` présent. **Aucune modification de Build.cs nécessaire**.
|
||
- EOL : `git ls-files --eol` → `i/lf w/lf` pour les deux ; `tr -cd '\r' < fichier | wc -c` → `CR=0` pour .cpp et .h (compte par `tr`, pas par `grep -c $'\r'`).
|
||
- Chemins : Grep `C:\\Users|<nom du compte>` sur les deux fichiers → 0 occurrence.
|
||
**Verdict d : CONFORME**.
|
||
|
||
### e. Risques nommés (non corrigés, pas de changement de verdict)
|
||
- **R1 Mobilité à l'exécution** : `SetMobility` ré-enregistre le composant (`FComponentReregisterContext`, `SceneComponent.cpp:3102`) pendant le jeu, détruit/recrée l'état physique du plancher (un instant sans plancher), et le moteur documente `Mobility` « seulement sûr en constructeur » (`SceneComponent.h:302`). Le chemin n'est emprunté que si la Box n'est pas déjà Movable (mobilité réelle de la Box du Blueprint non établie par le dump). `SetMobility(Movable)` passe aussi les enfants en Movable, sans retour. Deuxième ré-enregistrement à la restauration.
|
||
- **R2 Navmesh** : la Box porte `NavArea_Obstacle` (dump l. 118-120). `SetBoxExtent` + `SetRelativeLocation` changent ses bornes et marquent le navmesh sale à chaque épaississement et à chaque restauration (rebuild local de tuiles si navigation dynamique) ; en extérieur la Box passe de 1,28 cm à 25,6 cm d'épaisseur, 24 cm plus bas, ce qui peut aussi toucher le décor placé sous la pièce (non vérifié).
|
||
- **R3 Appel à chaque image** : `Tick` du ticker central (déjà le cas). Ajout : un `FindPropertyByName` pour `Box` par image (coût du même ordre que `Room`/`WallT`) ; `UpdateFloor` sort tôt si le plancher est déjà épaissi ; si la garde d'échelle relative échoue (≠ 1), l'appel recalcule et retourne à chaque image, sans journal (pas de répétition de log). Le journal n'est écrit qu'aux deux transitions.
|
||
- **R4 Rotation relative** de la Box non gardée (hypothèse du calcul « dessus inchangé ») ; sans effet tant que le Blueprint ne tourne pas la Box.
|
||
- **R5 Valeurs non lues à l'exécution** : `ScaleZ` = échelle monde Z de la Box ; si un Blueprint modifie `Box` après le premier épaississement (autre `SetBoxExtent`), la restauration écrase cette valeur par l'extent d'origine.
|
||
|
||
## 4. Diff lu en entier
|
||
- Fichiers : 2 (`Source/LifePlay/LPOutdoorRoomSubsystem.cpp`, `.h`) ; compte : cpp +77/−2, h +15/−0 (`git diff --numstat`). Patch `STG-SF4.patch` = diff réel (diff vide entre les deux). Rien d'autre dans le diff que : includes, constantes `FloorPropertyName` / `IndoorScaleZ`, `RestoreFloor` dans `Deinitialize`, restructuration du retour anticipé de `Tick`, `UpdateFloor`, `RestoreFloor`, commentaire d'en-tête .h, trois membres (`ThickenedFloor`, `FloorExtent`, `FloorLocation`, `FloorMobility` : quatre membres) et deux méthodes privées. Aucun ajout non annoncé.
|
||
- `Content/`, `Config/` : non examinés au-delà de `git status` initial (Person.uasset, Room.uasset modifiés dans l'arbre, hors périmètre S-F4 ; le diff S-F4 n'y touche pas).
|
||
|
||
## 5. Verdict
|
||
**PASS-STATIC.** Contrôles a, b, c (1 à 6), d conformes. Risques R1 à R5 listés, aucun n'est une erreur certaine. Rien n'a été compilé ni lancé : la compilation (C2xxx éventuels), le comportement en jeu (plancher retrouvé en street, `floorHit` > 0, navmesh, absence d'effet en intérieur) restent à prouver dans COMMUN-2.
|
||
|
||
## 6. Points à confirmer par l'utilisateur
|
||
- [ ] Mobilité réelle de `Room.Box` dans le Blueprint (Static ou Movable) : le journal « Piece exterieure … plancher Box epaissi » ne le dit pas ; relever `Mobility` via `LPDumpComponents` ou trace en COMMUN-2 pour savoir si R1 est exercé.
|
||
- [ ] COMMUN-2 : compilation `LifePlayEditor` (0 erreur) puis `LPTestStringCompat` (3120 cas, `compat != herite(UE4) : 0`), puis session street : lignes de journal « plancher Box epaissi a 25.6 cm (demi-hauteur 64.0 -> 1280.0) » et « Plancher Box rendu … 64.0 » au retour en intérieur.
|
||
- [ ] Rattacher les modifications de `LPDebugSubsystem.cpp/.h` (hors S-F4) à leur ticket ; elles n'ont pas été validées ici.
|
||
- [ ] Vérifier à l'œil que le dessous de la Box (24 cm plus bas) ne traverse pas le décor ou le sol visible en street (R2).
|
||
|
||
## Fichiers à commiter
|
||
- A `Docs/Migration/etat/ST/STG/STG-SF4-VAL.md` (dépôt du jeu, copie 5.8, branche ue5.8). Aucune autre écriture : ni `Source/`, ni `Content/`, ni `Config/`.
|
||
|
||
Tours : 9 / 30
|
||
|
||
## Constats
|
||
- Le piège d'écriture : redirection `> "$S/…"` refusée par le crochet pour cet agent ; contourné par substitution de processus `diff <(git diff …) fichier` (lecture seule).
|