Close Roux Bound3 diagnostic packet

This commit is contained in:
axiomlogicnexus 2026-04-29 01:55:52 +02:00
parent 5afb58f31a
commit 45164a7790
11 changed files with 908 additions and 0 deletions

View file

@ -538,6 +538,100 @@ FHyperTwistTrainingDrillCaseModeCompatibility UHyperTwistContractLibrary::MakeSa
);
}
FHyperTwistTrainingMethodDrillDiagnosticPacket UHyperTwistContractLibrary::MakeSampleMethodDrillDiagnosticPacket()
{
const FHyperTwistTrainingMethodDrillDefinition DrillDefinition = MakeSampleMethodDrillDefinition();
if (!DrillDefinition.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillDiagnosticPacket();
}
FHyperTwistTrainingMethodDrillRunState RunState = UHyperTwistTrainingLibrary::StartMethodDrillRun(
DrillDefinition,
TEXT("local-user"),
TEXT("method_drill_run_diagnostics_01")
);
if (!RunState.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillDiagnosticPacket();
}
RunState.StartedAtUtc = TEXT("2026-04-29T09:00:00Z");
RunState.LastUpdatedAtUtc = RunState.StartedAtUtc;
RunState = UHyperTwistTrainingLibrary::StartNextMethodDrillCase(RunState);
if (!RunState.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillDiagnosticPacket();
}
RunState = UHyperTwistTrainingLibrary::RevealMethodDrillCase(RunState);
FHyperTwistTrainingMethodDrillResponseRecord AssistedResponse;
AssistedResponse.ResponseId = TEXT("method_drill_diag_response_01");
AssistedResponse.DrillRunId = RunState.RunId;
AssistedResponse.CaseId = RunState.CurrentCase.CaseId;
AssistedResponse.PresentedAtUtc = TEXT("2026-04-29T09:00:05Z");
AssistedResponse.RespondedAtUtc = TEXT("2026-04-29T09:00:17Z");
AssistedResponse.DrillMode = RunState.DrillDefinition.DrillMode;
AssistedResponse.VisibilityPolicy = RunState.DrillDefinition.VisibilityPolicy;
AssistedResponse.bAnswerWasRevealed = true;
AssistedResponse.bMarkedCorrect = true;
AssistedResponse.bWasRepeat = false;
AssistedResponse.ElapsedMs = 1700;
AssistedResponse.ResponseText = TEXT("recognized left block after reveal");
RunState.Responses.Add(AssistedResponse);
RunState.LastUpdatedAtUtc = AssistedResponse.RespondedAtUtc;
RunState = UHyperTwistTrainingLibrary::AdvanceMethodDrillCase(RunState);
RunState = UHyperTwistTrainingLibrary::StartNextMethodDrillCase(RunState);
if (!RunState.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillDiagnosticPacket();
}
FHyperTwistTrainingMethodDrillResponseRecord RepeatResponseA;
RepeatResponseA.ResponseId = TEXT("method_drill_diag_response_02");
RepeatResponseA.DrillRunId = RunState.RunId;
RepeatResponseA.CaseId = RunState.CurrentCase.CaseId;
RepeatResponseA.PresentedAtUtc = TEXT("2026-04-29T09:00:20Z");
RepeatResponseA.RespondedAtUtc = TEXT("2026-04-29T09:00:31Z");
RepeatResponseA.DrillMode = RunState.DrillDefinition.DrillMode;
RepeatResponseA.VisibilityPolicy = RunState.DrillDefinition.VisibilityPolicy;
RepeatResponseA.bAnswerWasRevealed = false;
RepeatResponseA.bMarkedCorrect = false;
RepeatResponseA.bWasRepeat = false;
RepeatResponseA.ElapsedMs = 2400;
RepeatResponseA.ResponseText = TEXT("missed block anchor on first pass");
RunState.Responses.Add(RepeatResponseA);
RunState.LastUpdatedAtUtc = RepeatResponseA.RespondedAtUtc;
RunState = UHyperTwistTrainingLibrary::RepeatMethodDrillCase(RunState);
FHyperTwistTrainingMethodDrillResponseRecord RepeatResponseB;
RepeatResponseB.ResponseId = TEXT("method_drill_diag_response_03");
RepeatResponseB.DrillRunId = RunState.RunId;
RepeatResponseB.CaseId = RunState.CurrentCase.CaseId;
RepeatResponseB.PresentedAtUtc = TEXT("2026-04-29T09:00:35Z");
RepeatResponseB.RespondedAtUtc = TEXT("2026-04-29T09:00:42Z");
RepeatResponseB.DrillMode = RunState.DrillDefinition.DrillMode;
RepeatResponseB.VisibilityPolicy = RunState.DrillDefinition.VisibilityPolicy;
RepeatResponseB.bAnswerWasRevealed = false;
RepeatResponseB.bMarkedCorrect = true;
RepeatResponseB.bWasRepeat = true;
RepeatResponseB.ElapsedMs = 1900;
RepeatResponseB.ResponseText = TEXT("corrected block anchor after repeat");
RunState.Responses.Add(RepeatResponseB);
RunState.LastUpdatedAtUtc = RepeatResponseB.RespondedAtUtc;
return UHyperTwistTrainingLibrary::DeriveMethodDrillDiagnosticPacket(RunState);
}
FHyperTwistTrainingSessionTemplate UHyperTwistContractLibrary::MakeSampleMethodDrillFollowUpTemplate()
{
return UHyperTwistTrainingRepositoryLibrary::BuildMethodDrillFollowUpTemplate(
MakeSampleMethodDrillDiagnosticPacket(),
TEXT("method_drill_follow_up_template_01")
);
}
FHyperTwistTrainingRunStepResult UHyperTwistContractLibrary::MakeSampleTrainingRunStepResult()
{
const FHyperTwistTrainingRunState RunState = MakeSampleTrainingRunState();

View file

@ -637,6 +637,297 @@ namespace HyperTwistTrainingLibraryInternal
Summary.LastUpdatedAtUtc = RunState.LastUpdatedAtUtc;
return Summary;
}
struct FMethodDrillCaseMetrics
{
int32 AttemptCount = 0;
int32 CorrectResponseCount = 0;
int32 IncorrectResponseCount = 0;
int32 RevealCount = 0;
int32 RepeatCount = 0;
int64 ElapsedAccumulator = 0;
int32 ElapsedSampleCount = 0;
int32 BestElapsedMs = 0;
int32 WorstElapsedMs = 0;
};
TArray<FString> BuildKnownDrillCaseIds(const FHyperTwistTrainingMethodDrillRunState& RunState)
{
TArray<FString> KnownCaseIds = BuildDrillCaseIds(RunState.DrillDefinition);
for (const FString& CaseId : RunState.PendingCaseIds)
{
if (!CaseId.IsEmpty())
{
KnownCaseIds.AddUnique(CaseId);
}
}
for (const FString& CaseId : RunState.CompletedCaseIds)
{
if (!CaseId.IsEmpty())
{
KnownCaseIds.AddUnique(CaseId);
}
}
if (!RunState.CurrentCase.CaseId.IsEmpty())
{
KnownCaseIds.AddUnique(RunState.CurrentCase.CaseId);
}
for (const FHyperTwistTrainingMethodDrillResponseRecord& Response : RunState.Responses)
{
if (!Response.CaseId.IsEmpty())
{
KnownCaseIds.AddUnique(Response.CaseId);
}
}
return KnownCaseIds;
}
FMethodDrillCaseMetrics CollectMethodDrillCaseMetrics(
const FHyperTwistTrainingMethodDrillRunState& RunState,
const FString& CaseId
)
{
FMethodDrillCaseMetrics Metrics;
for (const FHyperTwistTrainingMethodDrillResponseRecord& Response : RunState.Responses)
{
if (Response.CaseId != CaseId)
{
continue;
}
Metrics.AttemptCount += 1;
Metrics.CorrectResponseCount += Response.bMarkedCorrect ? 1 : 0;
Metrics.IncorrectResponseCount += Response.bMarkedCorrect ? 0 : 1;
Metrics.RevealCount += Response.bAnswerWasRevealed ? 1 : 0;
Metrics.RepeatCount += Response.bWasRepeat ? 1 : 0;
if (Response.ElapsedMs > 0)
{
Metrics.ElapsedAccumulator += Response.ElapsedMs;
Metrics.ElapsedSampleCount += 1;
Metrics.BestElapsedMs = Metrics.BestElapsedMs == 0
? Response.ElapsedMs
: FMath::Min(Metrics.BestElapsedMs, Response.ElapsedMs);
Metrics.WorstElapsedMs = FMath::Max(Metrics.WorstElapsedMs, Response.ElapsedMs);
}
}
return Metrics;
}
EHyperTwistTrainingMethodDrillDiagnosticState ResolveMethodDrillCaseDiagnosticState(
const FMethodDrillCaseMetrics& Metrics,
bool bCompleted,
bool bStillPending
)
{
if (bStillPending)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::Deferred;
}
if (Metrics.RepeatCount > 0 || Metrics.IncorrectResponseCount > 0)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::RepeatBurden;
}
if (Metrics.RevealCount > 0)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::AssistedCompletion;
}
if (bCompleted)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::CleanCompletion;
}
return EHyperTwistTrainingMethodDrillDiagnosticState::Unknown;
}
EHyperTwistTrainingReviewGrade ResolveMethodDrillCaseReviewGrade(
const FMethodDrillCaseMetrics& Metrics,
bool bCompleted,
bool bStillPending,
bool bWasCarryoverCase
)
{
if (bStillPending)
{
return EHyperTwistTrainingReviewGrade::Again;
}
if (Metrics.RepeatCount > 1 || Metrics.IncorrectResponseCount > 0)
{
return EHyperTwistTrainingReviewGrade::Again;
}
if (Metrics.RepeatCount == 1 || Metrics.RevealCount > 0)
{
return EHyperTwistTrainingReviewGrade::Hard;
}
if (bWasCarryoverCase)
{
return EHyperTwistTrainingReviewGrade::Good;
}
if (bCompleted && Metrics.AttemptCount > 0 && Metrics.CorrectResponseCount == Metrics.AttemptCount)
{
return EHyperTwistTrainingReviewGrade::Easy;
}
if (bCompleted)
{
return EHyperTwistTrainingReviewGrade::Good;
}
return EHyperTwistTrainingReviewGrade::Hard;
}
FString BuildMethodDrillCaseBoundaryReason(
const FMethodDrillCaseMetrics& Metrics,
bool bCompleted,
bool bStillPending,
bool bWasCarryoverCase
)
{
if (bStillPending)
{
return TEXT("case remains pending or active and should stay in follow-up focus");
}
if (Metrics.RepeatCount > 1 || Metrics.IncorrectResponseCount > 0)
{
return TEXT("case required repeated or incorrect responses before stabilization");
}
if (Metrics.RepeatCount == 1)
{
return TEXT("case required an immediate repeat before closure");
}
if (Metrics.RevealCount > 0)
{
return TEXT("case depended on answer reveal assistance");
}
if (bWasCarryoverCase)
{
return TEXT("case closed cleanly after resuming carried-over drill work");
}
if (bCompleted)
{
return TEXT("case closed cleanly without reveal or repeat pressure");
}
return TEXT("case has no actionable diagnostic boundary");
}
float BuildMethodDrillCasePriorityScore(
const FMethodDrillCaseMetrics& Metrics,
bool bStillPending,
bool bWasCarryoverCase
)
{
float Score = 0.0f;
if (bStillPending)
{
Score += 100.0f;
}
Score += static_cast<float>(Metrics.RepeatCount) * 45.0f;
Score += static_cast<float>(Metrics.IncorrectResponseCount) * 30.0f;
if (Metrics.RevealCount > 0)
{
Score += 20.0f;
}
if (bWasCarryoverCase)
{
Score += 10.0f;
}
return Score;
}
FString BuildMethodDrillRunBoundaryReason(const FHyperTwistTrainingMethodDrillDiagnosticPacket& Packet)
{
if (Packet.bBoundaryTriggeredByPendingCases)
{
return TEXT("deferred drill work remains open and should be revisited first");
}
if (Packet.bBoundaryTriggeredByRepeatBurden)
{
return TEXT("repeat burden or incorrect responses still dominate the follow-up set");
}
if (Packet.bBoundaryTriggeredByAssistance)
{
return TEXT("answer reveal assistance was needed on completed drill cases");
}
if (Packet.bBoundaryTriggeredByResumeCarryover)
{
return TEXT("resumed carryover work closed cleanly but should remain reviewable");
}
if (Packet.CompletedCaseCount > 0 && Packet.CompletedCaseCount == Packet.TotalCaseCount)
{
return TEXT("drill completed cleanly without reveal or repeat pressure");
}
return TEXT("drill diagnostic packet is neutral");
}
EHyperTwistTrainingReviewGrade ResolveMethodDrillRunReviewGrade(
const FHyperTwistTrainingMethodDrillDiagnosticPacket& Packet
)
{
if (Packet.bBoundaryTriggeredByPendingCases)
{
return EHyperTwistTrainingReviewGrade::Again;
}
const bool bHasAgainCase = Packet.CasePackets.ContainsByPredicate(
[](const FHyperTwistTrainingMethodDrillCaseComparisonPacket& CasePacket)
{
return CasePacket.RecommendedReviewGrade == EHyperTwistTrainingReviewGrade::Again;
}
);
if (bHasAgainCase)
{
return EHyperTwistTrainingReviewGrade::Again;
}
if (Packet.bBoundaryTriggeredByRepeatBurden || Packet.bBoundaryTriggeredByAssistance)
{
return EHyperTwistTrainingReviewGrade::Hard;
}
if (Packet.bBoundaryTriggeredByResumeCarryover)
{
return EHyperTwistTrainingReviewGrade::Good;
}
if (Packet.CompletedCaseCount > 0
&& Packet.CompletedCaseCount == Packet.TotalCaseCount
&& Packet.ResponseCount >= Packet.CompletedCaseCount)
{
return EHyperTwistTrainingReviewGrade::Easy;
}
if (Packet.CompletedCaseCount == Packet.TotalCaseCount)
{
return EHyperTwistTrainingReviewGrade::Good;
}
return EHyperTwistTrainingReviewGrade::Hard;
}
EHyperTwistTrainingMethodDrillDiagnosticState ResolveMethodDrillRunDiagnosticState(
const FHyperTwistTrainingMethodDrillDiagnosticPacket& Packet
)
{
if (Packet.bBoundaryTriggeredByPendingCases)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::Deferred;
}
if (Packet.bBoundaryTriggeredByRepeatBurden)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::RepeatBurden;
}
if (Packet.bBoundaryTriggeredByAssistance)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::AssistedCompletion;
}
if (Packet.CompletedCaseCount > 0 && Packet.CompletedCaseCount == Packet.TotalCaseCount)
{
return EHyperTwistTrainingMethodDrillDiagnosticState::CleanCompletion;
}
return EHyperTwistTrainingMethodDrillDiagnosticState::Unknown;
}
}
FHyperTwistContentPack UHyperTwistTrainingLibrary::MakeSampleClassicContentPack()
@ -1936,6 +2227,160 @@ FHyperTwistTrainingMethodDrillSummary UHyperTwistTrainingLibrary::DeriveMethodDr
return HyperTwistTrainingLibraryInternal::BuildDrillSummaryInternal(RunState);
}
FHyperTwistTrainingMethodDrillDiagnosticPacket UHyperTwistTrainingLibrary::DeriveMethodDrillDiagnosticPacket(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
FHyperTwistTrainingMethodDrillDiagnosticPacket Packet;
if (!RunState.IsStructurallyValid())
{
return Packet;
}
Packet.RunId = RunState.RunId;
Packet.UserId = RunState.UserId;
Packet.DrillId = RunState.DrillDefinition.DrillId;
Packet.SourceDeckId = RunState.DrillDefinition.SourceDeckId;
Packet.MethodSegmentId = RunState.DrillDefinition.MethodDescriptor.SegmentId;
Packet.StageId = RunState.DrillDefinition.StageId;
Packet.StageLabel = RunState.DrillDefinition.StageLabel;
Packet.SourceBatchId = RunState.SourceBatchId;
Packet.ReferenceUtc = HyperTwistTrainingLibraryInternal::ResolveDrillTimestamp(RunState.LastUpdatedAtUtc);
Packet.DrillMode = RunState.DrillDefinition.DrillMode;
Packet.VisibilityPolicy = RunState.DrillDefinition.VisibilityPolicy;
Packet.LifecycleState = RunState.LifecycleState;
Packet.ResponseCount = RunState.Responses.Num();
Packet.bWasResumedRun = !RunState.SourceBatchId.IsEmpty();
const TArray<FString> KnownCaseIds = HyperTwistTrainingLibraryInternal::BuildKnownDrillCaseIds(RunState);
TSet<FString> CompletedCaseSet(RunState.CompletedCaseIds);
TSet<FString> UnresolvedCaseSet;
for (const FString& CaseId : RunState.PendingCaseIds)
{
if (!CaseId.IsEmpty())
{
UnresolvedCaseSet.Add(CaseId);
}
}
if (!RunState.CurrentCase.CaseId.IsEmpty())
{
UnresolvedCaseSet.Add(RunState.CurrentCase.CaseId);
}
for (const FString& CompletedCaseId : RunState.CompletedCaseIds)
{
UnresolvedCaseSet.Remove(CompletedCaseId);
}
Packet.TotalCaseCount = KnownCaseIds.Num();
Packet.CompletedCaseCount = CompletedCaseSet.Num();
Packet.PendingCaseCount = UnresolvedCaseSet.Num();
float MaxCasePriority = 0.0f;
for (const FString& CaseId : KnownCaseIds)
{
const HyperTwistTrainingLibraryInternal::FMethodDrillCaseMetrics Metrics =
HyperTwistTrainingLibraryInternal::CollectMethodDrillCaseMetrics(RunState, CaseId);
const bool bCompleted = CompletedCaseSet.Contains(CaseId);
const bool bStillPending = UnresolvedCaseSet.Contains(CaseId);
const bool bWasCarryoverCase = Packet.bWasResumedRun;
FHyperTwistTrainingMethodDrillCaseComparisonPacket CasePacket;
CasePacket.CaseId = CaseId;
CasePacket.AttemptCount = Metrics.AttemptCount;
CasePacket.CorrectResponseCount = Metrics.CorrectResponseCount;
CasePacket.IncorrectResponseCount = Metrics.IncorrectResponseCount;
CasePacket.RevealCount = Metrics.RevealCount;
CasePacket.RepeatCount = Metrics.RepeatCount;
CasePacket.AverageElapsedMs = Metrics.ElapsedSampleCount > 0
? static_cast<int32>(Metrics.ElapsedAccumulator / Metrics.ElapsedSampleCount)
: 0;
CasePacket.BestElapsedMs = Metrics.BestElapsedMs;
CasePacket.WorstElapsedMs = Metrics.WorstElapsedMs;
CasePacket.bCompleted = bCompleted;
CasePacket.bStillPending = bStillPending;
CasePacket.bWasCarryoverCase = bWasCarryoverCase;
CasePacket.DiagnosticState = HyperTwistTrainingLibraryInternal::ResolveMethodDrillCaseDiagnosticState(
Metrics,
bCompleted,
bStillPending
);
CasePacket.RecommendedReviewGrade = HyperTwistTrainingLibraryInternal::ResolveMethodDrillCaseReviewGrade(
Metrics,
bCompleted,
bStillPending,
bWasCarryoverCase
);
CasePacket.bBoundaryTriggeredByReveal = Metrics.RevealCount > 0;
CasePacket.bBoundaryTriggeredByRepeat = Metrics.RepeatCount > 0 || Metrics.IncorrectResponseCount > 0;
CasePacket.bBoundaryTriggeredByDeferredState = bStillPending;
CasePacket.bBoundaryTriggeredByCarryover = bWasCarryoverCase;
CasePacket.BoundaryReason = HyperTwistTrainingLibraryInternal::BuildMethodDrillCaseBoundaryReason(
Metrics,
bCompleted,
bStillPending,
bWasCarryoverCase
);
CasePacket.PriorityScore = HyperTwistTrainingLibraryInternal::BuildMethodDrillCasePriorityScore(
Metrics,
bStillPending,
bWasCarryoverCase
);
Packet.CorrectResponseCount += Metrics.CorrectResponseCount;
Packet.IncorrectResponseCount += Metrics.IncorrectResponseCount;
Packet.RevealedCaseCount += Metrics.RevealCount > 0 ? 1 : 0;
Packet.AssistedCompletionCount += CasePacket.bBoundaryTriggeredByReveal && bCompleted ? 1 : 0;
Packet.RepeatBurdenCaseCount += CasePacket.bBoundaryTriggeredByRepeat ? 1 : 0;
Packet.DeferredCaseCount +=
CasePacket.DiagnosticState == EHyperTwistTrainingMethodDrillDiagnosticState::Deferred ? 1 : 0;
Packet.ResolvedCarryoverCaseCount += bWasCarryoverCase && bCompleted ? 1 : 0;
MaxCasePriority = FMath::Max(MaxCasePriority, CasePacket.PriorityScore);
Packet.CasePackets.Add(CasePacket);
}
Packet.CasePackets.Sort([](
const FHyperTwistTrainingMethodDrillCaseComparisonPacket& Left,
const FHyperTwistTrainingMethodDrillCaseComparisonPacket& Right
)
{
if (!FMath::IsNearlyEqual(Left.PriorityScore, Right.PriorityScore))
{
return Left.PriorityScore > Right.PriorityScore;
}
return Left.CaseId < Right.CaseId;
});
for (const FHyperTwistTrainingMethodDrillCaseComparisonPacket& CasePacket : Packet.CasePackets)
{
if (CasePacket.PriorityScore > 0.0f)
{
Packet.FocusCaseIds.Add(CasePacket.CaseId);
}
}
Packet.CompletionFraction = Packet.TotalCaseCount > 0
? static_cast<float>(Packet.CompletedCaseCount) / static_cast<float>(Packet.TotalCaseCount)
: 0.0f;
const int32 AssistanceCaseCount = Packet.AssistedCompletionCount + Packet.RepeatBurdenCaseCount;
Packet.AssistanceFraction = Packet.TotalCaseCount > 0
? static_cast<float>(AssistanceCaseCount) / static_cast<float>(Packet.TotalCaseCount)
: 0.0f;
Packet.bBoundaryTriggeredByPendingCases = Packet.DeferredCaseCount > 0;
Packet.bBoundaryTriggeredByRepeatBurden = Packet.RepeatBurdenCaseCount > 0;
Packet.bBoundaryTriggeredByAssistance = Packet.AssistedCompletionCount > 0;
Packet.bBoundaryTriggeredByResumeCarryover = Packet.bWasResumedRun;
Packet.bNeedsFollowUpTemplate = Packet.FocusCaseIds.Num() > 0;
Packet.bNeedsReviewPlanBridge = Packet.FocusCaseIds.Num() > 0 && !Packet.bBoundaryTriggeredByPendingCases;
Packet.PriorityScore = MaxCasePriority
+ static_cast<float>(Packet.DeferredCaseCount * 10)
+ static_cast<float>(Packet.RepeatBurdenCaseCount * 5)
+ static_cast<float>(Packet.AssistedCompletionCount * 2);
Packet.DiagnosticState = HyperTwistTrainingLibraryInternal::ResolveMethodDrillRunDiagnosticState(Packet);
Packet.RecommendedReviewGrade = HyperTwistTrainingLibraryInternal::ResolveMethodDrillRunReviewGrade(Packet);
Packet.BoundaryReason = HyperTwistTrainingLibraryInternal::BuildMethodDrillRunBoundaryReason(Packet);
return Packet;
}
FHyperTwistTrainingMethodDrillBatch UHyperTwistTrainingLibrary::BuildMethodDrillBatch(
const FHyperTwistTrainingMethodDrillRunState& RunState,
const FString& BatchId,

View file

@ -3158,6 +3158,82 @@ FHyperTwistTrainingMethodDrillDefinition UHyperTwistTrainingRepositoryLibrary::B
return Result;
}
FHyperTwistTrainingSessionTemplate UHyperTwistTrainingRepositoryLibrary::BuildMethodDrillFollowUpTemplate(
const FHyperTwistTrainingMethodDrillDiagnosticPacket& DiagnosticPacket,
const FString& TemplateId
)
{
FHyperTwistTrainingSessionTemplate SessionTemplate;
if (!DiagnosticPacket.IsStructurallyValid()
|| !DiagnosticPacket.bNeedsFollowUpTemplate
|| DiagnosticPacket.SourceDeckId.IsEmpty())
{
return SessionTemplate;
}
SessionTemplate.UserId = DiagnosticPacket.UserId;
SessionTemplate.TemplateId = TemplateId.IsEmpty()
? FString::Printf(TEXT("method_drill_follow_up_%s"), *DiagnosticPacket.RunId)
: TemplateId;
SessionTemplate.Title = !DiagnosticPacket.StageLabel.IsEmpty()
? FString::Printf(TEXT("%s Drill Follow-Up"), *DiagnosticPacket.StageLabel)
: TEXT("Method Drill Follow-Up");
SessionTemplate.TemplateKind =
DiagnosticPacket.bBoundaryTriggeredByPendingCases || DiagnosticPacket.bBoundaryTriggeredByRepeatBurden
? EHyperTwistTrainingSessionTemplateKind::Recovery
: EHyperTwistTrainingSessionTemplateKind::ReviewPlan;
SessionTemplate.FocusDeckId = DiagnosticPacket.SourceDeckId;
SessionTemplate.MethodSegmentId = DiagnosticPacket.MethodSegmentId;
switch (DiagnosticPacket.DrillMode)
{
case EHyperTwistTrainingDrillModeKind::Recognition:
case EHyperTwistTrainingDrillModeKind::Reconstruction:
SessionTemplate.SuggestedMode = EHyperTwistTrainingDeliveryMode::RecognitionAssisted;
break;
case EHyperTwistTrainingDrillModeKind::Execution:
SessionTemplate.SuggestedMode = EHyperTwistTrainingDeliveryMode::VirtualCube;
break;
case EHyperTwistTrainingDrillModeKind::Recall:
default:
SessionTemplate.SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
break;
}
SessionTemplate.SuggestedSelectionPolicy =
DiagnosticPacket.bBoundaryTriggeredByPendingCases
? EHyperTwistTrainingSelectionPolicy::Scripted
: EHyperTwistTrainingSelectionPolicy::Weighted;
SessionTemplate.FocusCaseIds =
HyperTwistTrainingRepositoryLibraryInternal::DeduplicateTemplateCaseIds(DiagnosticPacket.FocusCaseIds);
if (SessionTemplate.FocusCaseIds.Num() == 0)
{
return FHyperTwistTrainingSessionTemplate();
}
SessionTemplate.RecommendedCaseCount = FMath::Max(
1,
FMath::Min(
SessionTemplate.FocusCaseIds.Num(),
DiagnosticPacket.RecommendedReviewGrade == EHyperTwistTrainingReviewGrade::Again ? 6 : 4
)
);
SessionTemplate.GuidanceLabel = !DiagnosticPacket.BoundaryReason.IsEmpty()
? DiagnosticPacket.BoundaryReason
: TEXT("method-drill follow-up");
SessionTemplate.bPreferReviewRun =
DiagnosticPacket.bNeedsReviewPlanBridge
|| DiagnosticPacket.RecommendedReviewGrade != EHyperTwistTrainingReviewGrade::Easy;
SessionTemplate.bCoachReview = false;
SessionTemplate.bPreferShortReviewSet = SessionTemplate.RecommendedCaseCount <= 4;
SessionTemplate.bUserEditable = false;
SessionTemplate.bUserCustomized = false;
SessionTemplate.bUserCreated = false;
SessionTemplate.LastUpdatedAtUtc =
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(DiagnosticPacket.ReferenceUtc);
return SessionTemplate.IsStructurallyValid() ? SessionTemplate : FHyperTwistTrainingSessionTemplate();
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertRunRecord(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingRunRecord& RunRecord

View file

@ -92,6 +92,28 @@ FHyperTwistTrainingMethodDrillSummary UHyperTwistTrainingRuntimeLibrary::GetActi
return FHyperTwistTrainingMethodDrillSummary();
}
FHyperTwistTrainingMethodDrillDiagnosticPacket
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingMethodDrillDiagnosticPacket(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveMethodDrillDiagnosticPacket();
}
return FHyperTwistTrainingMethodDrillDiagnosticPacket();
}
FHyperTwistTrainingSessionTemplate
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingMethodDrillFollowUpTemplate(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveMethodDrillFollowUpTemplate();
}
return FHyperTwistTrainingSessionTemplate();
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRuntimeLibrary::GetTrainingRepositoryState(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))

