17 KiB
Fiche U7b-T1 — Niveau N3 — Modèle prévu lp-cart | utilisé claude-opus-5-5 — Agent lp-cart — Skills sk-protocol, lp-paths (préchargés aussi : ue-cpp-build, lp-commandlets)
U7b-T1 : cartographie de la capture des planches de la tournée Mesure (fenêtre minimisée, UE 5.8)
Date : 2026-10-09. Lecture seule : rien n'a été modifié, lancé ni commité. Addon = worktree D:\Game\LifePlay-Sandbox-5.8,
branche ue5.8, HEAD d1efdd1, git status --porcelain vide. Moteur = D:\Game\Launcher\Engine\UE_5.8, 5.8.3, CL 58210709
(Engine/Build/Build.version), sources présentes. Dans ce document, E58 = D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime.
Écart à la fiche (méthode § 4) : aucun outil de recherche web (WebSearch / WebFetch) n'est disponible dans cette session.
Les sources moteur 5.8 installées (source primaire, règle d'or de ue-cpp-build) les remplacent. Je ne cite aucune URL
parce que je n'en ai consulté aucune. Si l'orchestrateur veut un recoupement par la documentation Epic, il faut une session
qui a un accès web.
1. Où et comment la tournée capture aujourd'hui (établi)
1.1 Lancement
- Pilote :
Tools\anim\AnimPipeline.ps1, fonctionInvoke-Sandbox(l. 300-356). Il écritSaved\sandbox_mode.txt = MesureetSaved\sandbox_tour.txt = vignettes planches quitter(l. 329-331), puis lanceTools\debug\LifePlay_interaction_sandbox.bat(l. 306 et 334,Start-Process cmd.exe ... -WindowStyle Hidden). Ensuite, il attend que le processusUnrealEditorse termine, avec un plafond de 40 min (l. 338-340). - Lanceur
Tools\debug\LifePlay_interaction_sandbox.bat:9:UnrealEditor.exe <uproject> "/Engine/Maps/Entry?game=/Script/LPSandbox.LPPairSandboxGameMode" -game -windowed -ResX=1600 -ResY=900 "-ini:Engine:[...]:GameInstanceClass=...". Le .bat ne transmet pas d'arguments supplémentaires (%*absent). - Le même .bat sert aussi à l'ouverture interactive :
Tools\BuildMod.ps1:165(Open-AnimTarget -Which sandbox) etREADME.md:53. Changer son comportement par défaut touche donc aussi l'usage manuel.
1.2 Capture (fichier Plugins\LPSandbox\Source\LPSandbox\LPSandboxMeasureTour.cpp)
- Le mode de capture est unique :
FScreenshotRequest::RequestScreenshot(<chemin>, false, false), c'est-à-dire sans interface et sans suffixe. Il y a 4 appels (grep -c= 4) :- l. 127 : passages automatiques (
TickAuto) ; - l. 541 : planches (
TickTour,bPlancheTour) ; - l. 561 : vignettes ;
- l. 566 : tournée simple.
- l. 127 : passages automatiques (
- Les images ne sont pas écrites telles quelles. Elles passent par le délégué
UGameViewportClient::OnScreenshotCaptured(), branché l. 427 (SavePlancheFrame) et l. 433 (SaveVignette), débranché l. 458 et 468 (4 occurrences,grep -c= 4). Ensuite :SavePlancheFrame(l. 611-619) empile lesTArray<FColor>;WritePlanche(l. 621-681) recadre le carré commun, ramène chaque image à 256 px, encode en JPEG qualité 85 et écrit<Clip>.jpget<Clip>.json. Il tracePLANCHE ... (carre N px)à la l. 679.
- Le cadrage se fait dans l'espace écran du viewport :
ProjectWorldLocationToScreendes os (l. 533-539 pour les planches, l. 552-558 pour les vignettes), appliqué ensuite sur l'image reçue (l. 632-642, 581-591). Toute solution doit donc garder une image de la même taille que le viewport (1600×900), sinon le carré ne tombe plus au même endroit. - L'addon n'a aucune autre voie de capture :
HighResShot,SceneCaptureetReadPixelsn'apparaissent dans aucun fichier dePlugins\LPSandbox\Source(grep -rln, code 1). Aucune garde « fenêtre minimisée » n'existe non plus :IsWindowMinimized,RenderOffScreenetbAllowMinimizen'apparaissent ni dans les sources ni dans les outils de l'addon, ni dansConfigdu jeu 5.8. LPSandboxMeasureTour.cppest identique entreue5.8et la branche addon 5.5lot/DC3-atelier(git diff --stat ue5.8 lot/DC3-atelier -- <fichier>: seuls le .bat etAnimPipeline.ps1diffèrent).
1.3 Chemin moteur d'une capture (5.8)
FViewport::Drawcontinue tant queSizeX > 0 && SizeY > 0(E58\Engine\Private\UnrealClient.cpp:1789, test l. 1820). Pendant la minimisation, c'est la taille deFSceneViewportqui compte (1600×900), pas celle de la fenêtre.UGameViewportClient::ProcessScreenShots(E58\Engine\Private\GameViewportClient.cpp:2369) passe par la branche sans interface →GetViewportScreenShot(UnrealClient.cpp:2407-2416) →FRenderTarget::ReadPixels(UnrealClient.cpp:61-79). Comme le rectangle demandé est vide, il est remplacé par(0,0,GetSizeXY()), soit 1600×900. La lecture porte surGetShaderResourceTexture().- En rendu direct à la fenêtre, la cible du viewport est le back buffer Slate :
Target = ViewportProvider->GetBackBufferResource()(E58\Engine\Private\Slate\SceneViewport.cpp:2289-2300). - Les images arrivent ensuite dans le délégué (
GameViewportClient.cpp:2543-2552).
2. Pourquoi la fenêtre minimisée plante
2.1 Chaîne établie (sources 5.8 et journal)
- Minimisation →
WM_SIZEavecSIZE_MINIMIZED, zone client 0×0 →MessageHandler->OnSizeChanged(..., 0, 0, bWasMinimized)(E58\ApplicationCore\Private\Windows\WindowsApplication.cpp:3943-3966). FSlateApplication::OnSizeChanged→Renderer->RequestResize(Window, 0, 0)(E58\Slate\Private\Framework\Application\SlateApplication.cpp:6960-6970).FSlateRHIRenderer::RequestResize→FSlateViewportInfo::ResizeViewport(E58\SlateRHIRenderer\Private\SlateRHIRenderer.cpp:2114-2123).ClampExtentporte la taille àMIN_VIEWPORT_SIZE= 8 (l. 78, 287), puisRHIResizeViewport(8, 8)(l. 537, 576). Le back bufferBackBuffer0mesure alors 8×8.- Le viewport de jeu reste à 1600×900 et la capture demande donc une copie de 1600×900 depuis un back buffer de 8×8.
FD3D12DynamicRHI::GetStagingTextureappelleCopyTextureRegionChecked(E58\D3D12RHI\Private\D3D12RenderTarget.cpp:472). Le contrôleRHI_BREADCRUMB_CHECK_SHIPPINGF(CopyBox.right <= SubresourceWidth)échoue (E58\D3D12RHI\Private\D3D12Texture.cpp:1551). - Le journal de la 1re tentative U4-T6 (
D:\Game\LifePlay_Backup_5.5\U4-T6\E4_quaternius_echec1.log) le confirme :- l. 6721-6722 :
Source width out of bounds for: 'BackBuffer0' -> 'None' ... dimensions [8, 8, 1], copying from [0, 0, 0] to [1600, 900, 1]; - l. 6739 : callstack
GetStagingTexture ... D3D12RenderTarget.cpp:483(attribution de ligne d'un appel sur plusieurs lignes, l. 472-483).
- l. 6721-6722 :
2.2 Pourquoi 5.5 ne plantait pas
- Établi : en 5.5,
GetStagingTextureappelle directementGraphicsCommandList()->CopyTextureRegion(UE_5.5\...\D3D12RenderTarget.cpp:472). Le texte « out of bounds » ne figure nulle part dansUE_5.5\...\D3D12Texture.cpp(grep -c= 0). Le contrôle de bornes est donc nouveau en 5.8. - Estimé : en 5.5, une fenêtre minimisée aurait produit une copie invalide sans erreur (images fausses ou noires) plutôt qu'un plantage. L'assertion 5.8 transforme une corruption silencieuse en arrêt net.
- Estimé : une fenêtre seulement recouverte, ou un écran verrouillé, ne déclenche pas
WM_SIZE/SIZE_MINIMIZED. Le back buffer garde alors sa taille et la capture ne plante pas. Seule la minimisation est en cause (non essayé). - Établi (relance T6-bis,
U4-T6-VAL.md:110-120) : avec la fenêtre visible, il n'y a aucune assertion et les codes sont 0.
2.3 Pas de réglage moteur « rendre même minimisé »
grep -rli "ForceRenderingWhenMinimized|RenderWhenMinimized"surE58(*.cpp) ne trouve rien : ce CVar n'existe pas en 5.8.tick.MinimizedSyncDrawToGPU(E58\Engine\Private\GameEngine.cpp:105-109) ne fait que synchroniser le GPU quand la fenêtre est cachée (l. 2057). Il ne règle pas la taille du back buffer.
3. Référence de non-régression : ce qui est comparable (établi)
- Les pixels ne sont déjà pas identiques entre 5.5 et 5.8, fenêtre visible. Sur les 20 images de freeanims
(
U4-REF55\freeanims\sandboxetU4-T6\freeanims\sandbox), 0 sur 20 ont le même SHA-256. Écart de taille : planches -1,4 % à +2,5 %, vignettes +3,5 % à +19 % (sha256sum/stat, boucle sur les 30 fichiers). Le critère « pixels identiques » est donc hors d'atteinte quelle que soit l'option, y compris l'état actuel. - Trois points sont en revanche identiques :
- les
.jsondes planches (10/10, SHA-256) ; - les dimensions (20/20 : planches 6144×256, ou 3328×256 pour 13 images ; vignettes 512×512, lues dans l'en-tête par le
script
imgdims.pydu bloc-notes,python -I) ; - surtout, la géométrie de recadrage : lignes
VIGNETTE ... (carre N px a X,Y)etPLANCHE ... N images D s (carre N px), une fois le chemin et le nombre d'octets retirés : freeanims 20/20 et quaternius 86/86 identiques 5.5 / 5.8. Commande :grep -E "PLANCHE |VIGNETTE "surU4-REF55\E4_<pack>.logetU4-T6\E4_<pack>.log,seddes chemins et des octets, puisdiff, qui ne signale aucune différence.
- les
- Critère proposé pour U7b : code 0 ; 0
Assertion failed; géométrie 106/106 identique àU4-REF55;.jsondes planches identiques ; dimensions identiques ; contrôle visuel d'une planche par pack. Cette géométrie dépend de la taille du viewport (1600×900) et de la pose : elle détecte tout changement de résolution ou de cadrage.
4. Options
| Option | Principe | Points d'appel à changer | Effort | Risque | Non-régression attendue |
|---|---|---|---|---|---|
A. -RenderOffScreen (sans C++) |
Pas de fenêtre système : Slate rend dans une texture, donc pas de WM_SIZE ni de back buffer 8×8. Même chaîne de capture. |
.bat:9 : transmettre %*, ou un .bat dédié au pilote. AnimPipeline.ps1:334 : ajouter -RenderOffScreen. Libellé :317. Ne pas changer le défaut du .bat (BuildMod.ps1:165). |
XS, 2 à 5 lignes, 0 C++ | Faible à moyen (voir 4.1) | Géométrie identique attendue (même viewport, même lecture) : estimé. Pixels : variation déjà présente aujourd'hui. |
B. SceneCapture2D → render target 1600×900 → ReadPixels |
Capture hors du back buffer : on lit la texture du render target (UnrealClient.cpp:61-79), quel que soit l'état de la fenêtre. |
LPSandboxMeasureTour.cpp : les 4 RequestScreenshot et les 4 branchements du délégué remplacés par un composant de capture qui copie la vue (position, rotation, FOV) du PlayerCameraManager. |
M à L, environ 100 à 150 lignes C++ ; compilation 5.8, plus 5.5 si le fichier reste commun | Moyen à élevé | Géométrie identique seulement si la taille et le FOV sont copiés exactement. Rendu différent : historique TAA/TSR, exposition auto, post-process, drapeaux d'affichage (SceneCaptureComponent.h:130-132, SceneCaptureComponent2D.h:174-192). Il faut une nouvelle référence visuelle. |
C1. bAllowMinimize=False (sans C++) |
Retire le bouton de réduction : GetProjectSettingBool lit [/Script/EngineSettings.GeneralProjectSettings] de GGameIni (GameEngine.cpp:539-542, 558, 693) → pas de WS_MINIMIZEBOX (E58\ApplicationCore\Private\Windows\WindowsWindow.cpp:143-145). Surcharge possible en ligne de commande : -ini:Game:[/Script/EngineSettings.GeneralProjectSettings]:bAllowMinimize=False (syntaxe générique, E58\Core\Private\Misc\ConfigCacheIni.cpp:2322-2324). |
.bat:9 ou argument du pilote. |
XS | Moyen : couverture incomplète. Win+D, Win+M, clic sur la barre des tâches ou réduction programmée restent possibles (incertain, non vérifié). | Pixels et géométrie inchangés (même chemin). |
| C2. Garde dans la tournée (C++) | Avant chaque RequestScreenshot, si GEngine->GameViewport->GetWindow()->IsWindowMinimized() (SWindow.h:758, GameViewportClient.h:362), alors Restore() (SWindow.h:928), journaliser, reculer l'étape (--TourStep, comme l. 516-519) et attendre. |
LPSandboxMeasureTour.cpp : un point commun avant les l. 127, 541, 561 et 566. |
S, environ 15 à 25 lignes | Faible à moyen : la fenêtre reprend le premier plan. Une fenêtre réduite entre le tick et le rendu dans la même image : estimé impossible, puisque les messages sont traités en début d'image. | Pixels et géométrie inchangés (même chemin), après au moins une image d'attente une fois la fenêtre restaurée. |
4.1 Détail de l'option A (établi dans les sources 5.8, sauf mention)
- Sous Windows,
-RenderOffScreenremplace l'application Windows par l'application nulle (E58\ApplicationCore\Private\Windows\WindowsPlatformApplicationMisc.cpp:173-176). Cette voie est prévue sous Windows (Null\NullPlatformApplicationMisc.cpp:14-20, commentaire Epic « supported on Linux and Windows »). - La fenêtre est une
FNullWindowetIsRenderingOffScreen()vauttrue(Null\NullWindow.cpp:251-254). Slate crée alors une textureSlateOffScreenViewportTextureà la place d'une chaîne d'échange (SlateRHIRenderer.cpp:321-324, 340-353), etGetBackBufferResourcerenvoie cette texture (SlateRHIRenderer.cpp:471-474). Le viewport D3D12 n'a pas de chaîne d'échange (E58\D3D12RHI\Private\Windows\WindowsD3D12Viewport.cpp:34). - Il n'y a ni écran de démarrage (
E58\Launch\Private\LaunchEngineLoop.cpp:3111) ni fenêtre système, donc ni minimisation niWM_SIZE. Slate dessine quand même la fenêtre (SlateApplication.cpp:1249). La taille de la fenêtre n'est pas ajustée à la zone de travail (SceneViewport.cpp:1718-1720). - Risques à lever par l'essai décisif :
- (a) taille réelle du viewport. La zone de travail de l'application nulle part de
r.SetRes(Null\NullApplication.cpp:103-112). La config poser.setres:1280x720(E4_freeanims.log:523) avant que-ResX=1600 -ResY=900ne s'applique. La géométrie identique prouvera que la taille est bien 1600×900. - (b) rythme des images : sans présentation à l'écran, il n'y a ni vsync ni limite d'images. Les attentes de la tournée
sont en secondes (
LoadWait0,9 s,CameraWait0,5 s, l. 206-207). La géométrie ne doit donc pas bouger, mais la convergence TAA peut changer les pixels (estimé). - (c) aucune fenêtre visible pendant la tournée : l'utilisateur ne la voit plus. Le pilote n'attend que le processus, donc c'est sans effet sur lui.
- (d) l'application nulle ne reçoit pas de clavier. C'est sans effet : la tournée est pilotée par
sandbox_tour.txt(l. 290-311) et se ferme parRequestExit(l. 356).
- (a) taille réelle du viewport. La zone de travail de l'application nulle part de
5. Recommandation
- A d'abord, réservée au pilote. Le .bat transmet
%*, ou un .bat dédié est créé, etAnimPipeline.ps1:334passe-RenderOffScreen. Le défaut interactif reste inchangé. Cette option supprime la cause (pas de back buffer de fenêtre), sans C++ et sans nouvelle compilation, sur les deux moteurs. - Si A échoue (géométrie différente, plantage, viewport ≠ 1600×900) : C2, une garde de quelques lignes sur le même chemin de capture, donc une non-régression assurée par construction.
- B en dernier recours seulement : c'est la voie la plus coûteuse, elle change le rendu et impose une nouvelle référence visuelle.
- C1 n'est utile qu'en complément : elle ne couvre pas tous les moyens de réduire une fenêtre.
6. Essai décisif (à faire dans le lot IMPL/VAL, pas ici)
- Pré-essai court, d'une à deux minutes. Reproduire
Invoke-Sandboxà la main pour un seul clip :sandbox_pack.inidu pack freeanims,sandbox_mode.txt = Mesure,sandbox_tour.txt = vignettes planches quitter AS_RightLegKick2(liste partielle autorisée, l. 294-303). Lancer avec-RenderOffScreen. Attendu : code 0, 0Assertion failed, et les lignesVIGNETTE ... (carre 695 px a 621,173)etPLANCHE ... 13 images 1.10 s ... (carre 724 px), identiques à U4-REF55. - Essai complet :
AnimPipeline.ps1 -Pack freeanims -From 5 -To 5(446 s en 5.8). Comparer la géométrie (20/20), les.jsondes planches (10/10) et les dimensions (20/20) àU4-REF55\freeanims, avec le mêmediffqu'au § 3. Puis quaternius (86/86, environ 1210 s). - Essai contraire, si C2 est retenu : lancer fenêtre visible, la réduire pendant les planches. Attendu : ligne de
journal « fenêtre restaurée », code 0, géométrie identique. Sans la garde, le plantage
D3D12Texture.cpp:1551doit se reproduire : c'est la preuve par l'échec, à documenter avant le correctif.
7. Preuves, incertitudes
- Établi : §§ 1, 2.1, 2.2 (premier point), 2.3, 3 et 4.1 (mécanique), avec les lignes et commandes citées.
- Estimé :
- comportement 5.5 en fenêtre minimisée (corruption silencieuse) ;
- fenêtre recouverte ou écran verrouillé sans effet ;
- géométrie conservée sous
-RenderOffScreen; - absence de course dans C2.
- Incertain :
- taille effective du viewport sous
-RenderOffScreenavecr.setres:1280x720en config (essai 6.1) ; - couverture de C1 face à Win+D / Win+M ;
- déterminisme des pixels d'une exécution 5.8 à l'autre (aucune paire 5.8/5.8 conservée) ;
- recoupement par la documentation Epic, non fait faute d'outil web.
- taille effective du viewport sous