LifePlay-Revival/Docs/Migration/etat/ST/STE/STE-ORG5-I1-IMPL.md
edujiro5972 8084622450 LifePlay UE 5.8 : état de développement au 2026-10-19
Tickets : REP1-T4

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

117 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

Fiche STE-ORG5-I1 — Niveau N3 — Modèle prévu (modèle de l'agent lp-impl) | utilisé claude-opus-5-5 — Agent lp-impl — Skills sk-protocol, ue-cpp-build, lp-build, lp-ue-compat (préchargés), sk-change-safety (invoqué)
# STE-ORG5-I1-IMPL : bonus d'orgasme ORG5, version minimale (V-min) en C++ dans deux fichiers neufs (2026-10-14)
Périmètre tenu : deux fichiers neufs et ce livrable ; aucun fichier existant modifié, aucun asset, ni éditeur, ni MCP, ni commandlet, ni build. **Aucun refus du crochet** (commandes Bash sans `>`, sans `python -c`, sans heredoc). Le code n'est **pas compilé** (build commun, fiche BUILD à venir) : tout ce qui suit sur la compilation est « proposé, non compilé ».
## 1. Fichiers modifiés (chemins, lignes)
| Fichier | Lignes (LF, sans BOM) | Contenu |
|---|---|---|
| `D:\Game\LifePlay-master 5.8\Source\LifePlay\LPBonusRewardSubsystem.h` | 119 | en-tête d'origine et de licence (l. 1-10), description V-min, accroche H1, réglage (l. 12-33) ; `ULPBonusRewardSettings` `UCLASS(config = Game)`, `bDisabled = false` (l. 45-55) ; `FLPBonusStat` (l. 57) ; `FLPBonusPending` (l. 64) ; `ULPBonusRewardSubsystem : UGameInstanceSubsystem` (l. 72-) |
| `D:\Game\LifePlay-master 5.8\Source\LifePlay\LPBonusRewardSubsystem.cpp` | 667 | outils de réflexion dans l'espace de noms nommé `LPBonusReward` (l. 14-173) ; `Initialize` (l. 175 : contrôle de formule, ligne de réglage, minuterie `FTSTicker` 0,1 s) ; `Table` 20 clés (l. 207) ; `RewardChange` (l. 232) ; `CheckFormula` (l. 243) ; `Overlaps` (l. 269) ; `Pick` (l. 287) ; `Tick` / `Poll` (l. 334, 345) ; `FirePending` (l. 434) ; `OnOrgasm` (l. 451) ; `AttractionAllowed` (l. 488) ; `IsInterestedIn` (l. 527) ; `QueryStat` (l. 567) ; `ModifyStat` (l. 603) ; `PersonName` (l. 638) ; `WarnOnce` (l. 659) |
Contrôles (sortie brute) :
- `od -An -v -tx1 <f> | tr ' ' '\n' | grep -c '^0d$'` → `.h CR=0 LF=119`, `.cpp CR=0 LF=667` ; 3 premiers octets `2f 2f 20` (pas de BOM), comme `LPUserSettings.h` (`CR=0 LF=59`, même début).
- Grep `\{ TEXT\("[a-z_]+-?"\), \{` dans le `.cpp` → `20` (les 20 lignes de la table) ; Grep `Users|<nom>` dans `LPBonusReward*` → `0`.
- 5 lignes non ASCII dans le `.cpp` (accents des formats de journal imposés par la SPEC § 2.4 : contrôle, réglage, désactivé, récompense éligible) ; même pratique que `LPDirectControlSubsystem.cpp:449` (« » dans un `TEXT`, compilé en 5.8).
### 1.1 Choix du sous-système
`UGameInstanceSubsystem` + `FTSTicker` à 0,1 s (précédent `LPColorGradeSubsystem.h:51`, `.cpp:76-82`). Raisons : `SexOn`, `SexPersons`, `CurrentTags`, `PlayerCharacter` sont sur GI ; le sous-système vit toute la session, sans dépendre d'un monde ; aucun tick d'acteur à créer. Coût (estimé) : hors scène, une recherche de propriété (`SexOn`) par tick ; en scène, une par personne (`Sex_OrgasmCounts`) plus `SexPersons` : quelques recherches de `FName` dans les listes de propriétés de GI et de Person, 10 fois par seconde, de l'ordre de la microseconde ; aucun appel Blueprint hors orgasme.
### 1.2 Accroche (H1, documentée, sans nœud Blueprint)
- **Aucun nœud Blueprint n'est nécessaire** pour V-min : le C++ lit l'état par réflexion. Aucune lecture du journal, aucun crochet de texte.
- Sondage (`Poll`, l. 345) : `GI.SexOn` faux → mémoire des compteurs vidée ; vrai → pour chaque personne valide de `GI.SexPersons` : premier passage = mémorisation sans récompense ; hausse de `Sex_OrgasmCounts` → copie de `GI.CurrentTags`, armement d'un orgasme en attente à t + 0,2 s (remplace l'attente en cours, comme `RetriggerableDelay`) ; baisse → resynchronisation sans récompense ; personne sortie de la scène → oubliée.
- `FirePending` (l. 434) : à échéance, personne valide → `OnOrgasm` : ligne `orgasme de`, puis `désactivé` (réglage) **ou** tirage `Pick` → `aucune récompense éligible` **ou** `QueryStat` + `RewardChange` + `ModifyStat(Stat, Change, false, false)` sur la **première** récompense retenue, puis la ligne `<nom> <stat> <±valeur> (x=…, tags=…)` (marqueur V6).
- Règle commune ORG4 / ORG5 (SPEC fin de fichier, `STE-ORG4-SPEC.md:265-269`) : un incrément de `Sex_OrgasmCounts` arme une seule attente, qui donne au plus une récompense appliquée ; `Endsex` sans `Cum` n'incrémente pas le compteur et ne déclenche rien ici.
### 1.3 Appels et lectures par réflexion, preuves
| Élément | Nom | Type exigé par le code | Preuve |
|---|---|---|---|
| GI | `SexOn` | `FBoolProperty` | précédent `LPDirectControlSubsystem.cpp:460-464` |
| GI | `SexPersons` | `FArrayProperty` d'objets | précédent `LPShowCoupleSubsystem.cpp:80-96`, `:303` |
| GI | `PlayerCharacter` | `FObjectPropertyBase` | précédents `LPDebugSubsystem.cpp:481`, `LPOutdoorRoomSubsystem.cpp:334` |
| GI | `CurrentTags` | `FArrayProperty` de `FStrProperty` | SPEC E1 (`MakeStruct StringArray(Strings = GI.CurrentTags)`, `Person.uexp:21184` de l'auteur) ; nom présent dans `O\BPs\GI.uasset` (SPEC annexe A point 10) ; **type chez nous estimé** |
| Person | `Sex_OrgasmCounts` | `FIntProperty` | `Person__EventGraph.txt:16118-16125` (macro `IncrementInt`), `:15908-15911` (remise à `0`) |
| Person | `isPlayer` | `FBoolProperty` | `L6f_dump_Person.txt:558-559` (variable reliée à `IfThenElse.Condition`) |
| Person | `Name_First` | `FTextProperty` ou `FStrProperty` | SPEC § 2.4 (texte chez l'auteur, `Cursor.uexp:22666`) ; type chez nous non lu, les deux acceptés |
| Person (fonction) | `QueryStat(Stat, WithoutBuff, RNG)` → `Value`, `Bonus` | `Stat` chaîne, `WithoutBuff` et `RNG` booléens, `Value` réel en sortie | `L6f_dump_Person.txt:20-29` ; appel de l'auteur `QueryStat(Stat, false, false)` (SPEC E6d) |
| Person (fonction) | `ModifyStat(Stat, Change, DifficultyAdjusted, NoDisplay)` → `Value` | `Stat` chaîne, `Change` réel d'entrée, deux booléens | `L6f_dump_Person.txt:561-570` ; appel de l'auteur `ModifyStat(Stat, Change, false, false)` (SPEC E7) |
| GI (fonction) | `IsInterestedIn(WhoIsInterested, WhoInterests)` → `Interested` | deux objets de la classe du paramètre, booléen en sortie | `L6f_dump_GI.txt:23421-23424`, `:23442-23443` ; ordre retenu `WhoIsInterested = Person`, `WhoInterests = PlayerCharacter` (estimé, SPEC § 2.4) |
| GI (fonction) | `Overlaps(Array1, Array2, IfEmptyThenTrue)` | **non appelée** : réécrite en C++ (`Overlaps`, l. 269) | `L6f_dump_GI.txt:19622-19673` : `IfEmptyThenTrue ET (Len(A1)=0 OU Len(A2)=0)` → vrai ; sinon un élément de `Array1` dans `Array2` (`Array_Contains`) → vrai ; sinon faux |
Appels par `FindFunction` + `ProcessEvent`, paramètres trouvés **par nom** (`GetAuthoredName` ou `GetName`), mémoire `FMemory_Alloca_Aligned(ParmsSize, GetMinAlignment())` dans le cadre de l'appelant, mise à zéro, `InitializeValue_InContainer` puis `DestroyValue_InContainer` en sortie de portée (structure `FScopedParms`, même séquence que `LPDirectControlSubsystem.cpp:426-455`). Signature ou type inattendu : un avertissement `Bonus : …` par cause (liste des paramètres et de leurs types dans le message), abandon sans effet.
### 1.4 API moteur relues dans UE 5.8 (lecture, `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\`)
- `FTSTicker` : `Runtime\Core\Public\Containers\Ticker.h:21` (`FTickerDelegate` bool(float)), `:32` (`FDelegateHandle`), `:45` (`AddTicker(delegate, InDelay)`), `:66` (`RemoveTicker`).
- `UGameInstanceSubsystem` / `GetGameInstance` : `Runtime\Engine\Public\Subsystems\GameInstanceSubsystem.h:16`, `:25`.
- `UKismetMathLibrary::FCeil(double)` → `int32` : `Runtime\Engine\Classes\Kismet\KismetMathLibrary.h:796` (`UE_INL_API`, `:180-183`), corps `KismetMathLibrary.inl:743-746` (`FMath::CeilToInt`) ; `MapRangeClamped` : `.h:836` (`ENGINE_API`), corps `Private\KismetMathLibrary.cpp:328-331` (`FMath::GetMappedRangeValueClamped`). Le code appelle ces deux fonctions elles-mêmes : arithmétique identique à celle des nœuds Blueprint.
- `Array_Shuffle` : `Runtime\Engine\Private\KismetArrayLibrary.cpp:338-352` (Fisher-Yates, `RandRange(i, LastIndex)`), variante par flux `:363-366` ; reproduit avec `FRandomStream::RandRange` (`Runtime\Core\Public\Math\RandomStream.h:209-214`, bornes incluses) ; graine `GenerateNewSeed` (`:105-108`).
- `Array_Contains` → `GenericArray_Find` (`KismetArrayLibrary.cpp:547-559`) → `Inner->Identical` ; `TProperty_WithEqualityAndSerializer::Identical` compare par `operator==` (`Runtime\CoreUObject\Public\UObject\UnrealType.h:1769`) : le C++ (`TArray<FString>::Contains`) utilise le même opérateur (estimé : l'héritage de `FStrProperty` depuis ce gabarit n'est pas relu).
- `UObject::FindFunction` `Runtime\CoreUObject\Public\UObject\Object.h:1258` ; `ProcessEvent` `:1490` ; `UStruct::GetMinAlignment` `Class.h:790` ; `FField::GetAuthoredName` `Field.h:903` ; `InitializeValue_InContainer` / `DestroyValue_InContainer` `UnrealType.h:1128`, `:1040` ; `FObjectPropertyBase::GetObjectPropertyValue_InContainer` / `SetObjectPropertyValue_InContainer` `UnrealType.h:2896`, `:2901` ; `StrProperty.h` inclus par `UnrealType.h:7815`.
- `FMemory_Alloca_Aligned` : `Runtime\Core\Public\GenericPlatform\GenericPlatformMemory.h:221`.
- `TArray::Swap(i, j)` `Runtime\Core\Public\Containers\Array.h:3536` ; `SetNum` `:2531` ; `TOptional::operator->` `Misc\Optional.h:384-390`, `Reset` `:235`.
- Formats de journal : indicateur `+` accepté (`Runtime\Core\Public\String\FormatStringSan.h:164`), conversions `f` et `g` (`:424`, `:426`).
### 1.5 Réglage
`ULPBonusRewardSettings` `UCLASS(config = Game)`, `UPROPERTY(config) bool bDisabled = false` : section `[/Script/LifePlay.LPBonusRewardSettings]`, clé `bDisabled`, de la config Game du projet (`Config\DefaultGame.ini`) ; section absente → valeur du code (faux, fonction active). Lu par `GetDefault<ULPBonusRewardSettings>()` ; **aucun `SaveConfig`** : rien n'est écrit, ni dans `Config\` ni dans `Saved\`. `DefaultGame.ini` n'est pas modifié (fiche § 3, consigne 2). Une ligne au démarrage donne la valeur lue : `Bonus : réglage bDisabled = vrai|faux (…)`.
### 1.6 En-tête
`.h` l. 1-10 : origine « comportement reconstruit d'après LifePlay 2023.04 de Vinfamy » (sans accents, comme les commentaires du module), licence MIT (`SPDX-License-Identifier: MIT`, texte court des droits et de la condition de mention), renvoi à `Licenses/Vinfamy_License.txt` (ligne 1 : « … are under MIT », relue). `.cpp` l. 1-3 : rappel de l'origine et de la licence.
### 1.7 Build.cs
**Inchangé, aucun module nouveau requis** : `Engine` (KismetMathLibrary, GameInstanceSubsystem, GameInstance), `Core` (Ticker, RandomStream, Optional), `CoreUObject` (réflexion) sont déjà des dépendances publiques (`Source\LifePlay\LifePlay.Build.cs:17`). Catégorie de journal `LogLifePlay` : `Source\LifePlay\LifePlay.h:9`.
## 2. Écarts à la SPEC (obligatoire)
| # | SPEC | Fait | Pourquoi |
|---|---|---|---|
| E1 | § 2.4 (b), § 2.5 : `UCLASS(config = GameUserSettings)`, ini utilisateur ; § 3 : désactivation par `bDisabled=True` dans l'ini utilisateur | `UCLASS(config = Game)`, valeur par défaut du code, rien écrit | consigne 2 de Big Boss (fiche § 3) : réglage dans `Config\`, rien dans `Saved\`. Conséquence : le moyen de désactiver **sans reconstruire** dans un jeu empaqueté n'est plus établi (point VAL-11) |
| E2 | § 2.4 : journal niveau `Display` | niveau `Log` (avertissements en `Warning`, contrôle de formule fautif en `Error`) | fiche § 4 : `UE_LOG(LogLifePlay, Log, …)`. Les formats de texte sont ceux de la SPEC ; dans le fichier de journal, `LogLifePlay: Bonus : …` au lieu de `LogLifePlay: Display: Bonus : …` ; le motif `Bonus :` de VAL 7.5 / 7.7 / 7.9 trouve les deux |
| E3 | § 2.4 sondage : « si `GI.SexOn` faux → vider le suivi » | la mémoire des compteurs est vidée ; un orgasme **déjà détecté** garde son tempo de 0,2 s et reçoit sa récompense même si la scène vient de finir | comme l'auteur (`RetriggerableDelay` indépendant de la scène, E3 / E10) ; évite de perdre la récompense de l'orgasme qui termine la scène |
| E4 | E3 : `RetriggerableDelay` (temps du monde) | tempo en temps réel (`FPlatformTime::Seconds`) | sous-système de GameInstance sans monde garanti ; différence seulement en pause ou en temps dilaté pendant les 0,2 s |
| E5 | E6c : la condition `attractiontoplayer` est évaluée quand la clé passe | `bAttractionAllowed` calculé avant `Pick` (signature de la SPEC), avec court-circuit : `isPlayer` → `PlayerCharacter` → présence dans `SexPersons` → `IsInterestedIn` (appel Blueprint) ; l'appel a lieu pour tout PNJ de scène avec le joueur, même si la clé n'est pas tirée | signature imposée (`bool bAttractionAllowed`) ; équivalent si `IsInterestedIn` est sans effet de bord (non relu en entier : VAL-9) |
| E6 | § 5 : fonction ou propriété absente = avertissement et abandon | sur le chemin `attractiontoplayer` (`isPlayer`, `PlayerCharacter`, `IsInterestedIn`), l'abandon porte sur **la clé** (refusée), le reste du tirage continue ; ailleurs, abandon de la récompense | la condition ne peut pas être prouvée vraie : clé refusée, comme si la condition était fausse ; avertissement unique écrit |
| E7 | § 2.4 (b) signatures | ajouts privés : `Poll`, `FirePending`, `AttractionAllowed`, `IsInterestedIn`, `PersonName`, `WarnOnce`, structure `FLPBonusPending` ; `CheckFormula` statique publique ; paramètre de `Pick` nommé `InRng` | découpage du sondage ; `InRng` évite un C4458 (paramètre qui masque le membre `Rng`) |
| E8 | § 2.4 formats | en plus : `Bonus : réglage bDisabled = …` au démarrage ; deux lignes `Verbose` (attente remplacée, personne disparue) invisibles par défaut ; avertissements préfixés `Bonus :` | diagnostic J1 ; un `LogStats -Pattern 'Bonus :'` compte aussi ces lignes (VAL-5) |
| E9 | § 2.4 contrôle de formule sur 7 valeurs | 7 valeurs + 1 cas de signe (50 avec `-` → −1,1) | contrôle du signe |
| E10 | § 2.4 : `QueryStat` sorties `Value`, `Bonus` | signature exigée stricte : `Stat` chaîne, `WithoutBuff` et `RNG` booléens, `Value` réel en sortie ; sinon avertissement et pas de récompense | règle « pas de repli silencieux » ; risque si `RNG` n'est pas booléen chez nous (VAL-8) |
## 3. Instantané (lot, manifeste)
sans objet : la fiche ne demande pas d'instantané et aucun fichier existant n'est touché (deux fichiers **neufs**). État de départ et d'arrivée relevés (`sk-change-safety` règles 2 et 10, invoqué après l'écriture des deux fichiers : écart de méthode sans effet, rien n'a été écrasé) :
- Avant : `git -C "D:/Game/LifePlay-master 5.8" status --short` → dans `Content`/`Source` : `M` `Content/BPs/GI.uasset`, `PC.uasset`, `Person.uasset`, `Room.uasset`, `Source/LifePlay/LPDebugSubsystem.cpp/.h`, `LPDirectControlSubsystem.cpp/.h`, `LPOutdoorRoomSubsystem.cpp/.h`, `LifePlay.cpp` ; `??` `LPCameraModeSubsystem.cpp/.h`, `LPSaveRelaunch.cpp/.h`.
- Après : `git -C "D:/Game/LifePlay-master 5.8" status --porcelain -- Content Source Config` → mêmes 11 `M`, 4 `??` d'avant, plus `?? Source/LifePlay/LPBonusRewardSubsystem.cpp` et `?? Source/LifePlay/LPBonusRewardSubsystem.h`. Différence = le changement prévu.
- Note pour VAL 7.1 : la SPEC comptait « 10 `M`, 4 `??` » ; `Content/BPs/PC.uasset` était déjà `M` à l'entrée de cette fiche (autre lot), donc 11 `M`.
- Journal du changement : `2026-10-14 | ORG5 V-min, 2 fichiers C++ neufs | Source/LifePlay/LPBonusRewardSubsystem.h, .cpp | retour arrière (proposé, non lancé) : déplacer les deux fichiers vers D:\Game\LifePlay_Backup_5.5\STE\ORG5-I1\ | instantané : sans objet (fichiers neufs) | vérification : git status ci-dessus`.
## 4. Ce qui n'a pas été fait
- Compilation (fiche BUILD du build commun) ; aucune vérification UHT ni MSVC.
- V-1 (panneau Slate, 3 boutons, choix), V-2 (case de réglage, onglet Lewd), H2 (nœud dans `GiveOrgasmReward`) : hors V-min.
- Section `[/Script/LifePlay.LPBonusRewardSettings]` dans `Config\DefaultGame.ini` : non ajoutée (fichier existant ; décision ultérieure).
- Formatage de nombre par `FText` (SPEC § 6, incertain) : sans objet en V-min (le journal utilise `%+.1f`) ; à lire pour V-1.
- Lecture complète de `GI.IsInterestedIn` (effets de bord) et des types réels des variables de GI et Person (lecture L-M2 de la SPEC) : non faites (pas de MCP).
## 5. Points à confirmer en VAL (un par ligne)
- VAL-1 : build du module `LifePlay` code 0 ; UHT accepte les structures C++ simples `FLPBonusStat` / `FLPBonusPending` dans l'en-tête et le membre `TOptional<FLPBonusPending>`.
- VAL-2 : aucun avertissement-erreur de masquage (C4456 / C4458) ni C4996 sur les deux fichiers ; `UKismetMathLibrary::FCeil` (`UE_INL_API`) se lie en Development.
- VAL-3 : `FMemory_Alloca_Aligned(ParmsSize, Alignment)` avec des `int32` locaux compile sans avertissement (macro `GenericPlatformMemory.h:221`).
- VAL-4 : table : 20 clés et 20 listes de tags identiques à SPEC § 1.2 (relecture ligne à ligne de `.cpp` l. 207-229) ; `RewardChange` dans l'ordre de SPEC § 1.3 ; `Pick` : 10 candidates, 3 au plus, `Overlaps(…, true)`, règle `attractiontoplayer`.
- VAL-5 : aucun widget créé (Grep `UUserWidget|SNew|AddViewportWidgetContent|CreateWidget` dans les deux fichiers = 0) ; les avertissements `Bonus :` ne comptent pas comme lignes de récompense dans 7.7.
- VAL-6 : journal de démarrage : 1 ligne `Bonus : contrôle formule OK`, 0 `Error` du sous-système, 1 ligne `Bonus : réglage bDisabled = faux`.
- VAL-7 : journal J1 : aucun avertissement `Bonus : pas de propriete …` ni `signature inattendue …` (types de `SexOn`, `SexPersons`, `CurrentTags`, `Sex_OrgasmCounts`, `isPlayer`, `Name_First`, `QueryStat`, `ModifyStat`, `IsInterestedIn` conformes).
- VAL-8 : `Person.QueryStat.RNG` est booléen (`L6f_dump_Person.txt:25` : relié à `RngActual`, type non lu) ; sinon aucune récompense ne sera appliquée.
- VAL-9 : `GI.IsInterestedIn` sans effet de bord sur GI ou Person (écart E5) et ordre `WhoIsInterested = PNJ` correct.
- VAL-10 : J1 par orgasme : 1 ligne `orgasme de`, puis 1 ligne `<stat> <±valeur>` ou `aucune récompense éligible` ; valeur conforme à SPEC § 1.3 pour le `x` journalisé ; la ligne « stat ±n » de `ModifyStat` apparaît en jeu.
- VAL-11 : désactivation : où écrire `bDisabled=True` pour qu'un jeu empaqueté la lise sans reconstruire (couche `Saved\Config\Windows\Game.ini` lue ou non) ; sinon seul `DefaultGame.ini` avant cuisson vaut (écart E1).
- VAL-12 : règle commune ORG4 / ORG5 : fin par `Endsex` sans `Cum` → 0 ligne ORG5 ; un orgasme → au plus 1 ligne de récompense appliquée par personne (V6).
- VAL-13 : 7.1 : `git status --porcelain -- Content Source Config` = état d'entrée (11 `M`, 4 `??`) + 2 `??` de cette fiche.
## 6. Fichiers à commiter
Dépôt du jeu `D:\Game\LifePlay-master 5.8`, branche `ue5.8` (après BUILD et VAL, sur ticket au gardien) :
- `A Source/LifePlay/LPBonusRewardSubsystem.h`
- `A Source/LifePlay/LPBonusRewardSubsystem.cpp`
- `A Docs/Migration/etat/ST/STE/STE-ORG5-I1-IMPL.md`
Tours : 28 / 45 (déclaré ; `python -I Tools/FicheStats.py --lot STE` fait foi)
## Constats
- Points à vérifier à la compilation : VAL-1 à VAL-3 ; code commun 5.5 / 5.8 non exigé (développement 5.5 arrêté), mais aucune API propre à 5.8 n'est utilisée (`FTSTicker`, `FDoubleProperty`, `UE_INL_API` existent depuis 5.0, estimé, non relu en 5.5).
- Un orgasme dans les 0,2 s d'un autre (deux personnes) : seul le dernier reçoit une récompense, comme l'auteur (SPEC E3) ; la règle commune (au plus une fois par personne) est tenue.
- À verser : `lp-ue-compat` § 1 (ou `ue-cpp-build` § 0) — en 5.8, le validateur de format de `UE_LOG` accepte l'indicateur `+` et les conversions `f` / `g` (`Core\Public\String\FormatStringSan.h:164`, `:424-426`) ; `UKismetMathLibrary::FCeil` est `UE_INL_API` (en ligne hors Debug, `KismetMathLibrary.h:180-183`) ; `FMemory_Alloca_Aligned` est dans `GenericPlatform\GenericPlatformMemory.h:221`. `sk-protocol` § 7 — compter les CR d'un fichier sans redirection : `od -An -v -tx1 <f> | tr ' ' '\n' | grep -c '^0d$'` (le `od -c | grep -c '\\r'` compte des lignes d'`od`, pas des octets : faux).