17 KiB
Fiche STG-C7a-VAL — Niveau N1 — Modèle prévu (agent lp-validator) | utilisé claude-sonnet-5-5 — Agent lp-validator — Skills sk-protocol, sk-recount, debug-verification, lp-validate
STG-C7a-VAL : joueur posé hors du siège qu'un Blueprint libère sans marche (2026-10-14)
VAL statique (sans UE, sans compilation). Aucune écriture hors de ce livrable. Copie D:\Game\LifePlay-master 5.8, branche ue5.8.
1. Fiches amont relues
Docs\Migration\etat\ST\STG\STG-C7a-IMPL.md(lu en entier, 97 lignes).Docs\Migration\etat\ST\STG\STG-C7a.diff(lu en entier, 156 lignes).Docs\Migration\etat\ST\STG\STG-COMMUN7-DIAG.md§ A (motif : sections A.1 à A.6 repérées ; A.5 = C7-a).- Instantané :
D:\Game\LifePlay_Backup_5.5\STG-C7a\Source\LifePlay\+manifest.csv. - Pas de SPEC distincte : l'IMPL cite les gardes de Big Boss ; elles sont reprises dans la fiche VAL.
2. Points à confirmer des IMPL (IMPL § 5)
| # | Point (IMPL § 5) | Résultat |
|---|---|---|
| 1 | Chambre 9, house viewing : ligne C7-a … devant a 80 cm, William sur la chaise, aucun CLOSE |
reporté (essai en jeu, build COMMUN-8) |
| 2 | D6 : s'asseoir puis ZQSD, le joueur se lève, aucune ligne C7-a | reporté (essai) ; la logique est PASS-STATIC, § 3 point D6 |
| 3 | Sexe depuis une chaise : aucune ligne C7-a, placement du couple inchangé | reporté (essai) ; la logique est PASS-STATIC, § 3 point sexe |
| 4 | KO3c : comptoir du newsagent, sans régression | reporté (essai) ; EscapeOverlaps / UpdateEscape absents du diff (§ 4) |
3. Critères rejoués (commande → résultat brut)
3.1 MD5 (point 1 de la fiche) : PASS
Commande : md5sum sur les deux fichiers actuels puis sur l'instantané.
1a74d6b724bd63f04e04fda42682446d *Source/LifePlay/LPDirectControlSubsystem.cpp
519e23dc9eb2809fa36c1bbf942a7667 *Source/LifePlay/LPDirectControlSubsystem.h
44ace47d29424101b4127ba12e789beb *D:/Game/LifePlay_Backup_5.5/STG-C7a/Source/LifePlay/LPDirectControlSubsystem.cpp
acff924ae6e03548546ef3fb2e430eb8 *D:/Game/LifePlay_Backup_5.5/STG-C7a/Source/LifePlay/LPDirectControlSubsystem.h
manifest.csv : LPDirectControlSubsystem.cpp 48459 44ACE47D29424101B4127BA12E789BEB ; .h 3195 ACFF924AE6E03548546EF3FB2E430EB8.
Actuels = IMPL § 6 (1A74D6B7… / 519E23DC…) ; instantané = 44ACE47D… / ACFF924A… : confirmé.
Contrôle de périmètre : find Source Config -type f -newer manifest.csv ne rend que ces deux fichiers.
3.2 Diff régénéré (point 2) : PASS, un écart de comptage
Commande : diff -u <instantané> <actuel> pour .cpp et .h, lignes +/-/@ sans en-têtes, CR retirés, MD5 de l'ensemble, comparé au même filtre sur STG-C7a.diff :
ce0c484a4d6f96f625d405de216f20c0 *- (régénéré)
ce0c484a4d6f96f625d405de216f20c0 *- (STG-C7a.diff)
Identique. Comptes : grep -E '^\+' STG-C7a.diff | grep -vE '^\+\+\+' | wc -l = 117 ; ^- hors --- = 0.
Écart E1 : l'IMPL § 2 annonce « 116 lignes ajoutées » ; le recompte donne 117 (la ligne vide après la fonction, diff l. 120, n'est pas comptée). Sans conséquence sur le code.
Le diff ne contient que : appel dans Tick (1 ligne), fonction WatchSeatRelease (cpp), remise à vide dans Walk (4 lignes dont un commentaire), déclaration et 3 membres dans le .h. Rien d'autre que l'IMPL § 2.
Le fichier .diff est en LF alors que les sources sont en CRLF : c'est un diff dont les CR sont retirés, il n'est pas appliqué tel quel (information, pas un écart).
3.3 Gardes, par lecture (point 3)
Lectures : LPDirectControlSubsystem.cpp l. 863-905 (Tick), 962-1070 (WatchSeatRelease), 1278-1363 (Walk), 147-151 (DirectActor), 167 (AxisValue), 465-469 (ScenePlaying), 529-533 (CapsuleInside), 567-600 (LeaveSeat), 604-622 (UpdateEscape). Résultat : PASS-STATIC pour chaque garde.
- Sexe :
ScenePlaying(GI)(litGI.SexOn) est testée l. 976 à chaque tick, avant la pose de l'attente (l. 985) et avant l'action. Vrai :PendingSeat.Reset(),PendingSeatTime = -1, retour.LastSeatest mis à jour avant ce retour (l. 975), donc une libération pendant le sexe ne devient jamais « libérée » au tick suivant. Attente annulée aussi siSexOnapparaît dans les 0,5 s. - Marche : l. 1010, les axes
MoveForwardetMoveRight(AxisValue, mêmes constantes queWalkl. 1321-1322) doivent être nuls etVelocity.Size2D() <= 1.0, sinon retour sans action. L'attente est de toute façon consommée avant (l. 998 :PendingSeatTime = -1), donc aucune reprise ultérieure. - D6 :
LeaveSeatn'est appelé qu'àWalkl. 1350 (Grep : une seule occurrence d'appel). Ordre dansTick:WatchSeatReleasel. 873,Walkl. 901 (seule brancheelse, hors menu et horsSexOn). Au tick oùLeaveSeata lieu,WatchSeatRelease(déjà passé) a vuOccupyingFntvalide :bSeated, aucune libération détectée. Juste aprèsLeaveSeat, l. 1352-1354 remettentLastSeat,PendingSeatetPendingSeatTimeà vide. Au tick suivant,OccupyingFntest nul maisLastSeatest vide :bWasSeatedfaux,Releasednul,PendingSeatTime < 0: retour. Une place quittée en marchant ne déclenche donc jamais C7-a, y compris au tick duLeaveSeat. Cas voisin : si un Blueprint libère la place et qu'une touche est tenue au même tick,WatchSeatReleasemet l'attente (l. 985) puisWalkla remet à vide (l. 1352-1354) : conforme. Cas oùLeaveSeatéchoue (fonction introuvable, l. 582-590) :OccupyingFntreste valide,LastSeatest réarmé au tick suivant, pas de libération vue : pas d'action. - Hors meuble : pour chaque point, balayage de la capsule (ses réponses,
Capsule->GetCollisionResponseToChannels()) de +30 cm à -demi-hauteur, rejet si départ enfoncé ou sol à plus de 30 cm de la hauteur actuelle (l. 1032-1036) ; puisOverlapBlockingTestByChannelavec la capsule réduite de 2 cm (l. 1038) : tout recouvrement bloquant rejette le point. Aussi, avant tout,CapsuleInside(Player, Furniture)(l. 1006) : si le joueur n'est plus dans le meuble, aucune action. - Hors personne : boucle
TActorIterator<ACharacter>(l. 1044-1051) : rejet si un autre personnage non caché est à moins de50 + 2 × rayon(distance 2D entre centres). L'approximation « bord à bord = 50 cm » suppose des rayons égaux. Les personnages cachés ne comptent pas par ce test (le test de recouvrement bloquant les voit s'ils bloquent le Pawn). - Ligne au journal, deux issues : succès l. 1056-1057 (
C7-a : place %s liberee sans marche, joueur pose %s a %.0f cm du siege) ; échec des 4 points l. 1060-1061 (… aucun point libre a %.0f cm, joueur laisse en place), le joueur n'est pas déplacé. Les sorties anticipées « pièce changée / meuble détruit / déjà sorti / marche » (l. 1007-1013) n'écrivent pas de ligne (voir risque R5). - Changement de pièce / meuble détruit :
Furnitureest unTWeakObjectPtr: détruit,Get()rend nul et le test!Furniture(l. 1006) sort. Pièce changée :Furniture->GetOwner() != Roomsort. Meuble détruit pendant que le joueur est assis :IsValid(Seat)faux,LastSeat.IsValid()faux, pas de « libération ». Aucun déplacement dans ces cas.
3.4 Logique de l'attente (point 4) : PASS-STATIC
- Valide → nul :
bWasSeated && !bSeated(l. 973-974) donneReleased;Releasednon nul :PendingSeat = Released,PendingSeatTime = 0, retour (l. 985-987). Si la valeur précédente n'est pas unUPrimitiveComponent(Castnul),Releasedest nul, rien n'est mis en attente (fonctionnellement : ne réagit que pour les composants de Room, ce qui est attendu pourOccupyingFnt). - Annulation : rassis (
bSeated),SexOn, ou pas de joueur : l. 976-981, attente remise à-1. - Délai 0,5 s :
PendingSeatTime += DeltaTime,< SeatReleaseDelay (0.5f): retour ; sinon action. PendingSeatTime >= 0ne reste jamais indéfiniment : chaque tick, soit l. 976-981 (remise à -1), soit incrément jusqu'à 0,5 s puis-1(l. 998) avant tous les tests de garde. Seul risque théorique :DeltaTimenul en continu (le Ticker du sous-système est à intervalle 0, le delta est positif) ; sans effet nuisible de toute façon (aucune action sans le délai).- Une action par libération :
PendingSeatTime = -1etPendingSeat.Reset()sont posés (l. 998-1000) avant l'action ; il n'y a pas d'autre moyen de réarmer que la transition valide → nul suivante. Pas de déplacement répété.
3.5 Compilabilité par lecture (point 5) : PASS-STATIC (non compilé)
Signatures relues dans UE 5.8 (D:\Game\Launcher\Engine\UE_5.8\Engine\Source\Runtime\…) :
| Élément | Source lue | Constat |
|---|---|---|
UWorld::OverlapBlockingTestByChannel(const FVector&, const FQuat&, ECollisionChannel, const FCollisionShape&, const FCollisionQueryParams&, const FCollisionResponseParams&) |
Engine\Classes\Engine\World.h:2364 |
appel conforme (Pos, Rot, canal, forme, Query, Responses) |
UWorld::SweepSingleByChannel(FHitResult&, Start, End, FQuat, ECollisionChannel, Shape, Params, ResponseParam) |
World.h:2286 |
conforme |
UCapsuleComponent::GetCollisionShape(float Inflation = 0.0f) |
Engine\Classes\Components\CapsuleComponent.h:86 |
GetCollisionShape() et (-2.f) valides |
GetCollisionResponseToChannels() -> const FCollisionResponseContainer& |
PrimitiveComponent.h:2647 |
valide |
GetCollisionObjectType() -> ECollisionChannel |
PrimitiveComponent.h:2639 |
passe comme canal |
FCollisionResponseParams(const FCollisionResponseContainer&), non explicite |
Engine\Public\CollisionQueryParams.h:336 |
construction par Responses(…) valide |
UE_ARRAY_COUNT |
Core\Public\Templates\UnrealTemplate.h:222 |
int32 < UE_ARRAY_COUNT : même forme déjà compilée (LPKeyboardSubsystem.cpp:108, LPHairstyleSubsystem.cpp:619) |
FRotationMatrix::GetUnitAxis |
déjà employé Walk l. 1356-1357 (même fichier) |
valide |
SetActorLocation(Target, false, nullptr, ETeleportType::TeleportPhysics) |
signature d'AActor (Vector, bSweep, OutHit, Teleport) |
conforme |
TWeakObjectPtr<UObject> = UObject* et = nullptr |
Core\Public\UObject\WeakObjectPtrTemplates.h:106 (operator=(U*)) et :49 |
le ternaire bSeated ? Seat : nullptr a le type UObject* : valide |
Reset(), Get(), IsValid() sur TWeakObjectPtr |
WeakObjectPtrTemplates.h:93, 141 ; IsValid() hérité de FWeakObjectPtr (WeakObjectPtr.h:300) |
valides |
Cast<UPrimitiveComponent>(LastSeat.Get()) |
Get() rend UObject* |
valide |
AActor::IsHidden() const |
Engine\Classes\GameFramework\Actor.h:4523 |
valide |
AxisValue(const APlayerController*, FName) |
LPDirectControlSubsystem.cpp:167 ; PC déclaré const APlayerController* (l. 1002 du diff : const APlayerController* PC) |
constance respectée |
| Fonctions du namespace anonyme | DirectActor l. 147, AxisValue l. 167, ScenePlaying l. 465, CapsuleInside l. 529, constantes ForwardAxis/RightAxis l. 54-55 |
toutes définies avant WatchSeatRelease (l. 962) |
| Inclusions | EngineUtils.h (l. 29), Components/CapsuleComponent.h (l. 20), GameFramework/Character.h (l. 32), Engine/World.h (l. 28) |
présentes |
| Collisions de noms (unity) | Grep Source\ : WatchSeatRelease, LastSeat, PendingSeat*, LPSeatRelease n'existent que dans LPDirectControlSubsystem.cpp/.h ; SCENE_QUERY_STAT des autres fichiers emploie d'autres noms (LPCameraOcclusion, LPCameraWitness, LPPairSpot, WalkCamera) |
aucune collision |
| Noms locaux vs membres du .h | Grep des noms locaux (Location, Capsule, Furniture, Query, Shape, Inner, Names, Offsets, …) dans le .h : aucun membre du même nom |
pas de masquage |
Non établi : la compilation elle-même (aucun avertissement C4018 / C4458 ne peut être exclu sans build ; le motif int32 < UE_ARRAY_COUNT est déjà employé dans trois fichiers compilés).
3.6 Fins de ligne, ASCII, IMPL (point 6) : PASS
Commandes : tr -cd '\n', tr -cd '\r' (Git Bash), et comptage des octets par PowerShell ([IO.File]::ReadAllBytes) ; git ls-files --eol ; grep -nP '[^\x00-\x7F]'.
Source/LifePlay/LPDirectControlSubsystem.cpp LF=1594 CR=1594 ; CRLF=1594 LFseul=0 CRseul=0 ; i/crlf w/crlf
Source/LifePlay/LPDirectControlSubsystem.h LF=96 CR=96 ; CRLF=96 LFseul=0 CRseul=0 ; i/crlf w/crlf
Docs/…/STG-C7a-IMPL.md LF=97 CR=0
Docs/…/STG-C7a.diff LF=156 CR=0
Aucun BOM (premiers octets 2f 2f 20 / 23 20 46 / 2d 2d 2d). Non-ASCII dans le C++ : 2 lignes (cpp l. 454 « et l. 513 §), toutes deux dans l'instantané (même grep sur la copie d'avant, mêmes lignes) ; non-ASCII dans les lignes ajoutées : diff -u … | grep -P '^[-+].*[^\x00-\x7F]' | wc -l = 0.
IMPL : en LF ; en-tête complet à la forme de sk-protocol § 4 (Fiche, Niveau, Modèle prévu | utilisé, Agent, Skills) ; grep -c 'C:\\Users' = 0.
Écart E2 (forme) : l'IMPL ne suit pas le gabarit IMPL de lp-doc/gabarits.md (sections 1 « Fichiers modifiés », 2 « Écarts à la SPEC », 3 « Instantané », 4 « Ce qui n'a pas été fait », 5 « Points à confirmer en VAL », 6 « Fichiers à commiter » : ici l'instantané est § 1, les changements § 2, une section « D6, KO3c » § 3, pas de section « Écarts à la SPEC », « MD5 après » et « Retour arrière » en plus). Le contenu demandé est présent ; la section « Écarts à la SPEC » est absente (il n'y a pas de SPEC distincte).
4. Diff lu en entier
- Fichiers : 2 (
LPDirectControlSubsystem.cpp,LPDirectControlSubsystem.h), 4 blocs (3 dans le .cpp, 2 dans le .h). - Lignes : +117, -0 (recompté ; l'IMPL dit 116, écart E1).
LeaveSeat,EscapeOverlaps,UpdateEscape,CapsuleInside,ScenePlaying: absents du diff (inchangés).- Ajout non demandé : aucun. Les seules lignes touchant du code existant sont l'appel de
Tick(1 ligne) et les 3 remises à vide deWalk(aprèsLeaveSeat).
5. Verdict
PASS-STATIC. Tous les points de la fiche sont établis par lecture ou par commande : MD5, diff, gardes, attente, signatures moteur, fins de ligne. Rien n'est compilé ni exécuté : les 4 essais de l'IMPL § 5 sont reportés au build COMMUN-8. Écarts : E1 (117 lignes ajoutées et non 116), E2 (IMPL hors gabarit). Aucun écart bloquant.
6. Points à confirmer par l'utilisateur (cases) et risques
Risques relevés (aucun ne bloque la VAL statique) :
- R1 — Blueprint qui libère puis réassoit le joueur après plus de 0,5 s. Le joueur est déplacé à 80 cm au bout de 0,5 s, puis le Blueprint le réassoit (téléportation visible, peut-être déplacement à la chaise). À lire dans le journal du build COMMUN-8 : une ligne
C7-asuivie d'un nouvelOccupyingFntvalide. - R2 — Scène où le joueur doit rester exactement sur sa chaise (caméra, animation d'assise) : la pose à 80 cm s'applique quand même si le Blueprint libère sans sexe ni marche. Le sexe est exclu par
SexOnseul : une scène de dialogue sansSexOnn'est pas exclue. - R3 — Action « se lever » du menu (
PollInteract, hors marche) : si elle appelleunOccupyFnt, le joueur reste immobile, dans le meuble, puis est posé à 80 cm après 0,5 s. Probablement voulu ; à confirmer. - R4 — Pas de contrôle de ligne de vue entre la place et le point (balayage vertical seulement) : un point à 80 cm derrière une cloison ou un meuble mince peut être retenu (pièce voisine, extérieur). Le test de recouvrement garantit seulement que la capsule d'arrivée est libre. Non couvert par l'essai 1 de l'IMPL.
- R5 — Silence au journal pour les sorties anticipées (joueur déjà hors du meuble, pièce changée, marche en cours) : la consigne « une ligne au journal » est tenue pour les deux issues de l'action (pose ou aucun point libre), pas pour l'abandon avant. Confirmer que c'est le sens voulu par Big Boss.
- R6 — Capsule déjà hors meuble : aucune action, cas A.6 du DIAG non traité (IMPL § 4). Confirmé par lecture (l. 1006
CapsuleInside). - R7 — Volumes en Ignore (KO3c) : si un composant est en Pawn Ignore (liste
EscapeVolumes) au moment de l'arrivée, le test de recouvrement ne le voit pas ; le point peut être dans un volume queUpdateEscaperendra Block. Cas rare. - R8 — Personnes cachées (
IsHidden) ignorées par le test « personne » ; couvertes seulement par le test de recouvrement. - R9 — Essais 1 à 4 de l'IMPL § 5 à rejouer au build COMMUN-8 ; en particulier le cas house_viewing (distance de William, règle des 10 cm).
Fichiers écrits par cette VAL : D:\Game\LifePlay-master 5.8\Docs\Migration\etat\ST\STG\STG-C7a-VAL.md.
Fichiers à commiter : A Docs/Migration/etat/ST/STG/STG-C7a-VAL.md (dépôt du jeu, copie 5.8). Aucun autre fichier modifié.
Tours : 13 / 30