diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index e25aef8..db09e4b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -21232,6 +21232,27 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertC return UpdatedRepositoryState; } +FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveCoachSessionQueueState( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& QueueId +) +{ + if (UserId.IsEmpty() || QueueId.IsEmpty()) + { + return RepositoryState; + } + + FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState; + UpdatedRepositoryState.StoredCoachSessionQueues.RemoveAll( + [&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate) + { + return Candidate.UserId == UserId && Candidate.QueueId == QueueId; + } + ); + return UpdatedRepositoryState; +} + FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachSessionQueueExecutionSummary( const FHyperTwistTrainingRepositoryState& RepositoryState, const FString& UserId, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index a78b762..783a293 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -3372,7 +3372,11 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReview return FHyperTwistTrainingRunState(); } - const FString UserId = ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId; + const FString UserId = !ActiveCoachBrief.UserId.IsEmpty() + ? ActiveCoachBrief.UserId + : (!ActiveCoachSessionQueueState.UserId.IsEmpty() + ? ActiveCoachSessionQueueState.UserId + : (ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId)); FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(ReviewDeck, UserId, SessionId, Mode); if (!RunState.IsStructurallyValid()) { @@ -3417,7 +3421,11 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedR return FHyperTwistTrainingRunState(); } - const FString UserId = ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId; + const FString UserId = !ActiveCoachBrief.UserId.IsEmpty() + ? ActiveCoachBrief.UserId + : (!ActiveCoachSessionQueueState.UserId.IsEmpty() + ? ActiveCoachSessionQueueState.UserId + : (ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId)); const FHyperTwistTrainingCoachSessionQueueState QueueStateBeforeStart = ActiveCoachSessionQueueState; const HyperTwistTrainingSubsystemInternal::FResolvedCoachCaseBudget LaunchBudget = HyperTwistTrainingSubsystemInternal::ResolvePreferredCoachCaseBudget( @@ -5302,6 +5310,17 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() } else { + const FString QueueStateUserId = !ActiveCoachSessionQueueState.UserId.IsEmpty() + ? ActiveCoachSessionQueueState.UserId + : RepositoryViewUserId; + if (!QueueStateUserId.IsEmpty()) + { + TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RemoveCoachSessionQueueState( + TrainingRepositoryState, + QueueStateUserId, + HyperTwistTrainingSubsystemInternal::ActiveCoachSessionQueueId + ); + } ActiveCoachSessionQueueState = FHyperTwistTrainingCoachSessionQueueState(); ActiveCoachScheduleHorizonSummary = FHyperTwistCoachScheduleHorizonSummary(); ActiveCoachSchedulePolicySummary = FHyperTwistCoachSchedulePolicySummary(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h index 7994d30..bf09957 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h @@ -476,6 +476,13 @@ public: const FHyperTwistTrainingCoachSessionQueueState& QueueState ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") + static FHyperTwistTrainingRepositoryState RemoveCoachSessionQueueState( + const FHyperTwistTrainingRepositoryState& RepositoryState, + const FString& UserId, + const FString& QueueId + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") static FHyperTwistTrainingCoachSessionQueueExecutionSummary DeriveCoachSessionQueueExecutionSummary( const FHyperTwistTrainingRepositoryState& RepositoryState, diff --git a/docs/arch/HYPERTWIST_PHASE4_POST_CLEAR_QUEUE_IDLE_CONTINUITY_PACKET_2026-05-06.md b/docs/arch/HYPERTWIST_PHASE4_POST_CLEAR_QUEUE_IDLE_CONTINUITY_PACKET_2026-05-06.md new file mode 100644 index 0000000..4604274 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_POST_CLEAR_QUEUE_IDLE_CONTINUITY_PACKET_2026-05-06.md @@ -0,0 +1,101 @@ +# HyperTwist Phase 4 post-clear queue idle continuity packet + +Created on `2026-05-06` + +Status: + +- first-party HyperTwist packet +- bounded Phase `4` coach-to-review validation slice + +## Purpose + +This packet closes the next post-clear execution gap in the live recognition-assisted coach-to-review loop. + +The open tasks are: + +- keep post-clear recovered coach launches bound to the retained learner instead of silently falling back to `local-user` +- retire the stored active queue state when carryover-backed queue work is actually exhausted, so queue-recovery bias can collapse back to a truthful idle posture + +It is not: + +- a new queue-priority heuristic packet +- a broader recommendation-history redesign +- a new dashboard layout packet +- a recognition transport packet + +## Scope + +Bounded lane: + +- fix recovered recommended/review launches so they inherit the retained coach user context +- add repository support for removing the active coach queue state without deleting queue history +- clear the stored active queue state when repository-derived queue summary no longer materializes any live queue work +- keep queue history intact so execution analytics and lineage remain available + +Out of scope: + +- new review scoring +- queue-history pruning +- broad coach-memory retuning +- structural cleanup beyond the narrow queue retirement seam + +## Why this was the right next packet + +Before this slice: + +- clear-run repository-view continuity was already landed +- post-clear coach guidance and queue state could survive the run-clear boundary +- queue history and queue-derived recovery signals were already part of coach-memory shaping + +But two post-clear mismatches still remained: + +- `StartCoachRecommendedRun()` still defaulted to `local-user` whenever there was no active run, even if the recovered coach brief already carried the correct retained user +- when queue summary no longer produced any live queue work, the subsystem cleared only the in-memory queue state; the repository still retained the last active queue state + +That meant: + +- recovered recommended coach runs could bind to the wrong learner after run clear +- queue-recovery bias could stay live in repository-derived coach memory because `DeriveQueueRecoveryBiasSnapshot()` still found a stored active queue state even after the queue should have collapsed to idle + +So the next honest move was: + +- align recovered launch ownership with retained coach context and retire the stored active queue state once the live queue actually disappears + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h` + - added repository support for removing an active coach queue state by `UserId` and `QueueId` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp` + - implemented `RemoveCoachSessionQueueState()` without touching queue history +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` + - recovered recommended/review launch paths now prefer retained coach user context before falling back to `local-user` + - repository refresh now removes the stored active queue state when no live queue summary remains materialized + +## Product effect + +The post-clear execution lane is now more truthful: + +- recovered coach launches stay attached to the retained learner identity +- once carryover-backed queue work is gone, the stored active queue state no longer keeps queue-recovery bias artificially alive +- queue history remains available for analytics, but active queue posture can return cleanly to idle +- dashboard and coach-memory surfaces have a cleaner path back to “no active queue work” after carryover is actually consumed + +## Acceptance criteria + +- post-clear recovered recommended/review launches no longer fall back to `local-user` when retained coach user context exists +- stored active queue state is removed when repository-derived queue summary no longer materializes live queue work +- queue history is preserved while active queue state is retired +- repository-derived queue recovery bias can collapse after true queue exhaustion +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm recovered recommended/review launch paths prefer retained coach user context +3. confirm repository refresh removes the active queue state when queue summary is no longer structurally valid +4. confirm queue history remains untouched +5. confirm no broader queue or coach-memory redesign was reopened + +That is the packet.