LifePlay-Revival/Docs/Migration/etat/ST/STE/STE-ORG5-SPEC.md

301 lines
51 KiB
Markdown
Raw Normal View History

Fiche STE-ORG5 — Niveau N3 — Modèle prévu (modèle de l'agent lp-cart) | utilisé claude-sonnet-5-5 — Agent lp-cart — Skills lp-mcp (lecture), sk-protocol (préchargés avec l'agent : ue-cpp-build, lp-commandlets, lp-paths)
# STE-ORG5-SPEC : reconstruction du bonus d'orgasme de l'auteur (grille de récompenses), découpage en fiches et version minimale pour l'alpha (2026-10-14)
Hors gabarit : les sections suivent l'ordre du gabarit SPEC (1 objectif, 2 fichiers touchés et ordre, 3 repli, 4 risques et questions, 5 interdits, 6 valeurs moteur, 7 critères de VAL) ; les rubriques demandées par la fiche (rubrique 7) sont des sous-sections : comportement cible = 1.1, table des 20 entrées = 1.2, formule = 1.3, conception 5.8 = 2.1 à 2.3, widgets et fonctions = 2.4, option DisableBonus = 2.5, découpage en fiches = 2.6, version minimale pour l'alpha = 3.1, estimation = 4.1, risques = 4.2, lectures MCP manquantes = 4.3, Q# = 4.4. Annexes A (commandes et lectures, comptes bruts) et B (établi / estimé / incertain) après la section 7. Lecture seule : ni éditeur, ni MCP, ni commandlet, ni build, ni jeu ; l'exécutable de l'auteur n'a pas été lancé ; aucun fichier du projet modifié hormis ce livrable. **Aucun refus du crochet** (outil de lecture : cinq scripts Python en fichier dans le scratchpad de session, le désassembleur `kdis.py` / `kdbg.py` / `kall.py`, `calc.py` et `strs.py`, lancés par `python -I <fichier>` en 20 exécutions, aucune écriture hors scratchpad). **Changement par rapport à l'audit** : le désassemblage du bytecode de `PopulateBonusRewards` (L14) et de `GiveOrgasmReward` (L15) est fait ; il corrige trois points de `STE-ORG-AUDIT.md` § 9 (voir 1.1, 1.3 et 2.5 : trois récompenses au plus et non cinq ; valeur à 100 = 0,2 ; branche `DisableBonus` résolue).
## Sigles
| Sigle | Chemin | Contenu |
|---|---|---|
| B | `D:\XxX\LifePlay_2023_04_Stable_64bit\LifePlay\Content` | build cuit de l'auteur, version 2023.04 Stable, UE 4.20, sans `.pak` ; les nombres après `B\…\X.uexp` sont des offsets d'octets absolus du `.uexp` |
| O | `D:\Game\LifePlay-master 5.8\Content` | nos assets (copie 5.8) |
| P-STG | `D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STG\dumps\Person__EventGraph.txt` | dump de l'EventGraph de Person (copie de travail) |
| L6f-G / L6f-P | `…\etat\portage55\L6f\L6f_dump_GI.txt` / `L6f_dump_Person.txt` | dumps 5.5 de tous les graphes |
| AUD | `…\etat\ST\STE\STE-ORG-AUDIT.md` | audit amont |
| DIS | scratchpad de session : `kdis.py`, `kdbg.py`, `kall.py` | désassembleur Kismet (Python, lecture seule), voir annexe A |
## 1. Objectif mesurable
Reproduire dans les sources 5.8 le **bonus d'orgasme de l'auteur** (build 2023.04 Stable) : à chaque orgasme, jusqu'à **3 récompenses** « stat ±valeur » tirées parmi **20 clés** filtrées par les tags de la position, valeur décroissante avec la stat, application de la récompense choisie par `ModifyStat`, option de désactivation. Critères mesurables (reprennent la section 7) : (i) table de 1.2 identique (20 clés, mêmes tags) ; (ii) les 21 valeurs de 1.3 reproduites ; (iii) jamais plus de 3 récompenses ; (iv) application = un seul `ModifyStat(Stat, Change, DifficultyAdjusted = false, NoDisplay = false)` sur la personne qui vient de jouir ; (v) option désactivée : aucune récompense, bonus ancien intact ; (vi) version minimale : **0 asset modifié**.
### 1.1 Comportement cible (établi par désassemblage du bytecode de B, sauf mention)
| # | Comportement | Source | Niveau |
|---|---|---|---|
| E1 | **Déclencheur** : chaque exécution de l'événement `GiveOrgasmReward` de Person, donc chaque orgasme de **toute** personne (joueur ou PNJ). `JUMPIFNOT` sur `GI.DisableBonus` : faux → copie de `GI.CurrentTags` dans une structure `StringArray`, puis `GI.Cursor.PopulateBonusRewards(self, tags)`, puis `JUMP` vers le bonus ancien ; vrai → saute directement au bonus ancien. Le **bonus ancien est exécuté dans les deux cas** (`OrgasmIrBoost`, `mood +20` du joueur, `attractiontoplayer` ×3 ou ×6) : la grille s'y **ajoute** | `B\BPs\Person.uexp:21111` (`JUMPIFNOT m413 (GI.DisableBonus)`), `:21184` (StringArray), `:21218` (appel), `:21262` (`JUMP` vers `:21135`), `:21135-21652` (bonus ancien, identique à P-STG:16059-16110 et 17453-17496) | établi |
| E2 | **Appel** : `PopulateBonusRewards(Person, Tags)` est un talon qui copie ses deux paramètres et appelle `ExecuteUbergraph_Cursor(10856)` ; l'entrée 10856 saute à l'instruction m15 | `B\UI\Cursor.uexp:14450-14487` (talon), `:14006` (`JUMP m15`) | établi |
| E3 | **Temporisation** : `RetriggerableDelay(0,2 s)` avant tout : un second appel dans les 0,2 s remplace le premier (comportement documenté du nœud, non relu dans le `.cpp`) ; seul le dernier orgasme produit des récompenses (« last person who came ») | `Cursor.uexp:7141` ; signature moteur `KismetSystemLibrary.h:700-701` | établi pour le nœud, estimé pour le remplacement |
| E4 | **Remise à zéro** : `ScrollBonuses.ClearChildren()`, `lastCum := Person` | `Cursor.uexp:7198`, `:7218` | établi |
| E5 | **Candidats** : `Map_Keys(statTags)` (20 clés), `Array_Shuffle`, `Array_Resize(·, 10)` : 10 clés au hasard ; `countBR := 0` | `Cursor.uexp:7236`, `:7281`, `:7306`, `:7336` | établi |
| E6 | **Boucle** sur les 10 clés (tant que la sortie anticipée n'est pas levée), pour chaque clé : (a) `tags := statTags[clé].Strings` ; (b) `GI.Overlaps(Tags.Strings, tags, IfEmptyThenTrue = true)` : faux → clé ignorée ; (c) si clé = `attractiontoplayer` : exige `NON Person.isPlayer` ET `GI.IsInterestedIn(Person, GI.PlayerCharacter)` ET `GI.SexPersons` contient `GI.PlayerCharacter`, sinon ignorée ; (d) acceptation : `countBR++`, création d'un widget `BonusReward`, `Person`, `Stat` = clé sans le `-` final, `x := Person.QueryStat(Stat, false, false).Value`, `Change := signe × valeur(x)` (1.3), `ScrollBonuses.AddChild` ; (e) `NotEqual_IntInt(countBR, 3)` : à 3 acceptées, sortie de boucle. **Au plus 3 récompenses** | (a) `Cursor.uexp:7545-7586` ; (b) `:7606-7651` ; (c) `:7692-7743` et `:8686-8884` ; (d) `:7753-8576` ; (e) `:8607-8629`, `:8889` | établi (la constante 3 est dans le code ; le journal 5.26 disait 5, `Change_Logs.txt:39`, le code du build 2023.04 fait foi) |
| E7 | **Widget `BonusReward`** : bouton `RewardButton` + texte `RewardText` lié à `Format("{stat} {sign}{change}")`, signe vide si `Change < 0` sinon `+`, `change` argument flottant. **Clic** : `Person.ModifyStat(Stat, Change, false, false)` puis `GetParent().ClearChildren()` (retire les 3 boutons). Aucune pause du jeu, aucun appel de son dans le bytecode | `B\UI\BonusReward.uexp:1371-1973` (texte), `:1115-1201` (clic) | établi |
| E8 | **Texte d'en-tête** (`Cursor.Get_TextBlock_0_Text_0`) : `DisableBonus` → « Bonus rewards disabled » ; sinon si `ScrollBonuses` a des enfants → « Pick a bonus reward for {Name_First}'s last orgasm » ; sinon « Bonus rewards can be chosen here after each orgasm » | `Cursor.uexp:22465-23015` | établi |
| E9 | **Visibilité** du panneau `SexExclusives` : visible si `GI.SexOn`, sinon replié ; l'appartenance de `TextBlock_0` et de `ScrollBonuses` à ce panneau n'est pas lue (arbre de widgets non lu) | `Cursor.uexp:19759-19818` | établi pour la fonction, non établi pour l'arbre |
| E10 | **Aucun nettoyage** des récompenses en fin de scène : les 45 fonctions décodées de `Cursor` ne touchent `ScrollBonuses`, `lastCum`, `countBR`, `statTags` que dans `PopulateBonusRewards` et `Get_TextBlock_0_Text_0` ; seuls `Person`, `BonusReward` et `Cursor` citent ces noms (annexe A, point 8). Les boutons restent jusqu'au clic ou au prochain orgasme | annexe A points 8 et 9 | établi |
### 1.2 Table des 20 entrées (`statTags`, valeur par défaut de la variable, relue à la source)
Clé finissant par `-` : la récompense **baisse** la stat (signe −1, nom de stat sans le `-`). « vide » : `Overlaps` renvoie vrai quelles que soient les tags de la position (drapeau `IfEmptyThenTrue` à vrai). Offset = chaîne dans `B\UI\Cursor.uexp` (annexe A point 6).
| # | Clé | Tags de position | Offset | # | Clé | Tags de position | Offset |
|---|---|---|---|---|---|---|---|
| 1 | `perversion` | vide | 4643 | 11 | `likes_missionary` | Missionary | 5450 |
| 2 | `masochist` | Aggressive, AggressiveFM | 4703 | 12 | `likes_oral` | Oral, Blowjob, Cunnilingus, LickPussy, LickAss, Rimjob | 5531 |
| 3 | `prone_to_orgasm` | vide | 4794 | 13 | `likes_rcowgirl` | ReverseCow, ReverseCowgirl | 5665 |
| 4 | `attractiontoplayer` | vide (+ condition E6c) | 4859 | 14 | `likes_rough` | Aggressive, AggressiveFM | 5763 |
| 5 | `fitness` | Penetration, Foreplay, Cum | 4927 | 15 | `likes_sidefuck` | Spooning, Sidefuck, Spoon | 5856 |
| 6 | `likes_anal` | Anal | 5021 | 16 | `likes_toys` | Toys, Dildo, Vibrator | 5956 |
| 7 | `likes_boobjob` | Titfuck, Boobjob | 5090 | 17 | `likes_tribadism` | Tribadism, Scissor, Scissoring | 6048 |
| 8 | `likes_cowgirl` | Cow, Cowgirl | 5177 | 18 | `likes_vaginal` | Vaginal | 6154 |
| 9 | `likes_doggy` | Doggy, Doggystyle | 5260 | 19 | `masochist-` | Aggressive, AggressiveFM | 6229 |
| 10 | `likes_handjob` | Handjob, RubPussy, FingerPussy | 5346 | 20 | `prone_to_orgasm-` | Penetration, Foreplay, Cum | 6321 |
- Identique à `AUD` § 9.4 (relue indépendamment, mêmes 20 clés, mêmes tags). Deux groupes partagent les mêmes tags : {1 `masochist`, 14 `likes_rough`, 19 `masochist-`} et {5 `fitness`, 20 `prone_to_orgasm-`}.
- Les 18 stats (hors les deux clés en `-`) existent comme `.lpstat` chez l'auteur et chez nous (`AUD` § 9.4, 9.9 point 7 ; non rejoué).
- Les 34 mots de tags figurent tous dans `O\BPs\GI.uasset` (`grep -a -o -w -F`, annexe A point 10 : Penetration 396, Foreplay 158, Cum 33, Anal 215, Vaginal 327, Missionary 148, Titfuck 3, Tribadism 2…), ce qui rend les tags atteignables en principe (estimé : le vocabulaire d'animations de GI ; la valeur réelle de `Anim.Tags` n'est pas lue).
- Exemple (calculé sur la table) : position `{Missionary, Vaginal, Penetration}` → éligibles `perversion`, `prone_to_orgasm`, `attractiontoplayer` (si condition), `fitness`, `likes_missionary`, `likes_vaginal`, `prone_to_orgasm-` = 7 clés sur 20 ; `CurrentTags` vide → les 20 passent.
### 1.3 Formule de la valeur (établi pour les constantes et l'ordre, mesuré pour la table)
`Change = signe × ceil((2,0 − MapRangeClamped(|x|, 0, 100, 0, 1,9)) × 10) / 10`, `x` = valeur courante de la stat (`QueryStat`), `signe` = −1 si la clé finit par `-`, sinon +1. Instructions : `Abs` `Cursor.uexp:8339`, `MapRangeClamped(·, 0, 100, 0, 1.9)` `:8360`, `Subtract(2, ·)` `:8401`, `Multiply(·, 10)` `:8427`, `FCeil` `:8453`, `Conv_IntToFloat` `:8474`, `Divide(·, 10)` `:8495`, `Multiply(·, signe)` `:8521`, `SelectFloat(−1, 1, EndsWith)` `:8308`.
Table calculée (script `calc.py`, double = arithmétique des Blueprints UE 5.x, float32 = UE 4.20 de l'auteur ; **identiques sur les 21 points**) :
| x | 0 | 1 | 5 | 10 | 20 | 25 | 30 | 40 | 45 | 50 | 55 | 60 | 70 | 75 | 80 | 90 | 95 | 99 | 100 | 120 | −30 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| valeur | 2,0 | 2,0 | 2,0 | 1,9 | 1,7 | 1,6 | 1,5 | 1,3 | 1,2 | **1,1** | 1,0 | 0,9 | 0,7 | 0,6 | 0,5 | 0,3 | 0,2 | 0,2 | **0,2** | 0,2 | 1,5 |
Lecture : 1,1 vers x = 50 (accord avec « fitness +1.1 » et « prone_to_orgasm -1.1 » des captures) ; **100 donne 0,2 et non 0,1** (arrondi flottant de 2,0 − 1,9 ; corrige l'hésitation de `AUD` § 9.5). Le rendement dépend de la **valeur de la stat**, pas du nombre d'orgasmes.
## 2. Fichiers touchés, ordre
### 2.1 Conception 5.8 : trois options
| Critère | A. Blueprint fidèle (comme l'auteur) | **B. C++ autonome (recommandée)** | C. Hybride (C++ pour table / tirage / formule, BP pour les widgets) |
|---|---|---|---|
| Assets créés / modifiés | `Cursor` (variables `statTags`, `Stats`, `countBR`, `lastCum`, widgets `ScrollBonuses` et `TextBlock_0`, fonction, liaison de texte), `BonusReward` (nouveau), `Person` (+ ~6 nœuds), `GI` (`DisableBonus` + 2 nœuds Save / Load, `GI.uexp:866870` et `:869178` chez l'auteur), `GameSettings` (champ), `SettingCheckbox` / `MainMenu` (case) | aucun (version minimale) ; 2 fichiers C++ neufs | `Cursor` (widgets + appel), `BonusReward` (nouveau), `Person` (1 nœud), + 2 fichiers C++ |
| Dont modifiés par d'autres lots | `Person` (STG), `GI` (STG B7-a) : **oui**, `git status` du 2026-10-14 : `M` sur `GI`, `Person`, `Room` | **aucun** (Room non touché) | `Person` : oui |
| Faisable par MCP | nœuds : oui en principe (`BlueprintTools`, ~70 instructions décodées de 7141 à 8896 ≈ 60 à 80 nœuds, estimé) ; **arbre de widgets : non démontré** (seuls `scene.SceneTools`, `blueprint.BlueprintTools`, `asset.AssetTools`, `object.ObjectTools` apparaissent dans les 40 premières occurrences de `editor_toolset.toolsets.*` des Docs ; aucun toolset de widget vu, recherche tronquée) ; **valeur par défaut d'une `Map<String, StringArray>` : non démontrée** | sans objet (pas de MCP) | nœuds oui ; widgets : mêmes réserves que A |
| Précédent dans le dépôt | aucun pour de l'UMG créé par MCP | UI construite en C++ sans toucher de Blueprint : `LPSettingsSubsystem.h:6` (« Aucun Blueprint n'est modifié »), `LPColorGradeSubsystem.cpp:188-302` (lignes injectées dans `SettingsScrollBox`), `LPModInfoSubsystem.cpp:288-307` (cases), `LPKeyboardSubsystem.cpp:636`, `LPDirectControlSubsystem.cpp:1259`, `LPCameraModeSubsystem.cpp:469` (panneaux Slate par `AddViewportWidgetContent`) ; appels de fonctions Blueprint par `FindFunction` + `ProcessEvent` : `LPDirectControlSubsystem.cpp:411-455`, `LPDataCompat.cpp:149-164`, `LPPersonPropGuardSubsystem.cpp:129-131` ; réglages joueur hors `GameSettings.sav` : `LPUserSettings.h:3-9` | mixte |
| Build C++ | aucun | oui (module `LifePlay`, dépendances Slate, SlateCore, UMG déjà présentes `LifePlay.Build.cs:38`) | oui |
| Apparence | identique à l'auteur (police et styles du jeu) | panneau Slate au style à régler (police et couleur à copier d'un widget existant, comme `LPColorGradeSubsystem.cpp:289-297`) | identique à l'auteur |
| Testabilité hors jeu | faible | table, tirage et formule en fonctions statiques pures, contrôle de formule au démarrage | moyenne |
| Effort estimé | 5 à 8 sessions MCP (estimé, 4.1) | 3 fiches pour la version minimale, 3 de plus pour la grille | 4 à 5 sessions MCP + C++ |
**Recommandation : B.** Raisons : (1) aucun asset partagé touché (Person, GI sont en cours de modification par STG, Room par CAM) ; (2) aucune dépendance à des capacités MCP non démontrées (widgets, valeur par défaut de map) ; (3) tout le dépôt a déjà ce mode de construction ; (4) la version minimale n'a **aucune interface**, ce qui évite le point que l'auteur désigne comme suspect (`Change_Logs.txt:34`, plantages autour des orgasmes). Réserve : l'apparence du panneau (version 1) ne sera pas pixel-identique à celle de l'auteur. A et C restent possibles si l'utilisateur veut l'identique : elles exigent d'abord la lecture L-M1 (4.3).
### 2.2 Accroche à l'orgasme : deux manières, une seule active
| | **H1. Sondage en C++ (recommandée pour l'alpha)** | H2. Un nœud dans `GiveOrgasmReward` |
|---|---|---|
| Principe | le sous-système lit à 0,1 s `GI.SexOn` et, pour chaque `GI.SexPersons[i]`, `Person.Sex_OrgasmCounts` : une hausse = un orgasme (la macro `IncrementInt` est dans l'événement `Cum`, P-STG:16118-16125 ; remise à 0 avec `Sex_Energy` et `Sex_BoredCountdown`, P-STG:15908-15911 et 16175-16178 : une baisse = remise à zéro, on resynchronise sans récompense) | un nœud `CallFunction` (fonction statique C++ `NotifyOrgasm(Person)`) inséré entre `K2Node_CustomEvent_87.then` et `K2Node_CallFunction_1005.execute` (P-STG:16059-16061) : 1 nœud, 3 liens |
| Assets touchés | **aucun** | `Person.uasset` (STG + CAM non commités : annonce à Big Boss, instantané, session MCP, compilation, `CompileAllBlueprints`) |
| Couvre `Endsex` (STE-ORG4) | oui **si** ORG4 passe par `Person::Cum` (le compteur monte) ; non si ORG4 appelle seulement `GiveOrgasmReward` | oui dans les deux cas |
| Double déclenchement | impossible : un incrément par `Cum` ; deux orgasmes en moins de 0,2 s : seul le dernier compte (comme l'auteur, E3) | idem ; si H2 est ajouté plus tard, il écrit le compteur observé pour que H1 ne redéclenche pas |
`STE-ORG4-SPEC.md` n'existe pas encore (Glob `STE-ORG4*` dans le dossier du lot : seule la fiche `fiches\STE-ORG4.md`) : la compatibilité avec ORG4 est donc **non vérifiée** (Q-ORG5-6).
### 2.3 Fichiers touchés (option B)
| Fichier | Action | Version | Autre lot concerné |
|---|---|---|---|
| `Source/LifePlay/LPBonusRewardSubsystem.h` et `.cpp` | **créer** | V-min, V-1 | aucun (fichiers neufs ; `git status` actuel : `LPCameraModeSubsystem.*`, `LPSaveRelaunch.*` non suivis, `LPDebugSubsystem.*`, `LPDirectControlSubsystem.*`, `LPOutdoorRoomSubsystem.*`, `LifePlay.cpp` modifiés par d'autres lots : non touchés) |
| `Source/LifePlay/LPSettingsSubsystem.cpp` | 1 règle d'onglet (`GetName().StartsWith("LPBonus")` → onglet Lewd), près de `:173-181` | V-2 | aucun (fichier non modifié aujourd'hui) |
| `Content/BPs/Person.uasset` | 1 nœud (H2) | V-3, facultative | **STG, CAM** |
| `Saved/Config/Windows/GameUserSettings.ini` (utilisateur, pas le dépôt) | section `[/Script/LifePlay.LPBonusRewardSettings]`, clé `bDisabled` | V-min | aucun |
Aucun fichier de `GI`, `Cursor`, `GameSettings`, `MainMenu`, `SettingCheckbox`, `Room` n'est modifié par l'option B. Ordre : C++ (V-min) → build → VAL → essai en jeu → panneau (V-1) → réglage (V-2) → H2 (V-3).
### 2.4 Widgets et fonctions
**(a) Éléments de l'auteur à reproduire (référence, option A / C)** :
| Élément | Contenu | Source |
|---|---|---|
| `Cursor.PopulateBonusRewards(Person : Person, Tags : StringArray)` | E2 à E6 | `Cursor.uexp:14450-14487`, `:7141-8896` |
| `Cursor` variables | `statTags` (`Map<String, StringArray>`, 20 entrées par défaut), `Stats` (`String[]`), `countBR` (int), `lastCum` (Person) | `Cursor.uexp:7218-7336` |
| `Cursor` widgets | `ScrollBonuses` (`ScrollBox` ou panneau avec `ClearChildren` / `AddChild` / `GetChildrenCount`), `TextBlock_0` + liaison `Get_TextBlock_0_Text_0` | `Cursor.uexp:7198`, `:22465-23015` |
| `BonusReward` (UserWidget) | variables `Person` (Person), `Stat` (String), `Change` (réel) ; `RewardButton` (`OnClicked`) contenant `RewardText` lié à `Get_RewardText_Text_0` | `BonusReward.uexp:1069-1985` |
| `Person.GiveOrgasmReward` | `Branch(GI.DisableBonus)` ; faux → `MakeStruct StringArray(Strings = GI.CurrentTags)` → `GI.Cursor.PopulateBonusRewards(self, struct)` → rejoint `OrgasmIrBoost` | `Person.uexp:21111-21262` |
**(b) Conception C++ (option B), signatures à respecter** :
```
UCLASS(config = GameUserSettings) class ULPBonusRewardSettings : public UObject // dans LPBonusRewardSubsystem.h
UPROPERTY(config) bool bDisabled = false; // équivalent de DisableBonus
struct FLPBonusStat { FString Key; TArray<FString> Tags; }; // 20 entrées, section 1.2
UCLASS() class ULPBonusRewardSubsystem : public UGameInstanceSubsystem
Initialize / Deinitialize // FTSTicker 0,1 s, comme LPColorGradeSubsystem.h:51
bool Tick(float); // sondage 2.2 H1 ; débounce 0,2 s ; (V-1) retrait différé du panneau
static const TArray<FLPBonusStat>& Table();
static double RewardChange(double X, bool bNegative); // formule 1.3, ordre des opérations conservé, en double
static bool Overlaps(const TArray<FString>& A, const TArray<FString>& B, bool bIfEmptyThenTrue); // GI.Overlaps, B:1073624-1074121
static void Pick(const TArray<FString>& PositionTags, FRandomStream& Rng, bool bAttractionAllowed,
TArray<FString>& OutStats /*sans le '-'*/, TArray<bool>& OutNegative); // E5-E6, max 3, graine injectable
bool QueryStat(UObject* Person, const FString& Stat, double& OutValue); // FindFunction("QueryStat") + ProcessEvent, sortie Value
bool ModifyStat(UObject* Person, const FString& Stat, double Change); // FindFunction("ModifyStat"), DifficultyAdjusted = false, NoDisplay = false
void OnOrgasm(UObject* Person, const TArray<FString>& TagsCapturedAtDetection);
```
Algorithme de `Pick` (équivalent exact de E5-E6) :
```
Keys = les 20 clés ; Shuffle(Keys, Rng) ; garder 10
Out = []
pour Key dans Keys :
si NON Overlaps(PositionTags, Table[Key].Tags, true) : continuer
si Key == "attractiontoplayer" et NON bAttractionAllowed : continuer
// bAttractionAllowed = NON Person.isPlayer ET GI.IsInterestedIn(WhoIsInterested = Person, WhoInterests = GI.PlayerCharacter) ET SexPersons contient PlayerCharacter
Out.Add(Key sans '-' final, Key finit par '-')
si Out.Num() == 3 : arrêter
```
Sondage (V-min) : à 0,1 s, si `GI.SexOn` faux → vider le suivi ; sinon pour chaque personne de `GI.SexPersons` : premier passage → mémoriser le compteur sans récompense ; hausse → copier `GI.CurrentTags`, armer `Pending = {personne, tags, t + 0,2 s}` (remplace un `Pending` existant) ; baisse → resynchroniser. Quand `t` est atteint et la personne valide : `OnOrgasm`. Lecture des propriétés par réflexion, types contrôlés par `CastField` (précédents : `LPCameraModeSubsystem.cpp:40,407`, `LPShowCoupleSubsystem.cpp:303`, `LPUserSettings.cpp:14-55` pour réel float ou double) ; type absent ou inattendu : un avertissement journalisé **une fois** (précédent `LogParmsOnce`, `LPDirectControlSubsystem.cpp:425`), jamais de plantage.
Appels Blueprint (réflexion, paramètres par **nom**, valeurs initialisées puis détruites comme `LPDirectControlSubsystem.cpp:426-455`) : `Person.QueryStat(Stat, WithoutBuff, RNG)` sorties `Value`, `Bonus` (L6f-P:20-29) ; `Person.ModifyStat(Stat, Change, DifficultyAdjusted, NoDisplay)` sortie `Value` (L6f-P:561-567) ; `GI.IsInterestedIn(WhoIsInterested, WhoInterests)` sortie `Interested` (L6f-G:23421-23424). Ordre des paramètres de `IsInterestedIn` dans l'appel de l'auteur non relu : l'IMPL fixe `WhoIsInterested = Person` (estimé : l'auteur teste « le PNJ s'intéresse au joueur ») et le contrôle en VAL par le journal.
Journal (`LogLifePlay`, niveau Display), formats imposés pour la VAL : `Bonus : orgasme de <nom> (compteur <n>)` ; `Bonus : <nom> <stat> <±valeur> (x=<valeur>, tags=<n>)` ; `Bonus : aucune récompense éligible pour <nom>` ; `Bonus : désactivé (bDisabled)` ; au démarrage `Bonus : contrôle formule OK` (ou `Error` avec la valeur fautive) sur 0, 10, 25, 50, 75, 90, 100 → 2,0 ; 1,9 ; 1,6 ; 1,1 ; 0,6 ; 0,3 ; 0,2.
**V-1 (panneau)** : widget Slate `SLPBonusPanel` (titre vert « Pick a bonus reward for <Name_First>'s last orgasm », au plus 3 boutons « stat ±valeur ») ajouté par `GEngine->GameViewport->AddViewportWidgetContent(Panel, ZOrder)` (z entre 50 et 200 comme les précédents), `SelfHitTestInvisible` pour le conteneur et boutons `Visible` (le reste du jeu garde la souris). Clic : `ModifyStat`, puis **retrait différé au tick suivant** (jamais détruire le panneau dans le gestionnaire du bouton cliqué ; estimé : c'est la pratique à éviter, l'auteur le fait avec `ClearChildren()` dans le gestionnaire). Panneau retiré aussi si `GI.SexOn` devient faux (écart voulu : l'auteur laisse les boutons, E10). Texte du nombre : formatage par défaut de `FText::Format` avec un argument flottant ; attendu « 1.1 », « 2 » (non vérifié).
Nom de la personne : `Person.Name_First` est du **texte** chez l'auteur (`Cursor.uexp:22666`, argument de type texte) ; chez nous le type n'est pas lu : accepter `FTextProperty` et `FStrProperty`.
### 2.5 Option `DisableBonus` (L15 résolu)
| Point | Constat | Source |
|---|---|---|
| Sémantique | **`DisableBonus` vrai : pas de grille, bonus ancien conservé** ; faux : grille + bonus ancien | `Person.uexp:21111` → `:21135` (E1) |
| Stockage chez l'auteur | variable `GI.DisableBonus` ; copiée dans `GameSettings.DisableBonus` à l'enregistrement et relue au chargement | `B\BPs\GI.uexp:866870` (`GameSettingsInstance.DisableBonus = DisableBonus`), `:869178` (`DisableBonus = GameSettings.DisableBonus`) |
| Interface chez l'auteur | `SettingCheckbox` : clic → `GI.DisableBonus = bIsChecked` ; état → `BoolToCheckBoxState(GI.DisableBonus)` ; libellé « Disable Bonus Orgasm Rewards » dans `MainMenu` (aucune des 62 fonctions de `MainMenu` ne cite `DisableBonus`) | `B\UI\SettingCheckbox.uexp:1921`, `:13472` ; `AUD` § 9.7 |
| Message d'aide | « If you get crashes during sex scenes, Esc > Settings > tick Disable Bonus Orgasm Rewards … » | `B\UI\SexInterface.uexp:42851` (`AUD` § 9.7) |
| Chez nous | absent : `GameSettings`, `GI`, `MainMenu`, `SettingCheckbox`, `Cursor`, `Person` ne contiennent pas `DisableBonus` (`AUD` § 9.7, non rejoué) ; `SettingCheckbox` et `MainMenu` existent (`ls O\UI`) ; les réglages sont rangés en onglets par `ULPSettingsSubsystem`, dont **Lewd** (`LPSettingsSubsystem.cpp:35-43`, règle de mots `:198-203`, règle de préfixe de nom `:173-181`) |
| Conception retenue | **réglage C++ hors `GameSettings.sav`** (comme `LPUserSettings.h:3-9`, `LPColorGradeSubsystem.h:7`) : `ULPBonusRewardSettings::bDisabled` dans `GameUserSettings.ini` ; version minimale : ini seulement ; V-2 : case injectée dans `MainMenu.SettingsScrollBox` (modèle `LPModInfoSubsystem.cpp:288-307`) nommée `LPBonusCheck…` + règle d'onglet Lewd ; **aucune sauvegarde de partie touchée** (A ajouterait un champ à `GameSettings` : valeur par défaut faux pour les anciennes sauvegardes, non testé) |
### 2.6 Découpage en fiches (ordre et dépendances)
Les tours sont des estimations (comparées aux budgets des fiches du lot : 45 à 55 tours pour une SPEC N3) ; l'agent exact est à fixer par l'orchestrateur.
| Fiche | Rôle (agent) | Niv. | Contenu | Dépend de | Tours | Jeton UE |
|---|---|---|---|---|---|---|
| ORG5-I1 | IMPL (lp-impl) | N3 | `LPBonusRewardSubsystem.h/.cpp` **V-min** : table, `Pick`, `RewardChange`, sondage H1, `QueryStat` / `ModifyStat` par réflexion, application automatique, ini `bDisabled`, journal, contrôle de formule | Q-ORG5-1, 2, 3, 4, 5 | 40 | non (écriture de 2 fichiers) |
| ORG5-B1 | BUILD (lp-builder) | N1 | build du module `LifePlay`, comptes par `Build.ps1 -FromLog` | I1 | 12 | éditeur fermé (`UeState` = 0), build partagé avec CAM |
| ORG5-V1 | VAL (lp-validator) | N2 | critères 7.1 à 7.6 (PASS-STATIC) | B1 | 25 | `CompileAllBlueprints` et `LPTestStringCompat` : sous UE-GO |
| ORG5-J1 | essai en jeu (utilisateur) | – | critères 7.7 à 7.9 | V1 | – | une session de jeu (build empaqueté ; la PIE ne déroule pas ce parcours, `lp-mcp` § 1 règle 5) |
| ORG5-I2 | IMPL | N3 | **V-1** : panneau Slate, 3 boutons, clic, retrait différé | J1 PASS, Q-ORG5-7, 8 | 55 | non |
| ORG5-B2 / V2 / J2 | BUILD / VAL / jeu | N1 / N2 / – | idem | I2 | 12 / 25 / session | idem |
| ORG5-I3 | IMPL | N2 | **V-2** : case dans les réglages + règle d'onglet Lewd | I2 | 30 | non |
| ORG5-B3 / V3 | BUILD / VAL | N1 / N2 | idem | I3 | 12 / 20 | idem |
| ORG5-I4 | IMPL par MCP (lp-impl, `lp-mcp` § 3-4) | N3 | **H2** : 1 nœud dans `GiveOrgasmReward`, `P1/P2` (MD5 de `Person`, dumps avant), instantané, compilation, `save_assets`, dumps après | libération de `Person` (STG commité), Q-ORG5-6 | 25 | **oui** (MCP, copie partagée ; gel annoncé à toutes les sessions) |
Option A (non recommandée) : ~8 sessions MCP, ~200 tours, plus la lecture L-M1.
## 3. Repli
- **Repli de la version minimale** : supprimer les deux fichiers neufs (ou `git restore` par le gardien) ; aucun asset, aucune sauvegarde à restaurer. Désactivation sans reconstruire : `bDisabled=True` dans l'ini utilisateur.
- **Repli du panneau** : revenir à la version minimale (l'application automatique reste).
- Si le build est bloqué ou le jeton UE indisponible avant le 2026-10-16 : rien dans l'alpha, la fonction reste dans le lot STE pour la suite.
### 3.1 Version minimale pour l'alpha (2026-10-18)
**V-min = I1 + B1 + V1 + J1, option B, accroche H1** : à chaque orgasme (toute personne de la scène), tirage exact de E5-E6, **application automatique de la première récompense retenue** par `ModifyStat(Stat, Change, false, false)` (la ligne « stat ±n » s'affiche comme celle du `mood`), journal, ini `bDisabled`. Ce que V-min **laisse de côté** : la grille et le choix (E7), le texte d'en-tête (E8), la case de réglage (2.5), l'accroche exacte par `GiveOrgasmReward` (H2), le nettoyage en fin de scène (sans objet : pas de panneau), la couverture de `Endsex` si ORG4 n'appelle pas `Cum`. Ce qu'elle garde : table, filtre par tags, règle de `attractiontoplayer`, formule, débounce 0,2 s, bonus ancien intact, kill-switch.
Faisabilité (estimée) : ≈ 80 tours d'agents (≈ 2 h) + 1 session de jeu ; build incrémental de référence 18,56 s (`STE-H3d_Build_20261013-124425.txt:10`, ajout de fichiers non mesuré). **Entre dans l'alpha si** : Q-ORG5-1 à 5 tranchées le 2026-10-14 ou 15, jeton UE de build libre le 15, V1 PASS-STATIC le 16, J1 le 16 ou 17. **V-1 (panneau)** : seulement si J1 est PASS le 16 et si Big Boss accepte le risque d'interface (R2) ; sinon après l'alpha. V-2 et H2 : après l'alpha.
## 4. Risques R#, questions Q#
### 4.1 Estimation (récapitulatif)
| Périmètre | Fiches | Tours | Sessions MCP | Sessions de jeu | Durée d'agent |
|---|---|---|---|---|---|
| V-min | I1, B1, V1 | ≈ 77 | 0 | 1 | ≈ 2 h |
| + V-1 (panneau) | I2, B2, V2 | ≈ 92 | 0 | 1 à 2 | ≈ 2 h 30 |
| + V-2 (case de réglage) | I3, B3, V3 | ≈ 62 | 0 | 0 à 1 | ≈ 1 h 30 |
| + H2 | I4 (+ VAL) | ≈ 45 | 1 | 0 | ≈ 1 h |
| Option A complète (comparaison) | ~10 fiches | ≈ 200 | ≈ 8 (estimé : 70 instructions ≈ 70 nœuds ≈ 300 appels MCP, plus widgets et `DisableBonus`) | 2 | ≈ 8 h |
### 4.2 Risques
| # | Risque | Parade |
|---|---|---|
| R1 | **Plantages autour des orgasmes** : l'auteur a dû ajouter l'option 5.27 faute de reproduire des plantages (`Change_Logs.txt:34`) ; cause inconnue | V-min sans widget ni création d'objet UMG dans la pile de l'orgasme ; sondage hors de la pile de `Cum` ; aucune destruction dans un gestionnaire de clic (V-1) ; ini `bDisabled` ; journal de chaque étape |
| R2 | Interface (V-1) : panneau qui capte la souris, mauvais z-order avec le menu d'actions, apparence | conteneur non cliquable, boutons seuls cliquables ; z-order entre les précédents 50 et 200 ; essai J2 |
| R3 | Appels par réflexion : nom ou paramètre renommé, type inattendu (réel float ou double) | recherche par nom, `CastField` des deux types, avertissement unique, abandon sans effet |
| R4 | Sondage : faux positif (remise à zéro du compteur, personne qui entre dans la scène), orgasme manqué (scène qui finit avant le tick) | premier passage = mémorisation sans récompense ; baisse = resynchronisation ; tick 0,1 s ; l'orgasme manqué n'a pas d'autre effet que l'absence de récompense |
| R5 | Équilibrage : modifier les stats des PNJ (l'auteur le fait) | identique à l'auteur ; Q-ORG5-4 |
| R6 | Build partagé avec CAM (Room B2 non commité) et d'autres fichiers C++ modifiés | fichiers neufs seulement ; un seul build à la fois ; annonce de gel |
| R7 | ORG4 (fin par `Endsex`) : un appel direct de `GiveOrgasmReward` sans `Cum` n'incrémente pas le compteur | Q-ORG5-6 ; H2 si nécessaire |
| R8 | Licence : code de l'auteur sous MIT (`Licenses\Vinfamy_License.txt:1`, notre copie) ; la reconstruction reprend le comportement lu, pas le code | commentaire d'origine et de licence en tête du `.h` ; décision de l'utilisateur (Q-ORG5-9) |
| R9 | Délai : alpha le 2026-10-18 | V-min seule dans l'alpha, 3.1 |
| R10 | Faux sentiment de preuve : tout le comportement est lu par désassemblage, **jamais vu en jeu** | J1 obligatoire avant toute mention « conforme à l'auteur » |
### 4.3 Lectures MCP / jeton UE manquantes (non lancées)
| # | Lecture | Ce qu'elle trancherait |
|---|---|---|
| L-M1 | MCP `list_toolsets` puis `describe_toolset` **filtré** sur les noms contenant widget, umg ou user_widget (pas de description complète, `lp-mcp` § 1 règle 6) | si l'arbre de widgets et la valeur par défaut d'une map sont créables par MCP : seul point qui rend les options A et C réalistes |
| L-M2 | MCP `ObjectTools.get_properties` sur `/Game/BPs/GI.Default__GI_C` (`CurrentTags`, `SexPersons`, `Cursor`, `PlayerCharacter`, `SexOn`) et `/Game/BPs/Person.Default__Person_C` (`Sex_OrgasmCounts`, `isPlayer`, `Name_First`) — forme déjà utilisée : `STG-B1-IMPL.md:41` | types des propriétés (texte ou chaîne, réel float ou double) ; facultative : l'IMPL les gère par `CastField` et les journalise |
| L-M3 | MCP `BlueprintTools.get_node_infos` sur `K2Node_CustomEvent_87` et `K2Node_CallFunction_1005` de Person, puis `find_node_types` pour le nœud `NotifyOrgasm` | seulement pour H2 (I4) : broches, `type_id` |
| L-M4 | commandlet `LPDumpWidgetTree -BP=Cursor` (éditeur fermé, `lp-commandlets`) | seulement pour A et C : où placer `ScrollBonuses` dans notre arbre de `Cursor` (nos `SexExclusives`, `ActionBox`, `BondageBox` existent, `grep -a -c` 10 / 5 / 7 ; `ScrollBonuses`, `TextBlock_0` : 0) |
| L-J1 | essai en jeu de J1 (7.7 à 7.9) | effet réel : fréquence des récompenses, tags de `CurrentTags` à l'orgasme, texte du nombre |
| L-S1 | lecture non faite par manque d'intérêt pour B : propriétaire du son `ui_positive` du bouton `BonusReward` (style de bouton), arbre de widgets de `Cursor` de B | apparence uniquement |
### 4.4 Questions (utilisateur / orchestrateur)
| # | Question | Recommandation |
|---|---|---|
| Q-ORG5-1 | La **version minimale** (application automatique de la première récompense retenue, sans grille) convient-elle pour l'alpha, ou ne rien mettre dans l'alpha ? | oui, V-min ; la grille (V-1) seulement si J1 PASS le 16 |
| Q-ORG5-2 | Option **B (C++ autonome)** ou A / C (identique à l'auteur, exige L-M1) ? | B |
| Q-ORG5-3 | **Nombre de récompenses** : 3 (code du build 2023.04) ou 5 (journal 5.26) ? | 3 (le code fait foi) |
| Q-ORG5-4 | Récompenses pour **toute personne** qui jouit (comme l'auteur, PNJ compris) ou pour le joueur seulement ? | comme l'auteur ; `attractiontoplayer` reste réservée aux PNJ intéressés par le joueur |
| Q-ORG5-5 | **Valeur par défaut** de `bDisabled` dans l'alpha (faux = fonction active) ; case de réglage avant l'alpha (V-2) ou ini seulement ? | faux ; ini seulement |
| Q-ORG5-6 | Accroche **H1 (sondage)** ou **H2 (nœud dans `GiveOrgasmReward`, touche Person)** ; que fera ORG4 : `Person::Cum` ou `GiveOrgasmReward` seul ? | H1 pour l'alpha ; H2 après, selon ORG4 |
| Q-ORG5-7 | Position du panneau (V-1) : à droite au centre (hypothèse), près du menu d'actions, ailleurs ? Retrait en fin de scène (écart voulu) ou boutons conservés comme l'auteur ? | droite, centre ; retrait en fin de scène |
| Q-ORG5-8 | Affichage du texte d'en-tête et des trois cas de E8 (textes de l'auteur en anglais, à garder tels quels ?) | oui, tels quels |
| Q-ORG5-9 | Licence : reconstruire le comportement lu dans le build de l'auteur (MIT, `Licenses\Vinfamy_License.txt:1`) est-il accepté, avec mention d'origine ? | oui, avec commentaire d'origine |
| Q-ORG5-10 | Garder le désassembleur Kismet (3 scripts du scratchpad de session) : le ranger dans `Tools\` (lecture seule, utile à toute lecture de build cuit) ? | oui, par une fiche d'outillage |
## 5. Interdits
- Version minimale : **aucun asset modifié** (ni `GI`, `Person`, `Room`, `Cursor`, `GameSettings`, `MainMenu`, `SettingCheckbox`) ; aucun champ ajouté à `GameSettings.sav` ; le bonus ancien (`OrgasmIrBoost`, `mood`, `attractiontoplayer`) reste intact.
- Pas de création de widget, d'objet UMG ni d'`AddToViewport` dans la pile de l'événement `Cum` ; pas de destruction d'un widget dans son propre gestionnaire de clic.
- Pas de `ProcessEvent` sans `FindFunction` valide, sans paramètres initialisés puis détruits ; pas d'accès à une personne sans `IsValid`.
- Pas de contournement de garde : une propriété ou une fonction introuvable = avertissement unique et abandon, jamais de repli silencieux.
- Pas de `delete_node` ni de type_id improvisé (H2) ; pas de commit par l'agent (le gardien) ; build et commandlets seulement sous jeton UE, éditeur fermé.
- Aucune ligne de la grille d'un autre lot, aucun contenu de jeu (noms, textes) dans un skill ou un message de commit public (`lp-paths`).
## 6. Valeurs moteur lues dans le moteur installé
| Valeur | Source | Établi |
|---|---|---|
| `FCeil` prend un `double` et rend un `int32` ; `MapRangeClamped` prend et rend des `double` ; `Subtract_DoubleDouble`, `Multiply_DoubleDouble` | `D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\Engine\Classes\Kismet\KismetMathLibrary.h:796`, `:836`, `:551`, `:555` | oui |
| `RetriggerableDelay(WorldContext, float Duration, FLatentActionInfo)`, latent | `…\Kismet\KismetSystemLibrary.h:700-701` | signature oui, remplacement du délai non relu |
| `GameViewportClient::AddViewportWidgetContent(TSharedRef<SWidget>, int32 ZOrder)` et `RemoveViewportWidgetContent` | `…\Engine\GameViewportClient.h:258`, `:265` | oui |
| `EX_JumpIfNot` = « Goto if not expression » (saute quand la condition est fausse) ; `EX_Context`, `EX_VirtualFunction`, `EX_CallMath`, `EX_SwitchValue`, `EX_TextConst` : formats d'instruction | `D:\Game\Launcher\Engine\UE_4.27\Engine\Source\Runtime\CoreUObject\Public\UObject\Script.h:173` ; `…\ScriptSerialization.h:150-557` (moteur 4.27 le plus proche du 4.20 de l'auteur ; contrôle : décodage exact jusqu'à `EX_EndOfScript` et cibles de saut toutes sur un début d'instruction, annexe A) | oui |
| `FText::AsNumber` / formatage par défaut des flottants (nombre de décimales) | recherche de `MaximumFractionalDigits` dans `Core\Public\Internationalization\FastDecimalFormat.h` : **aucune occurrence** ; non lu ailleurs | **incertain** : à lire à l'IMPL (`Text.h`, `FastDecimalFormat.h` voisins) avant de fixer l'attendu « 1.1 » |
## 7. Critères de VAL (commande → attendu ; « attendu » écrit avant tout lancement)
| # | Fiche | Commande ou lecture | Attendu |
|---|---|---|---|
| 7.1 | V1 | `git status --porcelain -- Content Source Config` (jeu) comparé à l'état d'entrée (10 `M`, 4 `??` au 2026-10-14) | seules deux lignes `??` de plus : `Source/LifePlay/LPBonusRewardSubsystem.h` et `.cpp` ; aucun `M` de plus |
| 7.2 | V1 | `.\Tools\Build.ps1` (éditeur fermé) puis `.\Tools\Build.ps1 -FromLog <journal>` | code de sortie 0 ; module `LifePlay` recompilé ; 0 erreur ; 0 avertissement C4996 nouveau sur les deux fichiers |
| 7.3 | V1 | Grep des clés dans le `.cpp` : motifs des 20 clés de 1.2 | 20 clés, 20 listes de tags identiques à 1.2 (relecture ligne à ligne) |
| 7.4 | V1 | lecture du `.cpp` contre 1.3 et 2.4 | ordre des opérations de `RewardChange` identique ; `Pick` : 10 candidates, max 3, `Overlaps` avec drapeau vrai, règle `attractiontoplayer` ; aucun widget créé dans V-min |
| 7.5 | V1 | `.\Tools\LogStats.ps1 -Latest <journal de démarrage> -Pattern 'Bonus : contrôle formule'` | 1 ligne `contrôle formule OK` ; 0 ligne `Error` du sous-système |
| 7.6 | V1 | `CompileAllBlueprints` éditeur fermé ; `LPTestStringCompat` | 0 erreur `/Game/` ; `3120 / 0 / 60` (aucun Blueprint modifié, valeurs de référence du lot) |
| 7.7 | J1 | scène avec le joueur jusqu'à un orgasme, journal avec `-ScriptStackOnWarnings` ; `LogStats -Pattern 'Bonus :'` | par orgasme : 1 ligne `orgasme de`, puis soit 1 ligne `<stat> <±valeur>` soit `aucune récompense éligible` ; la valeur suit 1.3 pour le `x` journalisé |
| 7.8 | J1 | même journal, motifs `Accessed None` et `PersonCumming` | 0 occurrence nouvelle par rapport au journal de référence du lot |
| 7.9 | J1 | ini `bDisabled=True`, nouvel orgasme | 1 ligne `désactivé` ; 0 ligne de récompense ; la ligne `mood` du bonus ancien est toujours là |
| 7.10 | V2 / J2 (V-1) | clic sur un bouton | 1 `ModifyStat`, panneau retiré au tick suivant, 3 boutons au plus, 0 plantage ; clic hors panneau : les actions du jeu répondent |
## Annexe A. Commandes et lectures (CART 2), comptes bruts
Aucun caractère de redirection dans les commandes ; `B`, `O` comme dans les sigles. `<S>` = scratchpad de session.
1. Format du bytecode : `Read` de `UE_4.27\…\ScriptSerialization.h` (574 lignes) et `Script.h:150-300`.
2. Liste des fonctions de `Cursor` : `python -I <S>/kdis.py B/UI/Cursor.uasset B/UI/Cursor.uexp list` → `total=122954 names=703 exports=764 imports=293 ue=136556` ; `ExecuteUbergraph_Cursor size=7504 uexp_off=6522` ; `PopulateBonusRewards size=86 uexp_off=14414`.
3. Talon : `… kdbg.py B/UI/Cursor.uasset B/UI/Cursor.uexp PopulateBonusRewards 0 99999999 300` → `5 instructions`, `14470 v:ExecuteUbergraph_Cursor(10856)`.
4. Corps : `… kdbg.py … ExecuteUbergraph_Cursor 7000 9300 230` → `q=7122 end=14014 tail=12 mem=10864 decoded=370 last_p=14014 err=None` ; `jumps=66 sur debut=66` (les 66 cibles de saut tombent sur un début d'instruction : le décodage est cohérent).
5. Appel dans Person : `… kdbg.py B/BPs/Person.uasset B/BPs/Person.uexp ExecuteUbergraph_Person 20700 21700 420` → `decoded=5164 … err=None`, `jumps=911 sur debut=911`.
6. Table : `python -I <S>/strs.py B/UI/Cursor.uexp 4620 6420 3` → 20 clés et leurs tags (offsets de 1.2).
7. `BonusReward` : `… kdis.py B/UI/BonusReward.uasset B/UI/BonusReward.uexp list` → 3 fonctions ; `kdbg.py … ExecuteUbergraph_BonusReward` → `decoded=8` ; `Get_RewardText_Text_0` → `decoded=27`, texte `{stat} {sign}{change}`. `Get_TextBlock_0_Text_0` → `decoded=19`.
8. Utilisateurs des noms : `… kall.py B/UI/Cursor.uasset B/UI/Cursor.uexp 'ScrollBonuses|lastCum|countBR|BonusReward|statTags' 120` → `fonctions=45 decodees=45 correspondances=15` (13 dans l'ubergraph, 2 dans `Get_TextBlock_0_Text_0`). `grep -r -l -a -E "ScrollBonuses|BonusReward_C|PopulateBonusRewards" B/BPs B/UI --include=*.uasset` → `Person.uasset`, `BonusReward.uasset`, `Cursor.uasset`.
9. `DisableBonus` : `… kall.py` sur `BPs/GI`, `BPs/GameSettings`, `UI/MainMenu`, `UI/SettingCheckbox` avec le motif `DisableBonus` → GI `fonctions=609 decodees=609 correspondances=2` (`:866870`, `:869178`) ; GameSettings `fonctions=0` ; MainMenu `fonctions=62 decodees=62 correspondances=0` ; SettingCheckbox `fonctions=5 decodees=5 correspondances=2` (`:1921`, `:13472`). `Get_SexExclusives_Visibility_0` → `decoded=6`.
10. `GI.Overlaps` et `GI.IsInterestedIn` du build : `kdbg.py B/BPs/GI.uasset B/BPs/GI.uexp Overlaps` (`decoded=32`, `jumps=8 sur debut=8`) et `IsInterestedIn` (`decoded=20`, `jumps=6 sur debut=6`) ; nos fonctions : `L6f-G:19622-19673` (`Overlaps`, 14 nœuds, paramètres `Array1`, `Array2`, `IfEmptyThenTrue`) et `:23421` (`IsInterestedIn`, 28 nœuds) ; noms dans `O\BPs\GI.uasset` (`grep -a -c -F`) : `Overlaps` 5, `IsInterestedIn` 11, `Cursor` 10, `CurrentTags` 1, `PlayerCharacter` 1, `SexOn` 1, `SexPersons` 33, `PersonCumming` 1. Tags dans `O\BPs\GI.uasset` (`grep -a -o -w -F <mot> | wc -l`) : `Penetration 396, Foreplay 158, Cum 33, Anal 215, Titfuck 3, Boobjob 4, Cow 54, Cowgirl 204, Doggy 182, Doggystyle 11, Handjob 80, RubPussy 18, FingerPussy 9, Missionary 148, Oral 236, Blowjob 173, Cunnilingus 19, LickPussy 37, LickAss 4, Rimjob 20, ReverseCow 2, ReverseCowgirl 56, Aggressive 79, AggressiveFM 31, Spooning 19, Sidefuck 19, Spoon 5, Toys 30, Dildo 14, Vibrator 12, Tribadism 2, Scissor 12, Scissoring 2, Vaginal 327`. Noms dans `O\BPs\Person.uasset` : `Sex_OrgasmCounts` 1, `isPlayer` 1, `Name_First` 1, `ModifyStat` 51, `QueryStat` 18, `GiveOrgasmReward` 2, `OrgasmIrBoost` 2. `O\UI\Cursor.uasset` : `ScrollBonuses` 0, `TextBlock_0` 0, `PopulateBonusRewards` 0, `lastCum` 0, `SexExclusives` 10, `ActionBox` 5, `BondageBox` 7.
11. Formule : `python -I <S>/calc.py` → table de 1.3 (double et float32 identiques). Sortie recopiée : `0 → 2.0 ; 10 → 1.9 ; 25 → 1.6 ; 50 → 1.1 ; 75 → 0.6 ; 90 → 0.3 ; 95, 99, 100, 120 → 0.2 ; −30 → 1.5`.
12. Journal de l'auteur : `Read D:\XxX\LifePlay_2023_04_Stable_64bit\Docs\Change_Logs.txt` lignes 1-46 (`:34` option 5.27 et plantages, `:39` « 5 bonus rewards »).
13. Dépôt : `git -C "D:/Game/LifePlay-master 5.8" status --porcelain -- Content Source Config` → `M` : `Content/BPs/GI.uasset`, `Person.uasset`, `Room.uasset`, `Source/LifePlay/LPDebugSubsystem.cpp/.h`, `LPDirectControlSubsystem.cpp/.h`, `LPOutdoorRoomSubsystem.cpp/.h`, `LifePlay.cpp` ; `??` : `LPCameraModeSubsystem.cpp/.h`, `LPSaveRelaunch.cpp/.h` ; `rc=0`. `git log --oneline -3` : tête `0745917`.
14. Précédents C++ : Grep de `FindFunction|ProcessEvent|AddViewportWidgetContent|…` dans `Source\LifePlay` (lignes citées en 2.1) ; `LifePlay.Build.cs:38` (`Slate`, `SlateCore`, `UMG`).
15. Build de référence : Grep de `Total execution time` dans `STE-H3d_Build_20261013-124425.txt` → ligne 10 : `18.56 seconds` ; ligne 7 : toolchain `14.50.35739`.
16. Licence : `Read Licenses\Vinfamy_License.txt` → ligne 1 : « … are under MIT ».
17. Contrôle du livrable : fins de ligne (`tr -cd '\r' | wc -c` → 0 CR), aucun nom d'utilisateur ni `C:\Users` (Grep → 0).
Désassembleur (méthode) : en-tête `.uasset` UE 4.20 lu à la main (variante 104 octets par export, 28 octets par import, noms avec 4 octets de hachage) ; le bytecode d'une fonction commence après 8 octets de tailles (mémoire, disque) ; `EX_` décodés d'après `ScriptSerialization.h` 4.27 ; pointeurs de propriété ou d'objet = 4 octets sur disque, noms de fonction virtuelle = 8 octets sur disque et **12 en mémoire** (c'est l'hypothèse qui rend toutes les cibles de saut cohérentes : 66 / 66, 911 / 911, 13 / 13 sur `FilterActions`, avec 8 octets : 2 sur 13). Les offsets d'instruction (`m…`) sont ceux de la mémoire, les autres ceux du `.uexp`.
## Annexe B. Établi / estimé / incertain
- **Établi** : tout E1 à E10 (bytecode décodé, cohérence des sauts contrôlée) ; la table de 1.2 ; la formule et ses 21 valeurs ; `DisableBonus` : sémantique, stockage, interface chez l'auteur ; absence chez nous de `ScrollBonuses`, `PopulateBonusRewards`, `lastCum` dans `Cursor` ; présence chez nous de `Cursor` dans GI, de `Overlaps`, `IsInterestedIn`, `QueryStat`, `ModifyStat`, `Sex_OrgasmCounts` ; précédents C++ cités ; licence MIT de la copie `Licenses` ; état git du 2026-10-14.
- **Estimé** : la grille s'ajoute au bonus ancien (ordre des instructions, confirmé par le saut `:21262`) ; `RetriggerableDelay` remplace le délai en cours ; types des propriétés lues par réflexion (`CurrentTags` tableau de chaînes, `Sex_OrgasmCounts` entier, `Name_First` texte ou chaîne) ; `WhoIsInterested = Person` ; tags d'animation = mots présents dans GI ; tous les tours, durées et nombres de nœuds ; la pratique « ne pas détruire un widget dans son gestionnaire » (suspicion, non prouvée comme cause des plantages) ; faisabilité d'ici le 2026-10-18.
- **Incertain** : comportement **en jeu** (rien n'a été lancé) ; capacité du MCP à créer des widgets ou à fixer la valeur par défaut d'une map (L-M1) ; apparence exacte et position du panneau de l'auteur (arbre de widgets de `Cursor` non lu) ; formatage de « 1.1 » par `FText` (section 6) ; ce que fera ORG4 (SPEC absente) ; son `ui_positive` ; effet réel des tags de position sur la fréquence des récompenses.
Tours : 38 / 55 (deux Write du livrable, contrôles et compte rendu compris ; le premier Write portait deux comptes erronés — scripts et exécutions — corrigés ici)
## Constats
- **F-ORG10** : le nombre de récompenses est **3** dans le code (`Cursor.uexp:8607`), pas 5 (`Change_Logs.txt:39`, version 5.26) : le journal de l'auteur est antérieur au build 2023.04 ; corrige `AUD` § 9.1 et § 9.11 (L14 « origine du nombre 5 » résolu).
- **F-ORG11** : la valeur à stat = 100 est **0,2** (pas 0,1 ni « 0,1 ou 0,2 », `AUD` § 9.5) ; mesuré en double et en float32.
- **F-ORG12** : `DisableBonus` vrai **garde le bonus ancien** et supprime seulement la grille (L15 résolu, `AUD` § 9.3 et § 9.7 : « incertain » devient établi).
- **F-ORG13** : les deux lignes « prone_to_orgasm -1.1 » et « fitness +1.1 » des captures sont bien deux **boutons proposés** (`AUD` § 9.4) : elles partagent les tags {Penetration, Foreplay, Cum}, et la valeur 1,1 correspond à x vers 50 (1.3).
- **F-ORG14** : le bonus ancien et la grille sont **indépendants** : on peut livrer le tirage sans toucher `GiveOrgasmReward` (accroche H1).
- **F-ORG15** : un désassembleur Kismet de build cuit UE 4.20 existe maintenant (3 scripts Python de lecture seule dans le scratchpad de session) ; il décode 100 % des fonctions des assets lus (`Cursor` 45 / 45, `GI` 609 / 609, `MainMenu` 62 / 62, `SettingCheckbox` 5 / 5, ubergraph de `Person` 5 164 instructions) ; proposition : le ranger dans `Tools\` (Q-ORG5-10), ce qui lèverait la limite « aucun lecteur de Kismet cuit » de `AUD` § 9.11 L14.
- **À verser** : `lp-commandlets` (section à créer « Lire un build cuit ») : cohérence de lecture = toutes les cibles de saut sur un début d'instruction ; FName 12 octets en mémoire, pointeurs 4 octets sur disque ; l'EventGraph d'un événement personnalisé est un talon `ExecuteUbergraph_X(entrée)` et le corps est dans l'ubergraph. `sk-protocol` § 7 : `python -I <fichier du scratchpad>` n'a jamais été refusé par le crochet (20 exécutions), seuls `python -c` et les heredocs le sont.
- Fichiers modifiés par cette fiche : le présent livrable seulement (deux Write, le second corrigeant le premier ; fins de ligne et noms d'utilisateur contrôlés sur le premier, texte inchangé hors les trois corrections).
- Fichiers à commiter : `A D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STE\STE-ORG5-SPEC.md` (dépôt du jeu, branche `ue5.8` ; `.md` suivi sous `etat/`).
## Décisions de l'utilisateur (2026-10-14, session STE)
- Q-ORG5-1 : **version minimale pour l'alpha** (tirage comme l'auteur, application automatique de la première récompense, sans grille) ; grille V-1 seulement si J1 PASS le 16.
- Q-ORG5-2 : **option B, C++ autonome**. Q-ORG5-3 : **3 récompenses** (le code fait foi). Q-ORG5-4 : **toute personne qui jouit**, PNJ compris, comme l'auteur. Q-ORG5-9 : **licence MIT acceptée**, origine et licence citées en tête du `.h`.
- Orchestrateur : Q-ORG5-5 = `bDisabled` faux, ini seulement ; Q-ORG5-6 = H1 (sondage) pour l'alpha. Note : le bonus d'ORG4 à `Endsex` appelle `GiveOrgasmReward` sans incrémenter `Sex_OrgasmCounts` : H1 ne le verra pas (R7), accepté pour l'alpha.
## Règle commune ORG4 / ORG5 (Big Boss, 2026-10-14)
- Pour **un même orgasme**, une personne reçoit au plus **une fois** le bonus de base (`GiveOrgasmReward`) et au plus **une fois** les récompenses ORG5 (3 au plus) ; le cumul base + ORG5 est voulu, le même bonus deux fois non.
- Orgasme réel (`Person::Cum`, jauge à 100 ou choix « Cum on … ») : `GiveOrgasmReward` une fois (chemin existant) ; ORG5 une fois, déclenché par l'incrément de `Sex_OrgasmCounts` (accroche H1).
- `Endsex` (O4c) : `GiveOrgasmReward` une fois par participant éligible (homme > 90, femme = 100), **sans** `Person::Cum` : `Sex_OrgasmCounts` inchangé, donc **pas de récompense ORG5** à `Endsex` ; un participant qui vient de jouir a sa jauge remise à 0 par `Cum` et n'est pas éligible : pas de second bonus de base.
- Toute IMPL qui change l'un de ces déclencheurs relit cette règle et la VAL la contrôle (V6 : un orgasme = 1 marqueur de base + au plus 1 ligne ORG5 par personne).