From 88f8a8d7f89398d21adef050e0c694b98f2c81a3 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Tue, 5 May 2026 01:17:12 +0200 Subject: [PATCH] Tighten coach scheduling recovery and repository integrity --- .../HyperTwistTrainingCoachLibrary.cpp | 151 +- .../HyperTwistTrainingRepositoryLibrary.cpp | 593 +++++++- .../HyperTwistTrainingSubsystem.cpp | 16 + .../HyperTwistTrainingTypes.h | 20 + ...RTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md | 13 +- ...PERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md | 1348 +++++++++++++++++ 6 files changed, 2075 insertions(+), 66 deletions(-) create mode 100644 docs/arch/HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 4e92425..0764efc 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -3043,6 +3043,70 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachBrief( const FHyperTwistCoachSignal& PrimarySignal = Brief.Signals[0]; HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, PrimarySignal); + const bool bScheduleFacingBrief = + CoachMemorySnapshot.bNeedsScheduleFrictionRecovery + && (PrimarySignal.SignalId == TEXT("schedule_friction") + || Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence + || Brief.FocusLane == EHyperTwistCoachFocusLane::Stability + || Brief.bNeedsCadenceRecovery); + if (bScheduleFacingBrief) + { + const bool bPreferFollowUpRecovery = + CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; + const bool bPreferRecommendedRecovery = + CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended; + const bool bPromoteDeferredRecovery = + CoachMemorySnapshot.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred; + const bool bLaunchRecovery = + CoachMemorySnapshot.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; + + if (CoachMemorySnapshot.SchedulePressureCaseIds.Num() > 0 + && (Brief.FocusCaseIds.Num() == 0 + || PrimarySignal.SignalId == TEXT("schedule_friction") + || Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence + || Brief.FocusLane == EHyperTwistCoachFocusLane::Stability)) + { + Brief.FocusCaseIds = CoachMemorySnapshot.SchedulePressureCaseIds; + } + + if (bPreferFollowUpRecovery) + { + Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::CoachReviewed; + Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Scripted; + Brief.PrimaryActionLabel = bPromoteDeferredRecovery + ? TEXT("promote_deferred_follow_up_recovery") + : (bLaunchRecovery + ? TEXT("launch_follow_up_recovery") + : TEXT("recover_follow_up_schedule_friction")); + } + else if (bPreferRecommendedRecovery) + { + Brief.SuggestedMode = CoachMemorySnapshot.DominantCoachSuggestedMode; + Brief.SuggestedSelectionPolicy = CoachMemorySnapshot.bHasQueueExecutionCompletionGap + ? EHyperTwistTrainingSelectionPolicy::Weighted + : CoachMemorySnapshot.DominantCoachSelectionPolicy; + Brief.PrimaryActionLabel = bPromoteDeferredRecovery + ? TEXT("promote_deferred_recommended_recovery") + : (bLaunchRecovery + ? TEXT("launch_recommended_recovery") + : TEXT("recover_recommended_schedule_friction")); + } + else if (CoachMemorySnapshot.bHasArchiveAwareScheduleFriction) + { + Brief.PrimaryActionLabel = TEXT("stabilize_archive_aware_schedule"); + } + else + { + Brief.PrimaryActionLabel = bPromoteDeferredRecovery + ? TEXT("promote_deferred_schedule_recovery") + : (bLaunchRecovery + ? TEXT("launch_schedule_recovery") + : TEXT("recover_schedule_friction")); + } + } + if (Brief.FocusCaseIds.Num() == 0) { if (CoachMemorySnapshot.bNeedsScheduleFrictionRecovery @@ -4243,6 +4307,7 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithQueue const FString OriginalDeckId = Brief.FocusDeckId; const FString OriginalMethodSegmentId = Brief.FocusMethodSegmentId; const TArray OriginalCaseIds = Brief.FocusCaseIds; + const FString OriginalPrimaryActionLabel = Brief.PrimaryActionLabel; HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, Brief.Signals[0]); if (Brief.FocusDeckId.IsEmpty()) { @@ -4256,6 +4321,15 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithQueue { Brief.FocusCaseIds = OriginalCaseIds; } + if (bShouldPromoteSuppressionSignal + && Brief.PrimaryActionLabel == QueueSuppressionSignal.SignalId + && (!OriginalPrimaryActionLabel.IsEmpty() + && (OriginalPrimaryActionLabel.Contains(TEXT("schedule")) + || OriginalPrimaryActionLabel.Contains(TEXT("recovery")) + || OriginalPrimaryActionLabel.Contains(TEXT("promote_deferred"))))) + { + Brief.PrimaryActionLabel = OriginalPrimaryActionLabel; + } } return Brief; @@ -4447,6 +4521,12 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor } const bool bBroadenPreferred = CoachMemorySnapshot.PreferredLaunchBudgetBias > 0; + const bool bScheduleTighteningSource = + !bBroadenPreferred + && CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel.StartsWith(TEXT("coach-memory-schedule-")); + const bool bFollowUpScheduleTightening = + bScheduleTighteningSource + && CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; const bool bCompetingGuidancePressure = CoachMemorySnapshot.RecommendedGuidanceContinueBias > 0 && CoachMemorySnapshot.FollowUpGuidanceContinueBias > 0; @@ -4458,19 +4538,30 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor + (bCompetingGuidancePressure ? 4.0f : 0.0f) + ((CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Cadence || CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Stability - || CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog) ? 3.0f : 0.0f), + || CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog) ? 3.0f : 0.0f) + + (bScheduleTighteningSource ? 4.0f : 0.0f), 0.0f, 100.0f ); const FString SignalId = bBroadenPreferred ? TEXT("launch_budget_history_broaden") - : TEXT("launch_budget_history_tighten"); + : (bFollowUpScheduleTightening + ? TEXT("launch_budget_schedule_follow_up_tighten") + : (bScheduleTighteningSource + ? TEXT("launch_budget_schedule_recommended_tighten") + : TEXT("launch_budget_history_tighten"))); const EHyperTwistCoachFocusLane FocusLane = bBroadenPreferred ? EHyperTwistCoachFocusLane::ReviewBacklog - : EHyperTwistCoachFocusLane::Stability; + : (bScheduleTighteningSource + ? EHyperTwistCoachFocusLane::Cadence + : EHyperTwistCoachFocusLane::Stability); const FString Headline = bBroadenPreferred ? TEXT("Launch history favors a broader next coach run") - : TEXT("Launch history favors a tighter next coach run"); + : (bScheduleTighteningSource + ? (CoachMemorySnapshot.bHasBlockedFollowUpScheduleFriction + ? TEXT("Schedule friction favors a tighter next coach run before broader guidance resumes") + : TEXT("Schedule recovery favors a tighter next coach run before broader guidance resumes")) + : TEXT("Launch history favors a tighter next coach run")); const FString PreferredGuidanceLaneLabel = CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp ? TEXT("follow-up") @@ -4490,14 +4581,22 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor : *HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel) ); - const EHyperTwistTrainingDeliveryMode SuggestedMode = - CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer + const EHyperTwistTrainingDeliveryMode SuggestedMode = bScheduleTighteningSource + ? (bFollowUpScheduleTightening + ? EHyperTwistTrainingDeliveryMode::CoachReviewed + : CoachMemorySnapshot.DominantCoachSuggestedMode) + : (CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer ? CurrentBrief.SuggestedMode - : CoachMemorySnapshot.DominantCoachSuggestedMode; - const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = - CurrentBrief.SuggestedSelectionPolicy != EHyperTwistTrainingSelectionPolicy::Weighted + : CoachMemorySnapshot.DominantCoachSuggestedMode); + const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = bScheduleTighteningSource + ? (bFollowUpScheduleTightening + ? EHyperTwistTrainingSelectionPolicy::Scripted + : (CoachMemorySnapshot.bHasQueueExecutionCompletionGap + ? EHyperTwistTrainingSelectionPolicy::Weighted + : CoachMemorySnapshot.DominantCoachSelectionPolicy)) + : (CurrentBrief.SuggestedSelectionPolicy != EHyperTwistTrainingSelectionPolicy::Weighted ? CurrentBrief.SuggestedSelectionPolicy - : CoachMemorySnapshot.DominantCoachSelectionPolicy; + : CoachMemorySnapshot.DominantCoachSelectionPolicy); FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal( SignalId, @@ -4514,11 +4613,13 @@ FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistor Signal.SuggestedMethodSegmentId = !CurrentBrief.FocusMethodSegmentId.IsEmpty() ? CurrentBrief.FocusMethodSegmentId : CoachMemorySnapshot.FocusMethodSegmentId; - Signal.SuggestedCaseIds = CurrentBrief.FocusCaseIds.Num() > 0 - ? CurrentBrief.FocusCaseIds - : (CoachMemorySnapshot.WeakCaseIds.Num() > 0 - ? CoachMemorySnapshot.WeakCaseIds - : CoachMemorySnapshot.DueCaseIds); + Signal.SuggestedCaseIds = bScheduleTighteningSource && CoachMemorySnapshot.SchedulePressureCaseIds.Num() > 0 + ? CoachMemorySnapshot.SchedulePressureCaseIds + : (CurrentBrief.FocusCaseIds.Num() > 0 + ? CurrentBrief.FocusCaseIds + : (CoachMemorySnapshot.WeakCaseIds.Num() > 0 + ? CoachMemorySnapshot.WeakCaseIds + : CoachMemorySnapshot.DueCaseIds)); HyperTwistTrainingCoachLibraryInternal::ApplyGuidancePreferenceToSignal( Signal, CoachMemorySnapshot.PreferredGuidanceLane, @@ -4580,6 +4681,7 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunc const FString OriginalDeckId = Brief.FocusDeckId; const FString OriginalMethodSegmentId = Brief.FocusMethodSegmentId; const TArray OriginalCaseIds = Brief.FocusCaseIds; + const FString OriginalPrimaryActionLabel = Brief.PrimaryActionLabel; HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, Brief.Signals[0]); if (Brief.FocusDeckId.IsEmpty()) { @@ -4593,6 +4695,25 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunc { Brief.FocusCaseIds = OriginalCaseIds; } + if (bShouldPromoteLaunchSignal) + { + if (Brief.PrimaryActionLabel == TEXT("launch_budget_schedule_follow_up_tighten")) + { + Brief.PrimaryActionLabel = TEXT("launch_follow_up_recovery"); + } + else if (Brief.PrimaryActionLabel == TEXT("launch_budget_schedule_recommended_tighten")) + { + Brief.PrimaryActionLabel = TEXT("launch_recommended_recovery"); + } + else if (Brief.PrimaryActionLabel == LaunchBudgetHistorySignal.SignalId + && (!OriginalPrimaryActionLabel.IsEmpty() + && (OriginalPrimaryActionLabel.Contains(TEXT("schedule")) + || OriginalPrimaryActionLabel.Contains(TEXT("recovery")) + || OriginalPrimaryActionLabel.Contains(TEXT("promote_deferred"))))) + { + Brief.PrimaryActionLabel = OriginalPrimaryActionLabel; + } + } } return Brief; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index 62cec31..8e2d91b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -2,6 +2,7 @@ #include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h" #include "HyperTwistTraining/HyperTwistTrainingLibrary.h" +#include "JsonObjectConverter.h" namespace HyperTwistTrainingRepositoryLibraryInternal { @@ -4353,6 +4354,251 @@ namespace HyperTwistTrainingRepositoryLibraryInternal } } + template + FString SerializeStructToJson(const TStruct& Value) + { + FString Json; + FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0); + return Json; + } + + template + bool DoSerializedStructsMatch(const TStruct& Left, const TStruct& Right) + { + return SerializeStructToJson(Left) == SerializeStructToJson(Right); + } + + TArray NormalizeLearnerStates( + const TArray& LearnerStates + ) + { + TArray NormalizedStates; + TMap LearnerStateIndexByKey; + for (const FHyperTwistTrainingCaseLearnerState& LearnerState : LearnerStates) + { + if (!LearnerState.IsStructurallyValid()) + { + continue; + } + + const FString LearnerStateKey = MakeCaseKey( + LearnerState.UserId, + LearnerState.DeckId, + LearnerState.CaseId + ); + if (const int32* ExistingIndex = LearnerStateIndexByKey.Find(LearnerStateKey)) + { + NormalizedStates[*ExistingIndex] = LearnerState; + } + else + { + LearnerStateIndexByKey.Add(LearnerStateKey, NormalizedStates.Add(LearnerState)); + } + } + + NormalizedStates.Sort([]( + const FHyperTwistTrainingCaseLearnerState& Left, + const FHyperTwistTrainingCaseLearnerState& Right + ) + { + if (Left.UserId != Right.UserId) + { + return Left.UserId < Right.UserId; + } + if (Left.DeckId != Right.DeckId) + { + return Left.DeckId < Right.DeckId; + } + return Left.CaseId < Right.CaseId; + }); + return NormalizedStates; + } + + bool IsTerminalReviewPlanEntryState(const EHyperTwistTrainingReviewPlanEntryState EntryState) + { + return EntryState == EHyperTwistTrainingReviewPlanEntryState::Completed + || EntryState == EHyperTwistTrainingReviewPlanEntryState::Skipped; + } + + FHyperTwistTrainingReviewPlanEntry NormalizeReviewPlanEntry( + const FHyperTwistTrainingReviewPlanEntry& Entry + ) + { + FHyperTwistTrainingReviewPlanEntry NormalizedEntry = Entry; + NormalizedEntry.AttemptCount = FMath::Max(0, NormalizedEntry.AttemptCount); + NormalizedEntry.SuccessCount = FMath::Clamp( + NormalizedEntry.SuccessCount, + 0, + NormalizedEntry.AttemptCount + ); + if (NormalizedEntry.PromptLabel.IsEmpty()) + { + NormalizedEntry.PromptLabel = NormalizedEntry.CaseId; + } + + return NormalizedEntry; + } + + FHyperTwistTrainingReviewPlanState NormalizeReviewPlanState( + const FHyperTwistTrainingReviewPlanState& ReviewPlan + ) + { + FHyperTwistTrainingReviewPlanState NormalizedPlan = ReviewPlan; + const FString ReferenceSeed = !ReviewPlan.ReferenceUtc.IsEmpty() + ? ReviewPlan.ReferenceUtc + : (!ReviewPlan.CreatedAtUtc.IsEmpty() + ? ReviewPlan.CreatedAtUtc + : (!ReviewPlan.StartedAtUtc.IsEmpty() ? ReviewPlan.StartedAtUtc : ReviewPlan.CompletedAtUtc)); + NormalizedPlan.ReferenceUtc = ResolveReferenceUtc(ReferenceSeed); + if (NormalizedPlan.CreatedAtUtc.IsEmpty()) + { + NormalizedPlan.CreatedAtUtc = NormalizedPlan.ReferenceUtc; + } + if (NormalizedPlan.StartedAtUtc.IsEmpty()) + { + NormalizedPlan.StartedAtUtc = NormalizedPlan.CreatedAtUtc; + } + NormalizedPlan.Policy.MaxCases = FMath::Max(0, NormalizedPlan.Policy.MaxCases); + + TMap EntryIndexByCaseId; + TArray NormalizedEntries; + for (const FHyperTwistTrainingReviewPlanEntry& Entry : ReviewPlan.Entries) + { + const FHyperTwistTrainingReviewPlanEntry NormalizedEntry = NormalizeReviewPlanEntry(Entry); + if (!NormalizedEntry.IsStructurallyValid()) + { + continue; + } + + if (const int32* ExistingIndex = EntryIndexByCaseId.Find(NormalizedEntry.CaseId)) + { + NormalizedEntries[*ExistingIndex] = NormalizedEntry; + } + else + { + EntryIndexByCaseId.Add(NormalizedEntry.CaseId, NormalizedEntries.Add(NormalizedEntry)); + } + } + + NormalizedPlan.Entries = NormalizedEntries; + if (!NormalizedPlan.IsStructurallyValid()) + { + NormalizedPlan.CurrentEntryIndex = INDEX_NONE; + return NormalizedPlan; + } + + int32 ResolvedCurrentEntryIndex = INDEX_NONE; + if (!NormalizedPlan.bCompleted + && NormalizedPlan.Entries.IsValidIndex(NormalizedPlan.CurrentEntryIndex) + && !IsTerminalReviewPlanEntryState( + NormalizedPlan.Entries[NormalizedPlan.CurrentEntryIndex].EntryState)) + { + ResolvedCurrentEntryIndex = NormalizedPlan.CurrentEntryIndex; + } + if (ResolvedCurrentEntryIndex == INDEX_NONE && !NormalizedPlan.bCompleted) + { + ResolvedCurrentEntryIndex = NormalizedPlan.Entries.IndexOfByPredicate( + [](const FHyperTwistTrainingReviewPlanEntry& Entry) + { + return Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Current; + } + ); + if (ResolvedCurrentEntryIndex >= 0 + && IsTerminalReviewPlanEntryState( + NormalizedPlan.Entries[ResolvedCurrentEntryIndex].EntryState)) + { + ResolvedCurrentEntryIndex = INDEX_NONE; + } + } + if (ResolvedCurrentEntryIndex == INDEX_NONE && !NormalizedPlan.bCompleted) + { + ResolvedCurrentEntryIndex = NormalizedPlan.Entries.IndexOfByPredicate( + [](const FHyperTwistTrainingReviewPlanEntry& Entry) + { + return Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::NeedsRepeat + || Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Pending; + } + ); + } + + int32 TerminalCount = 0; + FString LatestCompletedAtUtc; + for (const FHyperTwistTrainingReviewPlanEntry& Entry : NormalizedPlan.Entries) + { + if (IsTerminalReviewPlanEntryState(Entry.EntryState)) + { + TerminalCount += 1; + } + if (!Entry.LastCompletedAtUtc.IsEmpty() + && (LatestCompletedAtUtc.IsEmpty() + || CompareUtcStrings(Entry.LastCompletedAtUtc, LatestCompletedAtUtc) > 0)) + { + LatestCompletedAtUtc = Entry.LastCompletedAtUtc; + } + } + if (TerminalCount >= NormalizedPlan.Entries.Num()) + { + NormalizedPlan.bCompleted = true; + } + + if (NormalizedPlan.bCompleted) + { + NormalizedPlan.CurrentEntryIndex = INDEX_NONE; + for (FHyperTwistTrainingReviewPlanEntry& Entry : NormalizedPlan.Entries) + { + if (Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Current) + { + Entry.EntryState = Entry.LastOutcome == EHyperTwistTrainingAttemptResult::Aborted + ? EHyperTwistTrainingReviewPlanEntryState::Skipped + : (Entry.SuccessCount > 0 + ? EHyperTwistTrainingReviewPlanEntryState::Completed + : (Entry.AttemptCount > 0 + ? EHyperTwistTrainingReviewPlanEntryState::NeedsRepeat + : EHyperTwistTrainingReviewPlanEntryState::Pending)); + } + } + if (NormalizedPlan.CompletedAtUtc.IsEmpty()) + { + NormalizedPlan.CompletedAtUtc = !LatestCompletedAtUtc.IsEmpty() + ? LatestCompletedAtUtc + : ResolveReferenceUtc( + !NormalizedPlan.StartedAtUtc.IsEmpty() + ? NormalizedPlan.StartedAtUtc + : NormalizedPlan.ReferenceUtc + ); + } + return NormalizedPlan; + } + + NormalizedPlan.CompletedAtUtc.Reset(); + NormalizedPlan.CurrentEntryIndex = ResolvedCurrentEntryIndex; + for (int32 EntryIndex = 0; EntryIndex < NormalizedPlan.Entries.Num(); ++EntryIndex) + { + FHyperTwistTrainingReviewPlanEntry& Entry = NormalizedPlan.Entries[EntryIndex]; + if (IsTerminalReviewPlanEntryState(Entry.EntryState)) + { + continue; + } + + if (EntryIndex == ResolvedCurrentEntryIndex) + { + Entry.EntryState = EHyperTwistTrainingReviewPlanEntryState::Current; + continue; + } + + if (Entry.EntryState == EHyperTwistTrainingReviewPlanEntryState::Current) + { + Entry.EntryState = Entry.LastOutcome == EHyperTwistTrainingAttemptResult::Aborted + ? EHyperTwistTrainingReviewPlanEntryState::Skipped + : (Entry.AttemptCount > 0 + ? EHyperTwistTrainingReviewPlanEntryState::NeedsRepeat + : EHyperTwistTrainingReviewPlanEntryState::Pending); + } + } + + return NormalizedPlan; + } + FHyperTwistTrainingMethodDrillFavoriteEntry NormalizeMethodDrillFavoriteEntry( const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry ) @@ -7334,6 +7580,82 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar SeenLearnerStateKeys.Add(LearnerStateKey); } + if (Report.TotalRunRecordCount > 0) + { + TArray ValidRunRecords; + TMap RunRecordIndexBySessionId; + for (const FHyperTwistTrainingRunRecord& RunRecord : RepositoryState.RunRecords) + { + if (!RunRecord.IsStructurallyValid()) + { + continue; + } + + const FString& SessionId = RunRecord.Session.TrainingSessionId; + if (const int32* ExistingIndex = RunRecordIndexBySessionId.Find(SessionId)) + { + ValidRunRecords[*ExistingIndex] = RunRecord; + } + else + { + RunRecordIndexBySessionId.Add(SessionId, ValidRunRecords.Add(RunRecord)); + } + } + ValidRunRecords = HyperTwistTrainingRepositoryLibraryInternal::SortRunRecordsByUpdateUtc( + ValidRunRecords + ); + + const TArray ExpectedLearnerStates = + HyperTwistTrainingRepositoryLibraryInternal::DeriveLearnerStatesFromRunRecords(ValidRunRecords); + const TArray ActualLearnerStates = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeLearnerStates(RepositoryState.LearnerStates); + + TMap ExpectedLearnerStateByKey; + for (const FHyperTwistTrainingCaseLearnerState& LearnerState : ExpectedLearnerStates) + { + ExpectedLearnerStateByKey.Add( + HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey( + LearnerState.UserId, + LearnerState.DeckId, + LearnerState.CaseId + ), + LearnerState + ); + } + + TMap ActualLearnerStateByKey; + for (const FHyperTwistTrainingCaseLearnerState& LearnerState : ActualLearnerStates) + { + ActualLearnerStateByKey.Add( + HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey( + LearnerState.UserId, + LearnerState.DeckId, + LearnerState.CaseId + ), + LearnerState + ); + } + + for (const TPair& Pair : ExpectedLearnerStateByKey) + { + const FHyperTwistTrainingCaseLearnerState* ActualLearnerState = ActualLearnerStateByKey.Find(Pair.Key); + if (ActualLearnerState == nullptr + || !HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch( + *ActualLearnerState, + Pair.Value)) + { + Report.OutOfSyncLearnerStateCount += 1; + } + } + for (const TPair& Pair : ActualLearnerStateByKey) + { + if (!ExpectedLearnerStateByKey.Contains(Pair.Key)) + { + Report.OutOfSyncLearnerStateCount += 1; + } + } + } + Report.TotalReviewPlanCount = RepositoryState.ReviewPlans.Num(); TSet SeenReviewPlanIds; for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans) @@ -7352,6 +7674,37 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar SeenReviewPlanIds.Add(ReviewPlan.PlanId); } + TArray ValidReviewPlans; + TSet UniqueValidReviewPlanIds; + for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans) + { + if (!ReviewPlan.IsStructurallyValid()) + { + continue; + } + + const FHyperTwistTrainingReviewPlanState NormalizedReviewPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan); + if (!NormalizedReviewPlan.IsStructurallyValid()) + { + Report.InvalidReviewPlanCount += 1; + continue; + } + if (UniqueValidReviewPlanIds.Contains(NormalizedReviewPlan.PlanId)) + { + continue; + } + + UniqueValidReviewPlanIds.Add(NormalizedReviewPlan.PlanId); + if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch( + ReviewPlan, + NormalizedReviewPlan)) + { + Report.OutOfSyncReviewPlanCount += 1; + } + ValidReviewPlans.Add(NormalizedReviewPlan); + } + Report.TotalStoredSessionTemplateCount = RepositoryState.StoredSessionTemplates.Num(); TSet SeenStoredTemplateKeys; for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : RepositoryState.StoredSessionTemplates) @@ -7484,6 +7837,12 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar Report.DuplicateCoachSessionQueueHistoryCount += 1; continue; } + if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch( + HistoryEntry, + NormalizedHistoryEntry)) + { + Report.OutOfSyncCoachSessionQueueHistoryCount += 1; + } SeenCoachSessionQueueHistoryIds.Add(NormalizedHistoryEntry.HistoryEntryId); } @@ -7498,7 +7857,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar } TSet ValidReviewPlanIds; - for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans) + for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : ValidReviewPlans) { if (ReviewPlan.IsStructurallyValid()) { @@ -7519,6 +7878,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan); if (!NormalizedPlan.IsStructurallyValid()) { + Report.InvalidCoachActionPlanCount += 1; continue; } @@ -7531,6 +7891,12 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar continue; } + if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch( + ActionPlan, + NormalizedPlan)) + { + Report.OutOfSyncCoachActionPlanCount += 1; + } ValidCoachActionPlanIndexByKey.Add(ActionPlanKey, ValidCoachActionPlans.Add(NormalizedPlan)); } @@ -7575,17 +7941,33 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar TSet UniqueCoachSessionQueueKeys; for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues) { - if (QueueState.IsStructurallyValid()) + if (!QueueState.IsStructurallyValid()) { - const FString QueueKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueKey( - QueueState.UserId, - QueueState.QueueId - ); - if (!UniqueCoachSessionQueueKeys.Contains(QueueKey)) + continue; + } + + const FHyperTwistTrainingCoachSessionQueueState NormalizedQueueState = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState); + if (!NormalizedQueueState.IsStructurallyValid()) + { + Report.InvalidCoachSessionQueueCount += 1; + continue; + } + + const FString QueueKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueKey( + NormalizedQueueState.UserId, + NormalizedQueueState.QueueId + ); + if (!UniqueCoachSessionQueueKeys.Contains(QueueKey)) + { + UniqueCoachSessionQueueKeys.Add(QueueKey); + if (!HyperTwistTrainingRepositoryLibraryInternal::DoSerializedStructsMatch( + QueueState, + NormalizedQueueState)) { - UniqueCoachSessionQueueKeys.Add(QueueKey); - ValidCoachSessionQueues.Add(QueueState); + Report.OutOfSyncCoachSessionQueueCount += 1; } + ValidCoachSessionQueues.Add(NormalizedQueueState); } } @@ -7660,14 +8042,17 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar if (Report.TotalRunRecordCount > 0 && (Report.TotalLearnerStateCount == 0 || Report.InvalidLearnerStateCount > 0 - || Report.DuplicateLearnerStateCount > 0)) + || Report.DuplicateLearnerStateCount > 0 + || Report.OutOfSyncLearnerStateCount > 0)) { HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( Report.RepairActions, TEXT("rebuild-learner-states-from-run-records") ); } - else if (Report.InvalidLearnerStateCount > 0 || Report.DuplicateLearnerStateCount > 0) + else if (Report.InvalidLearnerStateCount > 0 + || Report.DuplicateLearnerStateCount > 0 + || Report.OutOfSyncLearnerStateCount > 0) { HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( Report.RepairActions, @@ -7688,6 +8073,13 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar TEXT("deduplicate-review-plans-by-plan-id") ); } + if (Report.OutOfSyncReviewPlanCount > 0) + { + HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( + Report.RepairActions, + TEXT("normalize-review-plans") + ); + } if (Report.InvalidStoredSessionTemplateCount > 0) { HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( @@ -7737,7 +8129,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar TEXT("drop-invalid-coach-action-plans") ); } - if (Report.DuplicateCoachActionPlanCount > 0) + if (Report.DuplicateCoachActionPlanCount > 0 || Report.OutOfSyncCoachActionPlanCount > 0) { HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( Report.RepairActions, @@ -7775,7 +8167,7 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar TEXT("drop-invalid-coach-session-queues") ); } - if (Report.DuplicateCoachSessionQueueCount > 0) + if (Report.DuplicateCoachSessionQueueCount > 0 || Report.OutOfSyncCoachSessionQueueCount > 0) { HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( Report.RepairActions, @@ -7804,7 +8196,8 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar TEXT("drop-invalid-coach-session-queue-history") ); } - if (Report.DuplicateCoachSessionQueueHistoryCount > 0) + if (Report.DuplicateCoachSessionQueueHistoryCount > 0 + || Report.OutOfSyncCoachSessionQueueHistoryCount > 0) { HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction( Report.RepairActions, @@ -7858,28 +8251,9 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR } else { - TMap LearnerStateIndexByKey; - for (const FHyperTwistTrainingCaseLearnerState& LearnerState : RepositoryState.LearnerStates) - { - if (!LearnerState.IsStructurallyValid()) - { - continue; - } - - const FString LearnerStateKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey( - LearnerState.UserId, - LearnerState.DeckId, - LearnerState.CaseId - ); - if (const int32* ExistingIndex = LearnerStateIndexByKey.Find(LearnerStateKey)) - { - RepairedState.LearnerStates[*ExistingIndex] = LearnerState; - } - else - { - LearnerStateIndexByKey.Add(LearnerStateKey, RepairedState.LearnerStates.Add(LearnerState)); - } - } + RepairedState.LearnerStates = HyperTwistTrainingRepositoryLibraryInternal::NormalizeLearnerStates( + RepositoryState.LearnerStates + ); } TMap ReviewPlanIndexById; @@ -7890,16 +8264,50 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR continue; } - if (const int32* ExistingIndex = ReviewPlanIndexById.Find(ReviewPlan.PlanId)) + const FHyperTwistTrainingReviewPlanState NormalizedReviewPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan); + if (!NormalizedReviewPlan.IsStructurallyValid()) { - RepairedState.ReviewPlans[*ExistingIndex] = ReviewPlan; + continue; + } + + if (const int32* ExistingIndex = ReviewPlanIndexById.Find(NormalizedReviewPlan.PlanId)) + { + RepairedState.ReviewPlans[*ExistingIndex] = NormalizedReviewPlan; } else { - ReviewPlanIndexById.Add(ReviewPlan.PlanId, RepairedState.ReviewPlans.Add(ReviewPlan)); + ReviewPlanIndexById.Add( + NormalizedReviewPlan.PlanId, + RepairedState.ReviewPlans.Add(NormalizedReviewPlan) + ); } } + RepairedState.ReviewPlans.Sort([]( + const FHyperTwistTrainingReviewPlanState& Left, + const FHyperTwistTrainingReviewPlanState& Right + ) + { + if (Left.UserId != Right.UserId) + { + return Left.UserId < Right.UserId; + } + + const FString LeftSortUtc = !Left.CreatedAtUtc.IsEmpty() ? Left.CreatedAtUtc : Left.ReferenceUtc; + const FString RightSortUtc = !Right.CreatedAtUtc.IsEmpty() ? Right.CreatedAtUtc : Right.ReferenceUtc; + const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings( + LeftSortUtc, + RightSortUtc + ); + if (UtcCompare != 0) + { + return UtcCompare > 0; + } + + return Left.PlanId < Right.PlanId; + }); + TMap StoredTemplateIndexByKey; for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : RepositoryState.StoredSessionTemplates) { @@ -19057,12 +19465,24 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.ReferenceUtc ) > 3.0; + const bool bHasGenericPreferredGuidanceSource = + Snapshot.PreferredGuidanceSourceLabel.IsEmpty() + || Snapshot.PreferredGuidanceSourceLabel == TEXT("coach-memory-default") + || Snapshot.PreferredGuidanceSourceLabel == TEXT("coach-memory-follow-up") + || Snapshot.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-schedule-")); if (Snapshot.bHasBlockedFollowUpScheduleFriction || Snapshot.ScheduleBlockedFollowUpEventCount > 0 || Snapshot.FollowUpReadyCoachSequenceCount > 0) { Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp; - Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-follow-up"); + if (bHasGenericPreferredGuidanceSource) + { + Snapshot.PreferredGuidanceSourceLabel = + Snapshot.bHasBlockedFollowUpScheduleFriction + || Snapshot.ScheduleBlockedFollowUpEventCount > 0 + ? TEXT("coach-memory-schedule-follow-up") + : TEXT("coach-memory-follow-up"); + } } else if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None) { @@ -19070,7 +19490,10 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der || Snapshot.RecurringCoachCaseIds.Num() > 0 ? EHyperTwistTrainingCoachGuidanceLane::Recommended : EHyperTwistTrainingCoachGuidanceLane::FollowUp; - Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default"); + if (bHasGenericPreferredGuidanceSource) + { + Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default"); + } } if (Snapshot.PreferredQueueRecoveryAction == EHyperTwistTrainingQueueRecoveryAction::None && Snapshot.bNeedsScheduleFrictionRecovery) @@ -19081,6 +19504,74 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der : EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("coach-memory-schedule-default"); } + if (Snapshot.bNeedsScheduleFrictionRecovery) + { + const int32 ScheduleTighteningBias = FMath::Clamp( + (Snapshot.bHasBlockedFollowUpScheduleFriction ? 2 : 0) + + (Snapshot.ScheduleBlockedFollowUpEventCount > 0 ? 1 : 0) + + (Snapshot.bHasOutstandingDeferredScheduleWork ? 1 : 0) + + (Snapshot.bHasQueueExecutionCompletionGap ? 1 : 0) + + (Snapshot.bHasRepeatManualScheduleFriction ? 1 : 0), + 1, + 4 + ); + const bool bPreferFollowUpScheduleRecovery = + Snapshot.bHasBlockedFollowUpScheduleFriction + || Snapshot.ScheduleBlockedFollowUpEventCount > 0 + || Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; + const bool bPreferRecommendedScheduleRecovery = + !bPreferFollowUpScheduleRecovery + && (Snapshot.bHasQueueExecutionCompletionGap + || Snapshot.bHasOutstandingDeferredScheduleWork + || Snapshot.PreferredGuidanceLane + == EHyperTwistTrainingCoachGuidanceLane::Recommended); + const bool bHasGenericLaunchBudgetSource = + Snapshot.PreferredLaunchBudgetSourceLabel.IsEmpty() + || Snapshot.PreferredLaunchBudgetSourceLabel.StartsWith(TEXT("coach-memory-schedule-")); + + Snapshot.PreferredLaunchBudgetBias = + FMath::Min(Snapshot.PreferredLaunchBudgetBias, -ScheduleTighteningBias); + if (bHasGenericLaunchBudgetSource) + { + Snapshot.PreferredLaunchBudgetSourceLabel = bPreferFollowUpScheduleRecovery + ? TEXT("coach-memory-schedule-follow-up-tightened-launch") + : TEXT("coach-memory-schedule-tightened-launch"); + } + + if (bPreferFollowUpScheduleRecovery) + { + Snapshot.FollowUpGuidanceContinueBias += ScheduleTighteningBias; + Snapshot.SuppressedPrimaryQueuePressureCount += ScheduleTighteningBias; + Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp; + if (bHasGenericPreferredGuidanceSource) + { + Snapshot.PreferredGuidanceSourceLabel = + Snapshot.bHasBlockedFollowUpScheduleFriction + || Snapshot.ScheduleBlockedFollowUpEventCount > 0 + ? TEXT("coach-memory-schedule-follow-up") + : TEXT("coach-memory-follow-up"); + } + } + else if (bPreferRecommendedScheduleRecovery) + { + const int32 RecommendedScheduleRecoveryBias = FMath::Max( + 1, + ScheduleTighteningBias - (Snapshot.bHasArchiveAwareScheduleFriction ? 1 : 0) + ); + Snapshot.RecommendedGuidanceContinueBias += RecommendedScheduleRecoveryBias; + Snapshot.SuppressedFollowUpQueuePressureCount += RecommendedScheduleRecoveryBias; + if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None + || Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended + || bHasGenericPreferredGuidanceSource) + { + Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::Recommended; + if (bHasGenericPreferredGuidanceSource) + { + Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-schedule-recommended"); + } + } + } + } return Snapshot; } @@ -28828,7 +29319,7 @@ FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRepositoryLibrary::BuildRe ReviewPlan.Entries[0].EntryState = EHyperTwistTrainingReviewPlanEntryState::Current; } - return ReviewPlan; + return HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan); } FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan( @@ -28837,25 +29328,27 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertR ) { FHyperTwistTrainingRepositoryState UpdatedState = RepositoryState; - if (!ReviewPlan.IsStructurallyValid()) + const FHyperTwistTrainingReviewPlanState NormalizedReviewPlan = + HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(ReviewPlan); + if (!NormalizedReviewPlan.IsStructurallyValid()) { return UpdatedState; } const int32 ExistingIndex = UpdatedState.ReviewPlans.IndexOfByPredicate( - [&ReviewPlan](const FHyperTwistTrainingReviewPlanState& ExistingPlan) + [&NormalizedReviewPlan](const FHyperTwistTrainingReviewPlanState& ExistingPlan) { - return ExistingPlan.PlanId == ReviewPlan.PlanId; + return ExistingPlan.PlanId == NormalizedReviewPlan.PlanId; } ); if (ExistingIndex >= 0) { - UpdatedState.ReviewPlans[ExistingIndex] = ReviewPlan; + UpdatedState.ReviewPlans[ExistingIndex] = NormalizedReviewPlan; } else { - UpdatedState.ReviewPlans.Add(ReviewPlan); + UpdatedState.ReviewPlans.Add(NormalizedReviewPlan); } return UpdatedState; @@ -28885,7 +29378,7 @@ bool UHyperTwistTrainingRepositoryLibrary::TryGetReviewPlan( return false; } - OutReviewPlan = *ExistingPlan; + OutReviewPlan = HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(*ExistingPlan); return OutReviewPlan.IsStructurallyValid(); } @@ -28954,7 +29447,7 @@ FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRepositoryLibrary::UpdateR UpdatedPlan.CompletedAtUtc = !StepResult.UpdatedRunState.Session.EndedAtUtc.IsEmpty() ? StepResult.UpdatedRunState.Session.EndedAtUtc : Attempt.CompletedAtUtc; - return UpdatedPlan; + return HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(UpdatedPlan); } FString NextCaseId; @@ -28997,7 +29490,7 @@ FHyperTwistTrainingReviewPlanState UHyperTwistTrainingRepositoryLibrary::UpdateR UpdatedPlan.Entries[NextEntryIndex].EntryState = EHyperTwistTrainingReviewPlanEntryState::Current; } - return UpdatedPlan; + return HyperTwistTrainingRepositoryLibraryInternal::NormalizeReviewPlanState(UpdatedPlan); } FHyperTwistTrainingReviewFlowStatus UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 1cedbde..bab4ab0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -205,6 +205,22 @@ namespace HyperTwistTrainingSubsystemInternal : TEXT("successful deferred tightened-launch history is narrowing the next launch to continue deferred coach work")) ); } + else if (CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel.StartsWith(TEXT("coach-memory-schedule-"))) + { + BudgetReasons.Add( + CoachMemorySnapshot.bHasBlockedFollowUpScheduleFriction + ? (DeckLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + ? TEXT("blocked follow-up schedule friction is keeping the continuation slice narrow enough to unblock follow-up queue work") + : (bQueuedLaunch + ? TEXT("blocked follow-up schedule friction is keeping queued continuation work narrow enough to reduce deferred queue pressure") + : TEXT("blocked follow-up schedule friction is narrowing the next launch before broader guidance resumes"))) + : (DeckLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + ? TEXT("schedule-friction recovery is keeping the continuation slice narrow enough to stabilize follow-up queue work") + : (bQueuedLaunch + ? TEXT("schedule-friction recovery is keeping queued continuation work narrow enough to reduce deferred queue pressure") + : TEXT("schedule-friction recovery is narrowing the next launch before broader guidance resumes"))) + ); + } else { BudgetReasons.Add(TEXT("successful tightened-launch history is narrowing the next launch")); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 110012d..bba16c9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -6456,6 +6456,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 DuplicateLearnerStateCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OutOfSyncLearnerStateCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 TotalReviewPlanCount = 0; @@ -6465,6 +6468,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 DuplicateReviewPlanCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OutOfSyncReviewPlanCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 TotalStoredSessionTemplateCount = 0; @@ -6519,6 +6525,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 BrokenCoachActionPlanDepthCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OutOfSyncCoachActionPlanCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 TotalCoachSessionQueueCount = 0; @@ -6528,6 +6537,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 DuplicateCoachSessionQueueCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OutOfSyncCoachSessionQueueCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 MissingCoachSessionQueueSourceSessionCount = 0; @@ -6546,6 +6558,9 @@ struct FHyperTwistTrainingRepositoryIntegrityReport UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 DuplicateCoachSessionQueueHistoryCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OutOfSyncCoachSessionQueueHistoryCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 MissingCoachSessionQueueHistorySourceSessionCount = 0; @@ -6558,8 +6573,10 @@ struct FHyperTwistTrainingRepositoryIntegrityReport || DuplicateRunRecordCount > 0 || InvalidLearnerStateCount > 0 || DuplicateLearnerStateCount > 0 + || OutOfSyncLearnerStateCount > 0 || InvalidReviewPlanCount > 0 || DuplicateReviewPlanCount > 0 + || OutOfSyncReviewPlanCount > 0 || InvalidStoredSessionTemplateCount > 0 || DuplicateStoredSessionTemplateCount > 0 || InvalidMethodDrillFavoriteCount > 0 @@ -6574,13 +6591,16 @@ struct FHyperTwistTrainingRepositoryIntegrityReport || OrphanedCoachActionPlanRootCount > 0 || CyclicCoachActionPlanLineageCount > 0 || BrokenCoachActionPlanDepthCount > 0 + || OutOfSyncCoachActionPlanCount > 0 || InvalidCoachSessionQueueCount > 0 || DuplicateCoachSessionQueueCount > 0 + || OutOfSyncCoachSessionQueueCount > 0 || MissingCoachSessionQueueSourceSessionCount > 0 || InvalidCoachSessionQueueActiveEntryCount > 0 || MissingCoachSessionQueueActiveSessionCount > 0 || InvalidCoachSessionQueueHistoryCount > 0 || DuplicateCoachSessionQueueHistoryCount > 0 + || OutOfSyncCoachSessionQueueHistoryCount > 0 || MissingCoachSessionQueueHistorySourceSessionCount > 0; } }; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index df2b03a..07eb2ed 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -56,7 +56,7 @@ Evaluate providers second. Integrate providers only after the roles and feature-set map exist. -Current correction call from the source-exposed audit: +Current correction call from the source-exposed audit and current execution refresh: - preserve the roadmap direction - update the canonical planning docs so they reflect: @@ -64,6 +64,17 @@ Current correction call from the source-exposed audit: - manifest and custody governance gaps - pre-Phase-5 speech governance gates - retained benchmark-oracle and mirror-intake reservations + - current code reality in which training/coaching/catalog integration is materially ahead of recognition and hypercube runtime implementation + - the imported generated-mode gap, where request/config/selection plumbing exists but the actual clean-room executor is still absent + - the current execution-discipline rule that broad refactor / monolith-splitting work should not interrupt the active bounded roadmap packet unless structure is actually blocking it + +## Current execution-reality references + +For the current code-reality synthesis and active phase-discipline note, also read: + +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\116-hypertwist-handoff.md` +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\119-hypertwist-current-execution-reality-and-phase-discipline.md` +- `C:\HyperTwist\docs\arch\HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md` ## Higher-detail planning references diff --git a/docs/arch/HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md b/docs/arch/HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md new file mode 100644 index 0000000..19a3ca4 --- /dev/null +++ b/docs/arch/HYPERTWIST_PI_PRIMING_FINDINGS_2026-05-04.md @@ -0,0 +1,1348 @@ +# HyperTwist PI Priming Findings — 2026-05-04 + +## Purpose + +This file records my findings, interpretations, opinions, and working conclusions after performing a broad priming pass across the requested HyperTwist documentation, parse stack, handoff material, and live Unreal project tree. + +It is intentionally verbose. + +It is not meant to replace the canonical planning stack or the product handoff documents. +Instead, it captures what I now understand about the project after reading across the currently relevant working surfaces. + +## Scope of the priming pass + +Requested roots and files: + +- `C:\HyperTwist\docs` +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse` +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\116-hypertwist-handoff.md` +- `C:\HyperTwist\UnrealHyperTwist` + +Additional directly relevant supporting files also reviewed during the pass: + +- `C:\HyperTwist\AGENTS.md` +- `C:\HyperTwist\.woodpecker\hypertwist-bootstrap.yml` +- `C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject` +- `C:\HyperTwist\UnrealHyperTwist\Config\*.ini` +- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\*.cs` +- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Public\*` +- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Private\*` +- `C:\HyperTwist\UnrealHyperTwist\Plugins\UnrealMCPChong\*` +- `C:\HyperTwist\UnrealHyperTwist\Content\HyperTwistTraining\MaterializedCatalog\*` + +## Safety and disclosure note + +This priming pass included visibility into `docs/refs`, which is explicitly marked as reference-only and may contain sensitive operational material. + +Important boundary: + +- I am **not** reproducing credentials, auth secrets, or sensitive operational content here. +- I am also **not** treating `docs/refs` as the default implementation authority. +- Where security-sensitive or secret-bearing values appear in config or reference material, I describe the category of issue without reprinting the value. + +## Method used + +I used two different kinds of scan: + +### 1. Full byte-level pass + +I performed a front-to-back byte scan over the requested roots so that the file population was genuinely traversed, not just sampled. + +Byte-scan totals: + +- `C:\HyperTwist\docs` + - `67` files + - `10,527,728` bytes +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse` + - `457` files + - `26,779,316` bytes +- `C:\HyperTwist\UnrealHyperTwist` + - `1,473` files + - `5,835,427,401` bytes + +Combined total byte-pass footprint: + +- `1,997` files +- approximately `5.87 GB` + +### 2. Semantic text/code/document pass + +I also did a text-focused semantic pass over decodable files, headers, source files, markdown, JSON, INI, and related working artifacts. + +That pass was aimed at understanding actual project meaning, authority order, subsystem shape, and current implementation state rather than just storage volume. + +High-signal semantic observations were derived from: + +- the v6.5 manual pack +- roadmap-overhaul docs +- the parse stack README and continuity docs +- the current handoff files +- current product code headers and implementation files +- product content materialization JSON and raw extracts +- maintained plugin source in `UnrealMCPChong` + +## Repository working-state observation at priming time + +Observed `git status --short` before making this findings file: + +- modified: `UnrealHyperTwist/Source/UnrealHyperTwist/UnrealHyperTwist.cpp` +- modified: `UnrealHyperTwist/Source/UnrealHyperTwist/UnrealHyperTwist.h` +- deleted: `docs/Deepseek C# Prompt` +- untracked: `.idea/` + +Important note: + +- `docs/Deepseek C# Prompt` is shown by Git as deleted at the repo root path. +- A similarly named file also exists under `docs/refs/Deepseek C# Prompt` and was present during the scan. +- Those are not necessarily the same path or Git state. + +## High-level conclusion + +My strongest conclusion is this: + +HyperTwist is **not** in the same state described by the oldest “current state” documents. + +It is no longer just an Unreal template surrounded by research archives. + +It now has a **substantial first-party training backend** inside the Unreal project, along with imported catalog materialization plumbing, runtime selector surfaces, generated-mode launch request persistence, dashboard-facing coaching state, repository integrity logic, timer import/export, replay review analytics, and a maintained editor-side MCP bridge plugin. + +At the same time, the documentation stack is now split across multiple eras: + +1. preserved baseline / aspirational product identity docs +2. earlier parse-era state snapshots +3. active planning docs +4. active continuity / handoff docs +5. live code reality + +The biggest documentation problem is no longer “there is no architecture.” + +The biggest problem is: + +- some docs still describe an earlier bootstrap-era reality, +- while other docs correctly describe the newer integration lane, +- and the reader must already know which layer is current. + +## Top-level findings by requested root + +--- + +## Findings: `C:\HyperTwist\docs` + +### Structural facts + +The `docs` tree currently contains: + +- working product docs +- policy docs +- operations docs +- reference-only materials under `docs/refs` +- legacy/archive-ish narrative and research bundles +- the v6.5 deep manual pack + +Representative folders: + +- `docs/arch` +- `docs/auth` +- `docs/ops` +- `docs/quarantine` +- `docs/refs` +- `docs/v6_5_deep_manual_pack` + +### Most important working docs in this tree + +These were the highest-value non-reference docs for understanding current intended project posture: + +- `docs/v6_5_deep_manual_pack/README.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/AGENTS.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ARCHITECTURE.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/PRD.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/API.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/LICENSETRACKING.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/SKILLS.md` +- `docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md` +- `docs/MODEL_B_SOURCE_ACCESS_BOUNDARY.md` +- `docs/REPO_LICENSE_TRACKING.md` +- `docs/EXTERNAL_MIRROR_WORKSPACE_AND_SUBMODULE_HANDOFF.md` + +### What these docs establish clearly + +The docs root makes several things explicit: + +1. HyperTwist is intended to be a **native-first Unreal-centered product**. +2. The product is deliberately **dual-pillar**: + - classic cubing / hypercubing runtime and simulation + - recognition / replay / coaching +3. Architecture and planning are expected to preserve that duality. +4. Clean-room and donor-governance boundaries are treated seriously. +5. The old one-line-per-phase roadmap is preserved but no longer sufficient as the sole live planning layer. + +### Important policy / governance observations + +#### `MODEL_B_SOURCE_ACCESS_BOUNDARY.md` + +This file is unusually important. + +It formalizes which materials are safe for a clean-room `Model B` and which are forbidden. + +My opinion: + +- this is a strong governance document, +- and it is one of the clearest signs that the project now takes source-exposure boundaries seriously, +- but it also means readers must not casually mix the parse stack, restrictive dossier work, and live implementation context. + +#### `REPO_LICENSE_TRACKING.md` + +This file is concrete rather than merely policy-level. + +It records repo-by-repo legal treatment and practical posture. + +My opinion: + +- this file is useful, +- but it is very long and functions more like an evolving legal/provenance ledger than a concise engineering brief, +- so it should remain authoritative for licensing posture while being supplemented by short implementation-facing summaries when needed. + +### Sensitive content note + +`docs/refs` contains clearly sensitive or at least operationally delicate material, including admin/auth/runbook content. + +My opinion: + +- it was correct to mark this subtree as reference-only, +- it should remain non-default, +- and future implementation or planning docs should keep pointing readers away from it unless there is an explicit ops task. + +### Large authored/archive docs + +Some docs are very large, especially: + +- `hypertwist_narrative_bundle_v1/.../HyperTwist_Volume_4.md` +- `arch/HyperTwist Raw Repository Ledger.txt` +- `ops/TOOL_ACCESS_AND_REFACTORING_LOG.md` + +My opinion: + +- these are useful lineage/context stores, +- but they are not the best first read for implementation continuity, +- and they can easily overwhelm a restart session unless paired with short continuity docs. + +--- + +## Findings: `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse` + +### Structural facts + +This parse root is a large Markdown-and-materialization workspace containing: + +- project-state analysis docs +- donor and clean-room dossiers +- roadmap-planning docs +- continuity/handoff docs +- materialized catalog artifacts +- raw extracted JSONL/JSON data +- roadmap-overhaul working outputs + +It currently contains: + +- `457` files +- about `26.8 MB` + +### What the parse root is doing today + +It functions as a combined: + +- planning ledger +- evaluation ledger +- continuity ledger +- content materialization staging area +- roadmapping and governance workspace + +### Most important parse docs for current continuation + +In terms of current value, the following stood out most strongly: + +- `README.md` +- `93-hypertwist-roadmap-overhaul-document-map.md` +- `95-hypertwist-roadmap-invariants.md` +- `96-hypertwist-live-execution-roadmap.md` +- `97-hypertwist-feature-set-matrix.md` +- `98-hypertwist-model-role-and-provider-matrix.md` +- `115-hypertwist-product-integration-memory.md` +- `116-hypertwist-handoff.md` +- `88-roadmap-implementation-workbook.md` +- `90-clean-room-safe-project-context.md` + +### Most important continuity conclusion from the parse root + +The parse stack confirms that HyperTwist moved through several meaningful implementation phases after the earlier bootstrap-era assessment. + +That matters because some earlier “current state” docs are now historically interesting but operationally stale. + +### Parse README value + +The parse `README.md` is still very useful because it explains what the folder contains and records the broader canon/reconciliation state. + +My opinion: + +- it is a strong index, +- but it is not the best short continuity doc for the current product slice, +- because it remains broad and portfolio-heavy. + +### Roadmap-overhaul docs + +The planning layer centered around: + +- `95-hypertwist-roadmap-invariants.md` +- `96-hypertwist-live-execution-roadmap.md` +- `97-hypertwist-feature-set-matrix.md` +- `98-hypertwist-model-role-and-provider-matrix.md` + +is one of the healthiest parts of the current documentation stack. + +My opinion: + +- these documents correctly separate identity, execution sequencing, feature grouping, and provider evaluation, +- which is a real improvement over treating one phase list as the entire planning authority. + +### Clean-room-safe project context + +`90-clean-room-safe-project-context.md` is especially valuable because it gives a clean implementation instance enough context to work without touching forbidden material. + +My opinion: + +- this is a strong practice and should be retained, +- especially because the HyperTwist repo and workspace ecology are dense enough that accidental contamination would be easy without explicit guardrails. + +--- + +## Findings: `116-hypertwist-handoff.md` + +### This is the single strongest restart document + +Of all the continuity docs I read, this is the best current restart artifact for the active imported-training-catalog integration lane. + +It is clear about: + +- what is already done +- what is still missing +- which product-side files matter most +- what not to reopen +- what the next real implementation step should be + +### Core factual takeaway from `116` + +The imported training-catalog lane is **well beyond** “can we load the files?” + +According to the handoff and corroborated by the code surface, the system now has: + +- imported catalog overlay +- finite imported deck materialization +- non-finite generated-mode and selector-catalog materialization +- runtime accessors for imported runtime surfaces and selectors +- selector-catalog option application into active runs +- generated-mode selector choice state on runs +- generated-mode selector choice reporting in summaries and replay payloads +- generated-mode launch config bridge +- owned generated-mode launch request surface +- persistence of generated-mode launch requests into run records +- query surfaces for latest persisted generated-mode launch requests + +### Core missing piece from `116` + +What remains notably absent is the actual **clean-room generator executor** that turns a generated-mode launch request into a playable/generated training state or case. + +My opinion: + +- this handoff feels accurate relative to the current code and content surfaces, +- and it correctly identifies the important distinction between “request plumbing exists” and “generator execution exists.” + +That distinction is essential. + +### Practical value of `116` + +If I had to choose one continuity file to hand a future implementation session, I would choose `116` first. + +It is concise, direct, and well-scoped. + +--- + +## Findings: `C:\HyperTwist\UnrealHyperTwist` + +### Structural facts + +The Unreal project tree currently contains: + +- `1,473` files +- roughly `5.84 GB` + +But that headline number is misleading unless broken down. + +Breakdown by major subtree: + +- `Source` + - `51` files + - about `3.7 MB` +- `Config` + - `4` files +- `Content` + - `9` files + - all currently in the materialized training catalog lane +- `Plugins` + - `828` files + - dominated by plugin code and build artifacts +- `Binaries` + - compiled artifacts +- `Intermediate` + - very large generated/build output + +### Semantic reality of the Unreal tree + +The real first-party project surface is **much smaller than the total storage footprint**, because: + +- `Intermediate` +- `Binaries` +- `.vs` +- plugin build products + +inflate the tree heavily. + +The actual first-party product meaning lives mainly in: + +- `Source/UnrealHyperTwist` +- `Config` +- `Content/HyperTwistTraining/MaterializedCatalog` +- `Plugins/UnrealMCPChong` + +--- + +## Unreal project metadata findings + +### `UnrealHyperTwist.uproject` + +Current project facts: + +- Engine association: `5.7` +- One main runtime module: `UnrealHyperTwist` +- Explicitly enabled plugins in the `.uproject`: + - `ModelingToolsEditorMode` for editor + - `RemoteControl` + +My opinion: + +- the project is still physically one Unreal module from the build system’s perspective, +- even though the code is logically organized into multiple subdomains, +- which is acceptable for now but notable. + +### Build and targets + +Current target files: + +- `Source/UnrealHyperTwist.Target.cs` +- `Source/UnrealHyperTwistEditor.Target.cs` +- `Source/UnrealHyperTwist/UnrealHyperTwist.Build.cs` + +Module dependencies currently include: + +- `Core` +- `CoreUObject` +- `Engine` +- `InputCore` +- `EnhancedInput` +- `Json` +- `JsonUtilities` +- `UMG` +- private `Slate` +- private `SlateCore` + +My opinion: + +- the build file remains lean, +- which is fine, +- but the internal code surface has grown much larger than the build file alone would suggest. + +--- + +## Config findings + +### `DefaultEngine.ini` + +Notable observations: + +- default map remains `/Engine/Maps/Templates/OpenWorld` +- global default game mode is `HyperTwistCoachDashboardGameMode` +- Python remote execution is enabled +- developer-mode Python settings are enabled +- Android File Server settings are present and permissive +- hardware targeting is desktop / maximum +- renderer settings are high-end and modern + - DX12 default + - ray tracing enabled + - Substrate enabled + - SM6 targeting present + +My opinion: + +- this is not a minimal placeholder config anymore, +- but it still contains obvious development convenience settings, +- and it is not yet a hardened shipping baseline. + +### `DefaultGame.ini` + +Minimal but normal project identity material. + +### `DefaultInput.ini` + +Notable observations: + +- Enhanced Input is configured as default +- motion controls are enabled +- broad VR/XR axis config entries exist + +My opinion: + +- this aligns with the long-term XR / immersive direction, +- even though the current live runtime seems much more training/backend/dashboard-heavy than immersive-heavy. + +--- + +## First-party code findings + +### Big picture + +The first-party code surface in `Source/UnrealHyperTwist` is **not** a stub anymore. + +It is dominated by `HyperTwistTraining`. + +Logical code-area totals from the source tree: + +- `HyperTwistTraining` + - `28` files + - `74,859` lines + - about `3.55 MB` +- `HyperTwistBootstrap` + - `2` files + - `2,240` lines +- `HyperTwistReplay` + - `5` files + - `902` lines +- `HyperTwistRecognition` + - `5` files + - `657` lines +- `HyperTwistCore` + - `3` files + - `598` lines +- `HyperTwistSimulation` + - `3` files + - `104` lines + +### Strongest conclusion from code volume + +The live implementation center of gravity is the **training/coaching/repository/dashboard lane**. + +That is where the product currently has the most real code. + +### Largest first-party implementation files + +- `Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp` + - `29,320` lines +- `Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp` + - `16,037` lines +- `Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - `4,869` lines +- `Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` + - `3,577` lines +- `Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp` + - `2,985` lines +- `Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp` + - `2,552` lines + +My opinion: + +- the training lane is real and substantial, +- but some files are becoming extremely large and probably deserve eventual decomposition, +- especially the repository and dashboard implementation files. + +--- + +## First-party subsystem understanding + +### `HyperTwistBootstrap` + +This is largely represented by `UHyperTwistContractLibrary`. + +It contains a large number of sample / fixture / contract-construction helpers for: + +- core puzzle definitions and states +- replay packets +- recognition results +- training content packs +- imported catalog artifacts +- drill definitions and summaries +- repository and analytics snapshots +- coaching summaries and memory snapshots + +My opinion: + +- this library functions as a bridge between architecture/contracts and runtime experimentation, +- and it is very useful for early-stage integration and Blueprint-facing contract visibility, +- though long-term it may need clearer separation between test/sample factories and runtime-authoritative helpers. + +### `HyperTwistCore` + +Current exposed functions are intentionally small: + +- notation normalization +- solved-state check +- transformation application + +Core types include: + +- puzzle family +- state encoding kind +- state source +- transform kind +- puzzle state +- transformation +- snapshot types +- classic and hypercube-specific encoded state structures + +My opinion: + +- the core contract layer exists, +- but the heavy product sophistication is not yet concentrated here, +- which means the training lane is ahead of the deep puzzle/runtime lane in code maturity. + +### `HyperTwistReplay` + +Current replay surface includes: + +- append replay event +- derive replay summary +- replay review anchor collection +- replay review analytics + +My opinion: + +- replay is present and useful, +- but still much smaller than training, +- which is consistent with a product that has built a strong practice/coaching backend before building larger replay UX surfaces. + +### `HyperTwistRecognition` + +Recognition currently exposes: + +- vision session config and payload structs +- mock vision client interface and implementation +- recognition replay episode and summary helpers + +My opinion: + +- recognition is contract- and integration-ready, +- but not yet the most mature runtime lane, +- which matches the live roadmap docs. + +### `HyperTwistSimulation` + +Simulation is currently very light, with placeholder scene-context helpers and projection settings. + +My opinion: + +- this is the thinnest current first-party lane, +- and it reinforces that the product’s short-term execution reality is much more training/backend centric than hyper-simulation centric. + +### `HyperTwistTraining` + +This is by far the deepest current implementation area. + +It includes: + +- large training domain types +- training library and progression logic +- catalog import/materialization integration +- repository and persistence logic +- runtime/subsystem accessors +- drill favorites and resumable batches +- timing and analytics summaries +- learner profiles and presets +- session templates +- review-plan state +- coaching signals / briefs / queue / follow-up state +- dashboard actor/controller/widget surfaces + +My opinion: + +- this is no longer a toy scaffold, +- it is a substantial product backend, +- and any documentation still calling the repo “template-level only” is materially outdated. + +--- + +## Training domain findings + +### `HyperTwistTrainingTypes.h` + +This is the giant domain-contract file. + +Key type families observed include: + +- `FHyperTwistTrainingCase` +- imported runtime selector types +- imported runtime surface types +- imported selection state +- generated-mode launch config +- generated-mode launch request +- decks, tracks, content packs +- session/run/attempt/summaries +- progression memory and learner state +- timer export packets and integrity reports +- case/deck scoped stats +- learner profiles and coach memory snapshots +- method drill definitions, batches, favorites, diagnostics +- review planning and review flow state +- coach action plans and queue state +- repository state and integrity/round-trip verification +- coach panel state + +My opinion: + +- this file is acting as the canonical training/coaching/repository contract spine, +- which is powerful but also creates a very large single-header gravity well, +- and that may eventually become a maintainability issue. + +### Imported runtime surface and generated-mode findings + +Current owned types exist for: + +- imported runtime selector options +- imported runtime selectors +- imported runtime surfaces +- imported selector choices +- imported runtime selection state +- imported generated-mode launch config +- imported generated-mode launch request + +This is very significant. + +It means imported generative content is no longer just “parsed data in JSON files.” + +It now has owned runtime-facing domain shapes in the live codebase. + +--- + +## Catalog and materialization findings + +### Content materialization assets present in product repo + +Current materialized catalog files inside the Unreal content tree: + +- `hypertwist-catalog-materialization-summary.json` +- `hypertwist-content-pack-manifests.json` +- `hypertwist-deck-import-specs.json` +- `hypertwist-reference-incorporation-targets.json` +- `hypertwist-runtime-content-bundles.json` +- raw extracts: + - `repo-backed-exercises.jsonl` + - `httrack-retained-exercises.jsonl` + - `restrictive-static-exercises.jsonl` + - `restrictive-generative-exercise-surfaces.jsonl` + +### Materialization counts + +From `hypertwist-catalog-materialization-summary.json`: + +- `14` content packs +- `14` track manifests +- `498` deck import specs +- `498` runtime content bundles +- `9` reference targets +- `29` overlap-pruned exclusions + +### Meaning of those counts + +My opinion: + +- this is a real content ingestion/materialization surface, +- not just a placeholder data folder, +- and the scale is already non-trivial. + +### Example deck import spec posture + +The deck import specs show clearly that the product is preserving: + +- donor attribution +- source mode +- source repo +- source license +- preserved factual elements +- rewrite-required elements +- materialization notes +- real-scramble and delivery-mode support flags + +My opinion: + +- this is one of the most mature signs of the project’s provenance discipline, +- because it is not merely loading donor-shaped data, +- it is explicitly encoding the boundary between preserved factual content and rewritten product wording. + +### Runtime content bundle posture + +Runtime content bundles encode: + +- case bundle identity +- source file path +- source matching filters +- prompt kind +- retained counts +- runtime materialization policy +- governance classification + +Observed materialization policies include: + +- `instantiate-algorithm-cases` +- generated-mode related policies +- selector-catalog related policies + +My opinion: + +- the catalog/runtime bridge is deeper than a normal static content import, +- because it already reasons about finite and non-finite deck materialization at runtime. + +--- + +## Catalog library findings + +`HyperTwistTrainingCatalogLibrary` is one of the most important current code areas. + +It appears to own: + +- JSON loading of materialized catalog documents +- conversion from imported/source-side values into owned product enums and structs +- deck materialization from runtime bundles +- runtime-surface construction for generated-mode and selector-catalog decks +- imported runtime selector lookup helpers +- imported selector option case mapping +- imported generated-mode launch config derivation +- bundled catalog loading from product content + +My opinion: + +- this library is the heart of the current imported-catalog integration work, +- and it materially validates the claims in `115` and `116`. + +Specific corroborating signals found in the code: + +- deck tags like `imported-materialized` and `catalog-materialized` +- explicit selector-catalog runtime surface population +- explicit generated-mode runtime surface population +- generated-mode launch config building from selection state + +That is all strong evidence that the imported-catalog lane is genuinely implemented, not merely planned. + +--- + +## Training subsystem/runtime findings + +### `UHyperTwistTrainingSubsystem` + +Current subsystem APIs cover all of the following areas: + +- active run existence and state +- active method-drill state +- imported runtime surface lookup +- imported selector lookup +- imported selector option case lookup +- active imported runtime selection state +- active generated-mode launch config lookup +- active generated-mode launch request lookup +- creation of active generated-mode launch requests +- selector option application into active runs +- generated selector choice application into active runs +- repository state and integrity reporting +- persisted launch-request query surfaces +- coach panel state +- deck history / timing / PB / scoped stats +- learner deck state and learner profiles +- replay review analytics and recognition replay summaries +- coach signals / briefs / action plans / follow-up / queue / scheduling summaries +- learner method segments / presets / session templates +- method drill favorites / batches / readiness / resume rebuilding +- review-plan state and timer-export packet construction + +My opinion: + +- this subsystem is no longer narrow, +- it is a real gameplay/runtime-facing orchestration surface over a large training backend. + +### `UHyperTwistTrainingRuntimeLibrary` + +This exposes the subsystem functions to Blueprint/runtime consumers. + +My opinion: + +- the runtime library largely mirrors subsystem functionality, +- which is a practical Unreal integration pattern, +- but it also indicates the product is leaning hard into Blueprint-available runtime control and dashboard consumption. + +--- + +## Repository and persistence findings + +### `HyperTwistTrainingRepositoryLibrary.cpp` + +This is the largest first-party file and appears to own a huge amount of durable state shaping. + +Evidence observed includes logic around: + +- run record storage +- learner-state derivation +- review grading and review history +- drill favorites and batches +- generated-mode launch request retrieval +- coach memory and queue state interpretation +- queue-history / dashboard-handoff / follow-up source-label taxonomies +- round-trip and integrity oriented repository behavior + +My opinion: + +- this file contains a remarkable amount of product behavior, +- but it is also a warning sign for future maintainability, +- because at nearly thirty thousand lines it is now clearly doing the work of multiple conceptual modules. + +### Persistence library + +The persistence surface includes default paths and save/load/verification helpers for: + +- repository state +- timer export packets +- repository round-trip verification + +My opinion: + +- the project has crossed the threshold from “ephemeral runtime experiments” into “durable stateful product backend.” + +--- + +## Coach/dashboard findings + +### Dashboard surface is real + +The combination of: + +- `HyperTwistCoachDashboardWidget` +- `HyperTwistCoachDashboardActor` +- `HyperTwistCoachDashboardPlayerController` +- `HyperTwistCoachDashboardGameMode` + +shows that the project has an actual dashboard-oriented consumer surface, not just backend APIs. + +### Scale of dashboard logic + +`HyperTwistCoachDashboardWidget.cpp` is over `16,000` lines. + +That is substantial. + +My opinion: + +- the dashboard is now a major product surface, +- not a temporary debug toy, +- though it likely contains too much behavior for one widget implementation file. + +### Coach-memory / queue-state sophistication + +The subsystem and repository code strongly suggest sophisticated handling of: + +- queue suppression +- deferred launches +- follow-up shaping +- coach handoff kinds +- method-drill recovery patterns +- schedule friction +- source-label-driven behavioral branching + +My opinion: + +- the coaching backend has evolved far beyond a simple recommendation list, +- and the docs should reflect that more explicitly in current-state material. + +--- + +## Plugin findings: `UnrealMCPChong` + +### Maintained plugin status + +Per repo instructions, `UnrealMCPChong` is the only maintained in-repo plugin fork. + +That appears correct. + +### What this plugin does + +It is an **editor-only** plugin that provides an MCP bridge over a TCP socket. + +Current important facts: + +- category: `Editor` +- binds to localhost +- default port: `55557` +- routes JSON command messages to editor command handlers + +Current handler families include: + +- actor/editor commands +- blueprint commands +- blueprint node commands +- project commands +- UMG commands + +### My opinion of the plugin’s role + +This is clearly tooling / automation / editor-integration support. + +It is **not** the product runtime. + +That distinction matters because older repo descriptions can blur plugin activity with product activity. + +Current reality is cleaner: + +- `UnrealMCPChong` is useful editor automation infrastructure, +- but it is not where the main HyperTwist product value lives. + +--- + +## CI and project-shell findings + +### Woodpecker bootstrap + +The bootstrap pipeline in `.woodpecker/hypertwist-bootstrap.yml` is intentionally minimal. + +It validates: + +- the Unreal project exists +- the source module root exists +- the docs tree exists + +My opinion: + +- this is a sanity pipeline rather than a deep build-validation pipeline, +- which is acceptable as a bootstrap check, +- but it does not substitute for real Unreal build/test verification. + +### Root project-shell files + +At Unreal project root: + +- `UnrealHyperTwist.sln` +- `UnrealHyperTwist.uproject` +- `.vsconfig` + +My opinion: + +- the project shell looks normal enough for an active UE workspace, +- while the real complexity now lives under `Source`, `Content`, and the maintained plugin. + +--- + +## Working interpretation of product state + +### What HyperTwist is **today**, in practical code terms + +HyperTwist today is best described as: + +- an Unreal-native project shell +- with a substantial first-party training/coaching/repository backend +- with imported catalog materialization and runtime surface integration +- with early but real replay and recognition contract surfaces +- with a thin simulation lane +- with an editor-side automation plugin +- with a very large planning and provenance documentation stack + +### What it is **not** today + +It is not yet primarily: + +- a deep first-party hypercube runtime +- a production-grade recognition stack +- a finished XR product +- a completed clean-room generated-mode executor + +### Strongest product implementation truth right now + +The strongest implementation truth is: + +**training, coaching, persistence, imported curriculum integration, and generated-mode request plumbing are the most mature live product lanes.** + +That is the center of gravity. + +--- + +## Opinions on the documentation stack + +### Opinion 1: the project now has both strong docs and strong code, but they are in different time layers + +The project is no longer “docs ahead of code” in the simplistic sense. + +A better description now is: + +- some docs are ahead of code, +- some code is ahead of docs, +- and some continuity docs are the bridge between them. + +### Opinion 2: historical snapshot docs are being mistaken for live state docs + +The clearest example is the early parse-era “current state” writing that still describes the Unreal side as near-empty bootstrap/template territory. + +That was likely correct earlier. + +It is not correct now. + +### Opinion 3: the continuity docs are better than the canonical “current state” docs for actual restart use + +If a new session wants to know what HyperTwist is currently doing, the handoff docs and active runtime code are more reliable than the oldest “current state” writeups. + +### Opinion 4: the project should preserve historical docs but label them more aggressively + +I do **not** think the right answer is to erase older docs. + +I think the right answer is: + +- preserve them, +- but label them clearly as historical snapshots when superseded, +- and point readers toward the current continuity layer. + +### Opinion 5: the product architecture is still physically flatter than the conceptual docs imply + +The docs talk in terms of modules like: + +- `HyperTwistCore` +- `HyperTwistReplay` +- `HyperTwistTraining` +- `HyperTwistRecognition` +- `HyperTwistSimulation` +- `HyperTwistCoach` +- `HyperTwistXR` + +Conceptually that is right. + +But physically, from Unreal Build Tool’s perspective, the code still lives inside one main `UnrealHyperTwist` module with logical subfolders. + +That is not wrong. + +But it is an important reality distinction. + +### Opinion 6: the training lane needs architectural decomposition before it gets much bigger + +The current training subsystem is impressive, but several files are very large. + +I do not think that is an emergency today, but I do think it is a predictable future maintenance pressure point. + +### Opinion 7: the current handoff docs are stronger than the broad parse index for immediate implementation continuity + +The parse root is excellent as a working memory warehouse. + +But for action-oriented continuity, `116-hypertwist-handoff.md` is simply better. + +--- + +## Documentation staleness findings + +This section records the docs I currently consider stale, partially stale, or at risk of misleading new readers. + +### 1. `01-current-state-and-findings.md` — materially stale + +Why I think it is stale: + +- It describes the Unreal project as effectively template-level and near-empty. +- It reports only five first-party Unreal source files. +- It says there is no meaningful first-party implementation of the domain core, training scheduler, progression model, coaching decision engine, and so on. +- That no longer matches the current codebase. + +Current reality contradicting it: + +- `Source/UnrealHyperTwist` now contains `51` source/build files. +- `HyperTwistTraining` alone is roughly `74,859` lines. +- There are owned runtime/domain types for training, imported runtime surfaces, generated-mode launch requests, repository integrity, learner profiles, review plans, coach memory, queue state, timer exports, and more. +- The product now has actual catalog/materialization integration inside live product code. + +My opinion: + +- this doc should be treated as a historical snapshot, +- not as current-state truth. + +### 2. `115-hypertwist-product-integration-memory.md` — partially stale / transitional + +Why I think it is stale: + +- It still carries wording from an earlier phase where the next open slice was product-side catalog/runtime integration. +- The file itself later acknowledges that subsequent product-side slices advanced beyond that original handoff. +- In other words, it already contains its own partial obsolescence note. + +Current reality: + +- multiple post-`115` slices have landed, +- and `116-hypertwist-handoff.md` is the cleaner, fresher continuity anchor for the imported generated-mode lane. + +My opinion: + +- `115` still has value as lineage memory, +- but it should no longer be treated as the primary restart doc. + +### 3. `88-roadmap-implementation-workbook.md` — partially stale as a live status document + +Why I think it is partially stale: + +- It remains structurally useful as a workbook. +- But some “current execution note” and status language are behind the live code and handoff reality. +- The file emphasizes earlier Phase 3 donor incorporation and training-lane integration, but it does not fully reflect the now-landed imported catalog/runtime selector/generated-mode request work. + +Current reality: + +- imported content-pack overlays and runtime bundle integration are live, +- generated-mode selector choice state and launch request persistence/query surfaces are live, +- and those are important enough to deserve presence in the workbook’s current-state framing. + +My opinion: + +- keep the workbook, +- but refresh the “current execution note” sections. + +### 4. `96-hypertwist-live-execution-roadmap.md` — partially stale by omission + +Why I think it is partially stale: + +- Its overall sequencing still feels broadly correct. +- However, its “already landed in live code” and “remaining work” framing does not appear to foreground the imported catalog/runtime/generative request lane strongly enough relative to current code. + +Current reality: + +- imported runtime surfaces and selector plumbing are in live product code, +- generated-mode launch request creation/persistence/query support is in live code, +- and the next open item is not generic backend ambiguity but specifically the real generator executor and related thin consumers. + +My opinion: + +- the roadmap is conceptually sound, +- but the status bullets should be refreshed to match actual implementation progress. + +### 5. Any doc that still implies “the docs are ahead of the code” as a blanket statement — stale as a generalization + +Why: + +- that was once true in a broad sense, +- but now it is only partially true. + +Current reality: + +- some docs are ahead, +- but training/catalog/coaching code is already materially implemented. + +My opinion: + +- documentation should now describe the project as having a **substantial backend implementation** with several still-thin lanes, not as a nearly code-empty thesis. + +### What I do **not** consider stale in the same sense + +I do **not** consider these stale merely because they are older or high-level: + +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ARCHITECTURE.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/PRD.md` +- `95-hypertwist-roadmap-invariants.md` + +Why not: + +- some of these are explicitly preserved baselines, +- some are stable identity/architecture docs rather than status docs, +- and their role is not to mirror every landed code slice. + +--- + +## Recommended documentation update strategy + +I do **not** recommend bluntly overwriting historical documents. + +I recommend a layered approach: + +### A. Mark obviously superseded state snapshots as historical snapshots + +Especially: + +- `01-current-state-and-findings.md` +- possibly `115-hypertwist-product-integration-memory.md` + +### B. Refresh current-state planning docs where status bullets lag live code + +Especially: + +- `88-roadmap-implementation-workbook.md` +- `96-hypertwist-live-execution-roadmap.md` + +### C. Prefer additive updates over destructive rewrites + +Good options: + +- add a “status as of 2026-05-04” block +- add “superseded by / continue with” notes +- update “already landed” bullet lists +- clarify which handoff doc is currently primary + +### D. Do not rewrite preserved baseline docs just to make them status-perfect + +For example: + +- the v6.5 manual pack should remain product identity / posture documentation, +- not get overloaded with volatile implementation status detail. + +--- + +## My concise opinion on project direction after priming + +If I reduce all of this to one practical judgment: + +HyperTwist currently looks like a project whose **real product backend has emerged inside the training/coaching/imported-catalog lane**, while the deeper simulation, recognition productionization, and generator execution lanes remain the next major frontier. + +That is a much healthier position than the older bootstrap-era docs imply. + +The project is no longer asking “what could HyperTwist be?” in the abstract. + +It is now asking much more concrete questions such as: + +- how do we finish the generated-mode execution lane? +- how do we keep the documentation synchronized with the now-real backend? +- how do we keep very large training files maintainable as implementation continues? + +## Recommended next documentation edits + +If I were updating stale documentation after this pass, my first targets would be: + +1. `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\01-current-state-and-findings.md` + - add a strong superseded/historical note + - point readers to `116-hypertwist-handoff.md` + - stop presenting the Unreal codebase as near-empty + +2. `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\115-hypertwist-product-integration-memory.md` + - add a stronger “superseded by 116 for current continuation” note + - explicitly mark the earlier “next open slice” section as historical + +3. `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\88-roadmap-implementation-workbook.md` + - refresh current execution notes to include imported catalog overlay, runtime selectors, generated-mode launch config/request work, and persisted query surfaces + +4. `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\96-hypertwist-live-execution-roadmap.md` + - refresh “already landed” / “remaining work” bullets to better match current code and `116` + +## Final priming stance + +I am now primed with the following working model: + +- active first-party implementation surface: + - `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist` +- active first-party runtime data surface: + - `C:\HyperTwist\UnrealHyperTwist\Content\HyperTwistTraining\MaterializedCatalog` +- active maintained tooling/plugin fork: + - `C:\HyperTwist\UnrealHyperTwist\Plugins\UnrealMCPChong` +- current best continuity doc: + - `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\116-hypertwist-handoff.md` +- strongest stale-state risk: + - older parse-era docs that still describe the Unreal side as a near-empty template project + +That is the state of my understanding after this priming pass.