Fiche KEY2-IMPL-01 — Niveau N3 — Modèle prévu non précisé par la fiche (N3) | utilisé Opus 5.5 (claude-opus-5-5) — Agent A-IMPL-H (lp-impl) — Skills sk-protocol, ue-cpp-build, lp-build, lp-ue-compat (préchargés) ; sk-change-safety demandé par la fiche mais absent du contexte (instantané déjà pris par l'orchestratrice) # KEY2-IMPL-01 : panneau de touches K2, K5, K6, migration Version=2 et consommateurs C++ (2026-10-20) Ordre : GO de Big Boss, fiche transmise par l'orchestratrice. Hors copie UE : aucune compilation, aucun éditeur, aucun lancement. SPEC faisant foi : `KEY2-SPEC-01.md` révision 7 (§ 1 décisions 1 à 14 ; § 4, 5, 5b, 7, 8, 8b, 8c, 9, 10) et `KEY2-CART-02.md` § 2 à 5 et § 10. Niveaux de preuve : **établi** (fichier:ligne relu ou commande rejouée) ; **estimé** (déduit, à éprouver au build ou en jeu). ## 1. Fichiers modifiés ### 1.1 Empreintes et fins de ligne Commande : `md5sum` sur les 5 fichiers avant toute écriture, puis après ; référence = `scratchpad\key2_base` (copie de l'orchestratrice, MD5 identiques à la copie de travail au départ, établi). MD5 de départ de `LPCameraModeSubsystem.cpp` attendu par la fiche : `406fbf1d74eeff5b7af95998eaf2accf`, relevé identique (établi) : écriture autorisée. | Fichier | MD5 avant | MD5 après | Lignes avant → après | Fins de ligne (CR comptés par `tr -cd '\r' \| wc -c`) | |---|---|---|---|---| | `Config/DefaultInput.ini` | `20952b1fe933b283a414975f79a34317` | `1a8e89a1ac4acc462280e654ffe4ffd8` | 171 → 169 | CRLF gardé (169 CR / 169 lignes) | | `Source/LifePlay/LPKeyboardSubsystem.h` | `eed95e9860b26c5a7327ce4e3f90d8c8` | `06793e5749102b5a943ee8bbc8a1f625` | 68 → 86 | CRLF gardé (86 / 86) | | `Source/LifePlay/LPKeyboardSubsystem.cpp` | `5d60d3f8ffe721698820fe68e55f938c` | `94575a8eef58548484844ea3070a358e` | 958 → 1463 | CRLF gardé (1463 / 1463) | | `Source/LifePlay/LPCameraModeSubsystem.h` | `9aee8d04cf35c0c6eedf226e3b0a86c3` | `36feac2a77e13529a8ea689be74c954f` | 206 → 214 | LF gardé (0 CR) | | `Source/LifePlay/LPCameraModeSubsystem.cpp` | `406fbf1d74eeff5b7af95998eaf2accf` | `cd6427aa03bc6c530cfe7748eee2a4bb` | 2275 → 2380 | LF gardé (0 CR) | `git diff --no-index --numstat` contre la référence (ajouts, retraits) : DefaultInput.ini 5 / 7 ; LPKeyboardSubsystem.h 19 / 1 ; LPKeyboardSubsystem.cpp 519 / 14 ; LPCameraModeSubsystem.h 8 / 0 ; LPCameraModeSubsystem.cpp 105 / 0 (`105 insertions(+)`, aucun retrait). Toutes les écritures par l'outil Edit (aucun script, aucun `sed -i`). ### 1.2 `Config/DefaultInput.ini` (SPEC § 9 ; § 4, § 5, § 5b, § 8b) - Retirées (5 + 2) : `SpeedUp NumPadSix`, `SpeedDown NumPadFour` (anciennes l. 113-114), `TestScenes Tilde` (l. 121), `SpeedUp Equals`, `SpeedDown Hyphen` (l. 123-124), `MoveForward Z` (l. 155), `MoveRight Q −1` (l. 158). - Ajoutées : `+ActionMappings=(ActionName="ActionPanel",…,Key=Q)` après `Interact` ; `+AxisMappings` `CameraHeight` G (+1) / B (−1) et `CoupleMoveY` V (+1) / X (−1) après `PenisRotation`. - Gardées : `SpeedUp Add`, `SpeedDown Subtract`, `Favorite4/6` (Four, Six, NumPadFour, NumPadSix), `TestScenes Insert`, `ConsoleKeys`. Résultat : déplacement W S A D seul (préréglage QWERTY, décision 12). ### 1.3 `Source/LifePlay/LPKeyboardSubsystem.h` - Publics ajoutés (l. 40-50) : `ApplyLayout(const FString&)` (§ 4), `RefuseLeftClick()` et `CommitWheel(...)` (§ 7), `IsCoupleDragByKeys()` (accesseur lu par LPCameraModeSubsystem) et `SetCoupleDragByKeys(bool)` (§ 8b), `GetLayout()`. - Privés : `LoadSaved()` n'est plus `const` (il lit et migre la section) ; `ApplyPreset(bool, TArray&)` (l. 64-65) ; `IdleStatus()` ; membres `Layout` (défaut `QWERTY`) et `bCoupleDragKeys` (l. 83-85). ### 1.4 `Source/LifePlay/LPKeyboardSubsystem.cpp` | § SPEC | Lieu (lignes après) | Changement | |---|---|---| | § 4, § 5 | l. 36-74 (espace anonyme) | constantes `Kb*` : section `LPKeyboard`, `QWERTY` / `AZERTY` / `Custom`, `KbVersion = 2`, noms d'actions et d'axes ; `KbIsMoveGroup`, `KbLayoutPreset` (QWERTY : W +1, S −1, A −1, D +1, ActionPanel Q ; AZERTY : Z, S, Q, D, ActionPanel A ; Interact non touché), `KbGameReadKey` (LeftShift, RightShift, LeftControl, RightControl, F12, PageDown, Y, N, LeftMouseButton, RightMouseButton) | | § 8 | `ActionLabel`, `AxisLabel` | « Action Panel » ; « Camera Height Up / Down » ; « Couple Right / Left » | | § 8 | l. 198-249 `FBindGroup`, `GroupFor` | champs `Sub` et `SubTitle` ; section « Scene » (ordre 8) en 4 blocs : « Camera » (LeftRight, DownUp, ScrollDownUp, RotateCamera, SwitchCamera, CameraHeight), « Characters » (ManualAnimating, l'axe de rotation existant, CoupleMoveY), « Actions » (ActionPanel), « Other » (TestScenes, Recommended) | | § 7, 4 | `ChordText` | paramètre `bCmd`, préfixe « Cmd+ » ; appel de la ligne de touche mis à jour | | § 8 | l. 284-299 `KbAddMissing` ; l. 479-484 | ligne « (no key) » avec Add pour ActionPanel, CameraHeight ±, CoupleMoveY ± quand le fichier du joueur ne les a pas (écart E1) | | § 4 | l. 325-328, l. 352-388, l. 615-620, l. 686-689 | `SComboBox>` (QWERTY, AZERTY), bouton « Apply », texte « Layout: QWERTY / AZERTY / custom » en tête du panneau | | § 8, § 8b | l. 505-531 (Rebuild), l. 645-672 | tri par groupe puis bloc ; sous-titres de bloc ; option « Move couple with » (bouton « Mouse and arrows » / « Arrows only ») en tête du bloc « Characters » | | § 7, 1-3 | l. 703-804 (`FKeyboardProcessor`) | modificateur seul retenu à l'appui (hors répétition) et enregistré au relâchement (`HandleKeyUpEvent`) si rien d'autre n'a été pressé ; `HandleMouseButtonDownEvent` : droit, milieu, Thumb1, Thumb2 seuls, gauche seulement avec Shift / Ctrl / Alt / Cmd, sinon `RefuseLeftClick` ; `HandleMouseWheelOrGestureEvent` : `CommitWheel` (MouseScrollUp / MouseScrollDown) | | § 4, § 7, 5 | l. 1001-1040 (`CommitKey`) | groupe de déplacement touché (axe remplacé ou touche retirée d'un axe de déplacement) → `Layout = Custom` ; avertissement « Also used by the game: . » sans refus | | § 4 | l. 1059 (`RemoveBinding`) | retrait d'une liaison de déplacement → `Custom` | | § 4 | l. 1067-1144 | `ApplyPreset` (étapes 1 à 3 du § 4 : retire MoveForward, MoveRight, ActionPanel ; retire la même touche sans modificateur des autres actions et axes, avec annonce ; ajoute les 5 liaisons) ; `ApplyLayout` (+ `Layout`, `ApplyToPlayer` qui enregistre, ligne d'état, ligne `LogLifePlay`) | | § 7 | l. 1146-1161 | `RefuseLeftClick` (message, la capture continue) ; `CommitWheel` (refus pour un axe : les axes gardent `MouseWheelAxis`) | | § 8b | l. 1163-1172 | `SetCoupleDragByKeys` (enregistre aussitôt) | | § 4 | l. 1174-1187, appels dans `TakeEmbeddedPage` / `OpenKeyboardMenu` | `IdleStatus` : « Action Panel has no key: choose a layout and press Apply, or use Add. » si ActionPanel est sans touche | | § 4, décision 12 | l. 1252-1263 (`ResetToDefault`) | après DefaultInput, réapplique AZERTY si `Layout=AZERTY` (gardé) ; sinon `Layout = QWERTY` | | § 5, § 5b | l. 1306-1366, l. 1394-1398 (`LoadSaved`) | lit `Layout`, `CoupleDrag`, `Version` ; si `Version` < 2 (absente = 0) : retire les 5 liaisons exactes (SpeedUp NumPadSix, SpeedDown NumPadFour, SpeedUp Equals, SpeedDown Hyphen, TestScenes Tilde, modificateurs tous faux), ajoute ActionPanel=Q seulement si Q n'est lié à rien (actions et axes du fichier) et si ActionPanel n'y est pas, `Layout = Custom`, une ligne `LogLifePlay` avec le compte, puis réécrit le fichier | | § 4, § 5, § 8b | l. 1430-1433 (`SaveSaved`) | section `[LPKeyboard]` : `Layout`, `Version=2`, `CoupleDrag=Mouse|Keys` | ### 1.5 `Source/LifePlay/LPCameraModeSubsystem.h` (gel CAM2 : 8 lignes ajoutées en fin de classe, rien d'autre) Déclaration de `TickSceneKeys(APlayerController*, AActor* View, const UObject* Room, const TArray& Couple, bool bGrabbing, float Step)` et membres `SceneCameraLift`, `bLiftC3Was` (l. 206-213). ### 1.6 `Source/LifePlay/LPCameraModeSubsystem.cpp` (gel CAM2 : ajouts seulement, aucun bloc existant modifié ni déplacé) Blocs ajoutés : constantes KEY2 (l. 208-220 : `CameraHeightSpeed = 100` cm/s, borne relative `[-40, 150]`, `ECoupleMoveSpace` + constante unique `CoupleMoveSpace = World`, pas `CoupleMoveYStep = 5`) ; aide `ActionJustPressed` (l. 312-334, modificateurs exacts comme `InteractPressed`) ; un appel dans `TickSexCamera` (l. 1248-1249, en scène, hors menu, après `TickCoupleMouse`) ; fonction `TickSceneKeys` (l. 1344-1400) ; garde « Arrows only » dans `TickCoupleMouse` (l. 1627-1633, clic gauche seulement). Aides existantes réutilisées : `AxisValue`, `ReadObject`, `ReadBool`, `MappedKeys`, `PCLeftMouseSexOn`, `RoomSceneCamera`, `PCSexC3`, `LastSexView` ; garde de menu = le `return` existant de `TickSexCamera` sur `bMenuOnScreen`. Diff unifié complet contre la référence (`diff -u` ; code de sortie 1 = différences ; 0 ligne `-`) : ```diff --- key2_base/LPCameraModeSubsystem.cpp +++ Source/LifePlay/LPCameraModeSubsystem.cpp @@ -205,6 +205,20 @@ const float HelpIconSize = 28.f; const int32 HelpZOrder = 60; + // KEY2 (KEY2-SPEC-01 § 6, § 8b, § 8c) : touche d'action (meme evenement que G-g), hauteur de la camera de scene, + // couple sur Y. Actions et axes de DefaultInput.ini, remappables dans le menu Clavier. + const FName ActionPanelAction(TEXT("ActionPanel")); + const FName AxisCameraHeight(TEXT("CameraHeight")); + const FName AxisCoupleMoveY(TEXT("CoupleMoveY")); + const float CameraHeightSpeed = 100.f; // cm par seconde (a regler a l'essai) + // Hauteur ajoutee bornee comme C2Height de Room ([-40, 150], KEY2-CART-02 § 5), depuis le dernier recadrage. + const float CameraHeightMin = -40.f; + const float CameraHeightMax = 150.f; + // Repere de CoupleMoveY (Q12, decision 14) : World = Y du monde (comme le X de PC), Couple = droite du premier acteur. + enum class ECoupleMoveSpace : uint8 { World, Couple }; + const ECoupleMoveSpace CoupleMoveSpace = ECoupleMoveSpace::World; + const float CoupleMoveYStep = 5.f; // par unite d'axe et par image, comme le X de PC (KEY2-CART-02 § 5) + // Copie de LPDirectControlSubsystem.cpp (MenuClasses) : menus pendant lesquels le texte est masque. const TCHAR* const MenuClasses[] = { TEXT("Journal_C"), TEXT("MainMenu_C"), TEXT("CityMenu_C"), TEXT("StatMenu_C"), @@ -295,6 +309,30 @@ return FMath::Clamp(Value, -1.f, 1.f); } + // KEY2 : une touche de l'action Name vient d'etre pressee, modificateurs exacts (comme InteractPressed et + // ModifiersMatch de LPDirectControlSubsystem.cpp), mappages et remappages suivis. + bool ActionJustPressed(const APlayerController* PC, FName Action) + { + const UInputSettings* Settings = UInputSettings::GetInputSettings(); + if (!PC || !PC->PlayerInput || !Settings) + { + return false; + } + const bool bCtrl = PC->IsInputKeyDown(EKeys::LeftControl) || PC->IsInputKeyDown(EKeys::RightControl); + const bool bShift = PC->IsInputKeyDown(EKeys::LeftShift) || PC->IsInputKeyDown(EKeys::RightShift); + const bool bAlt = PC->IsInputKeyDown(EKeys::LeftAlt) || PC->IsInputKeyDown(EKeys::RightAlt); + const bool bCmd = PC->IsInputKeyDown(EKeys::LeftCommand) || PC->IsInputKeyDown(EKeys::RightCommand); + for (const FInputActionKeyMapping& Map : Settings->GetActionMappings()) + { + if (Map.ActionName == Action && Map.bCtrl == bCtrl && Map.bShift == bShift && Map.bAlt == bAlt && Map.bCmd == bCmd + && PC->PlayerInput->WasJustPressed(Map.Key)) + { + return true; + } + } + return false; + } + // Noms des touches du clavier de l'action ou de l'axe Name, mappages en cours (comme AxisValue, remappages suivis) ; // Sign : 1 ou -1 = echelle de l'axe de ce signe, 0 = toutes. Souris, manette et axes analogiques exclus. FString MappedKeys(FName Name, int32 Sign) @@ -1207,6 +1245,8 @@ // Prise du couple a la souris (B2p-D) ; pendant la prise, la camera est figee et ZQSD ne la deplace pas. const bool bGrabbing = TickCoupleMouse(GI, PC, View, Room, Couple, Pivot); const AActor* SceneCamera = Cast(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomSceneCamera)); + // KEY2 (KEY2-SPEC-01 § 8c) : touche d'action, hauteur de la camera de scene, couple sur Y. + TickSceneKeys(PC, View, Room, Couple, bGrabbing, Step); if (!bGrabbing && bKeys && Couple.Num() > 0 && View == SceneCamera) { // Mode Scene : la camera tourne autour du couple du meme angle que son lacet (cadrage garde, comme B2n), @@ -1301,6 +1341,64 @@ bMiddleWasDown = bMiddle; } +void ULPCameraModeSubsystem::TickSceneKeys(APlayerController* PC, AActor* View, const UObject* Room, const TArray& Couple, bool bGrabbing, float Step) +{ + // KEY2 (§ 8c, 1) : la touche d'action bascule le panneau d'action, par le meme evenement que TickPanelClick. + if (LPCameraModeLocal::ActionJustPressed(PC, LPCameraModeLocal::ActionPanelAction)) + { + UFunction* PanelToggle = PC->FindFunction(LPCameraModeLocal::PCLeftMouseSexOn); + if (PanelToggle && PanelToggle->NumParms == 0) + { + UE_LOG(LogLifePlay, Log, TEXT("Scene : panneau d'action bascule (ActionPanel, touche %s)"), *LPCameraModeLocal::MappedKeys(LPCameraModeLocal::ActionPanelAction, 0)); + PC->ProcessEvent(PanelToggle, nullptr); + } + else + { + UE_LOG(LogLifePlay, Log, TEXT("Scene : panneau d'action non bascule, evenement LeftMouseSexOn absent du PC")); + } + } + + // KEY2 (§ 8c, 5) : CameraHeight monte ou baisse SceneCamera ; hauteur ajoutee bornee, rien pendant une prise. + AActor* LiftCamera = Cast(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomSceneCamera)); + const bool bLiftC3 = LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCSexC3); + if (!IsValid(LiftCamera) || View != LiftCamera || LastSexView.Get() != LiftCamera || (bLiftC3 && !bLiftC3Was)) + { + // Hors vue Scene, ou recadrage a venir (entree en Scene, passage en C3) : la camera reprend sa hauteur par defaut. + SceneCameraLift = 0.f; + } + else if (!bGrabbing) + { + const float HeightAxis = LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisCameraHeight); + if (!FMath::IsNearlyZero(HeightAxis)) + { + const float NewLift = FMath::Clamp(SceneCameraLift + HeightAxis * LPCameraModeLocal::CameraHeightSpeed * Step, + LPCameraModeLocal::CameraHeightMin, LPCameraModeLocal::CameraHeightMax); + if (!FMath::IsNearlyEqual(NewLift, SceneCameraLift)) + { + LiftCamera->AddActorWorldOffset(FVector(0.0, 0.0, static_cast(NewLift - SceneCameraLift))); + SceneCameraLift = NewLift; + } + } + } + bLiftC3Was = bLiftC3; + + // KEY2 (§ 8c, 4) : CoupleMoveY, piece cachee seulement (meme garde que le X de PC), pas fixe par image. + const AActor* MoveRoom = Cast(Room); + const float MoveAxis = LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisCoupleMoveY); + if (bGrabbing || FMath::IsNearlyZero(MoveAxis) || Couple.Num() == 0 || !MoveRoom || !MoveRoom->IsHidden()) + { + return; + } + const FVector CoupleRight = Couple[0]->GetActorRightVector(); + const FVector Side = LPCameraModeLocal::CoupleMoveSpace == LPCameraModeLocal::ECoupleMoveSpace::World + ? FVector(0.0, 1.0, 0.0) : FVector(CoupleRight.X, CoupleRight.Y, 0.0).GetSafeNormal(); + const FVector MoveStep = Side * static_cast(MoveAxis * LPCameraModeLocal::CoupleMoveYStep); + for (AActor* Person : Couple) + { + Person->SetActorLocation(Person->GetActorLocation() + MoveStep); + } +} + void ULPCameraModeSubsystem::KeepMouseFree(APlayerController* PC, float DeltaTime) { // COMMUN-12 : a la fermeture du panneau, le PC repasse en GameOnly (CapturePermanently, LockOnCapture, mouvement @@ -1526,6 +1624,13 @@ { // Maj tenu : la voie d'origine (Maj + bouton + souris, Room) garde la main, aucune prise. const bool bShiftHeld = Input && (Input->IsPressed(EKeys::LeftShift) || Input->IsPressed(EKeys::RightShift)); + // KEY2 (KEY2-SPEC-01 § 8b, decision 13) : « Arrows only » (CoupleDrag=Keys), le clic gauche ne prend plus le + // couple et reste au jeu ; le clic droit garde la rotation. + const ULPKeyboardSubsystem* DragKeys = GI->GetSubsystem(); + if (DragKeys && DragKeys->IsCoupleDragByKeys() && bLeftPressed) + { + return false; + } if ((!bLeftPressed && !bRightPressed) || bShiftHeld) { return false; ``` ### 1.7 API moteur relues (UE 5.8, `D:\Game\Launcher\Engine\UE_5.8\Engine\Source`, établi) - `IInputProcessor` : `HandleKeyUpEvent` (`Runtime/Slate/Public/Framework/Application/IInputProcessor.h:29`), `HandleMouseButtonDownEvent` (:38), `HandleMouseWheelOrGestureEvent(FSlateApplication&, const FPointerEvent&, const FPointerEvent*)` (:47). Les préprocesseurs passent avant les widgets et un `true` arrête l'événement : `SlateApplication.cpp:5097`, `:5324`, `:6232`. - `FPointerEvent::GetEffectingButton` (`Runtime/SlateCore/Public/Input/Events.h:1078`), `GetWheelDelta` (:1081) ; `FInputEvent::IsRepeat` (:210), `IsShiftDown` (:220), `IsControlDown` (:244), `IsAltDown` (:268), `IsCommandDown` (:292). - `SComboBox` : `SLATE_ITEMS_SOURCE_ARGUMENT(OptionType, OptionsSource)` (`Runtime/Slate/Public/Widgets/Input/SComboBox.h:151`, surcharge `const TArray*` dans `Widgets/Views/IItemsSource.h:32`), `OnSelectionChanged` / `OnGenerateWidget` (:152-153 ; signatures `Framework/SlateDelegates.h:157-162` et `:205-210`), `InitiallySelectedItem` (:162), slot de contenu (:137). Styles « ComboBox » et « ComboBox.Row » présents dans le style du jeu (`Runtime/SlateCore/Private/Styling/StarshipCoreStyle.cpp:1595`, `:1646`). - `EKeys::MouseScrollUp/Down` (`Runtime/InputCore/Classes/InputCoreTypes.h:352-353`, détails `MouseButton | ButtonAxis` : `InputCoreTypes.cpp:539-540`), `ThumbMouseButton`, `ThumbMouseButton2` (`.h:359-360`) ; touches de modificateur = Shift, Ctrl, Alt, Cmd gauches et droites (`InputCoreTypes.cpp:646-653`). - `FConfigFile` (`Runtime/Core/Public/Misc/ConfigCacheIni.h:574`) : `GetString(Section, Key, FString&)` (:815), `GetInt(Section, Key, int32&)` (:833), `AddToSection(Section, FName, const FString&)` (:945). - `UPlayerInput::WasJustPressed` lit `EventCounts[IE_Pressed]` (`Runtime/Engine/Private/UserInterface/PlayerInput.cpp:2111-2130`), remis à zéro au début de `ProcessInputStack` (:1285, :1335) : lisible par un ticker du noyau après le tick du monde, comme `InteractPressed` (`LPDirectControlSubsystem.cpp:189-204`, ticker 0 s `:832`) (estimé pour l'ordre exact des ticks). ## 2. Écarts à la SPEC (obligatoire) 1. **E1, ajout** : lignes « (no key) » avec Add pour ActionPanel, CameraHeight ± et CoupleMoveY ± quand aucune liaison ne les porte (`KbAddMissing`). Motif : le menu ne montre que les liaisons existantes ; la migration § 5 n'ajoute ni CameraHeight ni CoupleMoveY, et ActionPanel reste sans touche si Q est pris : sans ces lignes, ces entrées seraient invisibles et non remappables pour un joueur déjà équipé d'un `LPKeyboard.ini`. 2. **E2, interprétation** : le bloc caméra quitte le groupe « Camera » (rang 1) pour la section « Scene » (rang 8, place de l'ancien groupe Scene), comme le demande l'ordre du § 8 ; la caméra apparaît donc plus bas dans la liste qu'avant. 3. **E3, sous-titres** : blocs « Camera », « Characters », « Actions » (ActionPanel seul), « Other » (TestScenes, Recommended, que la SPEC ne place pas). Libellés neutres seulement (décision 10). 4. **E4, Reset d'un joueur Custom** : après Reset, les touches sont celles de DefaultInput (QWERTY) et `Layout=QWERTY` est écrit. « Layout est gardé » (§ 4) est appliqué au seul cas AZERTY. 5. **E5, clic gauche seul pendant une capture** : refusé avec message et **consommé** (la capture continue, Escape l'annule). Choix : un clic laissé à l'interface pendant la reconstruction du panneau aurait pu viser un widget détruit. 6. **E6, option « Move couple with »** : un bouton bascule qui affiche la valeur en cours (« Mouse and arrows » / « Arrows only »), pas une liste ; enregistré aussitôt (`CoupleDrag=Mouse|Keys`). 7. **E7, CameraHeight** : translation verticale seule de SceneCamera (sans réorientation) ; vitesse 100 cm/s (SPEC : à fixer à l'essai) ; borne `[-40, 150]` sur la hauteur ajoutée depuis le dernier recadrage (remise à zéro hors vue Scene, à l'entrée en Scene et au passage en C3). L'avance ou le recul (MoveForward) met à l'échelle le décalage de la caméra : la hauteur ajoutée réelle peut s'écarter un peu du compteur (estimé). 8. **E8, CoupleMoveY** : ignoré pendant une prise à la souris (prudence, non demandé) ; aucune borne en Y (le X de PC n'est borné qu'en X). 9. **E9, CoupleDrag=Keys** : un appui gauche et droit dans la même image ne prend rien (cas limite ; garde ajoutée sans toucher à la ligne `CoupleGrabMode = bLeftPressed ? 1 : 2`). 10. **E10, migration** : `Version` absente vaut 0 ; ActionPanel n'est ajouté que si le fichier porte des actions (sinon les actions de DefaultInput, Q compris, restent) ; « Q lié à rien » est testé sur toutes les liaisons du fichier, tous modificateurs confondus. 11. **E11, Custom** : seul le groupe de déplacement (MoveForward, MoveRight) passe la disposition en Custom ; changer ActionPanel à la main ne la change pas (lecture stricte du § 4). 12. **E12, journal** : une ligne `LogLifePlay` à chaque Apply (« Clavier : disposition … appliquee »), non demandée. ## 3. Instantané Pris par l'orchestratrice avant la fiche : `Tools\Snapshot.ps1 -Lot KEY2-IMPL` (manifeste non relu ici) ; copie de référence `scratchpad\key2_base` (5 fichiers, MD5 = colonne « avant » du § 1.1, relevés au départ). Retour arrière proposé seulement : `Tools\Restore.ps1 -Lot KEY2-IMPL`, ou recopie des 5 fichiers de `key2_base` (attention : `LPCameraModeSubsystem.*` portent aussi les modifications non commitées de CAM2). ## 4. Ce qui n'a pas été fait - Aucune compilation ni aucun essai (fiche) : tout le code est « proposé, non compilé ». - Texte d'aide du jeu `BuildControlsHelp` (`LPCameraModeSubsystem.cpp`) non mis à jour (Arrows only, touche d'action, G / B, V / X) : fichier en gel, hors des blocs demandés. - Migration : pas d'ajout de CameraHeight G / B ni de CoupleMoveY V / X aux fichiers existants (non prévu au § 5) ; voir E1. Question pour Big Boss : faut-il les ajouter si les touches sont libres, comme ActionPanel ? - ActionPanel hors scène (`UI/Action`) : non (§ 8c, 1). Remappage de Y / N : build 2 (décision 11). `ConsoleKeys` non touchés (§ 5b). - Aucun Blueprint modifié ; nom technique de l'axe de rotation dans le C++ (§ 8, signalé) non traité. ## 5. Points à confirmer en VAL (un par ligne) - V1 Build `LifePlayEditor` puis Development (`Tools\Build.ps1`) à code 0, aucun avertissement nouveau ; `LPKeyboardSubsystem.cpp` et `LPCameraModeSubsystem.cpp` dans les unités compilées. - V2 Compilation de `SComboBox>` : `.OptionsSource(&LayoutOptions)` (choix de surcharge), `OnGenerateWidget_Lambda` et `OnSelectionChanged_Lambda` (paramètres `TSharedPtr` par valeur). - V3 Unity : noms `Kb*` uniques (grep sur `Source/` et `Plugins/` : seul `LPKeyboardSubsystem.cpp`) ; aucun C2374 / C2084. - V4 Aucun C4456 / C4458 (ombrage) ni C4702 (constante `CoupleMoveSpace` en ternaire, comme `bSexWheelZoom`) avec les réglages V7 de la copie 5.8. - V5 Initialisations d'agrégat `FBindGroup` à 4 champs (`{KbSceneOrder, TEXT("Scene"), 0, TEXT("Camera")}`) et tableau local `FRetiredKey` (FKey copiés d'`EKeys`). - V6 Premier lancement sans `LPKeyboard.ini` : `[LPKeyboard]` `Layout=QWERTY`, `Version=2`, `CoupleDrag=Mouse` écrits ; W A S D déplacent ; Z et Q (gauche) n'agissent plus ; « Layout: QWERTY ». - V7 Migration (copie d'un ancien `LPKeyboard.ini` avec les 5 liaisons) : les 5 disparaissent, journal « … passe de la version 0 a 2 : 5 liaisons retirees … », `Version=2`, `Layout=Custom`, autres liaisons intactes (diff avant / après). - V8 Migration d'un fichier issu de l'ancien DefaultInput (MoveRight Q −1 présent) : Q pris → ActionPanel sans touche, ligne « (no key) » visible, ligne d'état « Action Panel has no key… » ; Apply QWERTY ou AZERTY la pose. - V9 Apply QWERTY puis AZERTY : le diff de `LPKeyboard.ini` montre les 5 liaisons du préréglage et `Layout` ; en AZERTY Z Q S D déplacent, W n'agit plus, A ouvre le panneau d'action en scène, E interagit ; en QWERTY W A S D, Z n'agit plus, Q ouvre le panneau. - V10 Apply annonce les touches retirées ailleurs (« Removed from: … ») quand une touche du préréglage était liée sans modificateur à une autre action. - V11 Modification manuelle de MoveForward ou MoveRight (Change, Add, Remove, ou touche reprise par une autre action) → « Layout: custom » et `Layout=Custom`. - V12 Reset : joueur AZERTY → touches de DefaultInput puis AZERTY, « AZERTY layout kept » ; joueur QWERTY ou Custom → QWERTY. - V13 K5 : clic droit, bouton du milieu, Thumb1, Thumb2, Ctrl+clic gauche s'enregistrent ; clic gauche seul → message, capture maintenue. - V14 K5 : molette haut / bas sur une action → « Mouse Wheel Up / Down » enregistré et affiché (passe `IsRemappable` : à vérifier, `ButtonAxis` non relu au niveau de `FKeyDetails::IsAnalog`) ; molette pendant la capture d'un axe → message. - V15 K5 : Ctrl seul (appui puis relâchement sans autre touche) → « Left Ctrl saved » ; Ctrl+E pendant la capture → Ctrl+E, pas Ctrl seul. - V16 K5 : avertissement « Also used by the game: Y » (et Shift, Ctrl, F12, PageDown, N, clics gauche et droit) sans refus. - V17 K6 : section « Scene » dans l'ordre Camera (LeftRight, DownUp, ScrollDownUp, RotateCamera, SwitchCamera, CameraHeight), Characters (option « Move couple with », CoupleMoveY, ManualAnimating, axe de rotation), Actions (Action Panel), Other ; aucun libellé adulte ajouté au C++ (grep). - V18 Liste déroulante en jeu : s'ouvre, se ferme, ne laisse pas le focus clavier bloqué ; fonctionne en page intégrée (réglages) et en fenêtre. - V19 ActionPanel en scène : Q (QWERTY) ou A (AZERTY) bascule le panneau d'action (journal « Scene : panneau d'action bascule ») ; Ctrl+Q ne le bascule pas ; rien hors scène ni menu ouvert ; une saisie dans le filtre du panneau ne le rebascule pas. - V20 CameraHeight en vue Scene : G monte, B baisse, arrêt à +150 / −40 cm, remise à zéro à l'entrée en Scene et en C3 ; rien pendant une prise à la souris. - V21 CoupleMoveY : V / X déplacent le couple sur Y du monde de 5 par image, seulement quand `Room` est cachée (garde reprise du X de PC) ; à vérifier : si la pièce n'est pas cachée dans une scène ordinaire, V / X n'agissent pas (même comportement que DownUp dans PC). - V22 « Arrows only » : clic gauche sur le couple → aucune prise, le clic reste au jeu ; clic droit → rotation ; `CoupleDrag=Keys` relu après redémarrage. - V23 Aucun `Accessed None` nouveau ; Ctrl+C / Ctrl+E (§ 3) et Add / Subtract, pavé 4 / 6 sur les favoris seulement (§ 10, 4). ## 6. Fichiers à commiter (dépôt du jeu, branche `ue5.8`) - M `Config/DefaultInput.ini` - M `Source/LifePlay/LPKeyboardSubsystem.h` - M `Source/LifePlay/LPKeyboardSubsystem.cpp` - M `Source/LifePlay/LPCameraModeSubsystem.h` (porte aussi les changements non commités de CAM2 : ordre des commits à fixer par Big Boss) - M `Source/LifePlay/LPCameraModeSubsystem.cpp` (idem) - A `Docs/Migration/etat/REL/KEY2/KEY2-IMPL-01.md` Tours : 25 / 30 (estimé par l'agent ; `FicheStats.py` fait foi) ## Constats - L'ancien DefaultInput liait Q à MoveRight (−1) : presque tous les fichiers existants ont Q pris, donc la migration laissera ActionPanel sans touche (comportement voulu par le § 4) ; E1 rend l'entrée visible. - `LPCameraModeSubsystem.cpp` reste en LF, les fichiers clavier et `DefaultInput.ini` en CRLF : l'outil Edit a gardé chaque convention (comptes du § 1.1). - PubCheck du livrable (`pwsh -NoProfile -Command "& 'D:\Game\LifePlay-master 5.8\Tools\PubCheck.ps1' -Fichiers @('…\KEY2-IMPL-01.md')"`, avant l'ajout de cette ligne) : `PubCheck : 0 constat(s)`, code 0 ; livrable en LF (288 lignes, 0 CR). ## 7. Complément de l'orchestratrice (2026-10-20, arbitrages de Big Boss) Arbitrages de Big Boss : E1, E4, E5, E6 et E7 **acceptés**. Migration des anciens `LPKeyboard.ini` : ajouter G / B, V / X et ActionPanel quand la touche n'est liée à rien. Texte d'aide `BuildControlsHelp` à faire par KEY2, seul écrivain du fichier jusqu'au commit (gel CAM2). Instantané frais avant écriture : `Tools\Snapshot.ps1 -Lot KEY2-IMPL2` (LPKeyboardSubsystem.cpp 94575a8e, LPCameraModeSubsystem.cpp cd6427aa). | Fichier | Changement | MD5 avant → après | |---|---|---| | `Source/LifePlay/LPKeyboardSubsystem.cpp` (CRLF gardé : 1476 lignes, 1476 CR) | migration Version=2 : une aide `KeyFree` (touche utilisée par aucune action ni aucun axe du fichier du joueur) ; ActionPanel sur Q si libre ; CameraHeight G (+1) / B (−1) et CoupleMoveY V (+1) / X (−1), chaque sens ajouté seulement s'il n'est pas déjà lié et si sa touche est libre ; journal avec le nombre de touches d'axe ajoutées | 94575a8e → 7501ec6a | | `Source/LifePlay/LPCameraModeSubsystem.cpp` (LF) | `BuildControlsHelp` : 3 lignes d'aide (hauteur de la caméra, couple sur le côté, panneau d'action) et leurs 5 arguments ; une seule ligne existante remplacée (fin de la liste d'arguments, `);` déplacé) | cd6427aa → f15c2b9b | Diff contre la référence 406fbf1d : `git diff --no-index --numstat` → **112 / 1** (105 / 0 de la première passe, plus 7 / 1 de l'aide). La seule ligne retirée est la dernière ligne d'arguments de `BuildControlsHelp`, prolongée ; aucun bloc de CAM2 (A1, R-B1f, F2) n'est touché. Toujours non compilé : la compilation est KEY2-BUILD-01.