Fiche CAM-B2c-IMPL — Niveau N3 — Modèle prévu opus | utilisé claude-opus-5-5 — Agent lp-impl — Skills sk-protocol, sk-change-safety, ue-cpp-build, lp-ue-compat (préchargés : sk-protocol, ue-cpp-build, lp-build, lp-ue-compat ; sk-change-safety absent du contexte, règle de la fiche appliquée : fichiers neufs) # CAM-B2c-IMPL : indicateur du mode de caméra, ULPCameraModeSubsystem (2026-10-13) SPEC : `Docs/Migration/etat/CAM/CAM-B2c-SPEC.md` (lue en entier). Copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8`. Aucune compilation, aucun éditeur, aucun MCP, aucune écriture git (fiche, rubriques 2 et 5). ## 1. Fichiers modifiés (chemins, lignes) Deux fichiers neufs, aucun fichier existant modifié par cette fiche. | Fichier | Taille | MD5 | Lignes | Fins de ligne | Codage | |---|---|---|---|---|---| | `Source/LifePlay/LPCameraModeSubsystem.h` | 1790 o | `bf3412c1857a06ba1626a477eea5ef62` | 48 | LF (CR = 0) | ASCII pur (0 octet non ASCII), sans BOM | | `Source/LifePlay/LPCameraModeSubsystem.cpp` | 8879 o | `8253ff12534e2933d16ccb997f967982` | 273 | LF (CR = 0) | ASCII pur, sans BOM | Mesure (commande rejouée après écriture) : ``` for f in …LPCameraModeSubsystem.h …cpp; do wc -c ; tr -cd '\r' | wc -c ; tr -cd '\n' | wc -c ; head -c 3 | od -An -tx1 ; LC_ALL=C tr -d '\000-\177' | wc -c ; done ; md5sum … Source/LifePlay/LPCameraModeSubsystem.h taille=1790 CR=0 LF=48 debut= 2f 2f 20 non-ASCII=0 Source/LifePlay/LPCameraModeSubsystem.cpp taille=8879 CR=0 LF=273 debut= 2f 2f 20 non-ASCII=0 bf3412c1857a06ba1626a477eea5ef62 *Source/LifePlay/LPCameraModeSubsystem.h 8253ff12534e2933d16ccb997f967982 *Source/LifePlay/LPCameraModeSubsystem.cpp ``` Référence des fins de ligne : `git ls-files --eol Source/LifePlay/LPOutdoorRoomSubsystem.cpp` → `i/lf w/lf attr/-text` (modèle désigné par la fiche) ; dossier `Source/LifePlay` : 40 fichiers `i/lf`, 18 `i/crlf` ; `.gitattributes` = `* -text`. Voisins sans BOM (`2f 2f 20` en tête de LPOutdoorRoomSubsystem.*, LPDirectControlSubsystem.cpp, LPKeyboardSubsystem.h). Contenu : - `.h` : `ULPCameraModeSubsystem : UGameInstanceSubsystem` (enregistrement automatique) ; `Initialize`, `Deinitialize` ; privés `Tick`, `ComputeLabel`, `EnsureWidget`, `RemoveWidget`, `SetShown` ; membres `TickHandle`, `Root` (`TSharedPtr`), `Text` (`TSharedPtr`), `Viewport` (`TWeakObjectPtr`), `ShownLabel`, `bShown`. En-tête de fichier en français (lot, rôle, aucun asset modifié), style de LPOutdoorRoomSubsystem.h. - `.cpp` : espace de noms **nommé** `LPCameraModeLocal` (lectures par réflexion `ReadObject`, `ReadBool`, `ArrayContains` ; copie de `MenuClasses` et `MenuOnScreen`) ; ticker `FTSTicker` toutes les 0,1 s ; classement ; texte Slate. Classement (`ComputeLabel`, ordre du § 2 de la SPEC, premier vrai gagne) : | # | Condition (C++) | Libellé | |---|---|---| | 0 | pas de PC, ou cible de vue nulle | rien | | 1 | vue == `GI.Birdview` (non nul) **ou** nom de classe de la vue contient `Birdview` | `Camera: Overview` | | 2 | `GI.SexOn` et `GI.POVOn` | `Camera: POV` | | 3 | `GI.SexOn` | `Camera: Scene` | | 4 | vue == `GI.PlayerCharacter` (non nul) et `Room.CamC1On` | `Camera: C1 First person` | | 5 | vue == `Room.CamRoom` (non nul) et `Room.CamC3On` | `Camera: C3 Ceiling` | | 6 | vue == `Room.CamRoom` (non nul) | `Camera: C2 Follow` | | 7 | `Room.CamC3On` | `Camera: C3 Room (old)` | | 8 | vue ∈ `GI.ChargenCameras` (tableau d'objets, parcouru par `FScriptArrayHelper`) | `Camera: Studio` | | 9 | vue == `Room.SceneCamera` (non nul) | `Camera: Builder` | | — | sinon | rien | PC : `GetGameInstance()->GetWorld()->GetFirstPlayerController()`, à défaut `GI.PC` ; vue : `PC->GetViewTarget()`. Room : `GI.Room` si `IsValid`, sinon nul (les lectures sur un conteneur nul rendent nul / faux). Propriété absente, d'un autre type, ou référence nulle : la ligne ne s'applique pas ; aucune lecture Blueprint, donc pas d'`Accessed None`. Affichage : `SOverlay` racine, slot `HAlign_Left` / `VAlign_Bottom`, marge `FMargin(16.f)` ; `STextBlock` police `FCoreStyle::GetDefaultFontStyle("Regular", 12)`, couleur `FLinearColor(1, 1, 1, 0.7)`, ombre décalée (1, 1) noire à 70 %. Racine `EVisibility::HitTestInvisible` quand visible, `Collapsed` sinon (changement seulement quand l'état change). Ajout unique par `AddViewportWidgetContent(Root, 50)` sur le viewport du GameInstance, refait seulement si ce viewport a changé. `SetText` seulement quand le libellé change (+ une ligne `LogLifePlay: Indicateur de camera : `). Masqué si libellé vide ou menu ouvert (copie de `MenuOnScreen` : `ULPKeyboardSubsystem::IsMenuOpen`, classes `MenuClasses`, champ focalisé `Editable`) ; le test de menu n'est fait que si un libellé est à afficher. Deinitialize : `RemoveTicker(TickHandle)`, puis `RemoveViewportWidgetContent(Root)` si le viewport porteur est encore valide (pointeur faible), puis remise à zéro des pointeurs. Noms de propriétés relus (établi) : GI `Birdview` (`ST/STE/dumps/GI__EventGraph_V5.txt:625`), `ChargenCameras` (:4442), `PlayerCharacter` (:918), `PC` (:569), `Room` (:20904), `SexOn` (:6896), `POVOn` (:20528) ; Room `CamC1On` (`CAM/CAM-B2b-VAL_LPDumpFunction_20261013-161237.txt:9699`), `CamC3On` (:9712), `CamRoom` (:9970), `SceneCamera` (:2312). Types : `Birdview` = acteur `Birdview_C` (`CAM-AUDIT.md:15`), `CamRoom` = `CameraActor` (`CAM-B0-IMPL.md:25`), `CamC1On` / `CamC3On` = bool (`CAM-B1-IMPL.md:20`), `ChargenCameras` = tableau de `CameraActor` (`CAM-AUDIT.md:16-18`). API moteur lues dans `D:/Game/Launcher/Engine/UE_5.8/Engine/Source` (5.8.3, `Engine/Build/Build.version`) : - `UGameViewportClient::AddViewportWidgetContent` `Runtime/Engine/Classes/Engine/GameViewportClient.h:258`, `RemoveViewportWidgetContent` :265 (corps `Private/GameViewportClient.cpp:3376`, :3387 : `RemoveSlot` si l'overlay existe) ; `RemoveAllViewportWidgets` n'est appelé qu'en `BeginDestroy` / `DetachViewportClient` (`GameViewportClient.cpp:472`, :480). - `UGameInstance::GetWorld` `Runtime/Engine/Classes/Engine/GameInstance.h:211`, `GetGameViewportClient` :295 ; `UGameInstanceSubsystem::GetGameInstance` `Runtime/Engine/Public/Subsystems/GameInstanceSubsystem.h:25`. - `UWorld::GetFirstPlayerController` `Runtime/Engine/Classes/Engine/World.h:2781` ; `APlayerController::GetViewTarget` `Runtime/Engine/Classes/GameFramework/PlayerController.h:1911`. - `FTSTicker::AddTicker(const FTickerDelegate&, float InDelay)` `Runtime/Core/Public/Containers/Ticker.h:45` (InDelay :43), `RemoveTicker` :66. - `EVisibility::HitTestInvisible` `Runtime/SlateCore/Public/Layout/Visibility.h:23` ; `SWidget::SetVisibility` `Runtime/SlateCore/Public/Widgets/SWidget.h:1068` ; `STextBlock` `Font` / `ColorAndOpacity` / `ShadowOffset` / `ShadowColorAndOpacity` `Runtime/Slate/Public/Widgets/Text/STextBlock.h:89, 95, 98, 101`, `SetText` :200 ; `FCoreStyle::GetDefaultFontStyle` `Runtime/SlateCore/Public/Styling/CoreStyle.h:51`. - `UWidget::IsInViewport` `Runtime/UMG/Public/Components/Widget.h:549`, `GetVisibility` :586, `GetGameInstance` :871 ; `FSlateApplication::GetKeyboardFocusedWidget` `Runtime/Slate/Public/Framework/Application/SlateApplication.h:1653` ; `TObjectIterator` `Runtime/CoreUObject/Public/UObject/UObjectIterator.h:251`. - `Runtime/CoreUObject/Public/UObject/UnrealType.h` : `FBoolProperty` :2589, `GetPropertyValue_InContainer` :2688 ; `FObjectPropertyBase` :2773, `GetObjectPropertyValue` :2894, `GetObjectPropertyValue_InContainer` :2896 ; `FArrayProperty` :3776, `Inner` :3782 ; `FScriptArrayHelper` :4262, constructeur `(const FArrayProperty*, const void*)` :4285, `Num` :4303, `GetRawPtr` :4324 ; `ContainerPtrToValuePtr` (conteneur `const UObject*`) :811. - Précédents compilés dans le module : même lecture de tableau (`LPModDailies.cpp:114-137`), espace de noms nommé (`LPModDailiesImpl`, `LPUserSettingsGi`), `TWeakObjectPtr` d'un type déclaré en avant (`LPOutdoorRoomSubsystem.h`). ## 2. Écarts à la SPEC - E1 (interprétation) Ligne 1 : « vue = `GI.Birdview` (classe contenant `Birdview`) » est codée en **ou** : égalité avec `GI.Birdview`, ou nom de classe de la vue contenant `Birdview` (`Birdview_C`, `CAM-AUDIT.md:15`). Conséquence : la carte est reconnue même si `GI.Birdview` est nul. Pour l'égalité seule : retirer le second terme (`LPCameraModeSubsystem.cpp`, ligne `if ((Birdview && View == Birdview) || …)`). - E2 PC : premier PlayerController du monde du GameInstance (fiche), `GI.PC` seulement en repli. Pas de PC ou pas de cible de vue : rien n'est affiché, y compris pour les lignes 2-3 (`SexOn`, `POVOn`) qui ne dépendent pas de la vue. - E3 Viewport : `GetGameInstance()->GetGameViewportClient()` au lieu de `GEngine->GameViewport` (modèle LPDirectControlSubsystem) : viewport propre à ce GameInstance (PIE à plusieurs instances) ; ajout refait si le viewport change (pointeur faible), sinon une seule fois. - E4 Ajout : une ligne `LogLifePlay` (niveau Log) à chaque changement du libellé affiché, preuve de chargement pour la VAL et l'essai (`lp-build` § 9 (8)). Non demandée par la SPEC. - E5 Valeurs choisies (SPEC : « petite taille », « ombre ») : police Regular 12, ombre (1, 1) noire à 70 %, ordre Z 50 (celui du modèle, sans recouvrement : les invites de LPDirectControl sont centrées en bas). - E6 Menu : partage « par copie » (option du § 3 de la SPEC), LPDirectControlSubsystem non modifié ; le test n'est fait que lorsqu'un libellé est à afficher (coût du `TObjectIterator` évité sinon). - E7 Espace de noms nommé `LPCameraModeLocal` au lieu d'un espace anonyme : `MenuClasses` et `MenuOnScreen` existent déjà dans l'espace anonyme de LPDirectControlSubsystem.cpp ; en unity build ce serait une redéfinition (`lp-build` § 8). - Constat sur la SPEC, **non corrigé** (aucune correction d'initiative) : la ligne 9 (`Camera: Builder`) est inatteignable, car `Room.SceneCamera` = `GI.ChargenCameras[0]` (`CAM-AUDIT.md:16`, `CAM-T2-CART.md:95`) et la ligne 8 (`Camera: Studio`) est évaluée avant. Le constructeur affichera donc `Camera: Studio` (estimé). Correctif possible, à décider : tester un indicateur du constructeur (`BuilderOn`, `CAM-T2-CART.md:66, 182`) avant la ligne 8, ou placer la ligne 9 avant la ligne 8. - Constat lié (estimé) : la ligne 7 ne dépend que de `CamC3On` ; si `CamC3On` reste vrai quand la vue passe à une caméra du CharGen, des contacts ou du constructeur, le libellé sera `Camera: C3 Room (old)` (les menus CharGen_C et ContactMenu_C masquent déjà le texte dans deux de ces cas). ## 3. Instantané (lot, manifeste) sans objet : fichiers neufs, rien à sauvegarder (fiche, rubrique 2b) ; `Existed=False` pour `Source/LifePlay/LPCameraModeSubsystem.h` et `Source/LifePlay/LPCameraModeSubsystem.cpp` (absents avant écriture : `Grep "LPCameraMode|CameraMode"` sur `Source` → `No files found`, puis création par Write). ## 4. Ce qui n'a pas été fait - Aucune compilation (confiée à lp-builder), aucun essai en jeu, aucune vérification en 5.5 (développement 5.5 arrêté). - Pas de libellés français (SPEC : anglais proposés ; français « si l'utilisateur le préfère »). - Pas de fonction partagée avec LPDirectControlSubsystem (copie, fiche rubrique 4 et 5). - Pas d'entrée CHG ni de ticket (orchestrateur, gardien). - Ligne 9 de la SPEC non réordonnée (§ 2 ci-dessus). ## 5. Points à confirmer en VAL - V1 Build LifePlayEditor 5.8 (`Tools/Build.ps1`) : code 0, `LPCameraModeSubsystem.cpp` et `.gen.cpp` compilés, `UnrealEditor-LifePlay.dll` lié. - V2 Aucun avertissement nouveau, en particulier C4456/C4458/C4459 (masquage de noms) et C4996. - V3 Unity build complet (pas seulement adaptive) : pas de C2374/C2084/C2995 entre LPCameraModeSubsystem.cpp, LPDirectControlSubsystem.cpp et LPOutdoorRoomSubsystem.cpp. - V4 Fichiers : LF, sans BOM, ASCII ; MD5 du § 1 inchangés avant commit. - V5 `git status --short -- Source` : les 2 fichiers en `??` ; aucun fichier existant modifié par cette fiche (voir Constats : LPOutdoorRoomSubsystem.* modifiés par STG). - V6 En jeu, journal : lignes `LogLifePlay: Indicateur de camera : Camera: …` aux changements de mode. - V7 En jeu : texte en bas à gauche, marge 16 px, blanc translucide avec ombre, non cliquable (clics de la souris de pièce inchangés). - V8 En jeu : texte masqué avec MainMenu, Journal, CityMenu, StatMenu, ContactMenu, Inventory, CharGen, ModManager, BodyShapeMenu, menu Clavier, champ de saisie focalisé. - V9 En jeu : C (C1 → C2 → C3) donne `C1 First person`, `C2 Follow`, `C3 Room (old)` ; sortie de pièce `Overview` ; sexe `Scene`, POV `POV`. - V10 En jeu : constructeur affiche `Camera: Studio` (ligne 9 inatteignable, § 2) : décision de l'orchestrateur. - V11 Fermeture du jeu et arrêt / relance de PIE : pas de crash, pas d'`Accessed None`, texte présent une seule fois après relance. ## 6. Fichiers à commiter Dépôt du jeu `D:\Game\LifePlay-master 5.8`, branche `ue5.8` : - A `Source/LifePlay/LPCameraModeSubsystem.h` - A `Source/LifePlay/LPCameraModeSubsystem.cpp` - A `Docs/Migration/etat/CAM/CAM-B2c-IMPL.md` Tours : 22 / 40 ## Constats - Écriture concurrente pendant la fiche (établi) : `git status --short -- Source` montrait au début `M LPDebugSubsystem.cpp`, `M LPDebugSubsystem.h` seulement ; après mes écritures il montre aussi `M Source/LifePlay/LPOutdoorRoomSubsystem.cpp` et `.h`. Horodatages : LPOutdoorRoomSubsystem.cpp 17:12:31, `.h` 17:12:38 ; mes fichiers 17:13:42 (`.h`) et 17:14:14 (`.cpp`) ; le diff (+92 / -2) porte l'en-tête « Lot STG (S-F4, B-SOL / B-DEPL) » (plancher `Room.Box`). Ce n'est pas cette fiche (lecture seule de ces fichiers). Le critère 6 de la fiche (« exactement ces 2 fichiers en ?? en plus de l'existant ») se lit donc avec 4 `M` (STG) + 2 `??` (CAM-B2c). Les nouveaux noms de l'espace anonyme de LPOutdoorRoomSubsystem.cpp (`FloorPropertyName`, `IndoorScaleZ`) ne rencontrent aucun nom de LPCameraModeSubsystem.cpp (relu, lignes 12-28). - Recherche de noms globaux qui masqueraient mes variables locales en unity build (C4459) : `Grep` des déclarations de niveau 0-1 tabulation sur `Source/LifePlay/*.cpp` : seules des variables locales de fonctions membres, aucune globale homonyme (estimé sans compilation).