Persist method drill review follow-up plans

This commit is contained in:
axiomlogicnexus 2026-05-06 22:32:44 +02:00
parent f28f6df06d
commit 89a37bce08
2 changed files with 217 additions and 21 deletions

View file

@ -1348,6 +1348,19 @@ namespace HyperTwistTrainingSubsystemInternal
return ReviewDeck.Cases.Num() > 0 ? ReviewDeck : FHyperTwistTrainingDeck();
}
FHyperTwistTrainingReviewPolicy BuildTemplateReviewPolicy(
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
int32 CaseCount
);
TArray<FHyperTwistTrainingCaseRecommendation> BuildTemplateReviewRecommendations(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingDeck& SourceDeck,
const FHyperTwistTrainingDeck& ReviewDeck,
const FString& UserId,
const FHyperTwistTrainingSessionTemplate& SessionTemplate
);
bool ShouldResumeStoredReviewPlanFromTemplate(
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
const FHyperTwistTrainingReviewPlanState& ReviewPlan,
@ -1364,8 +1377,8 @@ namespace HyperTwistTrainingSubsystemInternal
{
return false;
}
if (!SessionTemplate.SourceReviewPlanId.IsEmpty()
&& SessionTemplate.SourceReviewPlanId != ReviewPlan.PlanId)
if (SessionTemplate.SourceReviewPlanId.IsEmpty()
|| SessionTemplate.SourceReviewPlanId != ReviewPlan.PlanId)
{
return false;
}
@ -1378,6 +1391,42 @@ namespace HyperTwistTrainingSubsystemInternal
return true;
}
bool TryPrepareReviewPlanBackedTemplateLaunch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
const FHyperTwistTrainingDeck& ReviewDeck,
const FString& UserId,
FHyperTwistTrainingDeck& OutSourceDeck,
FHyperTwistTrainingReviewPolicy& OutReviewPolicy,
TArray<FHyperTwistTrainingCaseRecommendation>& OutRecommendations
)
{
OutSourceDeck = FHyperTwistTrainingDeck();
OutReviewPolicy = FHyperTwistTrainingReviewPolicy();
OutRecommendations.Reset();
if (!SessionTemplate.bPreferReviewRun || !ReviewDeck.IsStructurallyValid() || UserId.IsEmpty())
{
return false;
}
if (!UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog(
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog(),
SessionTemplate.FocusDeckId,
OutSourceDeck))
{
return false;
}
OutReviewPolicy = BuildTemplateReviewPolicy(SessionTemplate, ReviewDeck.Cases.Num());
OutRecommendations = BuildTemplateReviewRecommendations(
RepositoryState,
OutSourceDeck,
ReviewDeck,
UserId,
SessionTemplate
);
return OutSourceDeck.IsStructurallyValid();
}
FHyperTwistTrainingReviewPolicy BuildTemplateReviewPolicy(
const FHyperTwistTrainingSessionTemplate& SessionTemplate,
int32 CaseCount
@ -3577,25 +3626,16 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTe
FHyperTwistTrainingDeck SourceDeck;
FHyperTwistTrainingReviewPolicy ReviewPolicy;
TArray<FHyperTwistTrainingCaseRecommendation> TemplateRecommendations;
if (MatchingTemplate->bPreferReviewRun
&& UHyperTwistTrainingCatalogLibrary::TryFindDeckInCatalog(
UHyperTwistTrainingCatalogLibrary::MakePhase3TrainingCatalog(),
MatchingTemplate->FocusDeckId,
SourceDeck))
{
ReviewPolicy = HyperTwistTrainingSubsystemInternal::BuildTemplateReviewPolicy(
const bool bPreparedReviewTemplateLaunch =
HyperTwistTrainingSubsystemInternal::TryPrepareReviewPlanBackedTemplateLaunch(
TrainingRepositoryState,
*MatchingTemplate,
MaterializedDeck.Cases.Num()
MaterializedDeck,
ResolvedUserId,
SourceDeck,
ReviewPolicy,
TemplateRecommendations
);
TemplateRecommendations =
HyperTwistTrainingSubsystemInternal::BuildTemplateReviewRecommendations(
TrainingRepositoryState,
SourceDeck,
MaterializedDeck,
ResolvedUserId,
*MatchingTemplate
);
}
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
MaterializedDeck,
@ -3605,7 +3645,7 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTe
);
if (!RunState.IsStructurallyValid()
|| !MatchingTemplate->bPreferReviewRun
|| !SourceDeck.IsStructurallyValid())
|| !bPreparedReviewTemplateLaunch)
{
return RunState;
}
@ -3667,12 +3707,72 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartActiveMethodDrill
*ResolvedSessionId,
*MethodDrillPacketStructureToken)
: ResolvedSessionId;
return StartTrainingRunFromDeck(
if (HyperTwistTrainingSubsystemInternal::ShouldResumeStoredReviewPlanFromTemplate(
ActiveMethodDrillFollowUpTemplate,
ActiveReviewPlanState,
ActiveMethodDrillRunState.UserId))
{
return StartRecommendedReviewRun(
StructuredSessionId,
FollowUpDeck.Cases.Num(),
ActiveMethodDrillFollowUpTemplate.SuggestedMode
);
}
FHyperTwistTrainingDeck SourceDeck;
FHyperTwistTrainingReviewPolicy ReviewPolicy;
TArray<FHyperTwistTrainingCaseRecommendation> TemplateRecommendations;
const bool bPreparedReviewTemplateLaunch =
HyperTwistTrainingSubsystemInternal::TryPrepareReviewPlanBackedTemplateLaunch(
TrainingRepositoryState,
ActiveMethodDrillFollowUpTemplate,
FollowUpDeck,
ActiveMethodDrillRunState.UserId,
SourceDeck,
ReviewPolicy,
TemplateRecommendations
);
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
FollowUpDeck,
ActiveMethodDrillRunState.UserId,
StructuredSessionId,
ActiveMethodDrillFollowUpTemplate.SuggestedMode
);
if (!RunState.IsStructurallyValid()
|| !ActiveMethodDrillFollowUpTemplate.bPreferReviewRun
|| !bPreparedReviewTemplateLaunch)
{
return RunState;
}
ActiveReviewPlanState = UHyperTwistTrainingRepositoryLibrary::BuildReviewPlan(
SourceDeck,
FollowUpDeck,
ActiveMethodDrillRunState.UserId,
TemplateRecommendations,
ReviewPolicy,
FString::Printf(TEXT("review_plan_%s"), *RunState.Session.TrainingSessionId),
RunState.Session.StartedAtUtc,
ActiveMethodDrillFollowUpTemplate.SuggestedMode
);
if (ActiveReviewPlanState.IsStructurallyValid())
{
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertReviewPlan(
TrainingRepositoryState,
ActiveReviewPlanState
);
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(
ActiveReviewPlanState
);
RefreshRepositoryViews();
}
else
{
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
}
return RunState;
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReviewRun(

View file

@ -0,0 +1,96 @@
# HyperTwist Phase 4 method-drill review template continuity packet
Created on `2026-05-06`
Status:
- first-party HyperTwist packet
- bounded Phase `4` method-drill follow-up review slice
## Purpose
This packet closes the remaining review-template continuity gap on the method-drill follow-up launch path.
The open tasks are:
- stop treating method-drill follow-up templates that prefer review execution as plain deck runs
- tighten stored review-plan resumption so only explicit pending-plan templates resume an already-open plan
It is not:
- a new drill diagnostic packet
- a broader dashboard launch-surface packet
- a rewrite of review-template construction
- a queue / coach orchestration packet
## Scope
Bounded lane:
- keep the existing method-drill follow-up template builder unchanged
- reuse the same review-template launch preparation logic now used by generic session templates
- make `StartActiveMethodDrillFollowUpRun()` persist review-plan state when its follow-up template prefers review execution
- require `SourceReviewPlanId` on template-backed stored-plan resume so generic review-preferring templates only resume when they explicitly target the open plan
Out of scope:
- widening into unrelated method-drill diagnostic heuristics
- changing review-plan scoring rules
- adding new template browsing or launch buttons
- changing follow-up packet structure selection
## Why this was the right next packet
Before this slice:
- generic session templates that preferred review execution already launched through review-plan semantics
- method-drill follow-up templates already set `bPreferReviewRun` when their diagnostic packet asked for a review-plan bridge
But two seams still remained:
- `StartActiveMethodDrillFollowUpRun()` still launched those follow-up templates through `StartTrainingRunFromDeck()` only
- and the new stored-plan resume helper was broad enough to resume any open plan on the same deck, even when the template did not explicitly target that plan
That meant:
- generic template review continuity had landed
- while method-drill follow-up review continuity still lagged behind it
- and the stored-plan resume path still needed a tighter contract to match the last packets intended behavior
So the next honest move was:
- narrow stored-plan resume down to explicit `SourceReviewPlanId` matches
- then extend review-plan persistence into method-drill follow-up launches using that safer rule
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp`
- tightened template-backed stored-plan resume matching so only explicit pending-plan templates resume an already-open plan
- added shared review-template launch preparation for source-deck lookup, review policy, and template-backed recommendations
- `StartActiveMethodDrillFollowUpRun()` now persists a review plan after launch when the active follow-up template prefers review execution
## Product effect
The review-template lane is now more coherent:
- `Resume Review Plan` keeps its direct stored-plan resumption path
- other review-preferring templates no longer accidentally reuse an unrelated open plan just because they share the same focus deck
- method-drill follow-up launches now keep repository-backed review state aligned with the review-preferring template that launched them
## Acceptance criteria
- template-backed stored-plan resume only occurs when the template explicitly names the open review plan
- method-drill follow-up templates with `bPreferReviewRun` persist a review plan after launch
- method-drill follow-up templates without review preference keep their existing plain-run behavior
- full product build succeeds
## Validation checklist
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
2. confirm generic review-preferring templates without `SourceReviewPlanId` no longer resume arbitrary open plans
3. confirm `StartActiveMethodDrillFollowUpRun()` persists review-plan state when the follow-up template prefers review execution
4. confirm non-review method-drill follow-up launches still behave as ordinary training runs
That is the packet.