Preserve coach state through run clear

This commit is contained in:
axiomlogicnexus 2026-05-06 19:16:48 +02:00
parent 69290897de
commit c7e02fe1cd
3 changed files with 403 additions and 106 deletions

View file

@ -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<FHyperTwistTrainingCase> 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<FHyperTwistTrainingCase> 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<FString> 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<FHyperTwistContentPack> 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<FHyperTwistTrainingCaseRecommendation>();
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

View file

@ -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;

View file

@ -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.