LifePlay-Revival/Docs/Migration/etat/CAM/CAM-SPEC.md

676 lines
85 KiB
Markdown
Raw Normal View History

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`).