diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index c4b7309..8dd93d9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -1199,6 +1199,100 @@ namespace HyperTwistTrainingSubsystemInternal ); } + bool IsStartableCoachQueueEntryState(const EHyperTwistTrainingCoachSessionQueueEntryState EntryState) + { + return EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Pending + || EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::Ready + || EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress; + } + + const FHyperTwistTrainingCoachSessionQueueStateEntry* FindCarryoverResumeQueueEntry( + const FHyperTwistTrainingCoachSessionQueueState& QueueState, + const FHyperTwistTrainingReviewPlanState& ReviewPlan, + const FHyperTwistTrainingCoachActionPlan& CarryForwardActionPlan, + const FHyperTwistTrainingDeck& ReviewDeck + ) + { + if (!QueueState.IsStructurallyValid()) + { + return nullptr; + } + + const FString RootPlanId = !CarryForwardActionPlan.RootPlanId.IsEmpty() + ? CarryForwardActionPlan.RootPlanId + : CarryForwardActionPlan.PlanId; + const TArray ReviewCaseIds = ExtractCaseIdsFromDeck(ReviewDeck); + const auto CountOverlap = [&ReviewCaseIds](const TArray& CandidateCaseIds) -> int32 + { + int32 MatchCount = 0; + for (const FString& ReviewCaseId : ReviewCaseIds) + { + if (!ReviewCaseId.IsEmpty() && CandidateCaseIds.Contains(ReviewCaseId)) + { + MatchCount += 1; + } + } + + return MatchCount; + }; + + const FHyperTwistTrainingCoachSessionQueueStateEntry* BestEntry = nullptr; + int32 BestScore = 0; + for (const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : QueueState.Entries) + { + if (!Entry.IsStructurallyValid() || !IsStartableCoachQueueEntryState(Entry.EntryState)) + { + continue; + } + + int32 Score = 0; + if (!ReviewPlan.PlanId.IsEmpty() && Entry.SourceReviewPlanId == ReviewPlan.PlanId) + { + Score += 500; + } + if (!CarryForwardActionPlan.PlanId.IsEmpty() + && Entry.CarryForwardPlanId == CarryForwardActionPlan.PlanId) + { + Score += 350; + } + if (!RootPlanId.IsEmpty() && Entry.RootPlanId == RootPlanId) + { + Score += 200; + } + if (!CarryForwardActionPlan.SourceSessionId.IsEmpty() + && Entry.SourceBriefTrainingSessionId == CarryForwardActionPlan.SourceSessionId) + { + Score += 125; + } + if (!ReviewPlan.SourceDeckId.IsEmpty() && Entry.FocusDeckId == ReviewPlan.SourceDeckId) + { + Score += 75; + } + + const int32 OverlapCount = CountOverlap(Entry.FocusCaseIds); + if (OverlapCount > 0) + { + Score += OverlapCount * 20; + if (OverlapCount == ReviewCaseIds.Num()) + { + Score += 40; + } + } + if (!QueueState.ActiveEntryId.IsEmpty() && Entry.EntryId == QueueState.ActiveEntryId) + { + Score += 10; + } + + if (Score > BestScore) + { + BestScore = Score; + BestEntry = &Entry; + } + } + + return BestEntry; + } + FHyperTwistTrainingDeck BuildCoachQueueDeckFromEntry( const FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry, const FHyperTwistTrainingRunState& ActiveRunState, @@ -4002,6 +4096,7 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReview const FHyperTwistTrainingCoachActionPlan StoredCoachActionPlan = ActiveCoachActionPlan; const FHyperTwistCoachBrief StoredCoachBrief = ActiveCoachBrief; const FHyperTwistCoachBrief StoredCoachFollowUpBrief = ActiveCoachFollowUpBrief; + const FHyperTwistTrainingCoachSessionQueueState StoredCoachQueueState = ActiveCoachSessionQueueState; const FHyperTwistTrainingDeck ReviewDeck = BuildActiveReviewDeck(MaxCases); if (!ReviewDeck.IsStructurallyValid()) { @@ -4063,6 +4158,45 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReview ); } } + if (StoredCoachQueueState.IsStructurallyValid()) + { + FHyperTwistTrainingCoachSessionQueueState RepositoryQueueState; + const bool bHasRepositoryQueueState = + UHyperTwistTrainingRepositoryLibrary::TryGetCoachSessionQueueState( + TrainingRepositoryState, + StoredCoachQueueState.UserId, + StoredCoachQueueState.QueueId, + RepositoryQueueState + ); + if (bHasRepositoryQueueState) + { + const FHyperTwistTrainingCoachSessionQueueStateEntry* ResumeQueueEntry = + HyperTwistTrainingSubsystemInternal::FindCarryoverResumeQueueEntry( + RepositoryQueueState, + StoredReviewPlan, + StoredCoachActionPlan, + ReviewDeck + ); + if (ResumeQueueEntry != nullptr) + { + const FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState = + UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryById( + RepositoryQueueState, + ResumeQueueEntry->EntryId, + RunState.Session.TrainingSessionId, + RunState.Session.StartedAtUtc + ); + if (UpdatedQueueState.IsStructurallyValid()) + { + TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState( + TrainingRepositoryState, + UpdatedQueueState + ); + ActiveCoachSessionQueueState = UpdatedQueueState; + } + } + } + } RefreshRepositoryViews(); return RunState; } diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 096f84b..193d2e1 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -66,9 +66,9 @@ Current correction call from the source-exposed audit and current execution refr - 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 is closed; request/config/selection plumbing and the bounded clean-room executor are already landed in first-party code - - the latest landed bounded `Phase 4` packet closes the carryover-review action-plan resumption gap inside the already-live recognition-assisted coach-to-review continuity lane - - resumed idle carryover review launches now rebuild and activate a fresh coach action plan tied to the resumed session instead of dropping the carried-forward closure lineage at relaunch - - the current next bounded packet is to continue the same recognition-assisted coach-to-review closure validation pass from resumed carryover completion through final carryover consumption, review-program exhaustion, and truthful idle dashboard closure, landing only the next continuity fix that validation exposes + - the latest landed bounded `Phase 4` packet closes the carryover-review queue execution gap inside the already-live recognition-assisted coach-to-review continuity lane + - resumed idle carryover review launches now bind back onto any matching live queue entry so completion can close the same queue lineage that the resumed session actually consumed + - the current next bounded packet is to continue the same recognition-assisted coach-to-review closure validation pass from queue-backed resumed carryover completion through final carryover consumption, review-program exhaustion, and truthful idle dashboard closure, landing only the next continuity fix that validation exposes - 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 diff --git a/docs/arch/HYPERTWIST_PHASE4_CARRYOVER_REVIEW_QUEUE_EXECUTION_CONTINUITY_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_CARRYOVER_REVIEW_QUEUE_EXECUTION_CONTINUITY_PACKET_2026-05-07.md new file mode 100644 index 0000000..6d0ed92 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_CARRYOVER_REVIEW_QUEUE_EXECUTION_CONTINUITY_PACKET_2026-05-07.md @@ -0,0 +1,102 @@ +# HyperTwist Phase 4 carryover review queue execution continuity packet + +Created on `2026-05-07` + +Status: + +- first-party HyperTwist packet +- bounded Phase `4` coach-to-review validation slice + +## Purpose + +This packet preserves queue execution lineage when an idle carryover review plan resumes after `ClearActiveRun()`. + +The open tasks are: + +- stop relaunching a stored carryover review plan without advancing any matching live coach queue entry in repository state +- let resumed carryover review completion close the same queue lineage it actually consumed so queue execution history, schedule feedback, and final idle collapse stay truthful + +It is not: + +- a new review-plan persistence packet +- a new action-plan packet +- a broader queue-priority redesign +- a dashboard layout rewrite + +## Scope + +Bounded lane: + +- inspect the stored live coach queue state before idle carryover review relaunch +- resolve the best matching queue entry from review-plan lineage, carry-forward action-plan lineage, and relaunched review cases +- mark that queue entry started against the resumed review session before the final repository refresh +- leave queue state untouched when no matching live repository queue entry exists + +Out of scope: + +- changing how review plans are scored or finalized +- changing queue ordering heuristics +- rewriting coach follow-up derivation +- widening into unrelated analytics or template-selection work + +## Why this was the right next packet + +Before this slice: + +- idle carryover review resumption was already landed +- resumed carryover review launches already rebuilt a fresh active coach action plan tied to the resumed session + +But one continuity seam still remained: + +- the resumed carryover review launch could restore review-plan lineage and action-plan lineage +- while any matching live coach queue entry still remained unstarted in repository state + +That meant: + +- the resumed session could consume carryover review work correctly +- while queue execution history and queue-derived closure feedback still had no truthful started session to close against that relaunched work + +So the next honest move was: + +- keep the resumed review relaunch logic +- and bind it back onto the matching live queue entry when that queue lineage actually exists + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` + - added `FindCarryoverResumeQueueEntry(...)` to match a live queue entry from stored review-plan id, carry-forward/root plan lineage, and relaunched review cases + - idle `StartRecommendedReviewRun()` now snapshots the pre-relaunch queue state + - after relaunch, it resolves the current repository queue state, marks the matched entry started with the resumed session id via `StartCoachSessionQueueEntryById(...)`, upserts it, and then refreshes repository views + +## Product effect + +The resumed carryover closure lane is now more truthful: + +- queue-backed carryover review relaunches no longer bypass queue execution lineage +- resumed carryover review completion can now finalize the same queue entry by session id instead of leaving queue execution history blind to the relaunch +- queue-derived schedule and closure feedback have a cleaner path back to true idle once the final carryover slice is actually consumed + +## Acceptance criteria + +- when a stored idle carryover review plan relaunches and a matching live queue entry exists, that queue entry is marked started against the resumed session +- when no matching live queue entry exists, the relaunch leaves queue state untouched +- the resumed queue entry remains repository-backed so completion can finalize it by session id +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm idle `StartRecommendedReviewRun()` snapshots the pre-relaunch queue state +3. confirm a matching live repository queue entry is resolved from review-plan / carry-forward lineage before queue mutation +4. confirm resumed carryover relaunch marks that queue entry started with the resumed session id +5. confirm no broader queue-priority or dashboard redesign was reopened + +## Validation result + +- full `Development Editor|Win64` build succeeded on `2026-05-07` + +## Next bounded follow-on slice + +- continue the same recognition-assisted coach-to-review closure validation pass from queue-backed resumed carryover completion through final carryover consumption, review-program exhaustion, and truthful idle dashboard closure, and land only the next continuity fix that validation exposes