Fiche CAM-T5 — Niveau N3 — Modèle prévu opus (opus-xhigh) | utilisé claude-opus-5-5 — Agent opus-xhigh — Skills sk-protocol, lp-mcp, lp-lot (préchargé aussi : debug-verification) — amendée CAM-T5b (même agent, contexte gardé, 2026-10-11) # CAM-T5-SPEC : phase 2, réduction et repositionnement des caméras par Blueprints (voie B, MCP) (2026-10-11, amendée CAM-T5b) Copie `D:\Game\LifePlay-master 5.8`, branche `ue5.8`, HEAD `4283c13`. Lecture seule : aucun éditeur, MCP, commandlet ni build ; seul ce fichier est écrit. Les décisions qui font foi sont celles de `CAM-CIBLE.md` § 4b et § 4c (réponses de l'utilisateur aux Q1-Q20 de cette SPEC : « q4 lissage et évitement, le reste oui »). **Amendement CAM-T5b** : § 4c intégré. Sections modifiées : en-tête, § 1, § 2, § 4 (R1, R5, R6, R12, R16 ; R17-R19 nouveaux ; Q1-Q20 décidées), § 5, § 6, § 7, (a) B0 à B6, (b), (c), (d), (e) C2, (f), (g), Constats. **Amendement CAM-B0a** (session CAM, 2026-10-13, après la sonde B0, `CAM-B0-IMPL.md`) : R1, R2, B1-1, B1-13, B2-6, (c), (d) ; nouveau § « Identifiants de nœuds (B0a) » après (g) ; **amendement B1b** (après l'IMPL de B1, `CAM-B1-IMPL.md` § 3) : B1-1, B1-3, B1-12, § 7 V5 ; **amendement B1c** (essai en jeu de l'utilisateur : caméra dans la tête) : B1-3, B1-12 ; **amendement B1d** (2e essai : rémanence d'une image, vue aux yeux) : B1-3 réordonnée, B1-4 sortie inversée ; **amendement B2d** (maniement Third Person, `CAM-B2d-TPS.md` ; copie d'avant `Backup_5.5\CAM\SPEC-avant-B2d.md`) ; **amendement B2b** (essais de B2, exigence « caméra toujours dans la pièce », option 1 de pilotage ; `CAM-B2b-AMENDEMENT.md`) : nouvelle section CAM-B2b, copie d'avant `Backup_5.5\CAM\SPEC-avant-B2b\` (`ecefee2f`) ; complément (avis de commit STE-H10) : § 7 V2 et V5, (f) B5 (MD5 de Person `D1B4C63D`, arrêt par handle). Aucun autre changement ; liste des points de GI inchangée. Copie d'avant : `D:\Game\LifePlay_Backup_5.5\CAM\SPEC-avant-B0a\CAM-SPEC.md` (MD5 `3f0f59bf…`). Abréviations (dumps texte ; « HEAD » = version citée par la fiche) : - `GI` = `Docs/Migration/etat/ST/STE/dumps/GI__EventGraph_V5.txt` ; `GICF` = `.../ST/STE/dumps/GI__CallFunction_V5.txt` ; `ROOM` = `.../ST/STE/dumps/Room__EventGraph_V5.txt` ; `PC` = `.../ST/STE/dumps/PC__EventGraph.txt` (tous avec broches). - `BV` = `Docs/Migration/etat/CAM/CAM-T6_LPDumpFunction_20261011-013552.txt` (Birdview, 4 graphes, 366 nœuds) ; `CM` = `...CAM-T6_LPDumpFunction_20261011-013659.txt` (ContactMenu, 34 graphes, 1 430 nœuds) ; `PERS` = `...CAM-T6_LPDumpFunction_20261011-013811.txt` (Person.EventGraph à HEAD, 3 684 nœuds) : **redumps avec broches** (CAM-T6, amendement CAM-T5b). `BV4`, `CM4`, `PERS4` = les listes sans broches de CAM-T4 (`CAM-T4_LPDumpFunction_20261011- 005305.txt`, `-005400.txt`, `-005458.txt`, `CAM-T4-BUILD.md:20-22`), gardées seulement où le texte d'origine les cite. - HEAD au moment de l'amendement : `c01b543` (commit U8 sur `Docs` seulement ; assets inchangés, mêmes MD5, d'après l'orchestrateur) ; les dumps V5 et CAM-T6 restent valables. - `SMS` = `Docs/Migration/etat/ST/STB/dumps/StreetMap_Static__EventGraph.txt` ; `PCOMP` = `Docs/Migration/etat/CAM/CAM-T4_LPDumpComponents_20261011-004910.txt` (Person) ; `RCOMP` = `Docs/Migration/etat/post-v1/PawnDyn/PawnDyn_LPDumpComponents_20261004-223842.txt` (Room, **copie 5.5**, 2026-10-04 : valeurs indicatives seulement). - `TS` = `D:\Game\Launcher\Engine\UE_5.8\Engine\Plugins\Experimental\Toolsets\EditorToolset\Content\Python\editor_toolset\toolsets\` (outils Epic du MCP) ; `ENG` = `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Engine\Private\`. - Marques : **(N)** = nœud nouveau ; « coupe » = `break_pins` ; « relie » = `connect_pins` ; « suppr. » = `delete_node`. ## 1. Objectif mesurable Dans une pièce (`GI.SpaceOn` vrai, hors constructeur, hors CharGen et contacts), la vue est l'une de trois caméras qui suivent le MC, et `CameraScene` (`ChargenCameras[0]`) ne sert plus qu'au CharGen (et au constructeur, Q9) : - **C1** : `FirstPersonSM` existant du MC (composant de Person), attaché à la tête comme la POV de sexe ; POV des partenaires inchangée pendant le sexe (décision Q5). - **C2** : un `CameraActor` créé à l'exécution par Room (aucune écriture de `.umap`), **non attaché** au MC : posé à l'initialisation à 175 cm derrière le MC (horizontale), à la hauteur sol + taille du MC + 50 cm, regard vers la tête ; puis **poursuite lissée avec évitement des murs** par une minuterie de Room : cible recalculée à chaque passage, balayage en sphère (12 cm) sur le canal Camera depuis la tête, retrait de 12 cm sur impact, `VInterpTo` et `RInterpTo` vitesse 8 ; portage BP de `FollowCamera` (`LPDirectControlSubsystem.cpp:929-940`) ; la minuterie s'arrête d'elle-même quand la vue n'est plus une caméra de pièce (Q1-Q4 décidées, `CAM-CIBLE.md` § 4c). - **C3** : le même `CameraActor` (Q19 décidée) placé sous le plafond au centre de la pièce ; en extérieur (échelle Z de la racine < 0,05), à 380 cm au-dessus du sol, au bout de l'axe long du lieu, regard dans l'axe (enfilade) (Q3 et Q1 décidées). - Touche C hors sexe : C1 → C2 → C3 → C1 ; pendant le sexe : cycle POV actuel (Q4 décidée) ; commandes de scène `cameraMove` / `cameraFocus` / `cameraFocusPoint` → C2 (Q8 décidée) ; Birdview, CameraFullBody, CameraPortrait inchangées (Q6 décidée) ; défauts du lot corrigés : pivot de l'acteur Room par `CameraFocus`, chaînes mortes, code `FollowCamera` (Q9 décidée). Mesures de réussite (rejouées en VAL, § 7) : `SetViewTargetWithBlend` vivants dans les dumps : GI 4 → 4, PC 3 → 3, Room 0 → 3 (C1, C2, C3) ; MD5 de Birdview, ContactMenu, CentralLondon.umap et Person (Q18 décidée : non touchée) identiques aux valeurs de CAM-T4 ; compilation : 0 diagnostic, `CompileAllBlueprints` 0 erreur ; build Development code 0 ; essais en jeu de l'utilisateur § (f) tous PASS ; coût de la poursuite de C2 sous le seuil de V11 ; `FollowCamera|FollowView|BirdView` dans `Source/` : 0 ligne. Sortes de caméras de vue : 6 avant (CAM-AUDIT § 2) ; 6 après (Q19 décidée : un seul acteur `CamRoom` ajouté, FollowView retirée). La réduction porte surtout sur les rôles : `CameraScene` passe de 4 rôles à 1 (+ constructeur). ## 2. Fichiers touchés, ordre Sous-lots, un commit chacun, dans cet ordre (dépendances au § (a)) : | Ordre | Sous-lot | Fichiers (dépôt du jeu, branche `ue5.8`) | Graphes | |---|---|---|---| | 0 | CAM-B0 sonde MCP (Q16 décidée) | aucun fichier suivi ; BP jetable `/Game/Dev/CAM_Probe` (non commité, déplacé ensuite vers `D:\Game\LifePlay_Backup_5.5\CAM-B0\`) | — | | 1 | CAM-B1 C1 hors sexe, commutation C, reclassement des appelants de `ChargenCameras[0]` | `Content/BPs/Room.uasset`, `Content/BPs/PC.uasset`, `Content/BPs/GI.uasset` | Room.EventGraph ; PC.EventGraph ; GI.EventGraph | | 2 | CAM-B2 C2 en poursuite lissée avec évitement des murs (Q4) | `Content/BPs/Room.uasset` | Room.EventGraph | | 3 | CAM-B3 C3 intérieur, extérieur, enfilade | `Content/BPs/Room.uasset` | Room.EventGraph | | 4 | CAM-B4 commandes de scène → C2, correctif `CameraFocus` | `Content/BPs/Room.uasset`, `Content/BPs/GI.uasset` | Room.EventGraph ; GI.CallFunction ; GI.EventGraph (Q12 décidée) | | 5 | CAM-B5 chaînes mortes et suppression d'`AutoCamera` (Q14) | `Content/BPs/Room.uasset`, `Content/BPs/PC.uasset`, `Content/BPs/GI.uasset` (Person non touchée, Q18) | Room.EventGraph ; PC.EventGraph ; GI.EventGraph | | 6 | CAM-B6 suppression du C++ `FollowCamera` (Q15), après la VAL de B2 (portage) | `Source/LifePlay/LPDirectControlSubsystem.h`, `.cpp` | — | Ordre dans un sous-lot MCP : Room d'abord (les événements appelés par PC et GI doivent exister et compiler), puis PC, puis GI ; `compile_blueprint` après chaque asset ; `save_assets` à la fin. Aucun fichier hors de cette liste : Birdview, ContactMenu, MainMenu, Person (Q18 décidée), `CentralLondon.umap`, `Config/` ne sont pas modifiés. ## 3. Repli - Par sous-lot, éditeur fermé, **proposé à l'utilisateur, jamais lancé sans accord** (lp-lot) : `.\Tools\Snapshot.ps1 -Lot CAM-Bn-retour -Path ` puis `.\Tools\Restore.ps1 -Lot CAM-Bn -WhatIf`, puis `.\Tools\Restore.ps1 -Lot CAM-Bn`. Après commit : ticket `RESTORE` au gardien (`git revert` du commit du sous-lot). - Les sous-lots B1 → B5 s'enchaînent sur les mêmes assets binaires : le retour de Bn impose celui de Bn+1… d'abord (ordre inverse). B6 (C++) est indépendant. B0 ne laisse rien dans le dépôt. - Repli fonctionnel sans retour : B1 seul laisse le jeu au comportement actuel pour C2/C3 (repli `ChargenCameras[0]`, § (a) ligne B1-4) ; on peut s'arrêter après n'importe quel sous-lot validé. - Un échec de compilation, un diagnostic ou un `Accessed None` nouveau : arrêt du sous-lot, aucun contournement, rapport (sk-protocol § 2). ## 4. Risques R#, questions Q# ### Risques - **R1 Capacités MCP non prouvées dans le projet.** Prouvées par usage : `find_nodes`, `get_connected_subgraph`, `get_node_infos`, `create_node` (Branche, IsValid, appels `Classe||`, `Définir`), `connect_pins`, `break_pins`, `delete_node`, `set_pin_value`, `compile_blueprint`, `save_assets` (`Docs/29_Projet_Stabilisation_V2.md:153`, `:166-169`, `:262-264` ; lp-mcp § 3). **Présentes dans le code Epic mais jamais utilisées ici** : `add_variable` (`TS blueprint.py:1154-1185`), `add_object_variable` (`:1224-1251`), `add_event` (`:324-366`), `add_function_graph` (`:287-320`), création d'un nœud `SpawnActorFromClass` et réglage de sa broche `Class` (`create_node` générique `:877-915` + `set_pin_value` `:1091`), `VariableGet` d'une variable d'un autre BP (contexte `find_node_types context_pins`, `:752`), `ActorTools.add_component` sur un Blueprint (SCS, `TS actor.py:392-484`), pose d'acteur dans la carte (`TS scene.py:102`, `:445`). S'ajoutent pour la poursuite de C2 (Q4) : nœuds `SphereTraceSingle`, `BreakHitResult`, `VInterpTo`, `RInterpTo`, `GetWorldDeltaSeconds`, `K2_ClearTimerDelegate`, et le rebranchement de la broche `OutputDelegate` d'un événement vers la broche `Delegate` d'une minuterie. Variante : sonde CAM-B0 (Q16 décidée) ; si une capacité échoue : arrêt et retour à l'utilisateur (Q20 décidée), aucune variante lancée d'office. **B0a (établi par la sonde, `CAM-B0-IMPL.md` § 2)** : `add_variable`, `add_object_variable`, `add_struct_variable`, `add_event` (personnalisé et surcharge), `SpawnActorFromClass` + `Class`, rebranchement `OutputDelegate` → `Delegate`, `SphereTraceByChannel` (`TraceTypeQuery2`), `BreakHitResult`, `VInterpTo`, `RInterpTo`, `GetWorldDeltaSeconds`, `FindLookAtRotation`, `GetComponentBounds`, `GetActorBounds`, `GetSocketLocation`, `GetScaledCapsuleHalfHeight`, affectation de `PostProcessSettings`, getter et appel d'un autre BP : **faits**. **Échec** : `K2_ClearTimerDelegate` (« ClearTimerbyEvent does not exist ») → remplacé par `RoomCamTimer` + `ClearandInvalidateTimerbyHandle` (B1-1, B1-13), prouvé par la sonde. `VariableGet` d'un autre BP : le type_id rendu par `find_node_types(context_pins)` (`Variables|…`) est refusé par `create_node` ; passer par `Classe||Obtenir` (§ « Identifiants de nœuds »). Non prouvé : `ActorTools.add_component`, pose d'acteur dans la carte (écartés, (c)). Réserve : la compilation des nœuds purs non câblés de la sonde n'est pas prouvée. - **R2 Opérateurs et comparateurs** (`Less_DoubleDouble`, opérations sur vecteurs, `EqualEqual_ObjectObject`) : lp-mcp § 3 signale que les comparateurs ne sortent pas de `find_node_types`. Ils existent dans le projet (`ROOM:3431`, `BV:1320-1324`, `PC:1816`) mais l'outil n'a pas de copie de nœud. Variante : recherche par nom de fonction `KismetMathLibrary` (`create_node` accepte `Utilities|Operators|Add`, `TS blueprint.py:886-887`) ; la commutation C1-C3 est conçue sans comparateur (deux booléens, § (a) B1). Si `Less_DoubleDouble` reste introuvable pour le test extérieur : retour à l'utilisateur (Q20 décidée). **B0a (établi)** : les opérateurs génériques `Utilitaires|Opérateurs|Inférieurà(<)`, `Égalà(==)`, `Ajouter`, `Soustraire`, `Multiplier` (`K2Node_PromotableOperator`) se créent et prennent leur type à la 1re liaison (`float UpdateRoomCam`, `Time = '0.016000'`, `bMaxOncePerFrame = 'true'`, GI 4, PC 3 à chaque sous-lot ; liens du § (a) présents (ex. `K2Node_CallFunction_30` → `then -> K2Node_CallFunction_.execute`) | | V6 | `CompileAllBlueprints` éditeur fermé (`RunCommandlet.ps1`, lp-commandlets) | code 0, 0 erreur, 0 avertissement nouveau | | V7 | build Development empaqueté (lp-build, `Tools/Build.ps1`) | code 0, 0 avertissement nouveau (`Build.ps1 -FromLog`) | | V8 | journal du build empaqueté après les essais : `Select-String -Path -Pattern "Accessed None"` filtré sur `ApplyRoomCamera|CycleRoomCamera|RestoreC1|UpdateRoomCam|EnsureCamRoom|ComputeC2Target|PlaceC2|PlaceC3|UseC2|ScriptC2` | 0 ligne | | V9 | B6 : Grep `FollowCamera|FollowView|BirdView` sur `Source` ; `Tools/Build.ps1` | 0 ligne ; code 0 | | V10 | essais en jeu de l'utilisateur (§ (f)) | chaque case PASS | | V11 | build Development, console `stat unit` : temps du fil Game relevé 30 s en pièce en C2 (MC qui marche), puis 30 s sur la carte Birdview, même partie | écart ≤ 0,5 ms (seuil proposé, R17) ; au-delà : repli `Time` 0,033 sur décision | Verdict attendu : `PASS` si V1-V10 ; `PASS-STATIC` pour B4 sans script de scène d'essai (R11). ## (a) Modifications par asset et par graphe, en sous-lots commitables Dépendances : B0 → B1 → B2 → B3 ; B4 après B2 ; B5 après B4 (B1 réemploie la minuterie morte de Room, que B5 ne supprime donc pas) ; B6 après la VAL de B2 (portage de `FollowCamera` validé, Q15) ; tout après la porte T11. Fenêtres GI pour l'arbitrage avec STE : B1, B4, B5. ### CAM-B0 — sonde (Q16 décidée), aucun fichier suivi `BlueprintTools.create` d'un Actor BP jetable `/Game/Dev/CAM_Probe`, puis dans l'ordre : `add_variable` (bool, Vector) ; `add_object_variable` (`CameraActor`) ; `add_event` (« ProbeEvent ») et surcharge `ReceiveEndPlay` ; `create_node` SpawnActorFromClass + `set_pin_value` `Class` = `/Script/Engine.CameraActor` ; **minuterie par événement** : nœud `K2_SetTimerDelegate` relié par `ProbeEvent.OutputDelegate` → `Delegate`, `set_pin_value` `Time` = 0,016 et `bMaxOncePerFrame` = vrai, puis `K2_ClearTimerDelegate` sur le même délégué ; **`SphereTraceSingle`** avec `set_pin_value` `TraceChannel` = Camera (`TraceTypeQuery2`, R19) et `BreakHitResult` ; **`VInterpTo`**, **`RInterpTo`**, `GetWorldDeltaSeconds` ; `GetComponentBounds`, `GetActorBounds`, `FindLookAtRotation`, `GetSocketLocation`, `GetScaledCapsuleHalfHeight`, `Less_DoubleDouble`, `EqualEqual_ObjectObject`, addition, soustraction et multiplication de vecteurs ; affectation d'un champ `PostProcessSettings` d'un `CameraComponent` ; dans un 2e BP jetable, `VariableGet` d'une variable du premier par `find_node_types(context_pins)` et appel de `ProbeEvent` ; `compile_blueprint`. Résultat attendu : chaque capacité de R1 et R2 = « fait » ou message d'erreur exact. Fichiers déplacés ensuite vers la sauvegarde (jamais supprimés). ### CAM-B1 — C1 hors sexe, commutation C, reclassement (Room, PC, GI) | # | Asset / graphe | Nœud existant ciblé (preuve) | Modification | Variables / valeurs | |---|---|---|---|---| | B1-1 | Room | — | `add_variable` ×4 ; `add_struct_variable` ×1 (B0a) | `CamC1On`, `CamC3On` (bool, défaut faux : mode C2, Q7) ; `C1Hidden` (bool, Q11, R18) ; (B1b) `WallWasHidden` retirée : la lecture de `bHidden` d'un autre acteur n'est pas créable par MCP, le mur reprend la règle de PC (B1-12) ; `RoomCamTimer` (`/Script/Engine.TimerHandle`, B0a : remplace `K2_ClearTimerDelegate`, non créable) | | B1-2 | Room.EventGraph | — | (N) événement `ApplyRoomCamera` : Branche(`CamC1On`) vrai → Branche(`GI.SexOn`) vrai → voie C2, faux → voie C1 ; faux → Branche(`CamC3On`) vrai → voie C3, faux → voie C2 | voie C2 pendant le sexe si mode C1 (Q8) | | B1-3 | Room.EventGraph | copie des valeurs de PC:3378-3388, 3389-3396 + 3403-3408, 3364-3373, 1788-1796, 2290-2297 | (N) voie C1 : `GI.PlayerCharacter.FirstPersonSM` → `K2_AttachToComponent(Mesh, 'head', KeepRelative ×3)` → `K2_SetRelativeTransform(MakeTransform((0,0,0), (90,90,0), (1,1,1)))` → `K2_AttachToComponent(Mesh, None, KeepWorld ×3)` → `SetViewTargetWithBlend(GI.PC, GI.PlayerCharacter, 0, VTBlend_Linear)` → Branche(`C1Hidden`) faux → `HairSM.SetVisibility(false)` (B1b : sans `WallWasHidden`) → `GI.PlayerCharacter.Mesh.HideBoneByName('neckLower', PBO_None)` (B1c, comme PC:2319-2324 ; omis par CAM-T2 : sans lui la caméra voit l'intérieur de la tête) → `SetActorHiddenInGame(SceneSun, vrai)` → `SetActorHiddenInGame(GI.Wall, vrai)` → `C1Hidden` := vrai ; puis (et si `C1Hidden` était vrai) → minuterie `K2Node_CallFunction_329` (B1-13) | Q11 décidée ; nœuds de même type dans PC : cheveux PC:2290-2297, soleil PC:2343-2355, mur PC:1797-1809 | | B1-3d | Room.EventGraph (B1d, remplace l'ordre de B1-3) | — | voie C1 : Branche(`C1Hidden`) faux → attache `head` KeepRelative → `MakeTransform((0,0,0),(90,90,0))` → (N) `FirstPersonSM.SetWorldLocation(0,5 × (Mesh.GetSocketLocation('lEye') + Mesh.GetSocketLocation('rEye')))` (lu **avant** `HideBoneByName` : un os masqué sort des os requis et ne bouge plus, `ENG SkeletalMeshComponent.cpp:2300-2330`) → attache KeepWorld → cheveux, `HideBoneByName('neckLower')`, soleil, mur → `C1Hidden` := vrai → (N) `DelayUntilNextFrame` → `SetViewTargetWithBlend(GI.PC, GI.PlayerCharacter)` → 329 ; Branche vrai → `SetViewTargetWithBlend` → 329 | os `lEye`, `rEye` (enfants de `head`) et `neckLower` présents sur Michael et Bridget (MCP `get_bone_children`) | | B1-4d | Room.EventGraph (B1d) | — | voies provisoires C2 et C3 : `SetViewTargetWithBlend(ChargenCameras[0])` **puis** `RestoreC1` (la tête ne revient qu'une fois la vue partie) | — | | B1-4 | Room.EventGraph | GI:3117-3125 (vue actuelle) | (N) voies C2 et C3 **provisoires** : (N) appel `RestoreC1` (B1-12) → `SetViewTargetWithBlend(GI.PC, GI.ChargenCameras[0], 0)` (comportement actuel) ; remplacées en B2 / B3 | — | | B1-5 | Room.EventGraph | — | (N) événement `CycleRoomCamera` : Branche(`CamC1On`) vrai → `CamC1On`:=faux ; faux → Branche(`CamC3On`) vrai → `CamC3On`:=faux → `CamC1On`:=vrai ; faux → `CamC3On`:=vrai ; puis → appel `ApplyRoomCamera` | C1 → C2 → C3 → C1, sans comparateur (R2) | | B1-6 | Room.EventGraph (`LoadRoom`) | `K2Node_CallFunction_290` MoveAllToCenterRoom `then -> K2Node_IfThenElse_63` (ROOM:3330-3332, 4068-4069) | coupe 290.then → 63.execute ; relie 290.then → (N) Branche(`GI.BuilderOn`) vrai → 63 ; faux → (N) Branche(`GI.SexOn`) vrai → 63 ; faux → (N) Branche(`GI.SpaceOn`) vrai → (N) `ApplyRoomCamera` → 63 ; faux → 63 | replace C2/C3 après un changement de pièce | | B1-7 | PC.EventGraph (touche C) | `K2Node_IfThenElse_23` (SexOn) `else (vide)` (PC:1723-1727), amont InputAction_12 → IfThenElse_65 (BuilderOn) → PlayClick (PC:1713-1716, 3876-3880, 4430-4432) | relie 23.else → (N) Branche(`GI.SpaceOn`) vrai → (N) appel `Room_C::CycleRoomCamera` (cible `GI.Room`) | hors pièce : rien (comme aujourd'hui) | | B1-8 | PC.EventGraph (fin du cycle POV, `ResetCamera`) | `K2Node_CallFunction_353` SetViewTargetWithBlend(`ChargenCameras[0]`) `then -> K2Node_CallFunction_194` (PC:1752-1760, 1805-1807 ; entrée par `CallFunction_1800`, PC:2351-2355, `ResetCamera` PC:3344-3346) | coupe 353.then → 194.execute ; relie 353.then → (N) appel `Room_C::ApplyRoomCamera` (cible `GI.Room`) → 194.execute | Q8 | | B1-9 | GI.EventGraph (`OpenSpace`) | `K2Node_CallFunction_30` HideRender(0) `then -> K2Node_CallFunction_645` (GI:23596-23600, 23624-23628 ; chaîne CustomEvent_66 → … → `SpaceOn`:=vrai → Exit Action Menu, GI:23571-23595) | coupe 30.then → 645.execute ; relie 30.then → (N) `VariableGet Room` + (N) appel `Room_C::ApplyRoomCamera` → 645.execute | entrée dans une pièce | | B1-10 | GI.EventGraph (`CloseContacts`, `NPCSelector` vrai) | `K2Node_CallFunction_3168` HideRender(0) `then -> K2Node_CallFunction_1039` (GI:17470-17474, 17481-17485, 18982-18986) | idem B1-9 entre 3168 et 1039 | retour des contacts en pièce | | B1-11 | GI.EventGraph (fin de sexe) | `K2Node_CallFunction_2699` SetViewTargetWithBlend(`ChargenCameras[0]`) `then -> K2Node_MacroInstance_3053` (GI:14498-14513, 6793-6796) | idem B1-9 entre 2699 et 3053 | — | | B1-12 | Room.EventGraph | — | (N) événement `RestoreC1` : Branche(`C1Hidden`) vrai → `HairSM.SetVisibility(true)` → `Mesh.UnHideBoneByName('neckLower')` (B1c, comme PC:2328-2340) → `SetActorHiddenInGame(SceneSun, faux)` → `SetActorHiddenInGame(GI.Wall, NOT(Room.bHidden))` (B1b, décision de l'utilisateur : règle de PC, PC:194 ← 152) → `C1Hidden` := faux ; faux → fin | R18 | | B1-13 | Room.EventGraph (minuterie de pièce) | `K2Node_CallFunction_329` `K2_SetTimerDelegate` **mort** (`execute (vide)`, `Delegate` ← `K2Node_CustomEvent_32.OutputDelegate` d'`AutoCamera`, `Time` 1,0, `bLooping` vrai, `bMaxOncePerFrame` faux, ROOM:3643-3653) | (N) événement `UpdateRoomCam` ; coupe 32.OutputDelegate → 329.Delegate ; relie `UpdateRoomCam.OutputDelegate` → 329.Delegate ; `set_pin_value` `Time` = 0,016, `bMaxOncePerFrame` = vrai ; 329.execute ← voie C1 (B1), voie C2 (B2), voie C3 (B3) ; **(B0a)** 329.then (vide, ROOM:3645) → (N) `Définir RoomCamTimer` ← 329.ReturnValue (vide, ROOM:3653). Corps en B1 (garde) : Branche(`GI.SexOn`) vrai → fin ; faux → Branche(`CamC1On`) vrai → Branche(`GetViewTarget(GI.PC)` = `GI.PlayerCharacter`) vrai → fin ; faux → `RestoreC1` → (N) `ClearandInvalidateTimerbyHandle(RoomCamTimer)` ; `CamC1On` faux → `RestoreC1` → `ClearandInvalidateTimerbyHandle(RoomCamTimer)` (B0a : `K2_ClearTimerDelegate` non créable par MCP, CAM-B0-IMPL § 2 n° 9) | ≈ 60 Hz ; arrêt automatique dès que la vue n'est plus une caméra de pièce (R17, R18) ; réemploi d'une chaîne morte ; un nouvel appel de 329 avec le même délégué remplace la minuterie en cours et rend un nouveau handle ; `ENG KismetSystemLibrary.cpp:735`, `:828` | Comportement attendu après B1 : identique à aujourd'hui en mode C2/C3 (repli B1-4), plus C1 hors sexe par la touche C. ### CAM-B2 — C2 en poursuite lissée avec évitement des murs (Room seul ; Q4 décidée) Portage BP de `FollowCamera` (`LPDirectControlSubsystem.cpp:929-940`) avec les valeurs décidées : cible derrière le MC (Q1-Q3) au lieu de la rotation de contrôle, même balayage (sphère 12 cm, canal Camera, retrait 12 cm), même vitesse 8. Pas de Tick : Room n'a aucun événement `Tick` (ROOM : seul `ReceiveBeginPlay`, ROOM:2) ; la minuterie de B1-13 (≈ 60 Hz, `bMaxOncePerFrame`) se démarre et s'arrête explicitement. | # | Graphe | Nœud existant ciblé (preuve) | Modification | Variables / valeurs | |---|---|---|---|---| | B2-1 | Room | — | `add_object_variable` ; `add_variable` ×2 | `CamRoom` : `CameraActor` (Q19) ; `C2Target` : Vector ; `C2Scripted` : bool, défaut faux (Q13) | | B2-2 | Room.EventGraph | modèle : `K2Node_SpawnActorFromClass_1` déjà présent dans Room (ROOM:248, `SelectUnderCursor` ROOM:191-198) ; post-traitement de `CameraScene` (CAM-AUDIT § 3b) | (N) événement `EnsureCamRoom` : `IsValid(CamRoom)` non valide → (N) `SpawnActorFromClass(CameraActor, transform du MC, AlwaysSpawn)` → `CamRoom` := → `CamRoom.CameraComponent.PostProcessSettings` := `GI.ChargenCameras[0].CameraComponent.PostProcessSettings` (Q17) ; valide → fin | « vide » normal avant la 1re pièce (garde écrite au CHG) | | B2-3 | Room.EventGraph | référence : `LPDirectControlSubsystem.cpp:931-938` | (N) événement `ComputeC2Target` : calcul § (e) de `Desired` (MC, capsule, os `head`, avant à plat) → (N) `SphereTraceSingle(Start = tête, End = Desired, Radius = 12, TraceChannel = Camera (TraceTypeQuery2, R19), bTraceComplex = faux, ActorsToIgnore = [GI.PlayerCharacter, CamRoom], DrawDebugType = None, bIgnoreSelf = vrai)` → Branche(`ReturnValue`) vrai → (N) `BreakHitResult` → `C2Target` := `Location + Normal × 12` ; faux → `C2Target` := `Desired` | 175 cm ; +50 cm ; 12 cm ; `ENG KismetSystemLibrary.cpp:1965` | | B2-4 | Room.EventGraph | — | (N) événement `PlaceC2` (initialisation, sans lissage) : `EnsureCamRoom` → `ComputeC2Target` → `K2_SetActorLocationAndRotation(CamRoom, C2Target, FindLookAtRotation(C2Target, tête))` | « lors de l'initialisation, à 1,75 m » | | B2-5 | Room.EventGraph | voie C2 provisoire de B1-4 | coupe et suppr. le `SetViewTargetWithBlend(ChargenCameras[0])` de la voie C2 ; relie `RestoreC1` → (N) `C2Scripted` := faux → (N) `PlaceC2` → (N) `SetViewTargetWithBlend(GI.PC, CamRoom, 0)` → `K2Node_CallFunction_329` (minuterie, B1-13) | le repli reste pour C3 jusqu'à B3 | | B2-6 | Room.EventGraph (`UpdateRoomCam`, B1-13) | entrée de `UpdateRoomCam` | insère en tête : (N) Branche(`GetViewTarget(GI.PC)` = `CamRoom`) vrai → Branche(`CamC3On`) faux → Branche(`C2Scripted`) vrai → fin ; faux → `ComputeC2Target` → (N) `Next` = `VInterpTo(CamRoom.K2_GetActorLocation, C2Target, GetWorldDeltaSeconds, 8)` → (N) `Rot` = `RInterpTo(CamRoom.K2_GetActorRotation, FindLookAtRotation(Next, tête), GetWorldDeltaSeconds, 8)` → `K2_SetActorLocationAndRotation(CamRoom, Next, Rot)` ; `CamC3On` vrai → voie de B3 ; vue ≠ `CamRoom` → garde de B1-13, inchangée (elle arrête la minuterie par `ClearandInvalidateTimerbyHandle(RoomCamTimer)`, B0a) | vitesse 8 (`.cpp:939`) ; `RInterpTo` ajouté (le C++ tourne directement, `.cpp:940`) ; `KismetMathLibrary.inl:1705`, `:862` ; `ENG GameplayStatics.cpp:2558` | | B2-7 | Room.EventGraph | aucun `EndPlay` dans Room (ROOM:2) | (N) surcharge `ReceiveEndPlay` → `IsValid(CamRoom)` vrai → `K2_DestroyActor(CamRoom)` | R10 | Arrêt de la minuterie : par la garde de B1-13 dès que la vue n'est ni `CamRoom` ni le MC en C1 (hors sexe) ; pendant une commande de scène (`C2Scripted` vrai) la minuterie tourne sans déplacer C2. ### CAM-B2b — confinement de C2 dans la pièce, repli, trajectoire protégée, pilotage (option 1) (amendement B2b) Origine : essais de B2 (« caméra trop proche près d'un mur », « perte de la commande de la caméra ») ; exigence de l'utilisateur « la caméra doit toujours être placée dans la pièce et jamais dehors » ; option 1 choisie par l'utilisateur. Justification et faits : `CAM-B2b-AMENDEMENT.md` (§ 2). Room seul. Identifiants : § « Identifiants de nœuds (B0a) » et `CAM-B2-journal.txt` (B2). | # | Graphe | Nœud existant ciblé | Modification | Variables / valeurs | |---|---|---|---|---| | B2b-0 | Room | — | `add_variable` ×5 | `C2BoxMin`, `C2BoxMax` : Vector ; `C2Yaw` : float (0) ; `C2Height` : float (0) ; `C2Dist` : float (0 ; décalage, distance = 175 + `C2Dist`, car `add_variable` ne règle pas le défaut) | | B2b-1 | Room.EventGraph | — | (N) événement `ComputeRoomBox` : `sol` = `StaticMeshComponent.GetWorldLocation`, `R` = `GetActorLocation(self)`, `Ex` = `Max(Abs(Wall2.X − sol.X) − 30, 0)`, `Ey` = `Max(Abs(Wall4.Y − sol.Y) − 30, 0)` (formule de `isPointInsideRoom`, marge 30) ; `ZTop` = `SelectFloat(1000000, WallT.Z − 25, StaticMeshComponent.RelativeScale3D.Z < 0,05)` ; `C2BoxMin` := (R.X − Ex, R.Y − Ey, sol.Z + 50) ; `C2BoxMax` := (R.X + Ex, R.Y + Ey, ZTop) | extérieur : Z sans borne haute ; boîte alignée sur les axes du monde (comme `isPointInsideRoom`) | | B2b-2 | Room.EventGraph (`ComputeC2Target`, CE47) | 47.then → 459 ; 458 → 459.End et → 62 ; 459.Radius 12 ; PO6 (12,12,12) | 47.then → (N) appel `ComputeRoomBox` → 459 ; `Desired` borné : (N) `VectorBoundedToBox(458, C2BoxMin, C2BoxMax)` → 459.End et 62 ; 459.Radius = **20** ; PO6 = (20,20,20) ; 61 reçoit `VectorBoundedToBox(PO7, Min, Max)` | rayon 20 ; retrait 20 | | B2b-3 | Room.EventGraph (`ComputeC2Target`) | sorties 61 et 62 | (N) après 61 et 62 : Branche(`Distance(C2Target, tête) < 80`) vrai → `Desired2` = `VectorBoundedToBox(tête + (0,0,100) + Bdir × 60, Min, Max)` → (N) `SphereTraceByChannel(tête → Desired2, 20, TraceTypeQuery2, ignorer [MC, CamRoom])` → `C2Target` := `VectorBoundedToBox(SelectVector(Hit.Location + Hit.Normal × 20, Desired2, ReturnValue), Min, Max)` ; faux → fin | repli vers le haut et vers l'intérieur ; `Bdir` = direction arrière de B2b-5 normalisée | | B2b-4 | Room.EventGraph (poursuite, `UpdateRoomCam`) | 467.then → 474 ; 468 → 474.NewLocation et 473.Start | 467.then → (N) `SphereTraceByChannel(tête → 468, 20, TraceTypeQuery2, ignorer [MC, CamRoom])` → 474 ; (N) `Safe` = `VectorBoundedToBox(SelectVector(Hit.Location + Hit.Normal × 20, 468, ReturnValue), Min, Max)` → 474.NewLocation et 473.Start | le lissage ne sort jamais de la boîte et ne passe pas derrière un obstacle Camera | | B2b-5 | Room.EventGraph (`ComputeC2Target`) | PO3 (−175 ×3) ; 457 → PO5 (+60) | `Bdir` = (N) `RotateVectorAroundAxis(Normalize2D(Fwd) × −1, C2Yaw, (0,0,1))` ; PO3 := `Bdir × (175 + C2Dist)` (remplace −175) ; Z := `tête.Z + 60 + C2Height` | option 1 ; à C2Yaw = 0, C2Height = 0, C2Dist = 0 : identique à B2 (correction E3 de F3) | | B2b-6 | Room.EventGraph (5 événements de déplacement) | `CircleCamera` CE1 → IfThenElse_26 ; `LeftRight` CE4 → IfThenElse_9 ; `RotateCamera` CE19 → IfThenElse_25 ; `DownUp` CE8 → IfThenElse_8 ; `ScrollDownUp` CE55 → IfThenElse_10 | pour chacun : coupe CE.then → branche existante ; (N) Branche(`GetViewTarget(GI.PC)` = `CamRoom`) vrai → réglage ; faux → branche existante (inchangée). Réglages : CircleCamera `C2Yaw` += D ; LeftRight `C2Yaw` += AxisValue × 2 ; RotateCamera `C2Height` := `Clamp(C2Height + D × 3, −40, 150)` ; DownUp `C2Height` := `Clamp(C2Height + AxisValue × 3, −40, 150)` ; ScrollDownUp `C2Dist` := `Clamp(C2Dist − Values × 15, −75, 125)` (distance 100 à 300 cm) | signes et gains ajustables à l'essai ; hors C2, comportement inchangé | Règle : toute position posée par CAM sur `CamRoom` (initialisation `PlaceC2`, poursuite, repli) passe par `VectorBoundedToBox(…, C2BoxMin, C2BoxMax)` ; `PlaceC2` pose sans lissage avant la bascule de vue (466), donc la première image de C2 est dans la pièce. C3 provisoire (`ChargenCameras[0]`) n'est pas couverte (ancienne vue) : B3 la remplace et doit utiliser la même boîte (B3-1 : `ComputeRoomBox` au lieu de `GetActorBounds`). V5 après B2b : `SphereTraceSingle` 3 ; `Vector_BoundedToBox` ≥ 4 ; `RotateAngleAxis` 1 ; `Vector_Distance` 1 ; événement `ComputeRoomBox` 1 ; `SetViewTargetWithBlend` 3 ; 5 événements de déplacement : `then` → nouvelle branche, `else` → branche d'origine. ### CAM-B2d — C2 sur la rotation de contrôle, comme le modèle Third Person d'Epic (amendement B2d) Origine : demande directe de l'utilisateur (« le personnage est difficile à manier »), analyse `CAM-B2d-TPS.md`, GO de Big Boss (2026-10-13). Room seul ; `LPDirectControlSubsystem` et Person non touchés (la marche lit déjà `GetControlRotation().Yaw`, `LPDirectControlSubsystem.cpp:887`). | # | Graphe | Nœud existant ciblé | Modification | Valeurs | |---|---|---|---|---| | B2d-1 | Room.EventGraph (`ComputeC2Target`) | 453 `GetActorForwardVector(MC)` → 454.A | coupe 453 → 454 ; (N) `GetControlRotation(GI.PC 262)` → (N) `GetForwardVector(rotateur)` → 454.A ; suppr. 453 | la direction arrière de C2 suit le lacet de contrôle (454 met à plat) | | B2d-2 | Room.EventGraph (`CircleCamera`, F6a) | 96.then → 67 `Définir C2Yaw` ← PO28 (292 + DeltaSeconds) | (N) `BreakRotator(GetControlRotation)` → Yaw → PO28.A (au lieu de 292) ; (N) `MakeRotator(Roll 0, Pitch 0, Yaw ← PO28)` → (N) `SetControlRotation(GI.PC 262)` ; 96.then → ce nœud ; suppr. 67 et 292 | lacet += delta souris | | B2d-3 | Room.EventGraph (`LeftRight`, F6a) | 97.then → 68 ← PO30 (293 + AxisValue × 2) | idem : Yaw du `BreakRotator` → PO30.A ; `MakeRotator(0, 0, PO30)` → (N) `SetControlRotation(262)` ; 97.then → ce nœud ; suppr. 68 et 293 | lacet += flèche × 2 | | B2d-4 | Room.EventGraph (`PlaceC2`) | CE48.then → 461 `EnsureCamRoom` | coupe ; CE48.then → (N) `SetControlRotation(262, MakeRotator(0, 0, BreakRotator(GetActorRotation(MC 261)).Yaw))` → 461 | à l'entrée en C2, la caméra part derrière le personnage | `C2Yaw` (288 → 497.AngleDeg) n'est plus écrit et reste à 0 : 497 garde sa place (aucun effet). Un seul `GetControlRotation` et un seul `BreakRotator` (purs) peuvent servir B2d-1 à B2d-3. V5 après B2d : `GetControlRotation` ≥ 1 ; `SetControlRotation` 3 ; `GetActorForwardVector` absent de `ComputeC2Target` ; variable `C2Yaw` écrite 0 fois ; critères de B2b inchangés. ### CAM-B3 — C3 intérieur, extérieur, enfilade (Room seul) | # | Graphe | Nœud existant ciblé (preuve) | Modification | Variables / valeurs | |---|---|---|---|---| | B3-1 | Room.EventGraph | `StaticMeshComponent` relu en ROOM:4050-4052 ; `WallT` en ROOM:4655 ; seuil `LPOutdoorRoomSubsystem.cpp:14, 55` | (N) événement `PlaceC3` : `EnsureCamRoom` → Branche(`Less(StaticMeshComponent.RelativeScale3D.Z, 0.05)`) faux → position intérieure ; vrai → position extérieure (§ (e)) → `K2_SetActorLocationAndRotation` (C2 n'étant plus attachée, aucun détachement) | 0,05 ; 380 cm ; marge 5 cm / 50 cm | | B3-2 | Room.EventGraph (`UpdateRoomCam`) | branche `CamC3On` vrai de B2-6 | relie → (N) `K2_SetActorRotation(CamRoom, Rc3)` (Rc3 § (e) : intérieur, regard vers la tête ; extérieur, lacet dans l'axe, tangage vers la tête) | Q5, Q6 décidées ; sans lissage (non demandé) | | B3-3 | Room.EventGraph | voie C3 provisoire de B1-4 | coupe et suppr. le repli ; relie `RestoreC1` → (N) `PlaceC3` → (N) `SetViewTargetWithBlend(GI.PC, CamRoom, 0)` → `K2Node_CallFunction_329` (minuterie de B1-13, déjà en place) | Room : 3 `SetViewTargetWithBlend` | ### CAM-B4 — commandes de scène → C2, correctif `CameraFocus` (Room, GI) | # | Asset / graphe | Nœud existant ciblé (preuve) | Modification | Valeurs | |---|---|---|---|---| | B4-1 | Room.EventGraph | — | (N) événement `UseC2` : `CamC1On` := faux → `CamC3On` := faux → `ApplyRoomCamera` ; (N) événement `ScriptC2` : `UseC2` → `C2Scripted` := vrai (la poursuite de C2 est suspendue jusqu'au prochain `ApplyRoomCamera`, qui remet `C2Scripted` à faux, B2-5) | Q13 décidée ; variable de B2-1 | | B4-2 | Room.EventGraph (`CameraFocus`) | `K2Node_CustomEvent_40` `then -> K2Node_CallFunction_320` ; 320 = `K2_SetActorRotation`, `self (vide)` = **acteur Room** (ROOM:4396-4399, 4435-4441) | coupe 40.then → 320.execute ; relie 40.then → (N) `ScriptC2` → 320.execute ; relie (N) `VariableGet CamRoom` → 320.self | corrige le pivot de Room | | B4-3 | Room.EventGraph (`CameraFocus`, point de départ du regard) | `K2Node_CallFunction_406` `K2_GetActorLocation` self ← `K2Node_VariableGet_133.SceneCamera` (ROOM:4416-4418 ; 133 en ROOM:2115) | coupe 133 → 406.self ; relie `VariableGet CamRoom` → 406.self | — | | B4-4 | Room.EventGraph (`CameraFocusPoint`) | `K2Node_CustomEvent_41` `then -> K2Node_CallFunction_404` ; 404 = `K2_SetActorRotation(SceneCamera)` (ROOM:4404-4410, 4431-4434) | coupe 41.then → 404.execute ; relie 41.then → (N) `ScriptC2` → 404.execute ; coupe 133 → 404.self ; relie `VariableGet CamRoom` → 404.self | — | | B4-5 | GI.CallFunction (`cameraMove`) | `K2Node_SwitchString_22.cameraMove -> K2Node_CallFunction_242` `K2_SetActorTransform` (GICF:13, 209, 6185-6193) ; self ← `K2Node_VariableGet_177.SceneCamera` (GICF:6182-6184), cible `K2Node_VariableGet_176.Room` (GICF:6179-6181) | coupe 22.cameraMove → 242.execute ; relie 22.cameraMove → (N) appel `Room_C::ScriptC2` (self ← 176) → 242.execute ; coupe 177 → 242.self ; relie (N) `VariableGet CamRoom` (cible 176) → 242.self ; suppr. 177 (orphelin) | `UpdateDOF` (GICF:6200-6203) inchangé | | B4-6 | GI.EventGraph (Q12 décidée : C2) | `K2Node_CallFunction_637` HideRender(0) `then -> K2Node_CallFunction_6127` (GI:5866-5870 ; amont `IfThenElse_117` SpaceOn faux, GI:23705-23709) | coupe 637.then → 6127.execute ; relie 637.then → (N) `VariableGet Room` + (N) appel `Room_C::UseC2` → 6127.execute (comme B1-9 ; C2 suit le MC tant qu'aucune commande de scène ne la fige) | — | | — | GI.CallFunction | `cameraFocus` → `IfThenElse_10` → `Room.CameraFocus` / `CameraFocusPoint` (GICF:210, 6418-6422, 6292-6299, 6466-6473) ; `cameraLock` / `cameraUnlock` (GICF:218-219, 6730-6739) | **inchangés** (le correctif est dans Room) | — | ### CAM-B5 — chaînes mortes et `AutoCamera` (CAM-T2 § 3.4 ; tête morte = `execute (vide)` ; Q14 et Q18 décidées) Règle : supprimer la tête morte, puis chaque nœud aval resté sans entrée d'exécution et chaque `VariableGet` resté sans sortie, après relecture `get_node_infos` (aucune suppression d'un nœud encore relié à un nœud vivant). | # | Asset / graphe | Nœud (preuve) | Modification | |---|---|---|---| | B5-1 | Room.EventGraph | `K2Node_CustomEvent_33` 'TestAutoCamera' vide (ROOM:3453-3455) et son appel `K2Node_CallFunction_307` (`execute -> VariableSet_5.then`, `then (vide)`, ROOM:3456-3459) | suppr. 307 puis 33 | | B5-2 | Room.EventGraph | `K2Node_CallFunction_26` DownUp, `execute (vide)` (ROOM:561-565) | suppr. | | B5-3 | Room.EventGraph | `K2Node_IfThenElse_1` (BuilderOn), `execute (vide)` (ROOM:389-393) | suppr. (+ aval resté mort, à relire : `CallFunction_23`, ROOM:417-418) | | B5-4 | Room.EventGraph | `K2Node_CallFunction_333` DrawDebugBox, `execute (vide)` (ROOM:3674-3677) | suppr. | | B5-5 | Room.EventGraph (Q14 décidée : supprimer) | `AutoCamera` : `K2Node_CustomEvent_32` et sa chaîne (ROOM:3406-3593, 3796-3872) ; appel `K2Node_CallFunction_363` (`execute -> IfThenElse_63.else`, ROOM:3873-3876) ; `MacroInstance_5.Exec` ← 363.then et 63.then (ROOM:1135-1136) | relie `IfThenElse_63.else` → `MacroInstance_5.Exec` ; suppr. 363 puis la chaîne ; la minuterie 329, réemployée par B1-13 (son lien de délégué vers `CustomEvent_32` y est déjà coupé), n'est pas supprimée | | B5-6 | PC.EventGraph | `K2Node_CallFunction_1077` `SetRelativeRotation(FirstPersonSM, 90,90,0)`, `execute (vide)` (PC:2055-2062) | suppr. | | B5-7 | PC.EventGraph | `K2Node_CallFunction_204` `AddWorldRotation(FirstPersonSM)`, `execute (vide)` (PC:1888-1895) | suppr. | | B5-8 | PC.EventGraph | `K2Node_CallFunction_61` Delay 1, `execute (vide)` (PC:1696-1702) | suppr. (aval `CallFunction_173` à relire) | | B5-9 | GI.EventGraph | `K2Node_CallFunction_682` `Room_C::refreshIdleDummies`, `execute (vide)`, `then -> CallFunction_2699` (GI:24030-24035) | suppr. (R7) | | B5-10 | GI.EventGraph | `K2Node_CallFunction_370` `StreetMap_Static_C::UpdateWall`, `execute (vide)` (GI:20939-20943) ; `VariableGet_269` (GI:20936-20938) | suppr. 370 ; 269 si orphelin ; `VariableGet_270` à relire | | B5-11 | GI.EventGraph | `K2Node_CallFunction_107` `SetActorHiddenInGame(Skylight)`, `execute (vide)` (GI:21049-21053) ; `VariableGet_279` (GI:21046-21048) | suppr. 107, puis 279 si orphelin | | B5-12 | Person.EventGraph | chaîne morte **établie à HEAD** : `K2Node_IfThenElse_70` `execute (vide)` → `K2Node_IfThenElse_71` (`GetViewTarget` = `Room.SceneCamera`, `CallFunction_296` / `_481`) → `K2Node_CallFunction_478` `K2_SetActorRotation(SceneCamera)` (PERS:13468-13472, 13482-13484, 13503-13529) | **aucune : Person non touchée (Q18 décidée)** ; chaîne laissée en place, inerte | | gardés | Room, PC | `CenterCameraToAction` (ROOM:2102-2104, R15) ; `K2Node_InputAction_51` (PC:563-566, R14) | aucun | ### CAM-B6 — suppression du C++ `FollowCamera` (Q15 décidée), après la VAL de B2 (portage en BP validé) | # | Fichier | Lignes (preuve) | Modification | |---|---|---|---| | B6-1 | `Source/LifePlay/LPDirectControlSubsystem.h` | `:12` (« La camera suiveuse n'est pas branchee »), `:19` include `Camera/CameraActor.h`, `:43` déclaration, `:63-67` membres `FollowView`, `BirdView` | retirer `:43`, `:63-67`, `:19` si plus utilisé ; `:12` réécrite (« la caméra suiveuse est portée en Blueprint dans Room, lot CAM ») | | B6-2 | `Source/LifePlay/LPDirectControlSubsystem.cpp` | `:573-577` destruction de `FollowView` ; `:897-941` définition ; includes `:15-16` | retirer ; includes retirés seulement si Grep `CameraActor|CameraComponent|UCameraComponent` = 0 dans le `.cpp` | Vérification : V9 ; aucun appelant à retirer (`FollowCamera` : 2 lignes, déclaration et définition, CAM-AUDIT § 5). ## (b) Fonctions et graphes touchés, par asset (arbitrage avec STE) | Asset | Graphe | Sous-lot | Nœuds existants touchés (liens coupés ou relus) | Nœuds (N) | |---|---|---|---|---| | **GI** | EventGraph, chaîne `OpenSpace` (`K2Node_CustomEvent_66`) | B1 | `K2Node_CallFunction_30` → `K2Node_CallFunction_645` (GI:23596-23600, 23624-23628) | `VariableGet Room`, appel `Room_C::ApplyRoomCamera` | | **GI** | EventGraph, chaîne `CloseContacts` (`K2Node_CustomEvent_734`) | B1 | `K2Node_CallFunction_3168` → `K2Node_CallFunction_1039` (GI:17470-17474, 18982-18986) | idem | | **GI** | EventGraph, fin de sexe | B1 | `K2Node_CallFunction_2699` → `K2Node_MacroInstance_3053` (GI:14505-14513, 6793-6796) | idem | | **GI** | CallFunction (fonction), `SwitchString_22.cameraMove` | B4 | `K2Node_CallFunction_242`, `K2Node_VariableGet_177` (suppr.), `K2Node_VariableGet_176` (GICF:6179-6193) | appel `Room_C::ScriptC2`, `VariableGet CamRoom` | | **GI** | EventGraph, démarrage de scène hors pièce | B4 (Q12 décidée) | `K2Node_CallFunction_637` → `K2Node_CallFunction_6127` (GI:5866-5870) | `VariableGet Room`, appel `Room_C::UseC2` (seul changement de GI dû à l'amendement : le nom de l'événement appelé ; la liste des points de GI est inchangée) | | **GI** | EventGraph, nœuds morts | B5 | suppr. `K2Node_CallFunction_682` (GI:24030-24035), `K2Node_CallFunction_370` (+ `VariableGet_269`) (GI:20936-20943), `K2Node_CallFunction_107` (+ `VariableGet_279`) (GI:21046-21053) | — | | GI | **non touchés** | — | `HideRender` (CustomEvent_471), `ShowRender` (519), `ZoomCharGen`, `OpenContacts` (531), édition de personnage, `ResetCameraAndPlayer`, `secondScreen`, `Sex`, `PanToBuilding`, `destroyDummy`, réglage DOF, variables de GI (aucune variable ajoutée à GI) | — | | Room | EventGraph | B1-B5 | `K2Node_CallFunction_290`, `K2Node_IfThenElse_63` (LoadRoom) ; `K2Node_CallFunction_329` (minuterie) ; `K2Node_CustomEvent_40`, `_41`, `K2Node_CallFunction_320`, `_404`, `_406`, `K2Node_VariableGet_133` ; morts B5-1 à B5-5 | événements `ApplyRoomCamera`, `CycleRoomCamera`, `RestoreC1`, `UpdateRoomCam` (B1) ; `EnsureCamRoom`, `ComputeC2Target`, `PlaceC2`, surcharge `ReceiveEndPlay` (B2) ; `PlaceC3` (B3) ; `UseC2`, `ScriptC2` (B4) ; variables `CamC1On`, `CamC3On`, `C1Hidden`, `RoomCamTimer` (B1 ; `WallWasHidden` retirée, B1b), `CamRoom`, `C2Target`, `C2Scripted` (B2) ; minuterie `K2Node_CallFunction_329` réemployée dès B1 | | PC | EventGraph | B1, B5 | `K2Node_IfThenElse_23.else` ; `K2Node_CallFunction_353` → `_194` ; morts `_1077`, `_204`, `_61` | Branche(SpaceOn), 2 appels Room | | Person | — | aucun (Q18 décidée) | chaîne morte établie à HEAD (PERS:13468-13529), laissée en place | — | | Birdview | — | aucun | — | — | | ContactMenu, MainMenu | — | aucun | — | — | | `CentralLondon.umap` | — | aucun | — | — | ## (c) Choix technique de chaque caméra, et preuve MCP | Caméra | Choix proposé | Ce que le MCP doit faire | Preuve | Variante sûre si non prouvé | |---|---|---|---|---| | C1 | composant existant `FirstPersonSM` de Person (aucun changement de Person) ; attache et vue posées par Room | `create_node` d'appels (`K2_AttachToComponent`, `K2_SetRelativeTransform`, `SetViewTargetWithBlend`, `SetVisibility`), `connect_pins`, `set_pin_value` | établi : mêmes familles d'outils utilisées en STB (`29_Projet_Stabilisation_V2.md:262-264`) ; nœuds de même type déjà dans PC (PC:3364-3396, 1788-1796) | — | | C2 / C3 | **un `CameraActor` créé à l'exécution par Room** (`SpawnActorFromClass` paresseux sous `IsValid`), tenu par la variable `CamRoom`, en poursuite du MC (C2, non attaché, Q4) ou posé au plafond (C3) ; aucune `.umap` écrite | `add_object_variable`, `add_variable`, `add_event`, `create_node` SpawnActorFromClass + broche `Class` | **établi par B0a** (`CAM-B0-IMPL.md` § 2 n° 2-7 ; `CamRoom.CameraComponent` par `Classe\|Acteurdecaméra\|ObtenirCameraComponent`, n° 22) ; avant B0 : non prouvé dans le projet (R1) ; code présent (`TS blueprint.py:1224-1251`, `:1154-1185`, `:324-366`, `:877-915`, `:1091`) ; un `SpawnActorFromClass` compile déjà dans Room (ROOM:248) | CAM-B0 ; si échec : retour à l'utilisateur (Q20 décidée) | | Poursuite de C2 (Q4) | minuterie de Room (nœud mort `K2Node_CallFunction_329` réemployé, `Time` 0,016, `bMaxOncePerFrame`) + `SphereTraceSingle` sur le canal Camera + `VInterpTo` / `RInterpTo` vitesse 8 ; pas de Tick (Room n'en a pas, ROOM:2) | `connect_pins` d'une broche `OutputDelegate` vers `Delegate`, `set_pin_value` (`Time`, `bMaxOncePerFrame`, `TraceChannel`), `create_node` des fonctions moteur | fonctions établies dans le moteur (`ENG KismetSystemLibrary.cpp:735`, `:796`, `:1965` ; `KismetMathLibrary.inl:862`, `:1705` ; `ENG GameplayStatics.cpp:2558`) ; un `K2_SetTimerDelegate` relié au délégué d'un événement existe déjà dans Room (ROOM:3643-3653) ; création et rebranchement par MCP **établis par B0a** (n° 8-14), arrêt par handle au lieu de `K2_ClearTimerDelegate` (n° 9, 9b) | CAM-B0 ; si échec : retour à l'utilisateur (Q20 décidée) | | (écartée) composant ajouté à Person | `ActorTools.add_component(Person, CameraComponent)` | code présent (`TS actor.py:392-484`, SCS par `SubobjectDataSubsystem`), **non prouvé** | une instance par Person (même défaut que `FirstPersonSM`, CAM-AUDIT § 4) ; ne convient pas à C3 ; Person est l'asset de STE-H4 | | (écartée) acteurs posés dans la carte | `SceneTools.add_to_scene_from_class` + enregistrement | code présent (`TS scene.py:102`, `:445`), **non prouvé** | écrit `CentralLondon.umap` (interdit § 5) ; références à poser par le script de niveau (non dumpable, R12) | | Commutation | deux booléens `CamC1On`, `CamC3On` (C2 = les deux faux) | Branche, `Définir` | établi (lp-mcp § 3) | — | | Test extérieur | `Less_DoubleDouble(échelle Z, 0,05)` | création d'un comparateur | **établi par B0a** (`Inférieurà(<)` → `float` vide pour le sous-lot ; ticket `TAG CAM-avant` au gardien avant B1 ; gel annoncé ; instantané ; MD5 de départ ; éditeur headless (`-ModelContextProtocolStartServer`, attente `Wait-HyperAIStudioMCP.ps1 -TimeoutSeconds 540`) ; `AssetTools.load_asset` ; `find_nodes` de chaque nœud existant du § (a) (noms revérifiés, R7) ; modifications ; V3 ; V4 ; `save_assets` ; `Stop-Process` de l'éditeur ; `Tools/UeState.ps1` = 0 ; V5, V6 ; build Development (V7) ; essais en jeu ; DOC (CHG) ; ticket `COMMIT`. | Sous-lot | Instantané | MD5 de départ attendus | |---|---|---| | B1 | `.\Tools\Snapshot.ps1 -Lot CAM-B1 -Path Content\BPs\Room.uasset, Content\BPs\PC.uasset, Content\BPs\GI.uasset` | Room `E8F58B2A4A79DA0D62D8FACC18411CE8`, PC `09F54974D093893EA43AE10056B37220`, GI `16394D438788E986AC23FAEB427402B8` (`CAM-T4-BUILD.md:79-81`) ; si STE a commité GI entre-temps, MD5 du commit de STE (R7) | | B2, B3 | `-Lot CAM-B2` (puis `CAM-B3`) `-Path Content\BPs\Room.uasset` | MD5 de Room après le commit du sous-lot précédent (relevé dans son IMPL) | | B4 | `-Lot CAM-B4 -Path Content\BPs\Room.uasset, Content\BPs\GI.uasset` | idem | | B5 | `-Lot CAM-B5 -Path Content\BPs\Room.uasset, Content\BPs\PC.uasset, Content\BPs\GI.uasset` (Person non touchée, Q18 ; son MD5 `D1B4C63D47621B1191AD9352C80C99CD` (après STE-H10, B0a) est contrôlé par V2) | idem | | B6 | `-Lot CAM-B6 -Path Source\LifePlay\LPDirectControlSubsystem.h, Source\LifePlay\LPDirectControlSubsystem.cpp` | git HEAD | (Paramètres de `Snapshot.ps1` : `-Lot`, `-Path` tableau, `-Recurse`, sauvegarde `D:\Game\LifePlay_Backup_5.5`, `Tools/Snapshot.ps1:17-22`.) Essais en jeu de l'utilisateur (build Development ; cases reprises par la VAL) : - [ ] Nouvelle partie (liste des villes) et Continuer ; Birdview au chargement, inchangée (flèches, molette, survol). - [ ] Pièce intérieure : entrée en C2 (B2+) ; C → C1 (vue de la tête ; cheveux, soleil et mur masqués), C → C2 (cheveux, soleil et mur rendus), C → C3 (plafond, B3+), C → C1 ; marche du MC : C2 suit en douceur (rattrapage ≈ 0,35 s, sans à-coup), C3 le regarde. - [ ] Évitement (Q4) : MC dos à un mur, puis près d'un meuble : C2 se rapproche du MC au lieu de passer derrière le mur ; aucun mur entre C2 et le MC. - [ ] C1, puis contacts (ou CharGen), puis retour en pièce : cheveux, soleil et mur rendus (R18). - [ ] Performance : V11. - [ ] Lieu extérieur (un des 7) : C3 à ≈ 3,80 m, dans l'axe long, au bout opposé au MC. - [ ] Sortie de pièce → Birdview ; réentrée → mode gardé (Q7). - [ ] Contacts ouverts puis fermés (avec et sans `NPCSelector`) ; CharGen et zoom (`ZoomCharGen` 0 → 1 → 2) : caméras du studio inchangées ; retour en pièce sur la caméra de pièce. - [ ] Constructeur de pièce : `CameraScene` (Q9) ; sortie → Birdview. - [ ] Sexe : C parcourt les POV du MC et des partenaires puis l'étape hors POV (Q8) ; `ResetCamera` ; fin de sexe → caméra de pièce. - [ ] Scène scriptée avec `cameraMove` / `cameraFocus` / `cameraFocusPoint` (si un script d'essai existe, R11) : C2 ; `cameraFocus` ne fait plus pivoter la pièce ; C2 reste où le script la pose et ne reprend sa poursuite qu'au changement de vue suivant (Q13) ; scène démarrée hors pièce : C2 qui suit le MC (Q12). - [ ] Sauvegarde puis rechargement : pas d'erreur, `CamRoom` recréée à la première pièce. - [ ] Journal : V8 = 0 ligne. ## (g) Durée de copie UE par sous-lot (pour la BUILD-DEMANDE à Big Boss) Bases mesurées : démarrage de l'éditeur ≈ 1 min et correctif STB de ~30 appels MCP ≈ 5 min (`29_Projet_Stabilisation_V2.md:264`) ; commandlet 41-56 s (`CAM-T4-BUILD.md:29-39`) ; compilation C++ 55-62 s (`U8-T5b-BUILD.md:22`, `U8-T2r-BUILD.md:10`) ; cuisson 2 min 17 s à 2 min 47 s (`U8-T3r-BUILD.md:40`, `U8-T3-BUILD.md:51`) ; mise en scène et archive 49 s (`U8-T4r-BUILD.md:63`). Estimé : ≈ 1 min d'appels MCP par nœud créé et relié, relecture comprise. | Sous-lot | Nœuds (N) estimés | MCP | Commandlets (V5, V6) | Build Development | Total copie UE (estimé) | |---|---|---|---|---|---| | B0 | ≈ 25 (jetables : minuterie, balayage, interpolations en plus) | 30-40 min | — | — | ≈ 40 min | | B1 | ≈ 70 (Room ≈ 60 dont `RestoreC1` et la garde de `UpdateRoomCam`, PC 4, GI 6) | 70-85 min | ≈ 5 min | ≈ 5 min | ≈ 1 h 35 | | B2 | ≈ 70 (`ComputeC2Target` ≈ 30, poursuite ≈ 15, `EnsureCamRoom` et post-traitement ≈ 10, `PlaceC2`, voie, `EndPlay` ≈ 15) | 70-85 min | ≈ 3 min | ≈ 5 min | ≈ 1 h 30 | | B3 | ≈ 45 | 45-55 min | ≈ 3 min | ≈ 5 min | ≈ 1 h | | B4 | ≈ 18 (Room 12, GI 6) | 20-25 min | ≈ 5 min | ≈ 5 min | ≈ 35 min | | B5 | 0 créé, ≈ 65 supprimés (chaîne `AutoCamera` ≈ 50, Q14) | 30-40 min | ≈ 5 min | ≈ 5 min | ≈ 50 min | | B6 | — (C++) | — | — | ≈ 6 min (compilation + paquet) | ≈ 10 min | Total ≈ 6 h 20 de copie UE (estimé ; ≈ 4 h 45 avant l'amendement), hors essais en jeu de l'utilisateur et mesure V11 ; GI est ouvert pendant B1, B4 et B5 seulement (inchangé). ## Identifiants de nœuds (B0a) Relevés par la sonde B0 dans l'éditeur 5.8 en français (`CAM-B0-IMPL.md` § 2). Les noms de catégorie sont localisés : relire par `find_node_types` (filtre court, sans `context_pins`) au début de chaque sous-lot ; un identifiant rendu **avec** `context_pins` peut être refusé par `create_node` (« does not exist », même avec `declaring_class`). | Besoin dans la SPEC | type_id à passer à `create_node` | Remarque | |---|---|---| | membre d'un autre BP (`GI.SexOn`, `GI.Room`, `GI.PC`, `GI.Wall`, `GI.PlayerCharacter`, `GI.BuilderOn`, `GI.SpaceOn`) | `Classe\|\|Obtenir` / `Définir` | prouvé sur `Classe\|CAMProbe\|ObtenirProbeBool` ; nom affiché sans `_` ni `_C` ; broche `self` (0) ← référence | | appel d'un événement ou d'une fonction d'un autre BP (`Room_C::ApplyRoomCamera`, `CycleRoomCamera`, `ScriptC2`, `UseC2`) | `Classe\|\|` | prouvé sur `Classe\|CAMProbe\|ProbeEvent` (`self` 1) ; R1 citait déjà `Classe\|\|` en STB | | `CamRoom.CameraComponent` (B2-2) | `Classe\|Acteurdecaméra\|ObtenirCameraComponent` | repli : `Acteur\|GetComponentbyClass` + `ComponentClass` = `/Script/Engine.CameraComponent` (sortie typée) | | `PostProcessSettings` (B2-2) | `Classe\|CameraComponent\|ObtenirPostProcessSettings` / `DéfinirPostProcessSettings` | `K2Node_VariableSet` : exécution 0, valeur 1, `self` 2 | | `SpawnActorFromClass` (B2-2) | `Jeu\|Faireapparaîtreunacteuràpartird'uneclasse` | `Class` (1) = `/Script/Engine.CameraActor` ; `ReturnValue` (1) typé caméra | | minuterie (B1-13) | nœud existant `K2Node_CallFunction_329` ; neuf : `Utilitaires\|Temps\|SetTimerbyEvent` | `Delegate` 1, `Time` 2, `bLooping` 3, `bMaxOncePerFrame` 4 ; `ReturnValue` 1 = handle | | arrêt de la minuterie (B1-13, B2-6) | `Utilitaires\|Temps\|ClearandInvalidateTimerbyHandle` | `Handle` (1, par référence) ← `Obtenir RoomCamTimer` ; `K2_ClearTimerDelegate` n'existe pas pour `create_node` | | `RoomCamTimer` (B1-1) | `add_struct_variable(struct_type = /Script/Engine.TimerHandle)` ; getters `Variables\|Pardéfaut\|Obtenir/DéfinirRoomCamTimer` | — | | `SphereTraceSingle` (B2-3) | `Collision\|SphereTraceByChannel` | `Radius` 3, `TraceChannel` 4 = `TraceTypeQuery2`, `ActorsToIgnore` 6, `DrawDebugType` 7, `bIgnoreSelf` 8 ; `OutHit` 1, `ReturnValue` 2 | | `BreakHitResult` | `Collision\|BreakHitResult` | entrée 0 | | `VInterpTo`, `RInterpTo` (B2-6) | `Math\|Interpolation\|VInterpTo` / `RInterpTo` | Current 0, Target 1, DeltaTime 2, InterpSpeed 3 | | `GetWorldDeltaSeconds` | `Utilitaires\|Temps\|GetWorldDeltaSeconds` | sortie double, acceptée sur DeltaTime float | | `FindLookAtRotation` | `Math\|Rotateur\|FindLookatRotation` | — | | `GetComponentBounds`, `GetActorBounds` | `Collision\|GetComponentBounds`, `Collision\|GetActorBounds` | broches non relues (nœuds non câblés en B0) | | `GetSocketLocation` (os `head`) | `Transformation\|GetSocketLocation` | idem | | `GetScaledCapsuleHalfHeight` | `Composants\|Capsule\|GetScaledCapsuleHalfHeight` | `self` 0, sortie float | | `Less_DoubleDouble`, `EqualEqual_ObjectObject`, + − × de vecteurs | `Utilitaires\|Opérateurs\|Inférieurà(<)`, `Égalà(==)`, `Ajouter`, `Soustraire`, `Multiplier` | typés à la 1re liaison (R2) | | surcharge `ReceiveEndPlay` (B2-7) | `add_event(event_name = "ReceiveEndPlay")` | rend un `K2Node_Event` | | Branche | `Utilitaires\|ContrôleDeFlux\|Branche` | exécution 0, Condition 1 ; then 0, else 1 (lp-mcp § 3) | Complément B1b : identifiants relevés pendant B1 (attache, transform, visibilité, `GetViewTarget`, tableau, appels `Classe|Room|…` depuis PC et GI) dans `CAM-B1-IMPL.md` § 2 ; `Variables|Rendu|ObtenirActorHiddenInGame` ne lit que l'acteur lui-même (aucune broche `self`), même avec `declaring_class`. ## Fichiers à commiter - A `Docs/Migration/etat/CAM/CAM-SPEC.md` (dépôt du jeu, branche `ue5.8`, avec les livrables du lot, sur ticket du gardien). Aucun autre fichier écrit. Tours : 29 / 60 (CAM-T5) ; CAM-T5b : 20 / 25 ## Constats - **Trous de CAM-T2 § 3.5 fermés ou réduits** : 1. Déclencheur de `OpenSpace` / `CloseSpace` : le BP `StreetMap_Static` (`GI_C::OpenSpace` depuis `IfThenElse_32.else` et après `PlayerMoving` := faux ; `GI_C::CloseSpace` après `CallFunction_71`, SMS:5709-5719, 5743-5749), plus un appel interne de GI (`K2Node_CallFunction_671`, GI:23971-23974). Établi (dump STB, version non comparée à HEAD). 2. Amont de `Room.AutoCamera` : `LoadRoom` (`K2Node_CustomEvent_14`, ROOM:1099-1102) → … → `currentRoom` → `clearObjects` → `previousScale` := échelle → `CallFunction_186` → murs (ROOM:4652-4665) → `MoveAllToCenterRoom` (ROOM:3330-3332) → `IfThenElse_63` « échelle inchangée » ; `else` → `AutoCamera` (ROOM:4059-4072, 3873-3876). Établi pour les maillons lus ; `CallFunction_377` → `VariableSet_6` et le contenu de `CallFunction_186` non lus (estimé : `AutoCamera` ne joue que si l'échelle de la pièce change). 3. Birdview relaie vers Room **quand `CheckIfCameraMovable` rend faux** : appels `Room_C::LeftRight`, `RotateCamera`, `CircleCamera`, `DownUp`, `ScrollDownUp` sur la sortie « faux » des branches `IfThenElse_24`, `_25`, `_26`, `_1`, `_0` (BV:511-555, 1538-1577) ; `Moveable` = ET(non `ActionBox.HasAnyChildren`, non `ScrollBoxOn`, non `CharGenOn`, `TopRight` visible, non `SpaceOn`) (BV:124-180) : en pièce, toujours relayé ; Room ne déplace alors que `SceneCamera`, invisible en C1-C3 : la décision Q10 (« rien ») ne demande aucun nœud (établi). Birdview écrit `GI.Birdview` à son `BeginPlay` : établi (`VariableSet_1937 GI` → `VariableSet_1 'Birdview'` := `Self`, BV:436-446). Birdview lit `PreventCameraMove` (BV4:480). 4. Écriture de `ChargenCameras` : **non fermé** (script du niveau ; aucun `VariableSet ChargenCameras` dans les dumps). 5. ContactMenu : `ReturnFromChargen` → `SelectCamera` → `CurrentCamera` := `SelectInt(2, 1, GI.isCreature(DummyPerson))` → `SetViewTargetWithBlend(GI.ChargenCameras[CurrentCamera])` (CM:5116-5118, 5334-5340, 5321-5333, 5168-5173, 5146-5154) : index 2 pour une créature, sinon 1, jamais 0 (établi) ; rôle CharGen, inchangé par la SPEC. 6. `ProjectionMode` non affiché, dump `FirstPerson` vide (CAM-T4) : sans effet (C1 garde le composant tel quel, FOV 90). - **Reclassement des 4 rôles de `ChargenCameras[0]`** : pièce (GI:23596, 17470 ; ROOM `LoadRoom`) → caméra de pièce (B1) ; scène (GICF:6182 ; ROOM `CameraFocus*` ; GI:5866) → C2 (B4, Q12) ; sexe (GI:14505 ; PC:1752) → caméra de pièce (B1, Q8) ; CharGen (GI:803-807, 3132-3140, 22424-22427 ; CM:5146-5173 ; `ResetSceneCamera`) → inchangé ; 5e rôle trouvé : constructeur (ROOM:28-36, déplacements ROOM:394-910) → inchangé (Q9, Q10). - Aucune décision du § 4b n'est contredite par un fait établi. Deux tensions, posées en questions : le cycle POV gardé finit aujourd'hui sur `CameraScene`, réservée au CharGen (Q8) ; « taille du MC » n'est portée par aucune donnée directe (capsule fixe de 88 cm de demi-hauteur pour toute Person, PCOMP:1768) (Q2). - Le but « moins de caméras » reste à 6 sortes avant comme après (Q19 décidée : un seul acteur ; FollowView retirée, `CamRoom` ajoutée). Les décisions Q6 (garder Birdview et les 3 caméras du studio) expliquent ce plancher. - Redumps avec broches CAM-T6 (Birdview, ContactMenu, Person) lus par l'amendement : R12 réduit (items 3 et 5 ci-dessus) ; chaîne de Person morte à HEAD (`IfThenElse_70 execute (vide)`, PERS:13468-13472), laissée en place (Q18). - Amendement CAM-T5b : C2 n'est plus attachée ; la minuterie de Room (nœud mort réemployé) sert dès B1 de garde pour C1 (R18) puis de moteur de la poursuite de C2 (B2) et du regard de C3 (B3) ; 7 nouveaux nœuds de fonctions moteur à prouver par la sonde B0 (R1) ; durée de copie UE ≈ 6 h 20 au lieu de ≈ 4 h 45 ; liste des points de GI inchangée. - `RCOMP` (composants de Room) vient de la copie 5.5 (2026-10-04) : un `LPDumpComponents -BP=Room -All` sur la copie 5.8 confirmerait `Box`, `WallT` et l'échelle de la racine (valeurs non utilisées comme constantes par la SPEC). - Méthode : lecture seule (Read, Grep, Glob) ; un Grep lancé avec un chemin erroné (`D:\Game\Launcher\..\LifePlay-master 5.8\...`) a rendu 0 résultat, refait avec le bon chemin (7 lignes `PreventCameraMove`).