LifePlay-Revival/Docs/Migration/etat/CAM/CAM-SPEC.md
edujiro5972 8084622450 LifePlay UE 5.8 : état de développement au 2026-10-19
Tickets : REP1-T4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 22:57:42 +02:00

675 lines
85 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <mêmes chemins>` 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|<BP>|<Fonction>`, `Définir<Var>`), `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|<Classe>|Obtenir<Var>` (§ « 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<float`
en double, `Equal(Object)`, `vector+vector`, `vector-vector` ; `Multiplier` accepte vecteur × double). Relier l'entrée
avant `set_pin_value`. R2 levé ; le repli `WallT.bHiddenInGame` n'est plus nécessaire.
- **R3 `hyper_blueprint_debug_inspect` peut manquer** après relance de l'éditeur (mémoire du projet « unreal-mcp
reconnect »). Variante : `compile_blueprint` sans erreur + `CompileAllBlueprints` éditeur fermé + redump `LPDumpFunction`.
- **R4 Ordre des appels de vue.** La SPEC suppose que `HideRender` (GI:772-775) pose sa vue de façon synchrone avant de
rendre la main (chemin lu sans nœud latent : GI:13526-13531, 782-787, 30049-30063, 4452-4466, 3117-3125, 17489-17492,
24207-24209 ; maillon `CallFunction_38555` → `CallFunction_2919` → `CallFunction_5578` non lu). Si un nœud latent
existait, `HideRender` reprendrait la vue après `ApplyRoomCamera`. Contrôle : `get_connected_subgraph` sur
`K2Node_CustomEvent_471` en début de B1. Variante : appeler `ApplyRoomCamera` au bout de la chaîne de `HideRender`
(après `K2Node_CallFunction_693`, GI:24207-24209) sous garde `CurrentCamera` = 0 et `SpaceOn` (modifie `HideRender`).
- **R5 Évitement des murs (transformé par Q4)** : l'attache rigide est abandonnée ; C2 fait le balayage de `FollowCamera`
(`LPDirectControlSubsystem.cpp:933-938`). Reste : le balayage ne voit que ce qui bloque le canal Camera. Murs `Wall1-4`
de Room : profil `Custom`, `QueryAndPhysics`, tableau de réponses vide (= réponses par défaut, bloquer : estimé) ;
plafond `WallT` : `NoCollision`, Camera ignoré (RCOMP:17-19, copie 5.5) : sans effet, C2 reste sous le plafond ; mobilier
(acteurs d'objets de la pièce) : collision non lue. Contrôle en jeu § (f) (MC dos à un mur, puis près d'un meuble).
- **R6 Rendu** : un `CameraActor` neuf a le FOV 90 (`ENG Camera/CameraActor.cpp:25-26`) mais aucun réglage de
post-traitement ; les caméras du studio ont gamma 0,85, AO, DOF (CAM-AUDIT § 3b) ; le réglage DOF du menu n'écrit que
`ChargenCameras[0]` (GI:21058-21106 cité par CAM-T2). Traité par B2-2 : copie du post-traitement de
`CameraScene` à la création de `CamRoom` (Q17 décidée) ; un changement ultérieur du réglage DOF du menu ne suit pas
`CamRoom` (constat, hors demande).
- **R7 Concurrence avec STE** : GI (EventGraph, CallFunction) et Person sont des assets binaires partagés ; aucun nœud
commun avec `GI.destroyDummy` n'est identifié ici (non lu), mais un même fichier ne se fusionne pas : passages l'un
après l'autre, MD5 de départ relus à l'entrée de chaque sous-lot, noms de nœuds revérifiés par `find_nodes` (les noms
`K2Node_*` de la SPEC sont ceux des dumps V5 à HEAD `4283c13`). B5 retire un appel mort à `Room.refreshIdleDummies`
(GI:24030-24035), domaine voisin des pantins de STE : à signaler à l'arbitrage.
- **R8 Point du studio** : la base codée en dur `(-3131543, -14967213, 75)` n'apparaît qu'en `ROOM:3576` dans les dumps
V5 (Grep `-313[0-9]{4}|-1496[0-9]{4}` sur `*V5.txt` : 1 ligne) ; la position réelle de l'acteur Room n'est pas
établie. Les formules § (e) n'en dépendent pas (bornes lues à l'exécution) ; `AutoCamera` la garde si elle est
conservée (Q14).
- **R9 `FirstPersonSM` partagé** entre C1 et la POV de sexe : chacun le rattache à la tête avant usage (PC:3364-3396 ;
B1). `SetViewTarget(Person)` prend le premier composant caméra actif (`ENG Actor.cpp:3688-3700`,
`bFindCameraComponentWhenViewTarget = true` `:320`) ; Person n'a qu'un composant caméra (8 SCS relevés,
`CAM-T4-BUILD.md:143`) et `bAutoActivate = True` (`CAM-T4-BUILD.md:123`) : établi pour la classe, non vérifié par
instance de PNJ.
- **R10 Vie de Room** : si l'acteur Room est recréé (rechargement), `CamRoom` est perdue ; la création paresseuse sous
`IsValid` (§ (a) B2) la recrée. Acteur orphelin possible : option `ReceiveEndPlay` → `DestroyActor(CamRoom)` (B2, à
confirmer : Room n'a pas d'`EndPlay` dans `ROOM`, Grep « evenement ' » : seul `ReceiveBeginPlay`, ROOM:2).
- **R11 Commandes de scène sans contenu de test** : aucun module livré n'utilise `cameraMove` / `cameraFocus` /
`cameraLock` (CAM-AUDIT § 2) ; B4 sera `PASS-STATIC` sauf script de scène d'essai fourni (et `Mods/` vidé avant tout
build ou commit, lp-lot).
- **R12 Trous restants (réduits par les redumps CAM-T6 avec broches)** : **fermés** : conditions du relais Birdview →
Room (BV:124-180, 511-555, 1538-1577), index de `ReturnFromChargen` (CM:5116-5118, 5146-5173, 5321-5340), état mort de
la chaîne de Person à HEAD (PERS:13468-13529) (Constats) ; **restent** : qui remplit `GI.ChargenCameras` (script de
niveau, non dumpable), `ProjectionMode` non affiché, dump `FirstPerson` vide. Aucun ne bloque la SPEC.
- **R13 Build** : tout sous-lot invalide le candidat Shipping d'U8 : rien avant la porte T11 (décision Q7).
- **R14 `K2Node_InputAction_51`** (PC:563-566, sans liens) : supprimer un nœud d'entrée peut changer la consommation de la
touche ; gardé tant que son action n'est pas identifiée (`get_node_infos`).
- **R15 Événements sans appelant vu** (`CenterCameraToAction`, ROOM:2102-2104) : des appelants hors des dumps lus (widgets)
ne sont pas exclus ; gardé.
- **R16 Commandes manuelles** : en pièce, flèches, molette et souris passent par Birdview vers `Room.LeftRight`,
`RotateCamera`, `CircleCamera`, `DownUp`, `ScrollDownUp` (BV:1538-1577 ; relais pris sur la sortie « faux » de `CheckIfCameraMovable`, BV:511-555, qui rend
faux dès que `GI.SpaceOn` est vrai : `Moveable` = ET(non `ActionBox.HasAnyChildren`, non `ScrollBoxOn`, non `CharGenOn`,
`TopRight` visible, non `SpaceOn`), BV:124-180), qui déplacent `SceneCamera` (ROOM:394-910) : avec
C1-C3, ces commandes n'auraient plus d'effet visible (perte du réglage manuel de la vue, y compris pendant le sexe) : accepté pour un premier passage
(Q10 décidée), aucun nœud de déplacement modifié.
- **R17 Coût de la minuterie de pièce** (nouveau, Q4) : `UpdateRoomCam` tourne à ≈ 60 Hz (`Time` 0,016,
`bMaxOncePerFrame` vrai) tant qu'une caméra de pièce est la vue ; en C2, un `SphereTraceSingle` et ≈ 30 nœuds BP par
passage (estimé : bien moins de 0,1 ms par image ; non mesuré). Bornes : arrêt automatique dès que la vue n'est plus une
caméra de pièce (B1-13) ; aucun coût sur la carte ni en CharGen. Mesure : V11. Repli si le seuil est dépassé : `Time`
0,033 (30 Hz), sur décision.
- **R18 État masqué de C1** (nouveau, Q11) : C1 masque cheveux, soleil de scène et mur ; une sortie de C1 par un autre
chemin qu'`ApplyRoomCamera` (contacts, CharGen, sortie de pièce : `HideRender` / `ShowRender` non modifiés) les
laisserait masqués. Parade : drapeau `C1Hidden` et événement `RestoreC1`, appelés par les voies C2 / C3 et par la garde
de la minuterie quand la vue n'est plus le MC (B1-12, B1-13). Pendant le sexe la garde n'agit pas : la POV du PC gère
soleil et mur (PC:2343-2355, 1797-1809).
- **R19 Canal du balayage** (nouveau) : en BP, `SphereTraceSingle` prend un `ETraceTypeQuery` (`ENG
KismetSystemLibrary.cpp:1965`) ; `TraceTypeQuery2` = Camera est déduit de l'ordre des canaux marqués `TraceQuery="1"`
(`D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Engine\Classes\Engine\EngineTypes.h:1104-1105`, `:1276-1277` ;
`ENG Collision/CollisionProfile.cpp:303-377`) et d'aucune redéfinition dans `Config/DefaultEngine.ini` (Grep
`CollisionProfile|DefaultChannelResponses|TraceType` : 0 ligne) : estimé. Contrôle : `get_node_infos` montre « Camera »
sur la broche `TraceChannel` après `set_pin_value`.
### Questions : toutes décidées (`CAM-CIBLE.md` § 4c, « q4 lissage et évitement, le reste oui »)
| Q | Décision | Où dans la SPEC |
|---|---|---|
| Q1 | **décidée** : derrière le MC, 175 cm à l'horizontale | (e) C2 |
| Q2 | **décidée** : taille = `Z(head) − Z(sol) + 10 cm` | (e) C2 |
| Q3 | **décidée** : C2 regarde la tête | (e) C2 |
| Q4 | **décidée autrement que proposé** : poursuite lissée avec évitement des murs (portage BP de `FollowCamera`), pas d'attache | § 1, (a) B1-13, B2, (e) C2, R5, R17 |
| Q5 | **décidée** : C3 intérieure fixe, rotation qui suit le MC | (a) B3-2 |
| Q6 | **décidée** : enfilade au bout opposé au MC, lacet dans l'axe, tangage vers le MC | (e) C3 extérieur |
| Q7 | **décidée** : C2 à l'entrée ; mode gardé pendant la session, non sauvegardé | (d) |
| Q8 | **décidée** : étape hors POV du sexe = caméra de pièce (C2 si le mode est C1) | (a) B1-2, B1-8 |
| Q9 | **décidée** : constructeur et édition de personnage gardent `CameraScene` | (d) |
| Q10 | **décidée** : flèches, molette, souris sans effet en pièce (premier passage) | R16 |
| Q11 | **décidée** : C1 masque cheveux, soleil, mur ; pas de regard à la souris | (a) B1-3, B1-12, R18 |
| Q12 | **décidée** : scène démarrée hors pièce → C2 | (a) B4-6 |
| Q13 | **décidée** : `cameraMove` « détache » C2, c'est-à-dire suspend sa poursuite (drapeau `C2Scripted`) jusqu'au prochain changement de vue ; `cameraLock` / `cameraUnlock` inchangés | (a) B2-6, B4-1 |
| Q14 | **décidée** : `AutoCamera` supprimée | (a) B5-5 |
| Q15 | **décidée** : `FollowCamera` C++ supprimée, après le portage | (a) B6 |
| Q16 | **décidée** : sonde B0 accordée | (a) B0 |
| Q17 | **décidée** : post-traitement de `CameraScene` copié sur `CamRoom` | (a) B2-2 |
| Q18 | **décidée** : Person non touchée | § 2, (a) B5, V2 |
| Q19 | **décidée** : un seul `CameraActor` (`CamRoom`) pour C2 et C3 | (a) B2-1 |
| Q20 | **décidée** : si le MCP échoue, retour à l'utilisateur (aucune variante lancée d'office) | R1, R2, (c) |
Texte d'origine des questions (CAM-T5), gardé pour la trace :
- **Q1 C2, 1,75 m** : distance **horizontale** de 175 cm à l'axe du MC, **derrière** lui (sens opposé à son regard) ?
(proposé ; la distance totale vaut alors ≈ 185 cm pour 60 cm d'écart de hauteur, calcul).
- **Q2 Taille du MC** : `Z(os head) − Z(sol) + 10 cm` (proposé) ; la capsule ne convient pas (88 cm de demi-hauteur fixe
pour toute Person, PCOMP:1767-1769) ; autre choix : bornes du maillage.
- **Q3 C2 regarde** la tête du MC (proposé, comme `AutoCamera`, ROOM:3441-3447) ou le centre du corps ?
- **Q4 Suivi de C2** : attachée au MC, rigide, sans anti-collision (proposé, aucun Tick) ; ou poursuite lissée par
minuterie avec balayage anti-murs (portage BP de `FollowCamera`, plus de nœuds) ?
- **Q5 C3 intérieure** : position fixe au plafond, **rotation qui suit le MC** (proposé, d'après « 3 caméras suivent les
mouvements du MC », CAM-CIBLE § 1) ou regard fixe vers le bas ?
- **Q6 Enfilade** : caméra au bout de l'axe long **opposé au MC**, lacet fixe dans l'axe, tangage qui suit le MC
(proposé) ; ou regard entièrement vers le MC ?
- **Q7 Caméra à l'entrée dans une pièce** : C2 (proposé) ; mode gardé d'une pièce à l'autre pendant la session, non
sauvegardé (proposé) ?
- **Q8 Sexe** : le cycle POV actuel finit sur `CameraScene` (PC:1752-1760) que la décision Q6 réserve au CharGen ;
l'étape « hors POV » devient la caméra de pièce du mode courant, C2 si le mode est C1 (proposé) ?
- **Q9 Constructeur de pièce** (`EditBedroom`, ROOM:5-36) **et édition de personnage** : garder `CameraScene` (proposé) ?
- **Q10 Flèches, molette, souris en pièce** (R16) : déplacer la caméra active (C2/C3 ; 8 nœuds `SceneCamera` à
rebrancher dans ROOM:394-910) ou rien (proposé pour un premier passage) ?
- **Q11 C1 hors sexe** : masquer les cheveux (proposé, comme PC:2290-2297), le soleil de scène et le mur comme en POV
(PC:2346-2350, 1800-1804) ? Regard à la souris (non prévu : la souris pilote Birdview quand `SexCameraIndex` = 0,
PC:1813-1830) ?
- **Q12 Scène scriptée démarrée hors pièce** (`SpaceOn` faux → `HideRender(0)`, GI:5866-5870, 23705-23709) : C2 ou
`CameraScene` ?
- **Q13 `cameraMove`** détache C2 du MC (la caméra reste où le script la pose jusqu'au prochain changement de vue,
proposé) ; `cameraLock` / `cameraUnlock` (GICF:6730-6739, lus par Birdview BV4:480) : gardés tels quels (proposé) ?
- **Q14 `Room.AutoCamera`** (ROOM:3406-3593, 3796-3872), rendue sans objet en pièce : supprimer ou garder ?
- **Q15 `FollowCamera` C++** : supprimer le code (proposé, B6) ou le garder mort ?
- **Q16 Sonde CAM-B0** sur un BP jetable (non commité, déplacé vers la sauvegarde après) : accord ?
- **Q17 Post-traitement** : copier celui de `CameraScene` (gamma 0,85, AO, DOF) sur `CamRoom` à sa création ?
- **Q18 Chaîne morte de Person** (suivi par `SceneCamera`, PERS4:2605, 2614 ; à HEAD avec broches : PERS:13468-13529 ; L6f
PERS-L6f:34520-34580) : supprimer (touche
Person, asset de STE-H4) ou laisser ?
- **Q19 Un seul `CameraActor` (`CamRoom`) pour C2 et C3** (proposé : un seul est actif à la fois) ou deux ?
- **Q20 Variante si `SpawnActor` ou le test extérieur ne se créent pas par MCP** : (a) acteur posé dans
`CentralLondon.umap` (écrit la carte, non prouvé) ; (b) petite fonction C++ `BlueprintCallable` (voie mixte) ; (c) arrêt.
## 5. Interdits
- Aucune écriture de `.umap`, de Birdview, ContactMenu, MainMenu, Person (Q18 décidée), `Config/`.
- Ne pas modifier `HideRender`, `ShowRender`, `ZoomCharGen`, `OpenContacts`, `ResetSceneCamera`, `ResetCameraAndPlayer`,
`secondScreen`, ni `destroyDummy` (STE) ; ne pas toucher aux chaînes CharGen et édition de personnage (GI:750-752,
803-807, 3863-3866, 4397-4400, 22424-22427, 23975-23985).
- Aucune écriture par HyperAIStudio (lp-mcp règle 1) ; aucun appel MCP chevauché ; aucune écriture sans instantané ni
sans trace (fiche, CHG) ; gel de la copie annoncé à toutes les sessions (lp-mcp règle 7).
- Aucun sous-lot avant la porte T11 d'U8 ; aucune écriture dans GI pendant une fenêtre de STE ; un sous-lot = un commit,
jamais de regroupement ; aucune commande git qui écrit hors du gardien ; rien poussé.
- Aucun `IsValid` hors des états « vide » normaux nommés ici (`CamRoom` avant sa création) ; cascades de branches, pas de
AND / OR purs (lp-mcp règle 4).
- Aucune suppression d'événement personnalisé dont un appelant hors dumps n'est pas exclu (R15) ; aucun retour arrière
lancé sans accord.
## 6. Valeurs moteur lues dans le moteur installé (`chemin:ligne`)
| Valeur | Source |
|---|---|
| `ACameraActor` crée son `CameraComponent`, FOV 90 | `ENG Camera/CameraActor.cpp:25-26` |
| `UCameraComponent` : `FieldOfView` 90, `FirstPersonFieldOfView` 90 | `ENG Camera/CameraComponent.cpp:78-79` |
| FOV par défaut du gestionnaire de caméra : 90 | `ENG PlayerCameraManager.cpp:46` |
| `bFindCameraComponentWhenViewTarget = true` ; `CalcCamera` prend le premier composant caméra actif | `ENG Actor.cpp:320` ; `:3688-3700` |
| Capsule par défaut d'un `ACharacter` : rayon 34, demi-hauteur 88 (Person : 20 / 88, PCOMP:1767-1769) | `ENG Character.cpp:78` |
| `APlayerController::SetViewTargetWithBlend` | `ENG PlayerController.cpp:2819` |
| `USceneComponent::K2_AttachToComponent` ; `AActor::K2_AttachToComponent` | `ENG Components/SceneComponent.cpp:2276` ; `ENG Actor.cpp:2711` |
| `UKismetSystemLibrary::GetComponentBounds` | `ENG KismetSystemLibrary.cpp:2591` |
| `UKismetSystemLibrary::SphereTraceSingle(WorldContext, Start, End, Radius, ETraceTypeQuery TraceChannel, bTraceComplex, ActorsToIgnore, DrawDebugType, OutHit, bIgnoreSelf, …)` | `ENG KismetSystemLibrary.cpp:1965` |
| `UKismetSystemLibrary::K2_SetTimerDelegate(Delegate, Time, bLooping, bMaxOncePerFrame, …)` ; `K2_ClearTimerDelegate(Delegate)` ; `K2_ClearAndInvalidateTimerHandle` | `ENG KismetSystemLibrary.cpp:735` ; `:796` ; `:828` |
| `UKismetMathLibrary::VInterpTo(Current, Target, DeltaTime, InterpSpeed)` ; `RInterpTo(Current, Target, DeltaTime, InterpSpeed)` | `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Engine\Classes\Kismet\KismetMathLibrary.inl:1705` ; `:862` |
| `UGameplayStatics::GetWorldDeltaSeconds` | `ENG GameplayStatics.cpp:2558` |
| Canaux de requête : `ECC_Visibility`, `ECC_Camera` marqués `TraceQuery="1"` ; énumérés `TraceTypeQuery1`, `TraceTypeQuery2` ; table remplie dans l'ordre des canaux (R19) | `…\Runtime\Engine\Classes\Engine\EngineTypes.h:1104-1105`, `:1276-1277` ; `ENG Collision/CollisionProfile.cpp:303-377` |
| Référence du portage `FollowCamera` : visée pawn + 50 cm, recul 340 cm, sphère 12 cm sur `ECC_Camera` en ignorant le pawn, retrait 12 cm le long de la normale, `VInterpTo` vitesse 8, rotation directe vers la visée | `Source/LifePlay/LPDirectControlSubsystem.cpp:929-940` |
| Outils MCP Epic : `create` 177, `compile_blueprint` 199, `add_function_graph` 287-320, `add_event` 324-366, `add_function_param` 394, `get_node_infos` 597, `find_nodes` 681, `find_node_types` 752, `create_node` 877-915, `delete_node` 919, `connect_pins` 1058, `break_pins` 1074, `set_pin_value` 1091, `add_variable` 1154-1185 (types `bool`, `int`, `float`, `Vector`, `Rotator`, `Transform`…), `add_object_variable` 1224-1251, `remove_variable` 1372 | `TS blueprint.py` |
| `ActorTools.add_component` sur Blueprint = SCS via `SubobjectDataSubsystem`, puis compilation ; `remove_component` | `TS actor.py:392-484` ; `:488-538` |
| `SceneTools.add_to_scene_from_class` ; `save_actor` | `TS scene.py:102` ; `:445` |
| Jeu : seuil extérieur 0,05 sur l'échelle Z relative de la racine de `GI.Room`, plafond `WallT` | `Source/LifePlay/LPOutdoorRoomSubsystem.cpp:13-16`, `:55` |
| Jeu : touche C = `SwitchCamera` | `Config/DefaultInput.ini:118` (CAM-T3 § 3.4) |
## 7. Critères de VAL (commande → attendu)
| # | Commande | Attendu |
|---|---|---|
| V1 | `git -C "D:\Game\LifePlay-master 5.8" status --short -- Content Source` | seuls les fichiers du sous-lot (§ 2) |
| V2 | `Get-FileHash -Algorithm MD5` sur `Content\BPs\Birdview.uasset`, `Content\UI\ContactMenu.uasset`, `Content\Scenes\CentralLondon.umap`, `Content\BPs\Person.uasset` (Q18) | `AC4B231463C3FF61335A33B81300793C`, `369088B422E1D4F43788D91068114A64`, `6CE42F91DCAEE468E073A1CDE465C4B5`, `D1B4C63D47621B1191AD9352C80C99CD` (Birdview, ContactMenu, carte : `CAM-T4-BUILD.md:78-84` ; Person : après STE-H10, commit `5b58ffd`, B0a) |
| V3 | MCP : `get_node_infos` sur chaque nœud (N) et chaque nœud existant relié du § (a) | liaisons exactement celles du tableau (a) |
| V4 | MCP : `compile_blueprint` (Room, PC, GI) puis `hyper_blueprint_debug_inspect` (si présent, R3) | 0 erreur, 0 diagnostic |
| V5 | `.\Tools\RunCommandlet.ps1 -Lot CAM-Bn-VAL -EtatDir "D:\Game\LifePlay-master 5.8\Docs\Migration\etat\CAM" -Commandlet LPDumpFunction -Args "-BP=Room -Fn=* -Pins -Exec"` (idem `-BP=PC -Fn=EventGraph`, `-BP=GI -Fn=EventGraph`, `-BP=GI -Fn=CallFunction`) | code 0 ; Grep `evenement personnalise '(ApplyRoomCamera|CycleRoomCamera|RestoreC1|UpdateRoomCam)'` = 4 après B1 ; + `EnsureCamRoom`, `ComputeC2Target`, `PlaceC2` et `evenement 'ReceiveEndPlay'` après B2 ; + `PlaceC3` après B3 ; + `UseC2`, `ScriptC2` après B4 ; `AutoCamera` et `TestAutoCamera` absents après B5 ; `SetViewTargetWithBlend` : Room 3 après B1 (C1, repli C2, repli C3 : voies provisoires séparées, B1b), 3 après B2 (C1, C2, repli) et après B3 (C1, C2, C3) ; dans Room après B2 : `SphereTraceSingle` 1, `VInterpTo` 1, `RInterpTo` 1 ; `K2_ClearAndInvalidateTimerHandle` ≥ 1 et variable `RoomCamTimer` après B1 (B0a ; `K2_ClearTimerDelegate` 0) ; `K2Node_CallFunction_329` : `Delegate -> 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_<N>.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 <journal> -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<Var>` | établi (lp-mcp § 3) | — |
| Test extérieur | `Less_DoubleDouble(échelle Z, 0,05)` | création d'un comparateur | **établi par B0a** (`Inférieurà(<)` → `float<float`, n° 18) ; avant B0 : non prouvé (R2) | lecture de `WallT.bHiddenInGame` (masqué par le C++ en extérieur, `LPOutdoorRoomSubsystem.cpp:55-60`, mais avec un décalage d'une image possible), seulement sur accord ; sinon retour à l'utilisateur (Q20 décidée) |
## (d) Règle de priorité de vue et caméra par défaut
Le moteur garde le **dernier** `SetViewTargetWithBlend` (`ENG PlayerController.cpp:2819`) ; la SPEC place l'appel de la
caméra de pièce **après** les appels existants, dans les seuls contextes de pièce.
| Contexte (déclencheur) | Vue résultante | Mécanisme | Qui a le dernier mot |
|---|---|---|---|
| Chargement, sortie de pièce, fin du constructeur (`BeginAutoLoad`, `ShowRender`) | Birdview | inchangé (PC:521-529, GI:3144-3152) | Birdview hors pièce |
| Entrée en pièce (`OpenSpace` → `HideRender(0)`) | caméra de pièce du mode courant (C2 par défaut, Q7) | B1-9 | `ApplyRoomCamera` après `HideRender` |
| `LoadRoom` en pièce, hors constructeur et hors sexe | idem (replacée) | B1-6 | idem |
| Touche C hors sexe, en pièce | C1 → C2 → C3 | B1-7 | `CycleRoomCamera` |
| Touche C pendant le sexe | cycle POV actuel ; étape « hors POV » = caméra de pièce (C2 si mode C1) | PC inchangé + B1-8 (Q8) | PC |
| `ResetCamera` (PC) | caméra de pièce | B1-8 (même nœud 353) | — |
| Fin de sexe (`ChargenCameras[0]`, GI:14505) | caméra de pièce | B1-11 | `ApplyRoomCamera` |
| Contacts (`HideRender(1)`, GI:6230-6234) | `ChargenCameras[1]` | inchangé | menus |
| Fermeture des contacts avec `NPCSelector` | caméra de pièce | B1-10 | `ApplyRoomCamera` |
| CharGen, édition de personnage, `ZoomCharGen`, ContactMenu `ReturnFromChargen` → `SelectCamera` (CM:5116-5118, 5334-5340) | `ChargenCameras[0..2]` ; ContactMenu : index 2 pour une créature, sinon 1 (`SelectInt(2, 1, GI.isCreature(DummyPerson))`, CM:5321-5333, 5146-5173) | inchangé | menus |
| Constructeur (`EditBedroom` → `HideRender(0)`, ROOM:5-36) | `CameraScene` | inchangé (Q9) ; B1-6 ne s'applique pas sous `BuilderOn` | constructeur |
| `cameraMove`, `cameraFocus`, `cameraFocusPoint` | C2 (forcée, poursuite suspendue, Q13) | B4 (`ScriptC2`) | commande de scène |
| Scène démarrée hors pièce (GI:5866) | C2 (Q12 décidée) | B4-6 (`UseC2`) | — |
| `ResetSceneCamera` | `CameraScene` replacée, vue inchangée | inchangé (ROOM:1250-1264) | — |
| Toute vue posée hors des caméras de pièce (menus, carte, CharGen) pendant que la minuterie tourne | celle du menu | garde de `UpdateRoomCam` : `RestoreC1` puis `ClearandInvalidateTimerbyHandle(RoomCamTimer)` (hors sexe) (B1-13, B0a) | le menu ; la minuterie s'efface |
Caméra par défaut à l'entrée : **C2** (deux booléens faux), mode gardé tant que l'acteur Room vit, non sauvegardé (Q7 décidée).
## (e) Formules
**C2** (Q1-Q4 décidées, `CAM-CIBLE.md` § 4c : 175 cm derrière, hauteur = taille + 50 cm, regard vers la tête, poursuite
lissée avec évitement des murs) :
- `Pmc` = `GI.PlayerCharacter.K2_GetActorLocation()` (centre de capsule) ; `HH` = `CapsuleComponent.GetScaledCapsuleHalfHeight()`
(capsule de Person : 88, PCOMP:1767-1768) ; `Zsol` = `Pmc.Z − HH`.
- `Ztete` = `Mesh.GetSocketLocation('head').Z` (socket déjà lu par Room, ROOM:3441-3447) ; taille `T` = `Ztete − Zsol + 10`
(Q2 : +10 cm = os de la tête au sommet du crâne, estimé).
- `F` = `GetActorForwardVector()` mis à plat (`Z` = 0, normalisé ; le MC s'oriente dans le sens de sa marche,
`LPDirectControlSubsystem.cpp:868-871` cité par CAM-T3).
- `Desired` = `(Pmc.X − 175·F.X, Pmc.Y − 175·F.Y, Zsol + T + 50)` (Q1, Q2) ; `tête` = `Mesh.GetSocketLocation('head')`.
- Évitement (Q4, portage de `LPDirectControlSubsystem.cpp:933-938`) : `SphereTraceSingle(tête → Desired, rayon 12, canal
Camera, en ignorant le MC et CamRoom)` ; impact → `C2Target` = `Hit.Location + Hit.Normal·12` ; sinon `C2Target` =
`Desired`. Le balayage part de la tête (le C++ partait du pawn + 50 cm, `.cpp:931`).
- Initialisation (`PlaceC2`) : `CamRoom` posée directement en `C2Target`, rotation `FindLookAtRotation(C2Target, tête)` (Q3).
- Poursuite, à chaque passage de la minuterie (Δt = `GetWorldDeltaSeconds`, ≈ 1/60 s) : `Next` = `VInterpTo(Pcam, C2Target,
Δt, 8)` ; `Rot` = `RInterpTo(Rcam, FindLookAtRotation(Next, tête), Δt, 8)` ; `K2_SetActorLocationAndRotation(Next, Rot)`.
Le C++ applique la rotation directement (`.cpp:940`) ; `RInterpTo` est ajouté par l'amendement (vitesse 8, ajustable à
l'essai).
- Loi de lissage : chaque passage parcourt la fraction `min(Δt·8, 1)` de l'écart (loi lue pour l'interpolation d'un
scalaire, `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Core\Public\Math\UnrealMathUtility.h:1530` ; même loi
supposée pour `VInterpTo`, définie ailleurs : estimé) ; à 60 Hz : 13,3 % de l'écart par image, 95 % rattrapé en ≈ 21
images, ≈ 0,35 s (calcul).
- Écarts au C++ : recul 175 cm (C++ : 340), cible derrière l'avant du MC mis à plat (C++ : rotation de contrôle, tangage
borné −55 / −12°, `.cpp:929-930`), hauteur sol + T + 50 (C++ : déduite du tangage), visée sur la tête (C++ : pawn + 50 cm).
- Exemple (calcul) : MC de 170 cm, os de la tête à 160 cm → caméra à 220 cm du sol, 175 cm derrière, tangage
`−atan(60/175)` ≈ −19° (sans impact).
**C3 intérieur** (échelle Z de `StaticMeshComponent` ≥ 0,05) :
- `(O, E)` = `GetActorBounds(Room, false)` ; `(Ot, Et)` = `GetComponentBounds(WallT)` (`ENG KismetSystemLibrary.cpp:2591`).
- `Pc3` = `(O.X, O.Y, Ot.Z − Et.Z − 5)` : centre de la pièce, 5 cm sous la sous-face du plafond (« collée au plafond »).
- `Rc3` = `FindLookAtRotation(Pc3, tête du MC)`, recalculée toutes les 0,05 s tant que la vue est C3 (Q5).
- Ordres de grandeur (indicatifs, RCOMP:24, :30, copie 5.5) : `WallT` à Z relatif 2040, échelle relative (8, 8, 0,2)
(attribution de cette échelle à `WallT` estimée) ; plafond 408-816 cm au-dessus de la racine pour une échelle Z de
0,2-0,4 (CAM-T3 § 3.5, calcul).
**C3 extérieur = caméra de rue en enfilade** (échelle Z < 0,05, comme `LPOutdoorRoomSubsystem.cpp:55`) :
- `(O, E)` = `GetActorBounds(Room, false)` (en extérieur, murs et plafond sont écrasés à ≈ 1 % de leur hauteur : les bornes
sont celles du sol, estimé) ; axe `A` = X si `E.X ≥ E.Y`, sinon Y.
- `s` = −signe(`Pmc.A − O.A`) (bout opposé au MC ; −1 si égal) ; `Pc3` = `O + s·(E.A − 50)·Â`, `Z` = `Zsol + 380`.
- `Rc3` : lacet fixe le long de l'axe, vers `O` (enfilade) ; tangage vers la tête du MC, recalculé par la minuterie (Q6).
- Proposition alternative argumentée (Q6) : si un lieu extérieur est presque carré (`E.X ≈ E.Y`), l'axe est arbitraire ;
on peut alors viser le MC entièrement.
## (f) Instantanés, MD5, compilation, build, essais
Pour chaque sous-lot MCP, dans cet ordre (lp-lot, lp-mcp) : entrée `git status --short -- <chemins>` 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\|<Classe affichée>\|Obtenir<Var>` / `Définir<Var>` | 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\|<Classe affichée>\|<Fonction>` | prouvé sur `Classe\|CAMProbe\|ProbeEvent` (`self` 1) ; R1 citait déjà `Classe\|<BP>\|<Fonction>` 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`).