diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 648ac37..a78b762 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -3073,7 +3073,13 @@ bool UHyperTwistTrainingSubsystem::TryMaterializeTrainingSessionTemplateForUser( FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveReviewDeck(int32 MaxCases) const { - if (!ActiveRunState.ActiveDeck.IsStructurallyValid()) + if (!HasActiveRun()) + { + return FHyperTwistTrainingDeck(); + } + + const FHyperTwistTrainingDeck SourceDeck = ResolveRepositoryFocusDeck(); + if (!SourceDeck.IsStructurallyValid()) { return FHyperTwistTrainingDeck(); } @@ -3092,7 +3098,7 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveReviewDeck(int3 ); return UHyperTwistTrainingRepositoryLibrary::BuildReviewDeckFromRecommendations( - ActiveRunState.ActiveDeck, + SourceDeck, FilteredRecommendations, MaxCases ); @@ -3100,7 +3106,8 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveReviewDeck(int3 FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachRecommendedDeck(int32 MaxCases) const { - if (!ActiveRunState.ActiveDeck.IsStructurallyValid()) + const FHyperTwistTrainingDeck SourceDeck = ResolveRepositoryFocusDeck(); + if (!SourceDeck.IsStructurallyValid()) { return FHyperTwistTrainingDeck(); } @@ -3115,7 +3122,7 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachRecommende TArray FocusCases; for (const FString& FocusCaseId : ActiveCoachBrief.FocusCaseIds) { - const FHyperTwistTrainingCase* MatchingCase = ActiveRunState.ActiveDeck.Cases.FindByPredicate( + const FHyperTwistTrainingCase* MatchingCase = SourceDeck.Cases.FindByPredicate( [&FocusCaseId](const FHyperTwistTrainingCase& Candidate) { return Candidate.CaseId == FocusCaseId; @@ -3133,13 +3140,16 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachRecommende if (FocusCases.Num() == 0) { - const FHyperTwistTrainingDeck ReviewDeck = BuildActiveReviewDeck(ResolvedBudget.ResolvedCaseCount); - if (ReviewDeck.IsStructurallyValid()) + if (HasActiveRun()) { - return ReviewDeck; + const FHyperTwistTrainingDeck ReviewDeck = BuildActiveReviewDeck(ResolvedBudget.ResolvedCaseCount); + if (ReviewDeck.IsStructurallyValid()) + { + return ReviewDeck; + } } - for (const FHyperTwistTrainingCase& Candidate : ActiveRunState.ActiveDeck.Cases) + for (const FHyperTwistTrainingCase& Candidate : SourceDeck.Cases) { FocusCases.Add(Candidate); if (FocusCases.Num() >= ResolvedBudget.ResolvedCaseCount) @@ -3154,7 +3164,7 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachRecommende return FHyperTwistTrainingDeck(); } - FHyperTwistTrainingDeck FocusDeck = ActiveRunState.ActiveDeck; + FHyperTwistTrainingDeck FocusDeck = SourceDeck; FocusDeck.Cases = FocusCases; FocusDeck.SelectionPolicy = ActiveCoachBrief.SuggestedSelectionPolicy; if (ActiveCoachBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer @@ -3180,7 +3190,8 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachRecommende FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachFollowUpDeck(int32 MaxCases) const { - if (!ActiveRunState.ActiveDeck.IsStructurallyValid() || ActiveCoachFollowUpBrief.UserId.IsEmpty()) + const FHyperTwistTrainingDeck SourceDeck = ResolveRepositoryFocusDeck(); + if (!SourceDeck.IsStructurallyValid() || ActiveCoachFollowUpBrief.UserId.IsEmpty()) { return FHyperTwistTrainingDeck(); } @@ -3195,7 +3206,7 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachFollowUpDe TArray FocusCases; for (const FString& FocusCaseId : ActiveCoachFollowUpBrief.FocusCaseIds) { - const FHyperTwistTrainingCase* MatchingCase = ActiveRunState.ActiveDeck.Cases.FindByPredicate( + const FHyperTwistTrainingCase* MatchingCase = SourceDeck.Cases.FindByPredicate( [&FocusCaseId](const FHyperTwistTrainingCase& Candidate) { return Candidate.CaseId == FocusCaseId; @@ -3216,7 +3227,7 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachFollowUpDe return BuildActiveCoachRecommendedDeck(ResolvedBudget.ResolvedCaseCount); } - FHyperTwistTrainingDeck FocusDeck = ActiveRunState.ActiveDeck; + FHyperTwistTrainingDeck FocusDeck = SourceDeck; FocusDeck.Cases = FocusCases; FocusDeck.SelectionPolicy = ActiveCoachFollowUpBrief.SuggestedSelectionPolicy; if (ActiveCoachFollowUpBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer @@ -3813,6 +3824,9 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDe ActiveCoachActionPlan = FHyperTwistTrainingCoachActionPlan(); ActiveCoachFollowUpBrief = FHyperTwistCoachBrief(); ActiveImportedGeneratedModeLaunchRequest = FHyperTwistTrainingImportedGeneratedModeLaunchRequest(); + RetainedRepositoryViewUserId.Reset(); + RetainedRepositoryViewDeckId.Reset(); + RetainedRepositoryViewReferenceUtc.Reset(); ActiveRunState = UHyperTwistTrainingLibrary::StartTrainingRun(Deck, UserId, SessionId, Mode); ActiveRunState.ImportedGeneratedModeLaunchRequest = ActiveImportedGeneratedModeLaunchRequest; bHasActiveRun = ActiveRunState.IsStructurallyValid(); @@ -4144,6 +4158,31 @@ void UHyperTwistTrainingSubsystem::ClearActiveRun() FString CloseError; CloseActiveRecognitionSession(CloseError); } + RetainedRepositoryViewUserId = !ActiveRunState.Session.UserId.IsEmpty() + ? ActiveRunState.Session.UserId + : (!ActiveCoachBrief.UserId.IsEmpty() ? ActiveCoachBrief.UserId : ActiveCoachSessionQueueSummary.UserId); + RetainedRepositoryViewDeckId = !ActiveReviewProgramSummary.FocusDeckId.IsEmpty() + ? ActiveReviewProgramSummary.FocusDeckId + : (!ActiveCoachBrief.FocusDeckId.IsEmpty() + ? ActiveCoachBrief.FocusDeckId + : (!ActiveCoachFollowUpBrief.FocusDeckId.IsEmpty() + ? ActiveCoachFollowUpBrief.FocusDeckId + : (!ActiveCoachSessionQueueState.FocusDeckId.IsEmpty() + ? ActiveCoachSessionQueueState.FocusDeckId + : (!ActiveCoachSessionQueueSummary.FocusDeckId.IsEmpty() + ? ActiveCoachSessionQueueSummary.FocusDeckId + : (!ActiveCoachMemorySnapshot.FocusDeckId.IsEmpty() + ? ActiveCoachMemorySnapshot.FocusDeckId + : ActiveRunState.Session.DeckId))))); + RetainedRepositoryViewReferenceUtc = !ActiveLearnerDeckStateSummary.ReferenceUtc.IsEmpty() + ? ActiveLearnerDeckStateSummary.ReferenceUtc + : (!ActiveReviewProgramSummary.ReferenceUtc.IsEmpty() + ? ActiveReviewProgramSummary.ReferenceUtc + : (!ActiveCoachMemorySnapshot.ReferenceUtc.IsEmpty() + ? ActiveCoachMemorySnapshot.ReferenceUtc + : (!ActiveRunSummary.LastCompletedAtUtc.IsEmpty() + ? ActiveRunSummary.LastCompletedAtUtc + : FDateTime::UtcNow().ToIso8601()))); ActiveRunState = FHyperTwistTrainingRunState(); ActiveRunSummary = FHyperTwistTrainingSessionSummary(); ActiveImportedGeneratedModeLaunchRequest = FHyperTwistTrainingImportedGeneratedModeLaunchRequest(); @@ -4781,6 +4820,83 @@ void UHyperTwistTrainingSubsystem::ResetRecognitionSessionState() RefreshRecognitionServiceHealth(); } +bool UHyperTwistTrainingSubsystem::TryResolveRepositoryViewContext( + FString& OutUserId, + FString& OutDeckId, + FString& OutReferenceUtc +) const +{ + OutUserId.Reset(); + OutDeckId.Reset(); + OutReferenceUtc.Reset(); + + if (ActiveRunState.Session.IsStructurallyValid()) + { + OutUserId = ActiveRunState.Session.UserId; + OutDeckId = ActiveRunState.Session.DeckId; + OutReferenceUtc = !ActiveLearnerDeckStateSummary.ReferenceUtc.IsEmpty() + ? ActiveLearnerDeckStateSummary.ReferenceUtc + : (!ActiveRunSummary.LastCompletedAtUtc.IsEmpty() + ? ActiveRunSummary.LastCompletedAtUtc + : FDateTime::UtcNow().ToIso8601()); + return !OutUserId.IsEmpty(); + } + + if (RetainedRepositoryViewUserId.IsEmpty()) + { + return false; + } + + OutUserId = RetainedRepositoryViewUserId; + OutDeckId = RetainedRepositoryViewDeckId; + OutReferenceUtc = !RetainedRepositoryViewReferenceUtc.IsEmpty() + ? RetainedRepositoryViewReferenceUtc + : FDateTime::UtcNow().ToIso8601(); + return true; +} + +FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::ResolveRepositoryFocusDeck() const +{ + if (ActiveRunState.ActiveDeck.IsStructurallyValid()) + { + return ActiveRunState.ActiveDeck; + } + + TArray CandidateDeckIds; + CandidateDeckIds.Reserve(8); + const auto AddCandidateDeckId = [&CandidateDeckIds](const FString& CandidateDeckId) + { + if (!CandidateDeckId.IsEmpty()) + { + CandidateDeckIds.AddUnique(CandidateDeckId); + } + }; + + AddCandidateDeckId(ActiveCoachBrief.FocusDeckId); + AddCandidateDeckId(ActiveCoachFollowUpBrief.FocusDeckId); + AddCandidateDeckId(ActiveReviewProgramSummary.FocusDeckId); + AddCandidateDeckId(ActiveCoachSessionQueueState.FocusDeckId); + AddCandidateDeckId(ActiveCoachSessionQueueSummary.FocusDeckId); + AddCandidateDeckId(ActiveCoachMemorySnapshot.FocusDeckId); + AddCandidateDeckId(RetainedRepositoryViewDeckId); + AddCandidateDeckId(ActiveLearnerProfile.FavoriteDeckId); + + const TArray TrainingCatalog = UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog(); + FHyperTwistTrainingDeck SourceDeck; + for (const FString& CandidateDeckId : CandidateDeckIds) + { + if (UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog( + TrainingCatalog, + CandidateDeckId, + SourceDeck)) + { + return SourceDeck; + } + } + + return FHyperTwistTrainingDeck(); +} + void UHyperTwistTrainingSubsystem::RefreshSummary() { if (!ActiveRunState.Session.IsStructurallyValid()) @@ -4831,7 +4947,13 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ); TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RepairRepositoryState(TrainingRepositoryState); - if (!ActiveRunState.Session.IsStructurallyValid()) + FString RepositoryViewUserId; + FString RepositoryViewDeckId; + FString RepositoryViewReferenceUtc; + if (!TryResolveRepositoryViewContext( + RepositoryViewUserId, + RepositoryViewDeckId, + RepositoryViewReferenceUtc)) { ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary(); ActiveTimingTrendSummary = FHyperTwistTrainingTimingTrendSummary(); @@ -4869,140 +4991,196 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() return; } - ActiveDeckHistorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveDeckHistorySummary( + const bool bHasActiveRunContext = ActiveRunState.Session.IsStructurallyValid(); + + ActiveLearnerProfile = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerProfile( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveRunState.Session.DeckId + RepositoryViewUserId, + RepositoryViewReferenceUtc ); - ActiveTimingTrendSummary = UHyperTwistTrainingRepositoryLibrary::DeriveTimingTrendSummary( + ActiveCoachMemorySnapshot = UHyperTwistTrainingRepositoryLibrary::DeriveCoachMemorySnapshot( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveRunState.Session.DeckId, - FString() + RepositoryViewUserId, + RepositoryViewReferenceUtc ); - ActivePersonalBestSummary = UHyperTwistTrainingRepositoryLibrary::DerivePersonalBestSummary( + ActiveCoachClosureMemorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveRunState.Session.DeckId, - ActiveTimingTrendSummary.ReferenceUtc + RepositoryViewUserId, + RepositoryViewReferenceUtc ); - ActiveDeckScopedStats = UHyperTwistTrainingRepositoryLibrary::DeriveDeckScopedStats( + ActiveCoachRecommendationHistorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachRecommendationHistorySummary( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveRunState.Session.DeckId, - FString() + RepositoryViewUserId, + RepositoryViewReferenceUtc ); - const FString CurrentCaseId = ActiveRunState.CurrentSelection.IsStructurallyValid() - ? ActiveRunState.CurrentSelection.TrainingCase.CaseId - : ActiveRunState.Session.CurrentCaseId; - ActiveCurrentCaseScopedStats = !CurrentCaseId.IsEmpty() + ActiveCoachCarryForwardPolicySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary( + TrainingRepositoryState, + RepositoryViewUserId, + RepositoryViewReferenceUtc + ); + ActiveCoachArchivePolicySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary( + TrainingRepositoryState, + RepositoryViewUserId, + RepositoryViewReferenceUtc + ); + ActiveReviewProgramSummary = UHyperTwistTrainingRepositoryLibrary::DeriveReviewProgramSummary( + TrainingRepositoryState, + RepositoryViewUserId, + RepositoryViewReferenceUtc + ); + + const FString ResolvedDeckId = bHasActiveRunContext + ? ActiveRunState.Session.DeckId + : (!ActiveReviewProgramSummary.FocusDeckId.IsEmpty() + ? ActiveReviewProgramSummary.FocusDeckId + : (!ActiveCoachMemorySnapshot.FocusDeckId.IsEmpty() + ? ActiveCoachMemorySnapshot.FocusDeckId + : (!RepositoryViewDeckId.IsEmpty() + ? RepositoryViewDeckId + : ActiveLearnerProfile.FavoriteDeckId))); + FHyperTwistTrainingDeck RepositoryFocusDeck = bHasActiveRunContext + ? ActiveRunState.ActiveDeck + : FHyperTwistTrainingDeck(); + if (!RepositoryFocusDeck.IsStructurallyValid()) + { + RepositoryFocusDeck = ResolveRepositoryFocusDeck(); + } + if (!RepositoryFocusDeck.IsStructurallyValid() && !ResolvedDeckId.IsEmpty()) + { + UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog( + UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog(), + ResolvedDeckId, + RepositoryFocusDeck + ); + } + + ActiveDeckHistorySummary = !ResolvedDeckId.IsEmpty() + ? UHyperTwistTrainingRepositoryLibrary::DeriveDeckHistorySummary( + TrainingRepositoryState, + RepositoryViewUserId, + ResolvedDeckId + ) + : FHyperTwistTrainingDeckHistorySummary(); + ActiveTimingTrendSummary = !ResolvedDeckId.IsEmpty() + ? UHyperTwistTrainingRepositoryLibrary::DeriveTimingTrendSummary( + TrainingRepositoryState, + RepositoryViewUserId, + ResolvedDeckId, + FString() + ) + : FHyperTwistTrainingTimingTrendSummary(); + ActivePersonalBestSummary = !ResolvedDeckId.IsEmpty() + ? UHyperTwistTrainingRepositoryLibrary::DerivePersonalBestSummary( + TrainingRepositoryState, + RepositoryViewUserId, + ResolvedDeckId, + ActiveTimingTrendSummary.ReferenceUtc + ) + : FHyperTwistTrainingPersonalBestSummary(); + ActiveDeckScopedStats = !ResolvedDeckId.IsEmpty() + ? UHyperTwistTrainingRepositoryLibrary::DeriveDeckScopedStats( + TrainingRepositoryState, + RepositoryViewUserId, + ResolvedDeckId, + FString() + ) + : FHyperTwistTrainingDeckScopedStats(); + const FString CurrentCaseId = bHasActiveRunContext + ? (ActiveRunState.CurrentSelection.IsStructurallyValid() + ? ActiveRunState.CurrentSelection.TrainingCase.CaseId + : ActiveRunState.Session.CurrentCaseId) + : FString(); + ActiveCurrentCaseScopedStats = (!ResolvedDeckId.IsEmpty() && !CurrentCaseId.IsEmpty()) ? UHyperTwistTrainingRepositoryLibrary::DeriveCaseScopedStats( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveRunState.Session.DeckId, + RepositoryViewUserId, + ResolvedDeckId, CurrentCaseId, ActiveDeckScopedStats.ReferenceUtc ) : FHyperTwistTrainingCaseScopedStats(); - ActiveLearnerDeckStateSummary = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerDeckStateSummary( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveRunState.Session.DeckId, - FString() - ); - ActiveLearnerProfile = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerProfile( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveCoachMemorySnapshot = UHyperTwistTrainingRepositoryLibrary::DeriveCoachMemorySnapshot( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveCoachClosureMemorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveCoachRecommendationHistorySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachRecommendationHistorySummary( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveCoachCarryForwardPolicySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveCoachArchivePolicySummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveReplayReviewAnalytics = UHyperTwistReplayReviewLibrary::DeriveReplayReviewAnalytics( - ActiveRunState.ReplayPacket - ); - ActiveRecognitionReplaySummary = UHyperTwistRecognitionReplayLibrary::DeriveRecognitionReplaySummary( - ActiveRunState.ReplayPacket - ); + ActiveLearnerDeckStateSummary = !ResolvedDeckId.IsEmpty() + ? UHyperTwistTrainingRepositoryLibrary::DeriveLearnerDeckStateSummary( + TrainingRepositoryState, + RepositoryViewUserId, + ResolvedDeckId, + FString() + ) + : FHyperTwistTrainingLearnerDeckStateSummary(); + const FString ResolvedReferenceUtc = !ActiveLearnerDeckStateSummary.ReferenceUtc.IsEmpty() + ? ActiveLearnerDeckStateSummary.ReferenceUtc + : RepositoryViewReferenceUtc; + ActiveReplayReviewAnalytics = bHasActiveRunContext + ? UHyperTwistReplayReviewLibrary::DeriveReplayReviewAnalytics(ActiveRunState.ReplayPacket) + : FHyperTwistReplayReviewAnalytics(); + ActiveRecognitionReplaySummary = bHasActiveRunContext + ? UHyperTwistRecognitionReplayLibrary::DeriveRecognitionReplaySummary(ActiveRunState.ReplayPacket) + : FHyperTwistRecognitionReplaySummary(); ActiveLearnerMethodSegments = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerMethodSegments( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc + RepositoryViewUserId, + ResolvedReferenceUtc ); ActiveLearnerPresets = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerPresets( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc + RepositoryViewUserId, + ResolvedReferenceUtc ); ActiveTrainingSessionTemplates = BuildTrainingSessionTemplatesForUser( - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); - ActiveCaseRecommendations = UHyperTwistTrainingRepositoryLibrary::ListCaseRecommendationsForDeck( - TrainingRepositoryState, - ActiveRunState.ActiveDeck, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc, - 5 + RepositoryViewUserId, + ResolvedReferenceUtc ); + ActiveCaseRecommendations = RepositoryFocusDeck.IsStructurallyValid() + ? UHyperTwistTrainingRepositoryLibrary::ListCaseRecommendationsForDeck( + TrainingRepositoryState, + RepositoryFocusDeck, + RepositoryViewUserId, + ResolvedReferenceUtc, + 5 + ) + : TArray(); ActiveReviewFlowStatus = ActiveReviewPlanState.IsStructurallyValid() ? UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState) : FHyperTwistTrainingReviewFlowStatus(); - ActiveReviewProgramSummary = UHyperTwistTrainingRepositoryLibrary::DeriveReviewProgramSummary( - TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc - ); ActiveCoachActionPlanSummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanSummary( TrainingRepositoryState, - ActiveRunState.Session.UserId, - ActiveLearnerDeckStateSummary.ReferenceUtc + RepositoryViewUserId, + ResolvedReferenceUtc ); ActiveCoachActionPlanOutcomeSummary = ActiveCoachActionPlan.IsStructurallyValid() ? UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary( TrainingRepositoryState, ActiveCoachActionPlan, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ) : FHyperTwistTrainingCoachActionPlanOutcomeSummary(); ActiveCoachActionSequenceSummary = ActiveCoachActionPlan.IsStructurallyValid() ? UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary( TrainingRepositoryState, ActiveCoachActionPlan, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ) : FHyperTwistTrainingCoachActionSequenceSummary(); ActiveCoachActionClosureSummary = ActiveCoachActionPlan.IsStructurallyValid() ? UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary( TrainingRepositoryState, ActiveCoachActionPlan, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ) : FHyperTwistTrainingCoachActionClosureSummary(); + FHyperTwistTrainingSessionSummary RepositoryViewSessionSummary = bHasActiveRunContext + ? ActiveRunSummary + : FHyperTwistTrainingSessionSummary(); + if (RepositoryViewSessionSummary.UserId.IsEmpty()) + { + RepositoryViewSessionSummary.UserId = RepositoryViewUserId; + } + if (RepositoryViewSessionSummary.DeckId.IsEmpty()) + { + RepositoryViewSessionSummary.DeckId = ResolvedDeckId; + } ActiveCoachSignals = UHyperTwistTrainingCoachLibrary::DeriveCoachSignals( - ActiveRunSummary, + RepositoryViewSessionSummary, ActiveDeckScopedStats, ActiveLearnerDeckStateSummary, ActiveLearnerProfile, @@ -5012,7 +5190,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveRecognitionReplaySummary ); ActiveCoachBrief = UHyperTwistTrainingCoachLibrary::DeriveCoachBrief( - ActiveRunSummary, + RepositoryViewSessionSummary, ActiveDeckScopedStats, ActiveLearnerDeckStateSummary, ActiveLearnerProfile, @@ -5087,12 +5265,12 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ? UHyperTwistTrainingRepositoryLibrary::ResolveCoachSessionQueueState( StoredQueueState, ActiveCoachSessionQueueSummary, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ) : UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState( ActiveCoachSessionQueueSummary, HyperTwistTrainingSubsystemInternal::ActiveCoachSessionQueueId, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ); if (ActiveCoachSessionQueueState.IsStructurallyValid()) { @@ -5104,7 +5282,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveCoachScheduleHorizonSummary = UHyperTwistTrainingCoachLibrary::DeriveCoachScheduleHorizonSummary( ActiveCoachSessionQueueState, ActiveCoachArchivePolicySummary, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ); ActiveCoachSchedulePolicySummary = UHyperTwistTrainingCoachLibrary::DeriveCoachSchedulePolicySummary( ActiveCoachSessionQueueState, @@ -5112,14 +5290,14 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveCoachActionClosureSummary, ActiveCoachMemorySnapshot, ActiveCoachArchivePolicySummary, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ); ActiveCoachSessionQueueExecutionSummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachSessionQueueExecutionSummary( TrainingRepositoryState, ActiveCoachSessionQueueState.UserId, ActiveCoachSessionQueueState.QueueId, - ActiveLearnerDeckStateSummary.ReferenceUtc + ResolvedReferenceUtc ); } else diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index 6f84002..2c75622 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -578,6 +578,12 @@ private: const FString& UserId, const FString& ReferenceUtc ) const; + bool TryResolveRepositoryViewContext( + FString& OutUserId, + FString& OutDeckId, + FString& OutReferenceUtc + ) const; + FHyperTwistTrainingDeck ResolveRepositoryFocusDeck() const; UPROPERTY() FHyperTwistTrainingRunState ActiveRunState; @@ -585,6 +591,15 @@ private: UPROPERTY() FHyperTwistTrainingSessionSummary ActiveRunSummary; + UPROPERTY() + FString RetainedRepositoryViewUserId; + + UPROPERTY() + FString RetainedRepositoryViewDeckId; + + UPROPERTY() + FString RetainedRepositoryViewReferenceUtc; + UPROPERTY() FHyperTwistTrainingImportedGeneratedModeLaunchRequest ActiveImportedGeneratedModeLaunchRequest; diff --git a/docs/arch/HYPERTWIST_PHASE4_CLEAR_RUN_REPOSITORY_VIEW_CONTINUITY_PACKET_2026-05-06.md b/docs/arch/HYPERTWIST_PHASE4_CLEAR_RUN_REPOSITORY_VIEW_CONTINUITY_PACKET_2026-05-06.md new file mode 100644 index 0000000..2f996ef --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_CLEAR_RUN_REPOSITORY_VIEW_CONTINUITY_PACKET_2026-05-06.md @@ -0,0 +1,104 @@ +# HyperTwist Phase 4 clear-run repository-view continuity packet + +Created on `2026-05-06` + +Status: + +- first-party HyperTwist packet +- bounded Phase `4` coach-to-review validation slice + +## Purpose + +This packet closes the next post-review continuity gap in the live recognition-assisted coach-to-review loop. + +The open task is: + +- preserve repository-derived carryover pressure, coach guidance, and queue state across `ClearActiveRun()` after a review run has already finalized truthfully + +It is not: + +- a new review-policy heuristic packet +- a new recognition transport packet +- a new queue-model redesign +- a dashboard layout packet + +## Scope + +Bounded lane: + +- retain the minimum repository-view context needed after a run clears +- rebuild repository-derived coach and queue state from that retained context when no run is active +- keep active review-plan inspection cleared so the dashboard does not imply a run is still open +- let post-run coach launch surfaces resolve their focus deck from repository state instead of requiring a live run deck + +Out of scope: + +- new review scoring +- new persistence formats +- startup-wide multi-user queue arbitration +- broader subsystem decomposition + +## Why this was the right next packet + +Before this slice: + +- review-start summary continuity was already landed +- review-finalization carryover continuity was already landed +- post-review carryover could survive explicit review-run completion inside repository state + +But one clear-run seam was still wrong: + +- `ClearActiveRun()` reset the live run state and then called `RefreshRepositoryViews()` +- `RefreshRepositoryViews()` treated `!ActiveRunState.Session.IsStructurallyValid()` as a full reset boundary +- that zeroed repository-derived coach brief, queue summary, learner deck summary, review-program summary, and dashboard-facing carryover pressure + +That meant the product could preserve open review carryover in the repository and still lose it immediately in the coach/dashboard lane as soon as the run was cleared. + +The practical consequence was: + +- queue-backed follow-up pressure could disappear right after run clear +- post-review coach guidance could collapse to idle state even though carryover still existed +- dashboard inspection could look clean only because the repository-derived view had been zeroed, not because the review program was actually resolved + +So the next honest move was: + +- keep the active review plan cleared, but stop discarding valid repository-derived coach and queue state at the clear-run boundary + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h` + - added retained repository-view user/deck/reference fields plus private helpers for repository-view context and focus-deck resolution +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` + - `ClearActiveRun()` now captures retained repository-view context before clearing the active run + - `StartTrainingRunFromDeck()` clears retained idle-context state when a new run begins + - `RefreshRepositoryViews()` now derives coach memory, review-program summary, queue state, and coach briefs from retained repository-view context when no run is active + - recommended/follow-up deck builders now resolve their source deck from repository context instead of requiring a live active deck + +## Product effect + +The post-review boundary is now more truthful: + +- explicit run clear no longer erases open carryover pressure that still exists in repository state +- coach guidance and queue materialization can remain visible after run clear when open review work still exists +- the dashboard can stay aligned with repository-derived carryover without reviving stale active-review-plan state +- launch-budget and queue-follow-up surfaces can still resolve a usable focus deck after run clear when repository context identifies one + +## Acceptance criteria + +- `ClearActiveRun()` preserves enough context for repository-derived coach state to survive the clear-run boundary +- `RefreshRepositoryViews()` no longer hard-resets repository-derived coach/queue state solely because there is no active run +- active review-plan inspection remains cleared when the run is gone +- coach recommended/follow-up deck resolution still works after the clear-run transition when repository focus deck context exists +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm `ClearActiveRun()` captures retained repository-view context before zeroing the active run +3. confirm `RefreshRepositoryViews()` can derive coach and queue state from retained repository-view context when idle +4. confirm active review-plan state is still cleared after run clear +5. confirm coach deck builders resolve focus deck state without requiring a live active run deck + +That is the packet.