View file

@ -425,6 +425,16 @@ FHyperTwistTrainingMethodDrillSummary UHyperTwistTrainingSubsystem::GetActiveMet
return ActiveMethodDrillSummary;
}
FHyperTwistTrainingMethodDrillDiagnosticPacket UHyperTwistTrainingSubsystem::GetActiveMethodDrillDiagnosticPacket() const
{
return ActiveMethodDrillDiagnosticPacket;
}
FHyperTwistTrainingSessionTemplate UHyperTwistTrainingSubsystem::GetActiveMethodDrillFollowUpTemplate() const
{
return ActiveMethodDrillFollowUpTemplate;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingSubsystem::GetTrainingRepositoryState() const
{
return TrainingRepositoryState;
@ -2364,6 +2374,8 @@ void UHyperTwistTrainingSubsystem::ClearActiveMethodDrillRun()
{
ActiveMethodDrillRunState = FHyperTwistTrainingMethodDrillRunState();
ActiveMethodDrillSummary = FHyperTwistTrainingMethodDrillSummary();
ActiveMethodDrillDiagnosticPacket = FHyperTwistTrainingMethodDrillDiagnosticPacket();
ActiveMethodDrillFollowUpTemplate = FHyperTwistTrainingSessionTemplate();
bHasActiveMethodDrillRun = false;
}
@ -2386,12 +2398,23 @@ void UHyperTwistTrainingSubsystem::RefreshMethodDrillSummary()
if (!ActiveMethodDrillRunState.IsStructurallyValid())
{
ActiveMethodDrillSummary = FHyperTwistTrainingMethodDrillSummary();
ActiveMethodDrillDiagnosticPacket = FHyperTwistTrainingMethodDrillDiagnosticPacket();
ActiveMethodDrillFollowUpTemplate = FHyperTwistTrainingSessionTemplate();
return;
}
ActiveMethodDrillSummary = UHyperTwistTrainingLibrary::DeriveMethodDrillSummary(
ActiveMethodDrillRunState
);
ActiveMethodDrillDiagnosticPacket = UHyperTwistTrainingLibrary::DeriveMethodDrillDiagnosticPacket(
ActiveMethodDrillRunState
);
ActiveMethodDrillFollowUpTemplate = ActiveMethodDrillDiagnosticPacket.IsStructurallyValid()
? UHyperTwistTrainingRepositoryLibrary::BuildMethodDrillFollowUpTemplate(
ActiveMethodDrillDiagnosticPacket,
FString()
)
: FHyperTwistTrainingSessionTemplate();
}
void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()

View file

@ -92,6 +92,12 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingDrillCaseModeCompatibility MakeSampleMethodDrillCaseModeCompatibility();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillDiagnosticPacket MakeSampleMethodDrillDiagnosticPacket();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingSessionTemplate MakeSampleMethodDrillFollowUpTemplate();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingRunStepResult MakeSampleTrainingRunStepResult();

View file

@ -201,6 +201,11 @@ public:
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillDiagnosticPacket DeriveMethodDrillDiagnosticPacket(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillBatch BuildMethodDrillBatch(
const FHyperTwistTrainingMethodDrillRunState& RunState,

View file

@ -124,6 +124,12 @@ public:
int32 MaxFavoriteCases
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static FHyperTwistTrainingSessionTemplate BuildMethodDrillFollowUpTemplate(
const FHyperTwistTrainingMethodDrillDiagnosticPacket& DiagnosticPacket,
const FString& TemplateId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingRepositoryState UpsertRunRecord(
const FHyperTwistTrainingRepositoryState& RepositoryState,

View file

@ -37,6 +37,16 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillSummary GetActiveMethodDrillSummary(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillDiagnosticPacket GetActiveTrainingMethodDrillDiagnosticPacket(
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingSessionTemplate GetActiveTrainingMethodDrillFollowUpTemplate(
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRepositoryState GetTrainingRepositoryState(UObject* WorldContextObject);

View file

@ -32,6 +32,12 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillSummary GetActiveMethodDrillSummary() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillDiagnosticPacket GetActiveMethodDrillDiagnosticPacket() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingSessionTemplate GetActiveMethodDrillFollowUpTemplate() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingRepositoryState GetTrainingRepositoryState() const;
@ -467,6 +473,12 @@ private:
UPROPERTY()
FHyperTwistTrainingMethodDrillSummary ActiveMethodDrillSummary;
UPROPERTY()
FHyperTwistTrainingMethodDrillDiagnosticPacket ActiveMethodDrillDiagnosticPacket;
UPROPERTY()
FHyperTwistTrainingSessionTemplate ActiveMethodDrillFollowUpTemplate;
UPROPERTY()
FHyperTwistTrainingRepositoryState TrainingRepositoryState;

View file

@ -155,6 +155,16 @@ enum class EHyperTwistTrainingDrillLifecycleState : uint8
Aborted UMETA(DisplayName = "Aborted")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingMethodDrillDiagnosticState : uint8
{
Unknown UMETA(DisplayName = "Unknown"),
CleanCompletion UMETA(DisplayName = "Clean Completion"),
AssistedCompletion UMETA(DisplayName = "Assisted Completion"),
RepeatBurden UMETA(DisplayName = "Repeat Burden"),
Deferred UMETA(DisplayName = "Deferred")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingDrillEntrySource : uint8
{
@ -2460,6 +2470,205 @@ struct FHyperTwistTrainingDrillCaseModeCompatibility
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillCaseComparisonPacket
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AttemptCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 CorrectResponseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 IncorrectResponseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RevealCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RepeatCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AverageElapsedMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 BestElapsedMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 WorstElapsedMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCompleted = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bStillPending = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWasCarryoverCase = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingMethodDrillDiagnosticState DiagnosticState =
EHyperTwistTrainingMethodDrillDiagnosticState::Unknown;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingReviewGrade RecommendedReviewGrade = EHyperTwistTrainingReviewGrade::Good;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByReveal = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByRepeat = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByDeferredState = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByCarryover = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString BoundaryReason;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float PriorityScore = 0.0f;
bool IsStructurallyValid() const
{
return !CaseId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillDiagnosticPacket
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RunId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString UserId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DrillId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceDeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString MethodSegmentId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StageId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StageLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceBatchId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ReferenceUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillModeKind DrillMode = EHyperTwistTrainingDrillModeKind::Recognition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy =
EHyperTwistTrainingDrillVisibilityPolicy::FullInformation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillLifecycleState LifecycleState = EHyperTwistTrainingDrillLifecycleState::Ready;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 CompletedCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PendingCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ResponseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 CorrectResponseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 IncorrectResponseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RevealedCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 AssistedCompletionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RepeatBurdenCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DeferredCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ResolvedCarryoverCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float CompletionFraction = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float AssistanceFraction = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWasResumedRun = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsFollowUpTemplate = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNeedsReviewPlanBridge = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingMethodDrillDiagnosticState DiagnosticState =
EHyperTwistTrainingMethodDrillDiagnosticState::Unknown;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingReviewGrade RecommendedReviewGrade = EHyperTwistTrainingReviewGrade::Good;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByPendingCases = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByRepeatBurden = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByAssistance = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundaryTriggeredByResumeCarryover = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString BoundaryReason;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float PriorityScore = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> FocusCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingMethodDrillCaseComparisonPacket> CasePackets;
bool IsStructurallyValid() const
{
return !RunId.IsEmpty() && !UserId.IsEmpty() && !DrillId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingLearnerPreset
{